<?php

namespace ccxt\async;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use ccxt\async\abstract\coinbase as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\BadRequest;
use ccxt\InvalidOrder;
use ccxt\NotSupported;
use ccxt\Precise;
use \React\Async;
use \React\Promise;
use \React\Promise\PromiseInterface;

class coinbase extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'coinbase',
            'name' => 'Coinbase Advanced',
            'countries' => array( 'US' ),
            'pro' => true,
            'certified' => false,
            // rate-limits:
            // ADVANCED API => https://docs.cloud.coinbase.com/advanced-trade/docs/rest-api-rate-limits
            // - max 30 req/second for private data, 10 req/s for public data
            // DATA API    : https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/rate-limiting
            // - max 10000 req/hour (to prevent userland mistakes we apply ~3 req/second RL per call
            'rateLimit' => 34,
            'version' => 'v2',
            'userAgent' => $this->userAgents['chrome'],
            'headers' => array(
                'CB-VERSION' => '2018-05-30',
            ),
            'has' => array(
                'CORS' => true,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'closeAllPositions' => false,
                'closePosition' => true,
                'createConvertTrade' => true,
                'createDepositAddress' => true,
                'createLimitBuyOrder' => true,
                'createLimitSellOrder' => true,
                'createMarketBuyOrder' => true,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrder' => true,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => false,
                'createOrderWithTakeProfitAndStopLossWs' => false,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => false,
                'createStopOrder' => true,
                'deposit' => true,
                'editOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true,
                'fetchConvertQuote' => true,
                'fetchConvertTrade' => true,
                'fetchConvertTradeHistory' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposit' => true,
                'fetchDepositAddress' => 'emulated',
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => true,
                'fetchDepositMethodId' => true,
                'fetchDepositMethodIds' => true,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => true,
                'fetchFundingHistory' => false,
                'fetchFundingInterval' => false,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchL2OrderBook' => false,
                'fetchLedger' => true,
                'fetchLeverage' => false,
                'fetchLeverages' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarginModes' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMarkPrices' => false,
                'fetchMyBuys' => true,
                'fetchMyLiquidations' => false,
                'fetchMySells' => true,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchPosition' => true,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => 'emulated',
                'fetchTradingFees' => true,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'repayMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'withdraw' => true,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg',
                'api' => array(
                    'rest' => 'https://api.coinbase.com',
                ),
                'www' => 'https://www.coinbase.com',
                'doc' => array(
                    'https://developers.coinbase.com/api/v2',
                    'https://docs.cloud.coinbase.com/advanced-trade/docs/welcome',
                ),
                'fees' => array(
                    'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees',
                    'https://www.coinbase.com/advanced-fees',
                ),
                'referral' => 'https://www.coinbase.com/join/58cbe25a355148797479dbd2',
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'api' => array(
                'v2' => array(
                    'public' => array(
                        'get' => array(
                            'currencies' => 10.6,
                            'currencies/crypto' => 10.6,
                            'time' => 10.6,
                            'exchange-rates' => 10.6,
                            'users/{user_id}' => 10.6,
                            'prices/{symbol}/buy' => 10.6,
                            'prices/{symbol}/sell' => 10.6,
                            'prices/{symbol}/spot' => 10.6,
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'accounts' => 10.6,
                            'accounts/{account_id}' => 10.6,
                            'accounts/{account_id}/addresses' => 10.6,
                            'accounts/{account_id}/addresses/{address_id}' => 10.6,
                            'accounts/{account_id}/addresses/{address_id}/transactions' => 10.6,
                            'accounts/{account_id}/transactions' => 10.6,
                            'accounts/{account_id}/transactions/{transaction_id}' => 10.6,
                            'accounts/{account_id}/buys' => 10.6,
                            'accounts/{account_id}/buys/{buy_id}' => 10.6,
                            'accounts/{account_id}/sells' => 10.6,
                            'accounts/{account_id}/sells/{sell_id}' => 10.6,
                            'accounts/{account_id}/deposits' => 10.6,
                            'accounts/{account_id}/deposits/{deposit_id}' => 10.6,
                            'accounts/{account_id}/withdrawals' => 10.6,
                            'accounts/{account_id}/withdrawals/{withdrawal_id}' => 10.6,
                            'payment-methods' => 10.6,
                            'payment-methods/{payment_method_id}' => 10.6,
                            'user' => 10.6,
                            'user/auth' => 10.6,
                        ),
                        'post' => array(
                            'accounts' => 10.6,
                            'accounts/{account_id}/primary' => 10.6,
                            'accounts/{account_id}/addresses' => 10.6,
                            'accounts/{account_id}/transactions' => 10.6,
                            'accounts/{account_id}/transactions/{transaction_id}/complete' => 10.6,
                            'accounts/{account_id}/transactions/{transaction_id}/resend' => 10.6,
                            'accounts/{account_id}/buys' => 10.6,
                            'accounts/{account_id}/buys/{buy_id}/commit' => 10.6,
                            'accounts/{account_id}/sells' => 10.6,
                            'accounts/{account_id}/sells/{sell_id}/commit' => 10.6,
                            'accounts/{account_id}/deposits' => 10.6,
                            'accounts/{account_id}/deposits/{deposit_id}/commit' => 10.6,
                            'accounts/{account_id}/withdrawals' => 10.6,
                            'accounts/{account_id}/withdrawals/{withdrawal_id}/commit' => 10.6,
                        ),
                        'put' => array(
                            'accounts/{account_id}' => 10.6,
                            'user' => 10.6,
                        ),
                        'delete' => array(
                            'accounts/{id}' => 10.6,
                            'accounts/{account_id}/transactions/{transaction_id}' => 10.6,
                        ),
                    ),
                ),
                'v3' => array(
                    'public' => array(
                        'get' => array(
                            'brokerage/time' => 3,
                            'brokerage/market/product_book' => 3,
                            'brokerage/market/products' => 3,
                            'brokerage/market/products/{product_id}' => 3,
                            'brokerage/market/products/{product_id}/candles' => 3,
                            'brokerage/market/products/{product_id}/ticker' => 3,
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'brokerage/accounts' => 1,
                            'brokerage/accounts/{account_uuid}' => 1,
                            'brokerage/orders/historical/batch' => 1,
                            'brokerage/orders/historical/fills' => 1,
                            'brokerage/orders/historical/{order_id}' => 1,
                            'brokerage/products' => 3,
                            'brokerage/products/{product_id}' => 3,
                            'brokerage/products/{product_id}/candles' => 3,
                            'brokerage/products/{product_id}/ticker' => 3,
                            'brokerage/best_bid_ask' => 3,
                            'brokerage/product_book' => 3,
                            'brokerage/transaction_summary' => 3,
                            'brokerage/portfolios' => 1,
                            'brokerage/portfolios/{portfolio_uuid}' => 1,
                            'brokerage/convert/trade/{trade_id}' => 1,
                            'brokerage/cfm/balance_summary' => 1,
                            'brokerage/cfm/positions' => 1,
                            'brokerage/cfm/positions/{product_id}' => 1,
                            'brokerage/cfm/sweeps' => 1,
                            'brokerage/intx/portfolio/{portfolio_uuid}' => 1,
                            'brokerage/intx/positions/{portfolio_uuid}' => 1,
                            'brokerage/intx/positions/{portfolio_uuid}/{symbol}' => 1,
                            'brokerage/payment_methods' => 1,
                            'brokerage/payment_methods/{payment_method_id}' => 1,
                        ),
                        'post' => array(
                            'brokerage/orders' => 1,
                            'brokerage/orders/batch_cancel' => 1,
                            'brokerage/orders/edit' => 1,
                            'brokerage/orders/edit_preview' => 1,
                            'brokerage/orders/preview' => 1,
                            'brokerage/portfolios' => 1,
                            'brokerage/portfolios/move_funds' => 1,
                            'brokerage/convert/quote' => 1,
                            'brokerage/convert/trade/{trade_id}' => 1,
                            'brokerage/cfm/sweeps/schedule' => 1,
                            'brokerage/intx/allocate' => 1,
                            // futures
                            'brokerage/orders/close_position' => 1,
                        ),
                        'put' => array(
                            'brokerage/portfolios/{portfolio_uuid}' => 1,
                        ),
                        'delete' => array(
                            'brokerage/portfolios/{portfolio_uuid}' => 1,
                            'brokerage/cfm/sweeps' => 1,
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'taker' => $this->parse_number('0.012'),
                    'maker' => $this->parse_number('0.006'), // array("pricing_tier":"Advanced 1","usd_from":"0","usd_to":"1000","taker_fee_rate":"0.012","maker_fee_rate":"0.006","aop_from":"","aop_to":"")
                    'tierBased' => true,
                    'percentage' => true,
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.006') ),
                            array( $this->parse_number('10000'), $this->parse_number('0.004') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0025') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.002') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0018') ),
                            array( $this->parse_number('15000000'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('75000000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('250000000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('400000000'), $this->parse_number('0.0005') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.004') ),
                            array( $this->parse_number('10000'), $this->parse_number('0.0025') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.001') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('15000000'), $this->parse_number('0.0006') ),
                            array( $this->parse_number('75000000'), $this->parse_number('0.0003') ),
                            array( $this->parse_number('250000000'), $this->parse_number('0.0') ),
                            array( $this->parse_number('400000000'), $this->parse_number('0.0') ),
                        ),
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    'two_factor_required' => '\\ccxt\\AuthenticationError', // 402 When sending money over 2fa limit
                    'param_required' => '\\ccxt\\ExchangeError', // 400 Missing parameter
                    'validation_error' => '\\ccxt\\ExchangeError', // 400 Unable to validate POST/PUT
                    'invalid_request' => '\\ccxt\\ExchangeError', // 400 Invalid request
                    'personal_details_required' => '\\ccxt\\AuthenticationError', // 400 User’s personal detail required to complete this request
                    'identity_verification_required' => '\\ccxt\\AuthenticationError', // 400 Identity verification is required to complete this request
                    'jumio_verification_required' => '\\ccxt\\AuthenticationError', // 400 Document verification is required to complete this request
                    'jumio_face_match_verification_required' => '\\ccxt\\AuthenticationError', // 400 Document verification including face match is required to complete this request
                    'unverified_email' => '\\ccxt\\AuthenticationError', // 400 User has not verified their email
                    'authentication_error' => '\\ccxt\\AuthenticationError', // 401 Invalid auth (generic)
                    'invalid_authentication_method' => '\\ccxt\\AuthenticationError', // 401 API access is blocked for deleted users.
                    'invalid_token' => '\\ccxt\\AuthenticationError', // 401 Invalid Oauth token
                    'revoked_token' => '\\ccxt\\AuthenticationError', // 401 Revoked Oauth token
                    'expired_token' => '\\ccxt\\AuthenticationError', // 401 Expired Oauth token
                    'invalid_scope' => '\\ccxt\\AuthenticationError', // 403 User hasn’t authenticated necessary scope
                    'not_found' => '\\ccxt\\ExchangeError', // 404 Resource not found
                    'rate_limit_exceeded' => '\\ccxt\\RateLimitExceeded', // 429 Rate limit exceeded
                    'internal_server_error' => '\\ccxt\\ExchangeError', // 500 Internal server error
                    'UNSUPPORTED_ORDER_CONFIGURATION' => '\\ccxt\\BadRequest',
                    'INSUFFICIENT_FUND' => '\\ccxt\\InsufficientFunds',
                    'PERMISSION_DENIED' => '\\ccxt\\PermissionDenied',
                    'INVALID_ARGUMENT' => '\\ccxt\\BadRequest',
                    'PREVIEW_STOP_PRICE_ABOVE_LAST_TRADE_PRICE' => '\\ccxt\\InvalidOrder',
                    'PREVIEW_INSUFFICIENT_FUND' => '\\ccxt\\InsufficientFunds',
                ),
                'broad' => array(
                    'Insufficient balance in source account' => '\\ccxt\\InsufficientFunds',
                    'request timestamp expired' => '\\ccxt\\InvalidNonce', // array("errors":[array("id":"authentication_error","message":"request timestamp expired")])
                    'order with this orderID was not found' => '\\ccxt\\OrderNotFound', // array("error":"unknown","error_details":"order with this orderID was not found","message":"order with this orderID was not found")
                ),
            ),
            'timeframes' => array(
                '1m' => 'ONE_MINUTE',
                '5m' => 'FIVE_MINUTE',
                '15m' => 'FIFTEEN_MINUTE',
                '30m' => 'THIRTY_MINUTE',
                '1h' => 'ONE_HOUR',
                '2h' => 'TWO_HOUR',
                '6h' => 'SIX_HOUR',
                '1d' => 'ONE_DAY',
            ),
            'commonCurrencies' => array(
                'CGLD' => 'CELO',
            ),
            'options' => array(
                'usePrivate' => false,
                'brokerId' => 'ccxt',
                'stablePairs' => array( 'BUSD-USD', 'CBETH-ETH', 'DAI-USD', 'GUSD-USD', 'GYEN-USD', 'PAX-USD', 'PAX-USDT', 'USDC-EUR', 'USDC-GBP', 'USDT-EUR', 'USDT-GBP', 'USDT-USD', 'USDT-USDC', 'WBTC-BTC' ),
                'fetchCurrencies' => array(
                    'expires' => 5000,
                ),
                'accounts' => array(
                    'wallet',
                    'fiat',
                    // 'vault',
                ),
                'v3Accounts' => array(
                    'ACCOUNT_TYPE_CRYPTO',
                    'ACCOUNT_TYPE_FIAT',
                ),
                'networks' => array(
                    'ERC20' => 'ethereum',
                    'XLM' => 'stellar',
                ),
                'createMarketBuyOrderRequiresPrice' => true,
                'advanced' => true, // set to true if using any v3 endpoints from the advanced trade API
                'fetchMarkets' => 'fetchMarketsV3', // 'fetchMarketsV3' or 'fetchMarketsV2'
                'timeDifference' => 0, // the difference between system clock and exchange server clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'fetchTicker' => 'fetchTickerV3', // 'fetchTickerV3' or 'fetchTickerV2'
                'fetchTickers' => 'fetchTickersV3', // 'fetchTickersV3' or 'fetchTickersV2'
                'fetchAccounts' => 'fetchAccountsV3', // 'fetchAccountsV3' or 'fetchAccountsV2'
                'fetchBalance' => 'v2PrivateGetAccounts', // 'v2PrivateGetAccounts' or 'v3PrivateGetBrokerageAccounts'
                'fetchTime' => 'v2PublicGetTime', // 'v2PublicGetTime' or 'v3PublicGetBrokerageTime'
                'user_native_currency' => 'USD', // needed to get fees for v3
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => true,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => true,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => true, // todo implement
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => true,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 3000,
                        'daysBack' => null,
                        'untilDays' => 10000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'untilDays' => 10000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 10000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 300,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches the current integer timestamp in milliseconds from the exchange server
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-time#http-request
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->method] 'v2PublicGetTime' or 'v3PublicGetBrokerageTime' default is 'v2PublicGetTime'
             * @return {int} the current integer timestamp in milliseconds from the exchange server
             */
            $defaultMethod = $this->safe_string($this->options, 'fetchTime', 'v2PublicGetTime');
            $method = $this->safe_string($params, 'method', $defaultMethod);
            $params = $this->omit($params, 'method');
            $response = null;
            if ($method === 'v2PublicGetTime') {
                $response = Async\await($this->v2PublicGetTime ($params));
                //
                //     {
                //         "data" => {
                //             "epoch" => 1589295679,
                //             "iso" => "2020-05-12T15:01:19Z"
                //         }
                //     }
                //
                $response = $this->safe_dict($response, 'data', array());
            } else {
                $response = Async\await($this->v3PublicGetBrokerageTime ($params));
                //
                //     {
                //         "iso" => "2024-02-27T03:37:14Z",
                //         "epochSeconds" => "1709005034",
                //         "epochMillis" => "1709005034333"
                //     }
                //
            }
            return $this->safe_timestamp_2($response, 'epoch', 'epochSeconds');
        }) ();
    }

    public function fetch_accounts($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch all the accounts associated with a profile
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=account-structure account structures~ indexed by the account type
             */
            $method = $this->safe_string($this->options, 'fetchAccounts', 'fetchAccountsV3');
            if ($method === 'fetchAccountsV3') {
                return Async\await($this->fetch_accounts_v3($params));
            }
            return Async\await($this->fetch_accounts_v2($params));
        }) ();
    }

    public function fetch_accounts_v2($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchAccounts', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchAccounts', null, null, null, $params, 'next_starting_after', 'starting_after', null, 100));
            }
            $request = array(
                'limit' => 100,
            );
            $response = Async\await($this->v2PrivateGetAccounts ($this->extend($request, $params)));
            //
            //     {
            //         "pagination" => array(
            //             "ending_before" => null,
            //             "starting_after" => null,
            //             "previous_ending_before" => null,
            //             "next_starting_after" => null,
            //             "limit" => 244,
            //             "order" => "desc",
            //             "previous_uri" => null,
            //             "next_uri" => null
            //         ),
            //         "data" => [
            //             array(
            //                 "id" => "XLM",
            //                 "name" => "XLM Wallet",
            //                 "primary" => false,
            //                 "type" => "wallet",
            //                 "currency" => array(
            //                     "code" => "XLM",
            //                     "name" => "Stellar Lumens",
            //                     "color" => "#000000",
            //                     "sort_index" => 127,
            //                     "exponent" => 7,
            //                     "type" => "crypto",
            //                     "address_regex" => "^G[A-Z2-7]{55}$",
            //                     "asset_id" => "13b83335-5ede-595b-821e-5bcdfa80560f",
            //                     "destination_tag_name" => "XLM Memo ID",
            //                     "destination_tag_regex" => "^[ -~]array(1,28)$"
            //                 ),
            //                 "balance" => array(
            //                     "amount" => "0.0000000",
            //                     "currency" => "XLM"
            //                 ),
            //                 "created_at" => null,
            //                 "updated_at" => null,
            //                 "resource" => "account",
            //                 "resource_path" => "/v2/accounts/XLM",
            //                 "allow_deposits" => true,
            //                 "allow_withdrawals" => true
            //             ),
            //         ]
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $pagination = $this->safe_dict($response, 'pagination', array());
            $cursor = $this->safe_string($pagination, 'next_starting_after');
            $accounts = $this->safe_list($response, 'data', array());
            $length = count($accounts);
            $lastIndex = $length - 1;
            $last = $this->safe_dict($accounts, $lastIndex);
            if (($cursor !== null) && ($cursor !== '')) {
                $last['next_starting_after'] = $cursor;
                $accounts[$lastIndex] = $last;
            }
            return $this->parse_accounts($data, $params);
        }) ();
    }

    public function fetch_accounts_v3($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchAccounts', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchAccounts', null, null, null, $params, 'cursor', 'cursor', null, 250));
            }
            $request = array(
                'limit' => 250,
            );
            $response = Async\await($this->v3PrivateGetBrokerageAccounts ($this->extend($request, $params)));
            //
            //     {
            //         "accounts" => array(
            //             {
            //                 "uuid" => "11111111-1111-1111-1111-111111111111",
            //                 "name" => "USDC Wallet",
            //                 "currency" => "USDC",
            //                 "available_balance" => array(
            //                     "value" => "0.0000000000000000",
            //                     "currency" => "USDC"
            //                 ),
            //                 "default" => true,
            //                 "active" => true,
            //                 "created_at" => "2023-01-04T06:20:06.456Z",
            //                 "updated_at" => "2023-01-04T06:20:07.181Z",
            //                 "deleted_at" => null,
            //                 "type" => "ACCOUNT_TYPE_CRYPTO",
            //                 "ready" => false,
            //                 "hold" => array(
            //                     "value" => "0.0000000000000000",
            //                     "currency" => "USDC"
            //                 }
            //             ),
            //             ...
            //         ),
            //         "has_next" => false,
            //         "cursor" => "",
            //         "size" => 9
            //     }
            //
            $accounts = $this->safe_list($response, 'accounts', array());
            $length = count($accounts);
            $lastIndex = $length - 1;
            $last = $this->safe_dict($accounts, $lastIndex);
            $cursor = $this->safe_string($response, 'cursor');
            if (($cursor !== null) && ($cursor !== '')) {
                $last['cursor'] = $cursor;
                $accounts[$lastIndex] = $last;
            }
            return $this->parse_accounts($accounts, $params);
        }) ();
    }

    public function fetch_portfolios($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch all the $portfolios
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getportfolios
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=account-structure account structures~ indexed by the account type
             */
            $response = Async\await($this->v3PrivateGetBrokeragePortfolios ($params));
            $portfolios = $this->safe_list($response, 'portfolios', array());
            $result = array();
            for ($i = 0; $i < count($portfolios); $i++) {
                $portfolio = $portfolios[$i];
                $result[] = array(
                    'id' => $this->safe_string($portfolio, 'uuid'),
                    'type' => $this->safe_string($portfolio, 'type'),
                    'code' => null,
                    'info' => $portfolio,
                );
            }
            return $result;
        }) ();
    }

    public function parse_account($account) {
        //
        // fetchAccountsV2
        //
        //     {
        //         "id" => "XLM",
        //         "name" => "XLM Wallet",
        //         "primary" => false,
        //         "type" => "wallet",
        //         "currency" => array(
        //             "code" => "XLM",
        //             "name" => "Stellar Lumens",
        //             "color" => "#000000",
        //             "sort_index" => 127,
        //             "exponent" => 7,
        //             "type" => "crypto",
        //             "address_regex" => "^G[A-Z2-7]{55}$",
        //             "asset_id" => "13b83335-5ede-595b-821e-5bcdfa80560f",
        //             "destination_tag_name" => "XLM Memo ID",
        //             "destination_tag_regex" => "^[ -~]array(1,28)$"
        //         ),
        //         "balance" => array(
        //             "amount" => "0.0000000",
        //             "currency" => "XLM"
        //         ),
        //         "created_at" => null,
        //         "updated_at" => null,
        //         "resource" => "account",
        //         "resource_path" => "/v2/accounts/XLM",
        //         "allow_deposits" => true,
        //         "allow_withdrawals" => true
        //     }
        //
        // fetchAccountsV3
        //
        //     {
        //         "uuid" => "11111111-1111-1111-1111-111111111111",
        //         "name" => "USDC Wallet",
        //         "currency" => "USDC",
        //         "available_balance" => array(
        //             "value" => "0.0000000000000000",
        //             "currency" => "USDC"
        //         ),
        //         "default" => true,
        //         "active" => true,
        //         "created_at" => "2023-01-04T06:20:06.456Z",
        //         "updated_at" => "2023-01-04T06:20:07.181Z",
        //         "deleted_at" => null,
        //         "type" => "ACCOUNT_TYPE_CRYPTO",
        //         "ready" => false,
        //         "hold" => {
        //             "value" => "0.0000000000000000",
        //             "currency" => "USDC"
        //         }
        //     }
        //
        $active = $this->safe_bool($account, 'active');
        $currencyIdV3 = $this->safe_string($account, 'currency');
        $currency = $this->safe_dict($account, 'currency', array());
        $currencyId = $this->safe_string($currency, 'code', $currencyIdV3);
        $typeV3 = $this->safe_string($account, 'name');
        $typeV2 = $this->safe_string($account, 'type');
        $parts = explode(' ', $typeV3);
        return array(
            'id' => $this->safe_string_2($account, 'id', 'uuid'),
            'type' => ($active !== null) ? $this->safe_string_lower($parts, 1) : $typeV2,
            'code' => $this->safe_currency_code($currencyId),
            'info' => $account,
        );
    }

    public function create_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * create a currency deposit $address
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-addresses#create-$address
             *
             * @param {string} $code unified currency $code of the currency for the deposit $address
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
             */
            $accountId = $this->safe_string($params, 'account_id');
            $params = $this->omit($params, 'account_id');
            if ($accountId === null) {
                Async\await($this->load_accounts());
                for ($i = 0; $i < count($this->accounts); $i++) {
                    $account = $this->accounts[$i];
                    if ($account['code'] === $code && $account['type'] === 'wallet') {
                        $accountId = $account['id'];
                        break;
                    }
                }
            }
            if ($accountId === null) {
                throw new ExchangeError($this->id . ' createDepositAddress() could not find the $account with matching currency $code ' . $code . ', specify an `account_id` extra param to target specific wallet');
            }
            $request = array(
                'account_id' => $accountId,
            );
            $response = Async\await($this->v2PrivatePostAccountsAccountIdAddresses ($this->extend($request, $params)));
            //
            //     {
            //         "data" => {
            //             "id" => "05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
            //             "address" => "coinbasebase",
            //             "address_info" => array(
            //                 "address" => "coinbasebase",
            //                 "destination_tag" => "287594668"
            //             ),
            //             "name" => null,
            //             "created_at" => "2019-07-01T14:39:29Z",
            //             "updated_at" => "2019-07-01T14:39:29Z",
            //             "network" => "eosio",
            //             "uri_scheme" => "eosio",
            //             "resource" => "address",
            //             "resource_path" => "/v2/accounts/14cfc769-e852-52f3-b831-711c104d194c/addresses/05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
            //             "warnings" => array(
            //                 array(
            //                     "title" => "Only send EOS (EOS) to this $address",
            //                     "details" => "Sending any other cryptocurrency will result in permanent loss.",
            //                     "image_url" => "https://dynamic-assets.coinbase.com/deaca3d47b10ed4a91a872e9618706eec34081127762d88f2476ac8e99ada4b48525a9565cf2206d18c04053f278f693434af4d4629ca084a9d01b7a286a7e26/asset_icons/1f8489bb280fb0a0fd643c1161312ba49655040e9aaaced5f9ad3eeaf868eadc.png"
            //                 ),
            //                 {
            //                     "title" => "Both an $address and EOS memo are required to receive EOS",
            //                     "details" => "If you send funds without an EOS memo or with an incorrect EOS memo, your funds cannot be credited to your $account->",
            //                     "image_url" => "https://www.coinbase.com/assets/receive-warning-2f3269d83547a7748fb39d6e0c1c393aee26669bfea6b9f12718094a1abff155.png"
            //                 }
            //             ),
            //             "warning_title" => "Only send EOS (EOS) to this $address",
            //             "warning_details" => "Sending any other cryptocurrency will result in permanent loss.",
            //             "destination_tag" => "287594668",
            //             "deposit_uri" => "eosio:coinbasebase?dt=287594668",
            //             "callback_url" => null
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $tag = $this->safe_string($data, 'destination_tag');
            $address = $this->safe_string($data, 'address');
            return array(
                'currency' => $code,
                'tag' => $tag,
                'address' => $address,
                'network' => null,
                'info' => $response,
            );
        }) ();
    }

    public function fetch_my_sells(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * @ignore
             * fetch $sells
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-$sells#list-$sells
             *
             * @param {string} $symbol not used by coinbase fetchMySells ()
             * @param {int} [$since] timestamp in ms of the earliest sell, default is null
             * @param {int} [$limit] max number of $sells to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=order-structure list of order structures~
             */
            // v2 did't have an endpoint for all historical trades
            $request = $this->prepare_account_request($limit, $params);
            Async\await($this->load_markets());
            $query = $this->omit($params, array( 'account_id', 'accountId' ));
            $sells = Async\await($this->v2PrivateGetAccountsAccountIdSells ($this->extend($request, $query)));
            return $this->parse_trades($sells['data'], null, $since, $limit);
        }) ();
    }

    public function fetch_my_buys(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * @ignore
             * fetch $buys
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-$buys#list-$buys
             *
             * @param {string} $symbol not used by coinbase fetchMyBuys ()
             * @param {int} [$since] timestamp in ms of the earliest buy, default is null
             * @param {int} [$limit] max number of $buys to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of  ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            // v2 did't have an endpoint for all historical trades
            $request = $this->prepare_account_request($limit, $params);
            Async\await($this->load_markets());
            $query = $this->omit($params, array( 'account_id', 'accountId' ));
            $buys = Async\await($this->v2PrivateGetAccountsAccountIdBuys ($this->extend($request, $query)));
            return $this->parse_trades($buys['data'], null, $since, $limit);
        }) ();
    }

    public function fetch_transactions_with_method($method, ?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($method, $code, $since, $limit, $params) {
            $request = null;
            list($request, $params) = Async\await($this->prepare_account_request_with_currency_code($code, $limit, $params));
            Async\await($this->load_markets());
            $response = Async\await($this->$method ($this->extend($request, $params)));
            return $this->parse_transactions($response['data'], null, $since, $limit);
        }) ();
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * Fetch all withdrawals made from an account. Won't return crypto withdrawals. Use fetchLedger for those.
             *
             * @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-withdrawals#list-withdrawals
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch withdrawals for
             * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->currencyType] "fiat" or "crypto"
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $currencyType = null;
            list($currencyType, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'currencyType');
            if ($currencyType === 'crypto') {
                $results = Async\await($this->fetch_transactions_with_method('v2PrivateGetAccountsAccountIdTransactions', $code, $since, $limit, $params));
                return $this->filter_by_array($results, 'type', 'withdrawal', false);
            }
            return Async\await($this->fetch_transactions_with_method('v2PrivateGetAccountsAccountIdWithdrawals', $code, $since, $limit, $params));
        }) ();
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * Fetch all fiat deposits made to an account. Won't return crypto deposits or staking rewards. Use fetchLedger for those.
             *
             * @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-deposits#list-deposits
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of deposits structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->currencyType] "fiat" or "crypto"
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $currencyType = null;
            list($currencyType, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'currencyType');
            if ($currencyType === 'crypto') {
                $results = Async\await($this->fetch_transactions_with_method('v2PrivateGetAccountsAccountIdTransactions', $code, $since, $limit, $params));
                return $this->filter_by_array($results, 'type', 'deposit', false);
            }
            return Async\await($this->fetch_transactions_with_method('v2PrivateGetAccountsAccountIdDeposits', $code, $since, $limit, $params));
        }) ();
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch history of deposits and withdrawals
             *
             * @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-transactions
             *
             * @param {string} [$code] unified currency $code for the currency of the deposit/withdrawals, default is null
             * @param {int} [$since] timestamp in ms of the earliest deposit/withdrawal, default is null
             * @param {int} [$limit] max number of deposit/withdrawals to return, default = 50, Min => 1, Max => 100
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $results = Async\await($this->fetch_transactions_with_method('v2PrivateGetAccountsAccountIdTransactions', $code, $since, $limit, $params));
            return $this->filter_by_array($results, 'type', array( 'deposit', 'withdrawal' ), false);
        }) ();
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'created' => 'pending',
            'completed' => 'ok',
            'canceled' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fiat deposit
        //
        //     {
        //         "id" => "f34c19f3-b730-5e3d-9f72",
        //         "status" => "completed",
        //         "payment_method" => array(
        //             "id" => "a022b31d-f9c7-5043-98f2",
        //             "resource" => "payment_method",
        //             "resource_path" => "/v2/payment-methods/a022b31d-f9c7-5043-98f2"
        //         ),
        //         "transaction" => array(
        //             "id" => "04ed4113-3732-5b0c-af86-b1d2146977d0",
        //             "resource" => "transaction",
        //             "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86"
        //         ),
        //         "user_reference" => "2VTYTH",
        //         "created_at" => "2017-02-09T07:01:18Z",
        //         "updated_at" => "2017-02-09T07:01:26Z",
        //         "resource" => "deposit",
        //         "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72",
        //         "committed" => true,
        //         "payout_at" => "2017-02-12T07:01:17Z",
        //         "instant" => false,
        //         "fee" => array( "amount" => "0.00", "currency" => "EUR" ),
        //         "amount" => array( "amount" => "114.02", "currency" => "EUR" ),
        //         "subtotal" => array( "amount" => "114.02", "currency" => "EUR" ),
        //         "hold_until" => null,
        //         "hold_days" => 0,
        //         "hold_business_days" => 0,
        //         "next_step" => null
        //     }
        //
        // fiat_withdrawal
        //
        //     {
        //         "id" => "cfcc3b4a-eeb6-5e8c-8058",
        //         "status" => "completed",
        //         "payment_method" => array(
        //             "id" => "8b94cfa4-f7fd-5a12-a76a",
        //             "resource" => "payment_method",
        //             "resource_path" => "/v2/payment-methods/8b94cfa4-f7fd-5a12-a76a"
        //         ),
        //         "transaction" => array(
        //             "id" => "fcc2550b-5104-5f83-a444",
        //             "resource" => "transaction",
        //             "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/fcc2550b-5104-5f83-a444"
        //         ),
        //         "user_reference" => "MEUGK",
        //         "created_at" => "2018-07-26T08:55:12Z",
        //         "updated_at" => "2018-07-26T08:58:18Z",
        //         "resource" => "withdrawal",
        //         "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/cfcc3b4a-eeb6-5e8c-8058",
        //         "committed" => true,
        //         "payout_at" => "2018-07-31T08:55:12Z",
        //         "instant" => false,
        //         "fee" => array( "amount" => "0.15", "currency" => "EUR" ),
        //         "amount" => array( "amount" => "13130.69", "currency" => "EUR" ),
        //         "subtotal" => array( "amount" => "13130.84", "currency" => "EUR" ),
        //         "idem" => "e549dee5-63ed-4e79-8a96",
        //         "next_step" => null
        //     }
        //
        // withdraw
        //
        //     {
        //         "id" => "a1794ecf-5693-55fa-70cf-ef731748ed82",
        //         "type" => "send",
        //         "status" => "pending",
        //         "amount" => array(
        //             "amount" => "-14.008308",
        //             "currency" => "USDC"
        //         ),
        //         "native_amount" => array(
        //             "amount" => "-18.74",
        //             "currency" => "CAD"
        //         ),
        //         "description" => null,
        //         "created_at" => "2024-01-12T01:27:31Z",
        //         "updated_at" => "2024-01-12T01:27:31Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/a34bgfad-ed67-538b-bffc-730c98c10da0/transactions/a1794ecf-5693-55fa-70cf-ef731748ed82",
        //         "instant_exchange" => false,
        //         "network" => array(
        //             "status" => "pending",
        //             "status_description" => "Pending (est. less than 10 minutes)",
        //             "transaction_fee" => array(
        //                 "amount" => "4.008308",
        //                 "currency" => "USDC"
        //             ),
        //             "transaction_amount" => array(
        //                 "amount" => "10.000000",
        //                 "currency" => "USDC"
        //             ),
        //             "confirmations" => 0
        //         ),
        //         "to" => {
        //             "resource" => "ethereum_address",
        //             "address" => "0x9...",
        //             "currency" => "USDC",
        //             "address_info" => array(
        //                 "address" => "0x9..."
        //             }
        //         ),
        //         "idem" => "748d8591-dg9a-7831-a45b-crd61dg78762",
        //         "details" => array(
        //             "title" => "Sent USDC",
        //             "subtitle" => "To USDC address on Ethereum $network",
        //             "header" => "Sent 14.008308 USDC ($18.74)",
        //             "health" => "warning"
        //         ),
        //         "hide_native_amount" => false
        //     }
        //
        //
        // crypto deposit & withdrawal (using `/transactions` endpoint)
        //    {
        //        "amount" => array(
        //            "amount" => "0.00014200", (negative for withdrawal)
        //            "currency" => "BTC"
        //        ),
        //        "created_at" => "2024-03-29T15:48:30Z",
        //        "id" => "0031a605-241d-514d-a97b-d4b99f3225d3",
        //        "idem" => "092a979b-017e-4403-940a-2ca57811f442", // field present only in case of withdrawal
        //        "native_amount" => array(
        //            "amount" => "9.85", (negative for withdrawal)
        //            "currency" => "USD"
        //        ),
        //        "network" => {
        //            "status" => "pending", // if $status is `off_blockchain` then no more other fields are property_exists($this, present) object
        //            "hash" => "5jYuvrNsvX2DZoMnzGYzVpYxJLfYu4GSK3xetG1H5LHrSovsuFCFYdFMwNRoiht3s6fBk92MM8QLLnz65xuEFTrE",
        //            "network_name" => "solana",
        //            "transaction_fee" => array(
        //                "amount" => "0.000100000",
        //                "currency" => "SOL"
        //            }
        //        ),
        //        "resource" => "transaction",
        //        "resource_path" => "/v2/accounts/dc504b1c-248e-5b68-a3b0-b991f7fa84e6/transactions/0031a605-241d-514d-a97b-d4b99f3225d3",
        //        "status" => "completed",
        //        "type" => "send",
        //        "from" => array( // in some cases, field might be present for deposit
        //            "id" => "7fd10cd7-b091-5cee-ba41-c29e49a7cccf",
        //            "name" => "Coinbase",
        //            "resource" => "user"
        //        ),
        //        "to" => array( // field only present for withdrawal
        //            "address" => "5HA12BNthAvBwNYARYf9y5MqqCpB4qhCNFCs1Qw48ACE",
        //            "resource" => "address"
        //        ),
        //        "description" => "C3 - One Time BTC Credit . Reference Case # 123.", //  in some cases, field might be present for deposit
        //    }
        //
        $transactionType = $this->safe_string($transaction, 'type');
        $amountAndCurrencyObject = null;
        $feeObject = null;
        $network = $this->safe_dict($transaction, 'network', array());
        if ($transactionType === 'send') {
            $amountAndCurrencyObject = $this->safe_dict($network, 'transaction_amount');
            $feeObject = $this->safe_dict($network, 'transaction_fee', array());
        } else {
            $amountAndCurrencyObject = $this->safe_dict($transaction, 'subtotal');
            $feeObject = $this->safe_dict($transaction, 'fee', array());
        }
        if ($amountAndCurrencyObject === null) {
            $amountAndCurrencyObject = $this->safe_dict($transaction, 'amount');
        }
        $amountString = $this->safe_string($amountAndCurrencyObject, 'amount');
        $amountStringAbs = Precise::string_abs($amountString);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        if ($status === null) {
            $committed = $this->safe_bool($transaction, 'committed');
            $status = $committed ? 'ok' : 'pending';
        }
        $id = $this->safe_string($transaction, 'id');
        $currencyId = $this->safe_string($amountAndCurrencyObject, 'currency');
        $feeCurrencyId = $this->safe_string($feeObject, 'currency');
        $datetime = $this->safe_string($transaction, 'created_at');
        $resource = $this->safe_string($transaction, 'resource');
        $type = $resource;
        if (!$this->in_array($type, array( 'deposit', 'withdrawal' ))) {
            if (Precise::string_gt($amountString, '0')) {
                $type = 'deposit';
            } elseif (Precise::string_lt($amountString, '0')) {
                $type = 'withdrawal';
            }
        }
        $toObject = $this->safe_dict($transaction, 'to');
        $addressTo = $this->safe_string($toObject, 'address');
        $networkId = $this->safe_string($network, 'network_name');
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $this->safe_string($network, 'hash', $id),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'network' => $this->network_id_to_code($networkId),
            'address' => $addressTo,
            'addressTo' => $addressTo,
            'addressFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $this->parse_number($amountStringAbs),
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'status' => $status,
            'updated' => $this->parse8601($this->safe_string($transaction, 'updated_at')),
            'fee' => array(
                'cost' => $this->safe_number($feeObject, 'amount'),
                'currency' => $this->safe_currency_code($feeCurrencyId),
            ),
        );
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchMyBuys, fetchMySells
        //
        //     {
        //         "id" => "67e0eaec-07d7-54c4-a72c-2e92826897df",
        //         "status" => "completed",
        //         "payment_method" => array(
        //             "id" => "83562370-3e5c-51db-87da-752af5ab9559",
        //             "resource" => "payment_method",
        //             "resource_path" => "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
        //         ),
        //         "transaction" => array(
        //             "id" => "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
        //             "resource" => "transaction",
        //             "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
        //         ),
        //         "amount" => array( "amount" => "1.00000000", "currency" => "BTC" ),
        //         "total" => array( "amount" => "10.25", "currency" => "USD" ),
        //         "subtotal" => array( "amount" => "10.10", "currency" => "USD" ),
        //         "created_at" => "2015-01-31T20:49:02Z",
        //         "updated_at" => "2015-02-11T16:54:02-08:00",
        //         "resource" => "buy",
        //         "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/buys/67e0eaec-07d7-54c4-a72c-2e92826897df",
        //         "committed" => true,
        //         "instant" => false,
        //         "fee" => array( "amount" => "0.15", "currency" => "USD" ),
        //         "payout_at" => "2015-02-18T16:54:00-08:00"
        //     }
        //
        // fetchTrades
        //
        //     {
        //         "trade_id" => "10092327",
        //         "product_id" => "BTC-USDT",
        //         "price" => "17488.12",
        //         "size" => "0.0000623",
        //         "time" => "2023-01-11T00:52:37.557001Z",
        //         "side" => "BUY",
        //         "bid" => "",
        //         "ask" => ""
        //     }
        //
        // fetchMyTrades
        //
        //     {
        //         "entry_id" => "b88b82cc89e326a2778874795102cbafd08dd979a2a7a3c69603fc4c23c2e010",
        //         "trade_id" => "cdc39e45-bbd3-44ec-bf02-61742dfb16a1",
        //         "order_id" => "813a53c5-3e39-47bb-863d-2faf685d22d8",
        //         "trade_time" => "2023-01-18T01:37:38.091377090Z",
        //         "trade_type" => "FILL",
        //         "price" => "21220.64",
        //         "size" => "0.0046830664333996",
        //         "commission" => "0.0000280983986004",
        //         "product_id" => "BTC-USDT",
        //         "sequence_timestamp" => "2023-01-18T01:37:38.092520Z",
        //         "liquidity_indicator" => "UNKNOWN_LIQUIDITY_INDICATOR",
        //         "size_in_quote" => true,
        //         "user_id" => "1111111-1111-1111-1111-111111111111",
        //         "side" => "BUY"
        //     }
        //
        $symbol = null;
        $totalObject = $this->safe_dict($trade, 'total', array());
        $amountObject = $this->safe_dict($trade, 'amount', array());
        $subtotalObject = $this->safe_dict($trade, 'subtotal', array());
        $feeObject = $this->safe_dict($trade, 'fee', array());
        $marketId = $this->safe_string($trade, 'product_id');
        $market = $this->safe_market($marketId, $market, '-');
        if ($market !== null) {
            $symbol = $market['symbol'];
        } else {
            $baseId = $this->safe_string($amountObject, 'currency');
            $quoteId = $this->safe_string($totalObject, 'currency');
            if (($baseId !== null) && ($quoteId !== null)) {
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $symbol = $base . '/' . $quote;
            }
        }
        $sizeInQuote = $this->safe_bool($trade, 'size_in_quote');
        $v3Price = $this->safe_string($trade, 'price');
        $v3Cost = null;
        $v3Amount = $this->safe_string($trade, 'size');
        if ($sizeInQuote) {
            // calculate $base size
            $v3Cost = $v3Amount;
            $v3Amount = Precise::string_div($v3Amount, $v3Price);
        }
        $v3FeeCost = $this->safe_string($trade, 'commission');
        $amountString = $this->safe_string($amountObject, 'amount', $v3Amount);
        $costString = $this->safe_string($subtotalObject, 'amount', $v3Cost);
        $priceString = null;
        $cost = null;
        if (($costString !== null) && ($amountString !== null)) {
            $priceString = Precise::string_div($costString, $amountString);
        } else {
            $priceString = $v3Price;
        }
        if (($priceString !== null) && ($amountString !== null)) {
            $cost = Precise::string_mul($priceString, $amountString);
        } else {
            $cost = $costString;
        }
        $feeCurrencyId = $this->safe_string($feeObject, 'currency');
        $feeCost = $this->safe_number($feeObject, 'amount', $this->parse_number($v3FeeCost));
        if (($feeCurrencyId === null) && ($market !== null) && ($feeCost !== null)) {
            $feeCurrencyId = $market['quote'];
        }
        $datetime = $this->safe_string_n($trade, array( 'created_at', 'trade_time', 'time' ));
        $side = $this->safe_string_lower_2($trade, 'resource', 'side');
        $takerOrMaker = $this->safe_string_lower($trade, 'liquidity_indicator');
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $this->safe_string_2($trade, 'id', 'trade_id'),
            'order' => $this->safe_string($trade, 'order_id'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'symbol' => $symbol,
            'type' => null,
            'side' => ($side === 'unknown_order_side') ? null : $side,
            'takerOrMaker' => ($takerOrMaker === 'unknown_liquidity_indicator') ? null : $takerOrMaker,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => $cost,
            'fee' => array(
                'cost' => $feeCost,
                'currency' => $this->safe_currency_code($feeCurrencyId),
            ),
        ));
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicproducts
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-currencies#get-fiat-currencies
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
             *
             * retrieves data on all markets for coinbase
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->usePrivate] use private endpoint for fetching markets
             * @return {array[]} an array of objects representing market data
             */
            if ($this->options['adjustForTimeDifference']) {
                Async\await($this->load_time_difference());
            }
            $method = $this->safe_string($this->options, 'fetchMarkets', 'fetchMarketsV3');
            if ($method === 'fetchMarketsV3') {
                return Async\await($this->fetch_markets_v3($params));
            }
            return Async\await($this->fetch_markets_v2($params));
        }) ();
    }

    public function fetch_markets_v2($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            $response = Async\await($this->fetch_currencies_from_cache($params));
            $currencies = $this->safe_dict($response, 'currencies', array());
            $exchangeRates = $this->safe_dict($response, 'exchangeRates', array());
            $data = $this->safe_list($currencies, 'data', array());
            $dataById = $this->index_by($data, 'id');
            $rates = $this->safe_dict($this->safe_dict($exchangeRates, 'data', array()), 'rates', array());
            $baseIds = is_array($rates) ? array_keys($rates) : array();
            $result = array();
            for ($i = 0; $i < count($baseIds); $i++) {
                $baseId = $baseIds[$i];
                $base = $this->safe_currency_code($baseId);
                $type = (is_array($dataById) && array_key_exists($baseId, $dataById)) ? 'fiat' : 'crypto';
                // https://github.com/ccxt/ccxt/issues/6066
                if ($type === 'crypto') {
                    for ($j = 0; $j < count($data); $j++) {
                        $quoteCurrency = $data[$j];
                        $quoteId = $this->safe_string($quoteCurrency, 'id');
                        $quote = $this->safe_currency_code($quoteId);
                        $result[] = $this->safe_market_structure(array(
                            'id' => $baseId . '-' . $quoteId,
                            'symbol' => $base . '/' . $quote,
                            'base' => $base,
                            'quote' => $quote,
                            'settle' => null,
                            'baseId' => $baseId,
                            'quoteId' => $quoteId,
                            'settleId' => null,
                            'type' => 'spot',
                            'spot' => true,
                            'margin' => false,
                            'swap' => false,
                            'future' => false,
                            'option' => false,
                            'active' => null,
                            'contract' => false,
                            'linear' => null,
                            'inverse' => null,
                            'contractSize' => null,
                            'expiry' => null,
                            'expiryDatetime' => null,
                            'strike' => null,
                            'optionType' => null,
                            'precision' => array(
                                'amount' => null,
                                'price' => null,
                            ),
                            'limits' => array(
                                'leverage' => array(
                                    'min' => null,
                                    'max' => null,
                                ),
                                'amount' => array(
                                    'min' => null,
                                    'max' => null,
                                ),
                                'price' => array(
                                    'min' => null,
                                    'max' => null,
                                ),
                                'cost' => array(
                                    'min' => $this->safe_number($quoteCurrency, 'min_size'),
                                    'max' => null,
                                ),
                            ),
                            'info' => $quoteCurrency,
                        ));
                    }
                }
            }
            return $result;
        }) ();
    }

    public function fetch_markets_v3($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchMarkets', 'usePrivate', false);
            $spotUnresolvedPromises = array();
            if ($usePrivate) {
                $spotUnresolvedPromises[] = $this->v3PrivateGetBrokerageProducts ($params);
            } else {
                $spotUnresolvedPromises[] = $this->v3PublicGetBrokerageMarketProducts ($params);
            }
            //
            //    {
            //        products => array(
            //            array(
            //                product_id => 'BTC-USD',
            //                price => '67060',
            //                price_percentage_change_24h => '3.30054960636883',
            //                volume_24h => '10967.87426597',
            //                volume_percentage_change_24h => '141.73048325503036',
            //                base_increment => '0.00000001',
            //                quote_increment => '0.01',
            //                quote_min_size => '1',
            //                quote_max_size => '150000000',
            //                base_min_size => '0.00000001',
            //                base_max_size => '3400',
            //                base_name => 'Bitcoin',
            //                quote_name => 'US Dollar',
            //                watched => false,
            //                is_disabled => false,
            //                new => false,
            //                status => 'online',
            //                cancel_only => false,
            //                limit_only => false,
            //                post_only => false,
            //                trading_disabled => false,
            //                auction_mode => false,
            //                product_type => 'SPOT',
            //                quote_currency_id => 'USD',
            //                base_currency_id => 'BTC',
            //                fcm_trading_session_details => null,
            //                mid_market_price => '',
            //                alias => '',
            //                alias_to => array( 'BTC-USDC' ),
            //                base_display_symbol => 'BTC',
            //                quote_display_symbol => 'USD',
            //                view_only => false,
            //                price_increment => '0.01',
            //                display_name => 'BTC-USD',
            //                product_venue => 'CBE'
            //            ),
            //            ...
            //        ),
            //        num_products => '646'
            //    }
            //
            if ($this->check_required_credentials(false)) {
                $spotUnresolvedPromises[] = $this->v3PrivateGetBrokerageTransactionSummary ($params);
            }
            //
            //    {
            //        total_volume => '9.995989116664404',
            //        total_fees => '0.07996791093331522',
            //        fee_tier => array(
            //            pricing_tier => 'Advanced 1',
            //            usd_from => '0',
            //            usd_to => '1000',
            //            taker_fee_rate => '0.008',
            //            maker_fee_rate => '0.006',
            //            aop_from => '',
            //            aop_to => ''
            //        ),
            //        margin_rate => null,
            //        goods_and_services_tax => null,
            //        advanced_trade_only_volume => '9.995989116664404',
            //        advanced_trade_only_fees => '0.07996791093331522',
            //        coinbase_pro_volume => '0',
            //        coinbase_pro_fees => '0',
            //        total_balance => '',
            //        has_promo_fee => false
            //    }
            //
            $unresolvedContractPromises = array();
            try {
                $unresolvedContractPromises = array(
                    $this->v3PublicGetBrokerageMarketProducts ($this->extend($params, array( 'product_type' => 'FUTURE' ))),
                    $this->v3PublicGetBrokerageMarketProducts ($this->extend($params, array( 'product_type' => 'FUTURE', 'contract_expiry_type' => 'PERPETUAL' ))),
                );
            } catch (Exception $e) {
                $unresolvedContractPromises = array(); // the sync version of ccxt won't have the promise.all line so the request is made here. Some users can't access perpetual products
            }
            $promises = Async\await(Promise\all($spotUnresolvedPromises));
            $contractPromises = null;
            try {
                $contractPromises = Async\await(Promise\all($unresolvedContractPromises)); // some users don't have access to contracts
            } catch (Exception $e) {
                $contractPromises = array();
            }
            $spot = $this->safe_dict($promises, 0, array());
            $fees = $this->safe_dict($promises, 1, array());
            $expiringFutures = $this->safe_dict($contractPromises, 0, array());
            $perpetualFutures = $this->safe_dict($contractPromises, 1, array());
            $expiringFees = $this->safe_dict($contractPromises, 0, array());
            $perpetualFees = $this->safe_dict($contractPromises, 1, array());
            //
            //     {
            //         "total_volume" => 0,
            //         "total_fees" => 0,
            //         "fee_tier" => array(
            //             "pricing_tier" => "",
            //             "usd_from" => "0",
            //             "usd_to" => "10000",
            //             "taker_fee_rate" => "0.006",
            //             "maker_fee_rate" => "0.004"
            //         ),
            //         "margin_rate" => null,
            //         "goods_and_services_tax" => null,
            //         "advanced_trade_only_volume" => 0,
            //         "advanced_trade_only_fees" => 0,
            //         "coinbase_pro_volume" => 0,
            //         "coinbase_pro_fees" => 0
            //     }
            //
            $feeTier = $this->safe_dict($fees, 'fee_tier', array());
            $expiringFeeTier = $this->safe_dict($expiringFees, 'fee_tier', array()); // fee tier null?
            $perpetualFeeTier = $this->safe_dict($perpetualFees, 'fee_tier', array()); // fee tier null?
            $data = $this->safe_list($spot, 'products', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $result[] = $this->parse_spot_market($data[$i], $feeTier);
            }
            $futureData = $this->safe_list($expiringFutures, 'products', array());
            for ($i = 0; $i < count($futureData); $i++) {
                $result[] = $this->parse_contract_market($futureData[$i], $expiringFeeTier);
            }
            $perpetualData = $this->safe_list($perpetualFutures, 'products', array());
            for ($i = 0; $i < count($perpetualData); $i++) {
                $result[] = $this->parse_contract_market($perpetualData[$i], $perpetualFeeTier);
            }
            $newMarkets = array();
            for ($i = 0; $i < count($result); $i++) {
                $market = $result[$i];
                $info = $this->safe_value($market, 'info', array());
                $realMarketIds = $this->safe_list($info, 'alias_to', array());
                $length = count($realMarketIds);
                if ($length > 0) {
                    $market['alias'] = $realMarketIds[0];
                } else {
                    $market['alias'] = null;
                }
                $newMarkets[] = $market;
            }
            return $newMarkets;
        }) ();
    }

    public function parse_spot_market($market, $feeTier): array {
        //
        //         {
        //             "product_id" => "TONE-USD",
        //             "price" => "0.01523",
        //             "price_percentage_change_24h" => "1.94109772423025",
        //             "volume_24h" => "19773129",
        //             "volume_percentage_change_24h" => "437.0170530929949",
        //             "base_increment" => "1",
        //             "quote_increment" => "0.00001",
        //             "quote_min_size" => "1",
        //             "quote_max_size" => "10000000",
        //             "base_min_size" => "26.7187147229469674",
        //             "base_max_size" => "267187147.2294696735908216",
        //             "base_name" => "TE-FOOD",
        //             "quote_name" => "US Dollar",
        //             "watched" => false,
        //             "is_disabled" => false,
        //             "new" => false,
        //             "status" => "online",
        //             "cancel_only" => false,
        //             "limit_only" => false,
        //             "post_only" => false,
        //             "trading_disabled" => false,
        //             "auction_mode" => false,
        //             "product_type" => "SPOT",
        //             "quote_currency_id" => "USD",
        //             "base_currency_id" => "TONE",
        //             "fcm_trading_session_details" => null,
        //             "mid_market_price" => ""
        //         }
        //
        $id = $this->safe_string($market, 'product_id');
        $baseId = $this->safe_string($market, 'base_currency_id');
        $quoteId = $this->safe_string($market, 'quote_currency_id');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $marketType = $this->safe_string_lower($market, 'product_type');
        $tradingDisabled = $this->safe_bool($market, 'trading_disabled');
        $stablePairs = $this->safe_list($this->options, 'stablePairs', array());
        $defaultTakerFee = $this->safe_number($this->fees['trading'], 'taker');
        $defaultMakerFee = $this->safe_number($this->fees['trading'], 'maker');
        $takerFee = $this->in_array($id, $stablePairs) ? 0.00001 : $this->safe_number($feeTier, 'taker_fee_rate', $defaultTakerFee);
        $makerFee = $this->in_array($id, $stablePairs) ? 0.0 : $this->safe_number($feeTier, 'maker_fee_rate', $defaultMakerFee);
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $base . '/' . $quote,
            'base' => $base,
            'quote' => $quote,
            'settle' => null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => null,
            'type' => $marketType,
            'spot' => ($marketType === 'spot'),
            'margin' => null,
            'swap' => false,
            'future' => false,
            'option' => false,
            'active' => !$tradingDisabled,
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'taker' => $takerFee,
            'maker' => $makerFee,
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'base_increment'),
                'price' => $this->safe_number_2($market, 'price_increment', 'quote_increment'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'base_min_size'),
                    'max' => $this->safe_number($market, 'base_max_size'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'quote_min_size'),
                    'max' => $this->safe_number($market, 'quote_max_size'),
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function parse_contract_market($market, $feeTier): array {
        // expiring
        //
        //        {
        //           "product_id":"BIT-26APR24-CDE",
        //           "price":"71145",
        //           "price_percentage_change_24h":"-2.36722931247427",
        //           "volume_24h":"108549",
        //           "volume_percentage_change_24h":"155.78255337197794",
        //           "base_increment":"1",
        //           "quote_increment":"0.01",
        //           "quote_min_size":"0",
        //           "quote_max_size":"100000000",
        //           "base_min_size":"1",
        //           "base_max_size":"100000000",
        //           "base_name":"",
        //           "quote_name":"US Dollar",
        //           "watched":false,
        //           "is_disabled":false,
        //           "new":false,
        //           "status":"",
        //           "cancel_only":false,
        //           "limit_only":false,
        //           "post_only":false,
        //           "trading_disabled":false,
        //           "auction_mode":false,
        //           "product_type":"FUTURE",
        //           "quote_currency_id":"USD",
        //           "base_currency_id":"",
        //           "fcm_trading_session_details":array(
        //              "is_session_open":true,
        //              "open_time":"2024-04-08T22:00:00Z",
        //              "close_time":"2024-04-09T21:00:00Z"
        //           ),
        //           "mid_market_price":"71105",
        //           "alias":"",
        //           "alias_to":array(
        //           ),
        //           "base_display_symbol":"",
        //           "quote_display_symbol":"USD",
        //           "view_only":false,
        //           "price_increment":"5",
        //           "display_name":"BTC 26 APR 24",
        //           "product_venue":"FCM",
        //           "future_product_details":{
        //              "venue":"cde",
        //              "contract_code":"BIT",
        //              "contract_expiry":"2024-04-26T15:00:00Z",
        //              "contract_size":"0.01",
        //              "contract_root_unit":"BTC",
        //              "group_description":"Nano Bitcoin Futures",
        //              "contract_expiry_timezone":"Europe/London",
        //              "group_short_description":"Nano BTC",
        //              "risk_managed_by":"MANAGED_BY_FCM",
        //              "contract_expiry_type":"EXPIRING",
        //              "contract_display_name":"BTC 26 APR 24"
        //           }
        //        }
        //
        // perpetual
        //
        //        {
        //           "product_id":"ETH-PERP-INTX",
        //           "price":"3630.98",
        //           "price_percentage_change_24h":"0.65142426292038",
        //           "volume_24h":"114020.1501",
        //           "volume_percentage_change_24h":"63.33650787154869",
        //           "base_increment":"0.0001",
        //           "quote_increment":"0.01",
        //           "quote_min_size":"10",
        //           "quote_max_size":"50000000",
        //           "base_min_size":"0.0001",
        //           "base_max_size":"50000",
        //           "base_name":"",
        //           "quote_name":"USDC",
        //           "watched":false,
        //           "is_disabled":false,
        //           "new":false,
        //           "status":"",
        //           "cancel_only":false,
        //           "limit_only":false,
        //           "post_only":false,
        //           "trading_disabled":false,
        //           "auction_mode":false,
        //           "product_type":"FUTURE",
        //           "quote_currency_id":"USDC",
        //           "base_currency_id":"",
        //           "fcm_trading_session_details":null,
        //           "mid_market_price":"3630.975",
        //           "alias":"",
        //           "alias_to":array(),
        //           "base_display_symbol":"",
        //           "quote_display_symbol":"USDC",
        //           "view_only":false,
        //           "price_increment":"0.01",
        //           "display_name":"ETH PERP",
        //           "product_venue":"INTX",
        //           "future_product_details":{
        //              "venue":"",
        //              "contract_code":"ETH",
        //              "contract_expiry":null,
        //              "contract_size":"1",
        //              "contract_root_unit":"ETH",
        //              "group_description":"",
        //              "contract_expiry_timezone":"",
        //              "group_short_description":"",
        //              "risk_managed_by":"MANAGED_BY_VENUE",
        //              "contract_expiry_type":"PERPETUAL",
        //              "perpetual_details":array(
        //                 "open_interest":"0",
        //                 "funding_rate":"0.000016",
        //                 "funding_time":"2024-04-09T09:00:00.000008Z",
        //                 "max_leverage":"10"
        //              ),
        //              "contract_display_name":"ETH PERPETUAL"
        //           }
        //        }
        //
        $id = $this->safe_string($market, 'product_id');
        $futureProductDetails = $this->safe_dict($market, 'future_product_details', array());
        $contractExpiryType = $this->safe_string($futureProductDetails, 'contract_expiry_type');
        $contractSize = $this->safe_number($futureProductDetails, 'contract_size');
        $contractExpire = $this->safe_string($futureProductDetails, 'contract_expiry');
        $expireTimestamp = $this->parse8601($contractExpire);
        $expireDateTime = $this->iso8601($expireTimestamp);
        $isSwap = ($contractExpiryType === 'PERPETUAL');
        $baseId = $this->safe_string($futureProductDetails, 'contract_root_unit');
        $quoteId = $this->safe_string($market, 'quote_currency_id');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $tradingDisabled = $this->safe_bool($market, 'is_disabled');
        $symbol = $base . '/' . $quote;
        $type = null;
        if ($isSwap) {
            $type = 'swap';
            $symbol = $symbol . ':' . $quote;
        } else {
            $type = 'future';
            $symbol = $symbol . ':' . $quote . '-' . $this->yymmdd($expireTimestamp);
        }
        $takerFeeRate = $this->safe_number($feeTier, 'taker_fee_rate');
        $makerFeeRate = $this->safe_number($feeTier, 'maker_fee_rate');
        $taker = $takerFeeRate ? $takerFeeRate : $this->parse_number('0.06');
        $maker = $makerFeeRate ? $makerFeeRate : $this->parse_number('0.04');
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $quoteId,
            'type' => $type,
            'spot' => false,
            'margin' => false,
            'swap' => $isSwap,
            'future' => !$isSwap,
            'option' => false,
            'active' => !$tradingDisabled,
            'contract' => true,
            'linear' => true,
            'inverse' => false,
            'taker' => $taker,
            'maker' => $maker,
            'contractSize' => $contractSize,
            'expiry' => $expireTimestamp,
            'expiryDatetime' => $expireDateTime,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'base_increment'),
                'price' => $this->safe_number_2($market, 'price_increment', 'quote_increment'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'base_min_size'),
                    'max' => $this->safe_number($market, 'base_max_size'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'quote_min_size'),
                    'max' => $this->safe_number($market, 'quote_max_size'),
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function fetch_currencies_from_cache($params = array ()) {
        return Async\async(function () use ($params) {
            $options = $this->safe_dict($this->options, 'fetchCurrencies', array());
            $timestamp = $this->safe_integer($options, 'timestamp');
            $expires = $this->safe_integer($options, 'expires', 1000);
            $now = $this->milliseconds();
            if (($timestamp === null) || (($now - $timestamp) > $expires)) {
                $promises = array(
                    $this->v2PublicGetCurrencies ($params),
                    $this->v2PublicGetCurrenciesCrypto ($params),
                );
                $promisesResult = Async\await(Promise\all($promises));
                $fiatResponse = $this->safe_dict($promisesResult, 0, array());
                //
                //    array(
                //        "data" => array(
                //            id => 'IMP',
                //            name => 'Isle of Man Pound',
                //            min_size => '0.01'
                //        ),
                //        ...
                //    )
                //
                $cryptoResponse = $this->safe_dict($promisesResult, 1, array());
                //
                //    {
                //        asset_id => '9476e3be-b731-47fa-82be-347fabc573d9',
                //        code => 'AERO',
                //        name => 'Aerodrome Finance',
                //        color => '#0433FF',
                //        sort_index => '340',
                //        exponent => '8',
                //        type => 'crypto',
                //        address_regex => '^(?:0x)?[0-9a-fA-F]{40}$'
                //    }
                //
                $fiatData = $this->safe_list($fiatResponse, 'data', array());
                $cryptoData = $this->safe_list($cryptoResponse, 'data', array());
                $exchangeRates = Async\await($this->v2PublicGetExchangeRates ($params));
                $this->options['fetchCurrencies'] = $this->extend($options, array(
                    'currencies' => $this->array_concat($fiatData, $cryptoData),
                    'exchangeRates' => $exchangeRates,
                    'timestamp' => $now,
                ));
            }
            return $this->safe_dict($this->options, 'fetchCurrencies', array());
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available $currencies on an exchange
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-$currencies#get-fiat-$currencies
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-$rates#get-exchange-$rates
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of $currencies
             */
            $promises = array(
                $this->v2PublicGetCurrencies ($params),
                $this->v2PublicGetCurrenciesCrypto ($params),
                $this->v2PublicGetExchangeRates ($params),
            );
            $promisesResult = Async\await(Promise\all($promises));
            $fiatResponse = $this->safe_dict($promisesResult, 0, array());
            //
            //    [
            //        "data" => [
            //            array(
            //                $id => 'IMP',
            //                $name => 'Isle of Man Pound',
            //                min_size => '0.01'
            //            ),
            //        ...
            //
            $cryptoResponse = $this->safe_dict($promisesResult, 1, array());
            //
            //     [
            //        "data" => [
            //           array(
            //              asset_id => '9476e3be-b731-47fa-82be-347fabc573d9',
            //              $code => 'AERO',
            //              $name => 'Aerodrome Finance',
            //              color => '#0433FF',
            //              sort_index => '340',
            //              exponent => '8',
            //              $type => 'crypto',
            //              address_regex => '^(?:0x)?[0-9a-fA-F]{40}$'
            //           ),
            //          ...
            //
            $ratesResponse = $this->safe_dict($promisesResult, 2, array());
            $fiatData = $this->safe_list($fiatResponse, 'data', array());
            $cryptoData = $this->safe_list($cryptoResponse, 'data', array());
            $ratesData = $this->safe_dict($ratesResponse, 'data', array());
            $rates = $this->safe_dict($ratesData, 'rates', array());
            $ratesIds = is_array($rates) ? array_keys($rates) : array();
            $currencies = $this->array_concat($fiatData, $cryptoData);
            $result = array();
            $networks = array();
            $networksById = array();
            for ($i = 0; $i < count($currencies); $i++) {
                $currency = $currencies[$i];
                $assetId = $this->safe_string($currency, 'asset_id');
                $id = $this->safe_string_2($currency, 'id', 'code');
                $code = $this->safe_currency_code($id);
                $name = $this->safe_string($currency, 'name');
                $this->options['networks'][$code] = strtolower($name);
                $this->options['networksById'][$code] = strtolower($name);
                $type = ($assetId !== null) ? 'crypto' : 'fiat';
                $result[$code] = $this->safe_currency_structure(array(
                    'info' => $currency,
                    'id' => $id,
                    'code' => $code,
                    'type' => $type,
                    'name' => $name,
                    'active' => true,
                    'deposit' => null,
                    'withdraw' => null,
                    'fee' => null,
                    'precision' => null,
                    'networks' => array(), // todo
                    'limits' => array(
                        'amount' => array(
                            'min' => $this->safe_number($currency, 'min_size'),
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                ));
                if ($assetId !== null) {
                    $lowerCaseName = strtolower($name);
                    $networks[$code] = $lowerCaseName;
                    $networksById[$lowerCaseName] = $code;
                }
            }
            // we have to add other $currencies here ( https://discord.com/channels/1220414409550336183/1220464770239430761/1372215891940479098 )
            for ($i = 0; $i < count($ratesIds); $i++) {
                $currencyId = $ratesIds[$i];
                $code = $this->safe_currency_code($currencyId);
                if (!(is_array($result) && array_key_exists($code, $result))) {
                    $result[$code] = $this->safe_currency_structure(array(
                        'info' => array(),
                        'id' => $currencyId,
                        'code' => $code,
                        'type' => 'crypto',
                        'networks' => array(), // todo
                    ));
                }
            }
            $this->options['networks'] = $this->extend($networks, $this->options['networks']);
            $this->options['networksById'] = $this->extend($networksById, $this->options['networksById']);
            return $result;
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getproducts
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
             *
             * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->usePrivate] use private endpoint for fetching tickers
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            $method = $this->safe_string($this->options, 'fetchTickers', 'fetchTickersV3');
            if ($method === 'fetchTickersV3') {
                return Async\await($this->fetch_tickers_v3($symbols, $params));
            }
            return Async\await($this->fetch_tickers_v2($symbols, $params));
        }) ();
    }

    public function fetch_tickers_v2(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $request = array(
                // 'currency' => 'USD',
            );
            $response = Async\await($this->v2PublicGetExchangeRates ($this->extend($request, $params)));
            //
            //     {
            //         "data":{
            //             "currency":"USD",
            //             "rates":{
            //                 "AED":"3.6731",
            //                 "AFN":"103.163942",
            //                 "ALL":"106.973038",
            //             }
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rates = $this->safe_dict($data, 'rates', array());
            $quoteId = $this->safe_string($data, 'currency');
            $result = array();
            $baseIds = is_array($rates) ? array_keys($rates) : array();
            $delimiter = '-';
            for ($i = 0; $i < count($baseIds); $i++) {
                $baseId = $baseIds[$i];
                $marketId = $baseId . $delimiter . $quoteId;
                $market = $this->safe_market($marketId, null, $delimiter);
                $symbol = $market['symbol'];
                $result[$symbol] = $this->parse_ticker($rates[$baseId], $market);
            }
            return $this->filter_by_array_tickers($result, 'symbol', $symbols);
        }) ();
    }

    public function fetch_tickers_v3(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $request = array();
            if ($symbols !== null) {
                $request['product_ids'] = $this->market_ids($symbols);
            }
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchTickers', $this->get_market_from_symbols($symbols), $params, 'default');
            if ($marketType !== null && $marketType !== 'default') {
                $request['product_type'] = ($marketType === 'swap') ? 'FUTURE' : 'SPOT';
            }
            $response = null;
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchTickers', 'usePrivate', false);
            if ($usePrivate) {
                $response = Async\await($this->v3PrivateGetBrokerageProducts ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PublicGetBrokerageMarketProducts ($this->extend($request, $params)));
            }
            //
            //     {
            //         "products" => array(
            //             array(
            //                 "product_id" => "TONE-USD",
            //                 "price" => "0.01523",
            //                 "price_percentage_change_24h" => "1.94109772423025",
            //                 "volume_24h" => "19773129",
            //                 "volume_percentage_change_24h" => "437.0170530929949",
            //                 "base_increment" => "1",
            //                 "quote_increment" => "0.00001",
            //                 "quote_min_size" => "1",
            //                 "quote_max_size" => "10000000",
            //                 "base_min_size" => "26.7187147229469674",
            //                 "base_max_size" => "267187147.2294696735908216",
            //                 "base_name" => "TE-FOOD",
            //                 "quote_name" => "US Dollar",
            //                 "watched" => false,
            //                 "is_disabled" => false,
            //                 "new" => false,
            //                 "status" => "online",
            //                 "cancel_only" => false,
            //                 "limit_only" => false,
            //                 "post_only" => false,
            //                 "trading_disabled" => false,
            //                 "auction_mode" => false,
            //                 "product_type" => "SPOT",
            //                 "quote_currency_id" => "USD",
            //                 "base_currency_id" => "TONE",
            //                 "fcm_trading_session_details" => null,
            //                 "mid_market_price" => ""
            //             ),
            //             ...
            //         ),
            //         "num_products" => 549
            //     }
            //
            $data = $this->safe_list($response, 'products', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $marketId = $this->safe_string($entry, 'product_id');
                $market = $this->safe_market($marketId, null, '-');
                $symbol = $market['symbol'];
                $result[$symbol] = $this->parse_ticker($entry, $market);
            }
            return $this->filter_by_array_tickers($result, 'symbol', $symbols);
        }) ();
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getmarkettrades
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-spot-price
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-buy-price
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-sell-price
             *
             * @param {string} $symbol unified $symbol of the market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->usePrivate] whether to use the private endpoint for fetching the ticker
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            $method = $this->safe_string($this->options, 'fetchTicker', 'fetchTickerV3');
            if ($method === 'fetchTickerV3') {
                return Async\await($this->fetch_ticker_v3($symbol, $params));
            }
            return Async\await($this->fetch_ticker_v2($symbol, $params));
        }) ();
    }

    public function fetch_ticker_v2(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = $this->extend(array(
                'symbol' => $market['id'],
            ), $params);
            $spot = Async\await($this->v2PublicGetPricesSymbolSpot ($request));
            //
            //     array("data":array("base":"BTC","currency":"USD","amount":"48691.23"))
            //
            $ask = Async\await($this->v2PublicGetPricesSymbolBuy ($request));
            //
            //     array("data":array("base":"BTC","currency":"USD","amount":"48691.23"))
            //
            $bid = Async\await($this->v2PublicGetPricesSymbolSell ($request));
            //
            //     array("data":array("base":"BTC","currency":"USD","amount":"48691.23"))
            //
            $spotData = $this->safe_dict($spot, 'data', array());
            $askData = $this->safe_dict($ask, 'data', array());
            $bidData = $this->safe_dict($bid, 'data', array());
            $bidAskLast = array(
                'bid' => $this->safe_number($bidData, 'amount'),
                'ask' => $this->safe_number($askData, 'amount'),
                'price' => $this->safe_number($spotData, 'amount'),
            );
            return $this->parse_ticker($bidAskLast, $market);
        }) ();
    }

    public function fetch_ticker_v3(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'product_id' => $market['id'],
                'limit' => 1,
            );
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchTicker', 'usePrivate', false);
            $response = null;
            if ($usePrivate) {
                $response = Async\await($this->v3PrivateGetBrokerageProductsProductIdTicker ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PublicGetBrokerageMarketProductsProductIdTicker ($this->extend($request, $params)));
            }
            //
            //     {
            //         "trades" => array(
            //             {
            //                 "trade_id" => "518078013",
            //                 "product_id" => "BTC-USD",
            //                 "price" => "28208.1",
            //                 "size" => "0.00659179",
            //                 "time" => "2023-04-04T23:05:34.492746Z",
            //                 "side" => "BUY",
            //                 "bid" => "",
            //                 "ask" => ""
            //             }
            //         ),
            //         "best_bid" => "28208.61",
            //         "best_ask" => "28208.62"
            //     }
            //
            $data = $this->safe_list($response, 'trades', array());
            $ticker = $this->parse_ticker($data[0], $market);
            $ticker['bid'] = $this->safe_number($response, 'best_bid');
            $ticker['ask'] = $this->safe_number($response, 'best_ask');
            return $ticker;
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // fetchTickerV2
        //
        //     {
        //         "bid" => 20713.37,
        //         "ask" => 20924.65,
        //         "price" => 20809.83
        //     }
        //
        // fetchTickerV3
        //
        //     {
        //         "trade_id" => "10209805",
        //         "product_id" => "BTC-USDT",
        //         "price" => "19381.27",
        //         "size" => "0.1",
        //         "time" => "2023-01-13T20:35:41.865970Z",
        //         "side" => "BUY",
        //         "bid" => "",
        //         "ask" => ""
        //     }
        //
        // fetchTickersV2
        //
        //     "48691.23"
        //
        // fetchTickersV3
        //
        //     array(
        //        array(
        //            "product_id" => "ETH-USD",
        //            "price" => "4471.59",
        //            "price_percentage_change_24h" => "0.14243387238731",
        //            "volume_24h" => "87329.92990204",
        //            "volume_percentage_change_24h" => "-60.7789801794578",
        //            "base_increment" => "0.00000001",
        //            "quote_increment" => "0.01",
        //            "quote_min_size" => "1",
        //            "quote_max_size" => "150000000",
        //            "base_min_size" => "0.00000001",
        //            "base_max_size" => "42000",
        //            "base_name" => "Ethereum",
        //            "quote_name" => "US Dollar",
        //            "watched" => false,
        //            "is_disabled" => false,
        //            "new" => false,
        //            "status" => "online",
        //            "cancel_only" => false,
        //            "limit_only" => false,
        //            "post_only" => false,
        //            "trading_disabled" => false,
        //            "auction_mode" => false,
        //            "product_type" => "SPOT",
        //            "quote_currency_id" => "USD",
        //            "base_currency_id" => "ETH",
        //            "fcm_trading_session_details" => null,
        //            "mid_market_price" => "",
        //            "alias" => "",
        //            "alias_to" => array( "ETH-USDC" ),
        //            "base_display_symbol" => "ETH",
        //            "quote_display_symbol" => "USD",
        //            "view_only" => false,
        //            "price_increment" => "0.01",
        //            "display_name" => "ETH-USD",
        //            "product_venue" => "CBE",
        //            "approximate_quote_24h_volume" => "390503641.25",
        //            "new_at" => "2023-01-01T00:00:00Z"
        //         ),
        //         ...
        //     )
        //
        // fetchBidsAsks
        //
        //     {
        //         "product_id" => "TRAC-EUR",
        //         "bids" => array(
        //             {
        //                 "price" => "0.2384",
        //                 "size" => "386.1"
        //             }
        //         ),
        //         "asks" => array(
        //             {
        //                 "price" => "0.2406",
        //                 "size" => "672"
        //             }
        //         ),
        //         "time" => "2023-06-30T07:15:24.656044Z"
        //     }
        //
        $bid = $this->safe_number($ticker, 'bid');
        $ask = $this->safe_number($ticker, 'ask');
        $bidVolume = null;
        $askVolume = null;
        if ((is_array($ticker) && array_key_exists('bids', $ticker))) {
            $bids = $this->safe_list($ticker, 'bids', array());
            $asks = $this->safe_list($ticker, 'asks', array());
            $firstBid = $this->safe_dict($bids, 0, array());
            $firstAsk = $this->safe_dict($asks, 0, array());
            $bid = $this->safe_number($firstBid, 'price');
            $bidVolume = $this->safe_number($firstBid, 'size');
            $ask = $this->safe_number($firstAsk, 'price');
            $askVolume = $this->safe_number($firstAsk, 'size');
        }
        $marketId = $this->safe_string($ticker, 'product_id');
        $market = $this->safe_market($marketId, $market);
        $last = $this->safe_number($ticker, 'price');
        $datetime = $this->safe_string($ticker, 'time');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'bid' => $bid,
            'ask' => $ask,
            'last' => $last,
            'high' => null,
            'low' => null,
            'bidVolume' => $bidVolume,
            'askVolume' => $askVolume,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $this->safe_number($ticker, 'price_percentage_change_24h'),
            'average' => null,
            'baseVolume' => $this->safe_number($ticker, 'volume_24h'),
            'quoteVolume' => $this->safe_number($ticker, 'approximate_quote_24h_volume'),
            'info' => $ticker,
        ), $market);
    }

    public function parse_custom_balance($response, $params = array ()) {
        $balances = $this->safe_list_2($response, 'data', 'accounts', array());
        $accounts = $this->safe_list($params, 'type', $this->options['accounts']);
        $v3Accounts = $this->safe_list($params, 'type', $this->options['v3Accounts']);
        $result = array( 'info' => $response );
        for ($b = 0; $b < count($balances); $b++) {
            $balance = $balances[$b];
            $type = $this->safe_string($balance, 'type');
            if ($this->in_array($type, $accounts)) {
                $value = $this->safe_dict($balance, 'balance');
                if ($value !== null) {
                    $currencyId = $this->safe_string($value, 'currency');
                    $code = $this->safe_currency_code($currencyId);
                    $total = $this->safe_string($value, 'amount');
                    $free = $total;
                    $account = $this->safe_dict($result, $code);
                    if ($account === null) {
                        $account = $this->account();
                        $account['free'] = $free;
                        $account['total'] = $total;
                    } else {
                        $account['free'] = Precise::string_add($account['free'], $total);
                        $account['total'] = Precise::string_add($account['total'], $total);
                    }
                    $result[$code] = $account;
                }
            } elseif ($this->in_array($type, $v3Accounts)) {
                $available = $this->safe_dict($balance, 'available_balance');
                $hold = $this->safe_dict($balance, 'hold');
                if ($available !== null && $hold !== null) {
                    $currencyId = $this->safe_string($available, 'currency');
                    $code = $this->safe_currency_code($currencyId);
                    $used = $this->safe_string($hold, 'value');
                    $free = $this->safe_string($available, 'value');
                    $total = Precise::string_add($used, $free);
                    $account = $this->safe_dict($result, $code);
                    if ($account === null) {
                        $account = $this->account();
                        $account['free'] = $free;
                        $account['used'] = $used;
                        $account['total'] = $total;
                    } else {
                        $account['free'] = Precise::string_add($account['free'], $free);
                        $account['used'] = Precise::string_add($account['used'], $used);
                        $account['total'] = Precise::string_add($account['total'], $total);
                    }
                    $result[$code] = $account;
                }
            }
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmbalancesummary
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->v3] default false, set true to use v3 api endpoint
             * @param {string} [$params->type] "spot" (default) or "swap" or "future"
             * @param {int} [$params->limit] default 250, maximum number of accounts to return
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $request = array();
            $response = null;
            $isV3 = $this->safe_bool($params, 'v3', false);
            $params = $this->omit($params, array( 'v3' ));
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
            $method = $this->safe_string($this->options, 'fetchBalance', 'v3PrivateGetBrokerageAccounts');
            if ($marketType === 'future') {
                $response = Async\await($this->v3PrivateGetBrokerageCfmBalanceSummary ($this->extend($request, $params)));
            } elseif (($isV3) || ($method === 'v3PrivateGetBrokerageAccounts')) {
                $request['limit'] = 250;
                $response = Async\await($this->v3PrivateGetBrokerageAccounts ($this->extend($request, $params)));
            } else {
                $request['limit'] = 250;
                $response = Async\await($this->v2PrivateGetAccounts ($this->extend($request, $params)));
            }
            //
            // v2PrivateGetAccounts
            //     {
            //         "pagination":array(
            //             "ending_before":null,
            //             "starting_after":null,
            //             "previous_ending_before":null,
            //             "next_starting_after":"6b17acd6-2e68-5eb0-9f45-72d67cef578a",
            //             "limit":100,
            //             "order":"desc",
            //             "previous_uri":null,
            //             "next_uri":"/v2/accounts?limit=100\u0026starting_after=6b17acd6-2e68-5eb0-9f45-72d67cef578b"
            //         ),
            //         "data":[
            //             array(
            //                 "id":"94ad58bc-0f15-5309-b35a-a4c86d7bad60",
            //                 "name":"MINA Wallet",
            //                 "primary":false,
            //                 "type":"wallet",
            //                 "currency":array(
            //                     "code":"MINA",
            //                     "name":"Mina",
            //                     "color":"#EA6B48",
            //                     "sort_index":397,
            //                     "exponent":9,
            //                     "type":"crypto",
            //                     "address_regex":"^(B62)[A-Za-z0-9]{52}$",
            //                     "asset_id":"a4ffc575-942c-5e26-b70c-cb3befdd4229",
            //                     "slug":"mina"
            //                 ),
            //                 "balance":array("amount":"0.000000000","currency":"MINA"),
            //                 "created_at":"2022-03-25T00:36:16Z",
            //                 "updated_at":"2022-03-25T00:36:16Z",
            //                 "resource":"account",
            //                 "resource_path":"/v2/accounts/94ad58bc-0f15-5309-b35a-a4c86d7bad60",
            //                 "allow_deposits":true,
            //                 "allow_withdrawals":true
            //             ),
            //         ]
            //     }
            //
            // v3PrivateGetBrokerageAccounts
            //     {
            //         "accounts" => array(
            //             {
            //                 "uuid" => "11111111-1111-1111-1111-111111111111",
            //                 "name" => "USDC Wallet",
            //                 "currency" => "USDC",
            //                 "available_balance" => array(
            //                     "value" => "0.0000000000000000",
            //                     "currency" => "USDC"
            //                 ),
            //                 "default" => true,
            //                 "active" => true,
            //                 "created_at" => "2023-01-04T06:20:06.456Z",
            //                 "updated_at" => "2023-01-04T06:20:07.181Z",
            //                 "deleted_at" => null,
            //                 "type" => "ACCOUNT_TYPE_CRYPTO",
            //                 "ready" => false,
            //                 "hold" => array(
            //                     "value" => "0.0000000000000000",
            //                     "currency" => "USDC"
            //                 }
            //             ),
            //             ...
            //         ),
            //         "has_next" => false,
            //         "cursor" => "",
            //         "size" => 9
            //     }
            //
            $params['type'] = $marketType;
            return $this->parse_custom_balance($response, $params);
        }) ();
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * Fetch the history of changes, i.e. actions done by the user or operations that altered the balance. Will return staking rewards, and crypto deposits or withdrawals.
             *
             * @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-transactions#list-transactions
             *
             * @param {string} [$code] unified $currency $code, default is null
             * @param {int} [$since] timestamp in ms of the earliest $ledger entry, default is null
             * @param {int} [$limit] max number of $ledger entries to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#$pagination-$params)
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ledger ledger structure~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchLedger', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchLedger', $code, $since, $limit, $params, 'next_starting_after', 'starting_after', null, 100));
            }
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
            }
            $request = null;
            list($request, $params) = Async\await($this->prepare_account_request_with_currency_code($code, $limit, $params));
            // for $pagination use parameter 'starting_after'
            // the value for the next page can be obtained from the result of the previous call in the 'pagination' field
            // eg => instance.last_http_response -> $pagination->next_starting_after
            $response = Async\await($this->v2PrivateGetAccountsAccountIdTransactions ($this->extend($request, $params)));
            $ledger = $this->parse_ledger($response['data'], $currency, $since, $limit);
            $length = count($ledger);
            if ($length === 0) {
                return $ledger;
            }
            $lastIndex = $length - 1;
            $last = $this->safe_dict($ledger, $lastIndex);
            $pagination = $this->safe_dict($response, 'pagination', array());
            $cursor = $this->safe_string($pagination, 'next_starting_after');
            if (($cursor !== null) && ($cursor !== '')) {
                $last['info']['next_starting_after'] = $cursor;
                $ledger[$lastIndex] = $last;
            }
            return $ledger;
        }) ();
    }

    public function parse_ledger_entry_status($status) {
        $types = array(
            'completed' => 'ok',
        );
        return $this->safe_string($types, $status, $status);
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'buy' => 'trade',
            'sell' => 'trade',
            'fiat_deposit' => 'transaction',
            'fiat_withdrawal' => 'transaction',
            'exchange_deposit' => 'transaction', // fiat withdrawal (from coinbase to coinbasepro)
            'exchange_withdrawal' => 'transaction', // fiat deposit (to coinbase from coinbasepro)
            'send' => 'transaction', // crypto deposit OR withdrawal
            'pro_deposit' => 'transaction', // crypto withdrawal (from coinbase to coinbasepro)
            'pro_withdrawal' => 'transaction', // crypto deposit (to coinbase from coinbasepro)
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        // crypto deposit transaction
        //
        //     {
        //         "id" => "34e4816b-4c8c-5323-a01c-35a9fa26e490",
        //         "type" => "send",
        //         "status" => "completed",
        //         "amount" => array( $amount => "28.31976528", $currency => "BCH" ),
        //         "native_amount" => array( $amount => "2799.65", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2019-02-28T12:35:20Z",
        //         "updated_at" => "2019-02-28T12:43:24Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/34e4816b-4c8c-5323-a01c-35a9fa26e490",
        //         "instant_exchange" => false,
        //         "network" => array(
        //             "status" => "confirmed",
        //             "hash" => "56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701",
        //             "transaction_url" => "https://bch.btc.com/56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701"
        //         ),
        //         "from" => array( resource => "bitcoin_cash_network", $currency => "BCH" ),
        //         "details" => array( title => 'Received Bitcoin Cash', subtitle => "From Bitcoin Cash $address" )
        //     }
        //
        // crypto withdrawal transaction
        //
        //     {
        //         "id" => "459aad99-2c41-5698-ac71-b6b81a05196c",
        //         "type" => "send",
        //         "status" => "completed",
        //         "amount" => array( $amount => "-0.36775642", $currency => "BTC" ),
        //         "native_amount" => array( $amount => "-1111.65", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2019-03-20T08:37:07Z",
        //         "updated_at" => "2019-03-20T08:49:33Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/459aad99-2c41-5698-ac71-b6b81a05196c",
        //         "instant_exchange" => false,
        //         "network" => array(
        //             "status" => "confirmed",
        //             "hash" => "2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b",
        //             "transaction_url" => "https://blockchain.info/tx/2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b",
        //             "transaction_fee" => array( $amount => "0.00000000", $currency => "BTC" ),
        //             "transaction_amount" => array( $amount => "0.36775642", $currency => "BTC" ),
        //             "confirmations" => 15682
        //         ),
        //         "to" => array(
        //             "resource" => "bitcoin_address",
        //             "address" => "1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX",
        //             "currency" => "BTC",
        //             "address_info" => array( $address => "1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX" )
        //         ),
        //         "idem" => "da0a2f14-a2af-4c5a-a37e-d4484caf582bsend",
        //         "application" => array(
        //             "id" => "5756ab6e-836b-553b-8950-5e389451225d",
        //             "resource" => "application",
        //             "resource_path" => "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
        //         ),
        //         "details" => array( title => 'Sent Bitcoin', subtitle => "To Bitcoin $address" )
        //     }
        //
        // withdrawal transaction from coinbase to coinbasepro
        //
        //     {
        //         "id" => "5b1b9fb8-5007-5393-b923-02903b973fdc",
        //         "type" => "pro_deposit",
        //         "status" => "completed",
        //         "amount" => array( $amount => "-0.00001111", $currency => "BCH" ),
        //         "native_amount" => array( $amount => "0.00", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2019-02-28T13:31:58Z",
        //         "updated_at" => "2019-02-28T13:31:58Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/5b1b9fb8-5007-5393-b923-02903b973fdc",
        //         "instant_exchange" => false,
        //         "application" => array(
        //             "id" => "5756ab6e-836b-553b-8950-5e389451225d",
        //             "resource" => "application",
        //             "resource_path" => "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
        //         ),
        //         "details" => array( title => 'Transferred Bitcoin Cash', subtitle => "To Coinbase Pro" )
        //     }
        //
        // withdrawal transaction from coinbase to gdax
        //
        //     {
        //         "id" => "badb7313-a9d3-5c07-abd0-00f8b44199b1",
        //         "type" => "exchange_deposit",
        //         "status" => "completed",
        //         "amount" => array( $amount => "-0.43704149", $currency => "BCH" ),
        //         "native_amount" => array( $amount => "-51.90", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2019-03-19T10:30:40Z",
        //         "updated_at" => "2019-03-19T10:30:40Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/badb7313-a9d3-5c07-abd0-00f8b44199b1",
        //         "instant_exchange" => false,
        //         "details" => array( title => 'Transferred Bitcoin Cash', subtitle => "To GDAX" )
        //     }
        //
        // deposit transaction from gdax to coinbase
        //
        //     {
        //         "id" => "9c4b642c-8688-58bf-8962-13cef64097de",
        //         "type" => "exchange_withdrawal",
        //         "status" => "completed",
        //         "amount" => array( $amount => "0.57729420", $currency => "BTC" ),
        //         "native_amount" => array( $amount => "4418.72", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2018-02-17T11:33:33Z",
        //         "updated_at" => "2018-02-17T11:33:33Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/9c4b642c-8688-58bf-8962-13cef64097de",
        //         "instant_exchange" => false,
        //         "details" => array( title => 'Transferred Bitcoin', subtitle => "From GDAX" )
        //     }
        //
        // deposit transaction from coinbasepro to coinbase
        //
        //     {
        //         "id" => "8d6dd0b9-3416-568a-889d-8f112fae9e81",
        //         "type" => "pro_withdrawal",
        //         "status" => "completed",
        //         "amount" => array( $amount => "0.40555386", $currency => "BTC" ),
        //         "native_amount" => array( $amount => "1140.27", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2019-03-04T19:41:58Z",
        //         "updated_at" => "2019-03-04T19:41:58Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/8d6dd0b9-3416-568a-889d-8f112fae9e81",
        //         "instant_exchange" => false,
        //         "application" => array(
        //             "id" => "5756ab6e-836b-553b-8950-5e389451225d",
        //             "resource" => "application",
        //             "resource_path" => "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
        //         ),
        //         "details" => array( title => 'Transferred Bitcoin', subtitle => "From Coinbase Pro" )
        //     }
        //
        // sell trade
        //
        //     {
        //         "id" => "a9409207-df64-585b-97ab-a50780d2149e",
        //         "type" => "sell",
        //         "status" => "completed",
        //         "amount" => array( $amount => "-9.09922880", $currency => "BTC" ),
        //         "native_amount" => array( $amount => "-7285.73", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2017-03-27T15:38:34Z",
        //         "updated_at" => "2017-03-27T15:38:34Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/a9409207-df64-585b-97ab-a50780d2149e",
        //         "instant_exchange" => false,
        //         "sell" => array(
        //             "id" => "e3550b4d-8ae6-5de3-95fe-1fb01ba83051",
        //             "resource" => "sell",
        //             "resource_path" => "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/sells/e3550b4d-8ae6-5de3-95fe-1fb01ba83051"
        //         ),
        //         "details" => {
        //             "title" => "Sold Bitcoin",
        //             "subtitle" => "Using EUR Wallet",
        //             "payment_method_name" => "EUR Wallet"
        //         }
        //     }
        //
        // buy trade
        //
        //     {
        //         "id" => "63eeed67-9396-5912-86e9-73c4f10fe147",
        //         "type" => "buy",
        //         "status" => "completed",
        //         "amount" => array( $amount => "2.39605772", $currency => "ETH" ),
        //         "native_amount" => array( $amount => "98.31", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2017-03-27T09:07:56Z",
        //         "updated_at" => "2017-03-27T09:07:57Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/transactions/63eeed67-9396-5912-86e9-73c4f10fe147",
        //         "instant_exchange" => false,
        //         "buy" => array(
        //             "id" => "20b25b36-76c6-5353-aa57-b06a29a39d82",
        //             "resource" => "buy",
        //             "resource_path" => "/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/buys/20b25b36-76c6-5353-aa57-b06a29a39d82"
        //         ),
        //         "details" => {
        //             "title" => "Bought Ethereum",
        //             "subtitle" => "Using EUR Wallet",
        //             "payment_method_name" => "EUR Wallet"
        //         }
        //     }
        //
        // fiat deposit transaction
        //
        //     {
        //         "id" => "04ed4113-3732-5b0c-af86-b1d2146977d0",
        //         "type" => "fiat_deposit",
        //         "status" => "completed",
        //         "amount" => array( $amount => "114.02", $currency => "EUR" ),
        //         "native_amount" => array( $amount => "97.23", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2017-02-09T07:01:21Z",
        //         "updated_at" => "2017-02-09T07:01:22Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86-b1d2146977d0",
        //         "instant_exchange" => false,
        //         "fiat_deposit" => array(
        //             "id" => "f34c19f3-b730-5e3d-9f72-96520448677a",
        //             "resource" => "fiat_deposit",
        //             "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72-96520448677a"
        //         ),
        //         "details" => {
        //             "title" => "Deposited funds",
        //             "subtitle" => "From SEPA Transfer (GB47 BARC 20..., reference CBADVI)",
        //             "payment_method_name" => "SEPA Transfer (GB47 BARC 20..., reference CBADVI)"
        //         }
        //     }
        //
        // fiat withdrawal transaction
        //
        //     {
        //         "id" => "957d98e2-f80e-5e2f-a28e-02945aa93079",
        //         "type" => "fiat_withdrawal",
        //         "status" => "completed",
        //         "amount" => array( $amount => "-11000.00", $currency => "EUR" ),
        //         "native_amount" => array( $amount => "-9698.22", $currency => "GBP" ),
        //         "description" => null,
        //         "created_at" => "2017-12-06T13:19:19Z",
        //         "updated_at" => "2017-12-06T13:19:19Z",
        //         "resource" => "transaction",
        //         "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/957d98e2-f80e-5e2f-a28e-02945aa93079",
        //         "instant_exchange" => false,
        //         "fiat_withdrawal" => array(
        //             "id" => "f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7",
        //             "resource" => "fiat_withdrawal",
        //             "resource_path" => "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7"
        //         ),
        //         "details" => {
        //             "title" => "Withdrew funds",
        //             "subtitle" => "To HSBC BANK PLC (GB74 MIDL...)",
        //             "payment_method_name" => "HSBC BANK PLC (GB74 MIDL...)"
        //         }
        //     }
        //
        $amountInfo = $this->safe_dict($item, 'amount', array());
        $amount = $this->safe_string($amountInfo, 'amount');
        $direction = null;
        if (Precise::string_lt($amount, '0')) {
            $direction = 'out';
            $amount = Precise::string_neg($amount);
        } else {
            $direction = 'in';
        }
        $currencyId = $this->safe_string($amountInfo, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        //
        // the $address and $txid do not belong to the unified ledger structure
        //
        //     $address = null;
        //     if ($item['to']) {
        //         $address = $this->safe_string($item['to'], 'address');
        //     }
        //     $txid = null;
        //
        $fee = null;
        $networkInfo = $this->safe_dict($item, 'network', array());
        // $txid = network['hash']; // $txid does not belong to the unified ledger structure
        $feeInfo = $this->safe_dict($networkInfo, 'transaction_fee');
        if ($feeInfo !== null) {
            $feeCurrencyId = $this->safe_string($feeInfo, 'currency');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId, $currency);
            $feeAmount = $this->safe_number($feeInfo, 'amount');
            $fee = array(
                'cost' => $feeAmount,
                'currency' => $feeCurrencyCode,
            );
        }
        $timestamp = $this->parse8601($this->safe_string($item, 'created_at'));
        $id = $this->safe_string($item, 'id');
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'type'));
        $status = $this->parse_ledger_entry_status($this->safe_string($item, 'status'));
        $path = $this->safe_string($item, 'resource_path');
        $accountId = null;
        if ($path !== null) {
            $parts = explode('/', $path);
            $numParts = count($parts);
            if ($numParts > 3) {
                $accountId = $parts[3];
            }
        }
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'direction' => $direction,
            'account' => $accountId,
            'referenceId' => null,
            'referenceAccount' => null,
            'type' => $type,
            'currency' => $code,
            'amount' => $this->parse_number($amount),
            'before' => null,
            'after' => null,
            'status' => $status,
            'fee' => $fee,
        ), $currency);
    }

    public function find_account_id($code, $params = array ()) {
        return Async\async(function () use ($code, $params) {
            Async\await($this->load_markets());
            Async\await($this->load_accounts(false, $params));
            for ($i = 0; $i < count($this->accounts); $i++) {
                $account = $this->accounts[$i];
                if ($account['code'] === $code) {
                    return $account['id'];
                }
            }
            return null;
        }) ();
    }

    public function prepare_account_request(?int $limit = null, $params = array ()) {
        $accountId = $this->safe_string_2($params, 'account_id', 'accountId');
        if ($accountId === null) {
            throw new ArgumentsRequired($this->id . ' prepareAccountRequest() method requires an account_id (or $accountId) parameter');
        }
        $request = array(
            'account_id' => $accountId,
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        return $request;
    }

    public function prepare_account_request_with_currency_code(?string $code = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($code, $limit, $params) {
            $accountId = $this->safe_string_2($params, 'account_id', 'accountId');
            $params = $this->omit($params, array( 'account_id', 'accountId' ));
            if ($accountId === null) {
                if ($code === null) {
                    throw new ArgumentsRequired($this->id . ' prepareAccountRequestWithCurrencyCode() method requires an account_id (or $accountId) parameter OR a currency $code argument');
                }
                $accountId = Async\await($this->find_account_id($code, $params));
                if ($accountId === null) {
                    throw new ExchangeError($this->id . ' prepareAccountRequestWithCurrencyCode() could not find account id for ' . $code . '. You might try to generate the deposit address in the website for that coin first.');
                }
            }
            $request = array(
                'account_id' => $accountId,
            );
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            return array( $request, $params );
        }) ();
    }

    public function create_market_buy_order_with_cost(string $symbol, float $cost, $params = array ()) {
        return Async\async(function () use ($symbol, $cost, $params) {
            /**
             * create a $market buy order by providing the $symbol and $cost
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_postorder
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {float} $cost how much you want to trade in units of the quote currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' createMarketBuyOrderWithCost() supports spot orders only');
            }
            $params['createMarketBuyOrderRequiresPrice'] = false;
            return Async\await($this->create_order($symbol, 'market', 'buy', $cost, null, $params));
        }) ();
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade order
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_postorder
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much you want to trade in units of the base currency, quote currency for 'market' 'buy' orders
             * @param {float} [$price] the $price to fulfill the order, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {float} [$params->stopPrice] $price to trigger stop orders
             * @param {float} [$params->triggerPrice] $price to trigger stop orders
             * @param {float} [$params->stopLossPrice] $price to trigger stop-loss orders
             * @param {float} [$params->takeProfitPrice] $price to trigger take-profit orders
             * @param {bool} [$params->postOnly] true or false
             * @param {string} [$params->timeInForce] 'GTC', 'IOC', 'GTD' or 'PO', 'FOK'
             * @param {string} [$params->stop_direction] 'UNKNOWN_STOP_DIRECTION', 'STOP_DIRECTION_STOP_UP', 'STOP_DIRECTION_STOP_DOWN' the direction the stopPrice is triggered from
             * @param {string} [$params->end_time] '2023-05-25T17:01:05.092Z' for 'GTD' orders
             * @param {float} [$params->cost] *spot $market buy only* the quote quantity that can be used alternative for the $amount
             * @param {boolean} [$params->preview] default to false, wether to use the test/preview endpoint or not
             * @param {float} [$params->leverage] default to 1, the leverage to use for the order
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @param {string} [$params->retail_portfolio_id] portfolio uid
             * @param {boolean} [$params->is_max] Used in conjunction with tradable_balance to indicate the user wants to use their entire tradable balance
             * @param {string} [$params->tradable_balance] $amount of tradable balance
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $id = $this->safe_string($this->options, 'brokerId', 'ccxt');
            $request = array(
                'client_order_id' => $id . '-' . $this->uuid(),
                'product_id' => $market['id'],
                'side' => strtoupper($side),
            );
            $triggerPrice = $this->safe_number_n($params, array( 'stopPrice', 'stop_price', 'triggerPrice' ));
            $stopLossPrice = $this->safe_number($params, 'stopLossPrice');
            $takeProfitPrice = $this->safe_number($params, 'takeProfitPrice');
            $isStop = $triggerPrice !== null;
            $isStopLoss = $stopLossPrice !== null;
            $isTakeProfit = $takeProfitPrice !== null;
            $timeInForce = $this->safe_string($params, 'timeInForce');
            $postOnly = ($timeInForce === 'PO') ? true : $this->safe_bool_2($params, 'postOnly', 'post_only', false);
            $endTime = $this->safe_string($params, 'end_time');
            $stopDirection = $this->safe_string($params, 'stop_direction');
            if ($type === 'limit') {
                if ($isStop) {
                    if ($stopDirection === null) {
                        $stopDirection = ($side === 'buy') ? 'STOP_DIRECTION_STOP_DOWN' : 'STOP_DIRECTION_STOP_UP';
                    }
                    if (($timeInForce === 'GTD') || ($endTime !== null)) {
                        if ($endTime === null) {
                            throw new ExchangeError($this->id . ' createOrder() requires an end_time parameter for a GTD order');
                        }
                        $request['order_configuration'] = array(
                            'stop_limit_stop_limit_gtd' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                                'stop_price' => $this->price_to_precision($symbol, $triggerPrice),
                                'stop_direction' => $stopDirection,
                                'end_time' => $endTime,
                            ),
                        );
                    } else {
                        $request['order_configuration'] = array(
                            'stop_limit_stop_limit_gtc' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                                'stop_price' => $this->price_to_precision($symbol, $triggerPrice),
                                'stop_direction' => $stopDirection,
                            ),
                        );
                    }
                } elseif ($isStopLoss || $isTakeProfit) {
                    $tpslPrice = null;
                    if ($isStopLoss) {
                        if ($stopDirection === null) {
                            $stopDirection = ($side === 'buy') ? 'STOP_DIRECTION_STOP_UP' : 'STOP_DIRECTION_STOP_DOWN';
                        }
                        $tpslPrice = $this->price_to_precision($symbol, $stopLossPrice);
                    } else {
                        if ($stopDirection === null) {
                            $stopDirection = ($side === 'buy') ? 'STOP_DIRECTION_STOP_DOWN' : 'STOP_DIRECTION_STOP_UP';
                        }
                        $tpslPrice = $this->price_to_precision($symbol, $takeProfitPrice);
                    }
                    $request['order_configuration'] = array(
                        'stop_limit_stop_limit_gtc' => array(
                            'base_size' => $this->amount_to_precision($symbol, $amount),
                            'limit_price' => $this->price_to_precision($symbol, $price),
                            'stop_price' => $tpslPrice,
                            'stop_direction' => $stopDirection,
                        ),
                    );
                } else {
                    if (($timeInForce === 'GTD') || ($endTime !== null)) {
                        if ($endTime === null) {
                            throw new ExchangeError($this->id . ' createOrder() requires an end_time parameter for a GTD order');
                        }
                        $request['order_configuration'] = array(
                            'limit_limit_gtd' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                                'end_time' => $endTime,
                                'post_only' => $postOnly,
                            ),
                        );
                    } elseif ($timeInForce === 'IOC') {
                        $request['order_configuration'] = array(
                            'sor_limit_ioc' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                            ),
                        );
                    } elseif ($timeInForce === 'FOK') {
                        $request['order_configuration'] = array(
                            'limit_limit_fok' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                            ),
                        );
                    } else {
                        $request['order_configuration'] = array(
                            'limit_limit_gtc' => array(
                                'base_size' => $this->amount_to_precision($symbol, $amount),
                                'limit_price' => $this->price_to_precision($symbol, $price),
                                'post_only' => $postOnly,
                            ),
                        );
                    }
                }
            } else {
                if ($isStop || $isStopLoss || $isTakeProfit) {
                    throw new NotSupported($this->id . ' createOrder() only stop limit orders are supported');
                }
                if ($market['spot'] && ($side === 'buy')) {
                    $total = null;
                    $createMarketBuyOrderRequiresPrice = true;
                    list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                    $cost = $this->safe_number($params, 'cost');
                    $params = $this->omit($params, 'cost');
                    if ($cost !== null) {
                        $total = $this->cost_to_precision($symbol, $cost);
                    } elseif ($createMarketBuyOrderRequiresPrice) {
                        if ($price === null) {
                            throw new InvalidOrder($this->id . ' createOrder() requires a $price argument for $market buy orders on spot markets to calculate the $total $amount to spend ($amount * $price), alternatively set the $createMarketBuyOrderRequiresPrice option or param to false and pass the $cost to spend in the $amount argument');
                        } else {
                            $amountString = $this->number_to_string($amount);
                            $priceString = $this->number_to_string($price);
                            $costRequest = Precise::string_mul($amountString, $priceString);
                            $total = $this->cost_to_precision($symbol, $costRequest);
                        }
                    } else {
                        $total = $this->cost_to_precision($symbol, $amount);
                    }
                    $request['order_configuration'] = array(
                        'market_market_ioc' => array(
                            'quote_size' => $total,
                        ),
                    );
                } else {
                    $request['order_configuration'] = array(
                        'market_market_ioc' => array(
                            'base_size' => $this->amount_to_precision($symbol, $amount),
                        ),
                    );
                }
            }
            $marginMode = $this->safe_string($params, 'marginMode');
            if ($marginMode !== null) {
                if ($marginMode === 'isolated') {
                    $request['margin_type'] = 'ISOLATED';
                } elseif ($marginMode === 'cross') {
                    $request['margin_type'] = 'CROSS';
                }
            }
            $params = $this->omit($params, array( 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time', 'marginMode' ));
            $preview = $this->safe_bool_2($params, 'preview', 'test', false);
            $response = null;
            if ($preview) {
                $params = $this->omit($params, array( 'preview', 'test' ));
                $request = $this->omit($request, 'client_order_id');
                $response = Async\await($this->v3PrivatePostBrokerageOrdersPreview ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PrivatePostBrokerageOrders ($this->extend($request, $params)));
            }
            //
            // successful order
            //
            //     {
            //         "success" => true,
            //         "failure_reason" => "UNKNOWN_FAILURE_REASON",
            //         "order_id" => "52cfe5e2-0b29-4c19-a245-a6a773de5030",
            //         "success_response" => array(
            //             "order_id" => "52cfe5e2-0b29-4c19-a245-a6a773de5030",
            //             "product_id" => "LTC-BTC",
            //             "side" => "SELL",
            //             "client_order_id" => "4d760580-6fca-4094-a70b-ebcca8626288"
            //         ),
            //         "order_configuration" => null
            //     }
            //
            // failed order
            //
            //     {
            //         "success" => false,
            //         "failure_reason" => "UNKNOWN_FAILURE_REASON",
            //         "order_id" => "",
            //         "error_response" => array(
            //             "error" => "UNSUPPORTED_ORDER_CONFIGURATION",
            //             "message" => "source is not enabled for trading",
            //             "error_details" => "",
            //             "new_order_failure_reason" => "UNSUPPORTED_ORDER_CONFIGURATION"
            //         ),
            //         "order_configuration" => {
            //             "limit_limit_gtc" => {
            //                 "base_size" => "100",
            //                 "limit_price" => "40000",
            //                 "post_only" => false
            //             }
            //         }
            //     }
            //
            $success = $this->safe_bool($response, 'success');
            if ($success !== true) {
                $errorResponse = $this->safe_dict($response, 'error_response');
                $errorTitle = $this->safe_string($errorResponse, 'error');
                $errorMessage = $this->safe_string($errorResponse, 'message');
                if ($errorResponse !== null) {
                    $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorTitle, $errorMessage);
                    $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorTitle, $errorMessage);
                    throw new ExchangeError($errorMessage);
                }
            }
            $data = $this->safe_dict($response, 'success_response', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder
        //
        //     {
        //         "order_id" => "52cfe5e2-0b29-4c19-a245-a6a773de5030",
        //         "product_id" => "LTC-BTC",
        //         "side" => "SELL",
        //         "client_order_id" => "4d760580-6fca-4094-a70b-ebcca8626288"
        //     }
        //
        // cancelOrder, cancelOrders
        //
        //     {
        //         "success" => true,
        //         "failure_reason" => "UNKNOWN_CANCEL_FAILURE_REASON",
        //         "order_id" => "bb8851a3-4fda-4a2c-aa06-9048db0e0f0d"
        //     }
        //
        // fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
        //
        //     {
        //         "order_id" => "9bc1eb3b-5b46-4b71-9628-ae2ed0cca75b",
        //         "product_id" => "LTC-BTC",
        //         "user_id" => "1111111-1111-1111-1111-111111111111",
        //         "order_configuration" => {
        //             "limit_limit_gtc" => array(
        //                 "base_size" => "0.2",
        //                 "limit_price" => "0.006",
        //                 "post_only" => false
        //             ),
        //             "stop_limit_stop_limit_gtc" => array(
        //                 "base_size" => "48.54",
        //                 "limit_price" => "6.998",
        //                 "stop_price" => "7.0687",
        //                 "stop_direction" => "STOP_DIRECTION_STOP_DOWN"
        //             }
        //         ),
        //         "side" => "SELL",
        //         "client_order_id" => "e5fe8482-05bb-428f-ad4d-dbc8ce39239c",
        //         "status" => "OPEN",
        //         "time_in_force" => "GOOD_UNTIL_CANCELLED",
        //         "created_time" => "2023-01-16T23:37:23.947030Z",
        //         "completion_percentage" => "0",
        //         "filled_size" => "0",
        //         "average_filled_price" => "0",
        //         "fee" => "",
        //         "number_of_fills" => "0",
        //         "filled_value" => "0",
        //         "pending_cancel" => false,
        //         "size_in_quote" => false,
        //         "total_fees" => "0",
        //         "size_inclusive_of_fees" => false,
        //         "total_value_after_fees" => "0",
        //         "trigger_status" => "INVALID_ORDER_TYPE",
        //         "order_type" => "LIMIT",
        //         "reject_reason" => "REJECT_REASON_UNSPECIFIED",
        //         "settled" => false,
        //         "product_type" => "SPOT",
        //         "reject_message" => "",
        //         "cancel_message" => ""
        //     }
        //
        $marketId = $this->safe_string($order, 'product_id');
        $symbol = $this->safe_symbol($marketId, $market, '-');
        if ($symbol !== null) {
            $market = $this->safe_market($symbol, $market);
        }
        $orderConfiguration = $this->safe_dict($order, 'order_configuration', array());
        $limitGTC = $this->safe_dict($orderConfiguration, 'limit_limit_gtc');
        $limitGTD = $this->safe_dict($orderConfiguration, 'limit_limit_gtd');
        $limitIOC = $this->safe_dict($orderConfiguration, 'sor_limit_ioc');
        $stopLimitGTC = $this->safe_dict($orderConfiguration, 'stop_limit_stop_limit_gtc');
        $stopLimitGTD = $this->safe_dict($orderConfiguration, 'stop_limit_stop_limit_gtd');
        $marketIOC = $this->safe_dict($orderConfiguration, 'market_market_ioc');
        $isLimit = (($limitGTC !== null) || ($limitGTD !== null) || ($limitIOC !== null));
        $isStop = (($stopLimitGTC !== null) || ($stopLimitGTD !== null));
        $price = null;
        $amount = null;
        $postOnly = null;
        $triggerPrice = null;
        if ($isLimit) {
            $target = null;
            if ($limitGTC !== null) {
                $target = $limitGTC;
            } elseif ($limitGTD !== null) {
                $target = $limitGTD;
            } else {
                $target = $limitIOC;
            }
            $price = $this->safe_string($target, 'limit_price');
            $amount = $this->safe_string($target, 'base_size');
            $postOnly = $this->safe_bool($target, 'post_only');
        } elseif ($isStop) {
            $stopTarget = ($stopLimitGTC !== null) ? $stopLimitGTC : $stopLimitGTD;
            $price = $this->safe_string($stopTarget, 'limit_price');
            $amount = $this->safe_string($stopTarget, 'base_size');
            $postOnly = $this->safe_bool($stopTarget, 'post_only');
            $triggerPrice = $this->safe_string($stopTarget, 'stop_price');
        } else {
            $amount = $this->safe_string($marketIOC, 'base_size');
        }
        $datetime = $this->safe_string($order, 'created_time');
        $totalFees = $this->safe_string($order, 'total_fees');
        $currencyFee = null;
        if (($totalFees !== null) && ($market !== null)) {
            $currencyFee = $market['quote'];
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($order, 'order_id'),
            'clientOrderId' => $this->safe_string($order, 'client_order_id'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $this->parse_order_type($this->safe_string($order, 'order_type')),
            'timeInForce' => $this->parse_time_in_force($this->safe_string($order, 'time_in_force')),
            'postOnly' => $postOnly,
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'amount' => $amount,
            'filled' => $this->safe_string($order, 'filled_size'),
            'remaining' => null,
            'cost' => null,
            'average' => $this->safe_string($order, 'average_filled_price'),
            'status' => $this->parse_order_status($this->safe_string($order, 'status')),
            'fee' => array(
                'cost' => $this->safe_string($order, 'total_fees'),
                'currency' => $currencyFee,
            ),
            'trades' => null,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'OPEN' => 'open',
            'FILLED' => 'closed',
            'CANCELLED' => 'canceled',
            'EXPIRED' => 'canceled',
            'FAILED' => 'canceled',
            'UNKNOWN_ORDER_STATUS' => null,
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type(?string $type) {
        if ($type === 'UNKNOWN_ORDER_TYPE') {
            return null;
        }
        $types = array(
            'MARKET' => 'market',
            'LIMIT' => 'limit',
            'STOP' => 'limit',
            'STOP_LIMIT' => 'limit',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'GOOD_UNTIL_CANCELLED' => 'GTC',
            'GOOD_UNTIL_DATE_TIME' => 'GTD',
            'IMMEDIATE_OR_CANCEL' => 'IOC',
            'FILL_OR_KILL' => 'FOK',
            'UNKNOWN_TIME_IN_FORCE' => null,
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * cancels an open order
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_cancelorders
             *
             * @param {string} $id order $id
             * @param {string} $symbol not used by coinbase cancelOrder()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $orders = Async\await($this->cancel_orders(array( $id ), $symbol, $params));
            return $this->safe_dict($orders, 0, array());
        }) ();
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple $orders
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_cancelorders
             *
             * @param {string[]} $ids order $ids
             * @param {string} $symbol not used by coinbase cancelOrders()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'order_ids' => $ids,
            );
            $response = Async\await($this->v3PrivatePostBrokerageOrdersBatchCancel ($this->extend($request, $params)));
            //
            //     {
            //         "results" => array(
            //             {
            //                 "success" => true,
            //                 "failure_reason" => "UNKNOWN_CANCEL_FAILURE_REASON",
            //                 "order_id" => "bb8851a3-4fda-4a2c-aa06-9048db0e0f0d"
            //             }
            //         )
            //     }
            //
            $orders = $this->safe_list($response, 'results', array());
            for ($i = 0; $i < count($orders); $i++) {
                $success = $this->safe_bool($orders[$i], 'success');
                if ($success !== true) {
                    throw new BadRequest($this->id . ' cancelOrders() has failed, check your arguments and parameters');
                }
            }
            return $this->parse_orders($orders, $market);
        }) ();
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            /**
             * edit a trade order
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_editorder
             *
             * @param {string} $id cancel order $id
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->preview] default to false, wether to use the test/preview endpoint or not
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'order_id' => $id,
            );
            if ($amount !== null) {
                $request['size'] = $this->amount_to_precision($symbol, $amount);
            }
            if ($price !== null) {
                $request['price'] = $this->price_to_precision($symbol, $price);
            }
            $preview = $this->safe_bool_2($params, 'preview', 'test', false);
            $response = null;
            if ($preview) {
                $params = $this->omit($params, array( 'preview', 'test' ));
                $response = Async\await($this->v3PrivatePostBrokerageOrdersEditPreview ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PrivatePostBrokerageOrdersEdit ($this->extend($request, $params)));
            }
            //
            //     {
            //         "success" => true,
            //         "errors" => {
            //           "edit_failure_reason" => "UNKNOWN_EDIT_ORDER_FAILURE_REASON",
            //           "preview_failure_reason" => "UNKNOWN_PREVIEW_FAILURE_REASON"
            //         }
            //     }
            //
            return $this->parse_order($response, $market);
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * fetches information on an $order made by the user
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorder
             *
             * @param {string} $id the $order $id
             * @param {string} $symbol unified $market $symbol that the $order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'order_id' => $id,
            );
            $response = Async\await($this->v3PrivateGetBrokerageOrdersHistoricalOrderId ($this->extend($request, $params)));
            //
            //     {
            //         "order" => {
            //             "order_id" => "9bc1eb3b-5b46-4b71-9628-ae2ed0cca75b",
            //             "product_id" => "LTC-BTC",
            //             "user_id" => "1111111-1111-1111-1111-111111111111",
            //             "order_configuration" => {
            //                 "limit_limit_gtc" => array(
            //                     "base_size" => "0.2",
            //                     "limit_price" => "0.006",
            //                     "post_only" => false
            //                 }
            //             ),
            //             "side" => "SELL",
            //             "client_order_id" => "e5fe8482-05bb-428f-ad4d-dbc8ce39239c",
            //             "status" => "OPEN",
            //             "time_in_force" => "GOOD_UNTIL_CANCELLED",
            //             "created_time" => "2023-01-16T23:37:23.947030Z",
            //             "completion_percentage" => "0",
            //             "filled_size" => "0",
            //             "average_filled_price" => "0",
            //             "fee" => "",
            //             "number_of_fills" => "0",
            //             "filled_value" => "0",
            //             "pending_cancel" => false,
            //             "size_in_quote" => false,
            //             "total_fees" => "0",
            //             "size_inclusive_of_fees" => false,
            //             "total_value_after_fees" => "0",
            //             "trigger_status" => "INVALID_ORDER_TYPE",
            //             "order_type" => "LIMIT",
            //             "reject_reason" => "REJECT_REASON_UNSPECIFIED",
            //             "settled" => false,
            //             "product_type" => "SPOT",
            //             "reject_message" => "",
            //             "cancel_message" => ""
            //         }
            //     }
            //
            $order = $this->safe_dict($response, 'order', array());
            return $this->parse_order($order, $market);
        }) ();
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = 100, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple $orders made by the user
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
             *
             * @param {string} $symbol unified $market $symbol that the $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch trades for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchOrders', $symbol, $since, $limit, $params, 'cursor', 'cursor', null, 1000));
            }
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            if ($market !== null) {
                $request['product_id'] = $market['id'];
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            if ($since !== null) {
                $request['start_date'] = $this->iso8601($since);
            }
            $until = $this->safe_integer_n($params, array( 'until' ));
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['end_date'] = $this->iso8601($until);
            }
            $response = Async\await($this->v3PrivateGetBrokerageOrdersHistoricalBatch ($this->extend($request, $params)));
            //
            //     {
            //         "orders" => array(
            //             {
            //                 "order_id" => "813a53c5-3e39-47bb-863d-2faf685d22d8",
            //                 "product_id" => "BTC-USDT",
            //                 "user_id" => "1111111-1111-1111-1111-111111111111",
            //                 "order_configuration" => array(
            //                     "market_market_ioc" => array(
            //                         "quote_size" => "6.36"
            //                     }
            //                 ),
            //                 "side" => "BUY",
            //                 "client_order_id" => "18eb9947-db49-4874-8e7b-39b8fe5f4317",
            //                 "status" => "FILLED",
            //                 "time_in_force" => "IMMEDIATE_OR_CANCEL",
            //                 "created_time" => "2023-01-18T01:37:37.975552Z",
            //                 "completion_percentage" => "100",
            //                 "filled_size" => "0.000297920684505",
            //                 "average_filled_price" => "21220.6399999973697697",
            //                 "fee" => "",
            //                 "number_of_fills" => "2",
            //                 "filled_value" => "6.3220675944333996",
            //                 "pending_cancel" => false,
            //                 "size_in_quote" => true,
            //                 "total_fees" => "0.0379324055666004",
            //                 "size_inclusive_of_fees" => true,
            //                 "total_value_after_fees" => "6.36",
            //                 "trigger_status" => "INVALID_ORDER_TYPE",
            //                 "order_type" => "MARKET",
            //                 "reject_reason" => "REJECT_REASON_UNSPECIFIED",
            //                 "settled" => true,
            //                 "product_type" => "SPOT",
            //                 "reject_message" => "",
            //                 "cancel_message" => "Internal error"
            //             ),
            //         ),
            //         "sequence" => "0",
            //         "has_next" => false,
            //         "cursor" => ""
            //     }
            //
            $orders = $this->safe_list($response, 'orders', array());
            $first = $this->safe_dict($orders, 0);
            $cursor = $this->safe_string($response, 'cursor');
            if (($cursor !== null) && ($cursor !== '')) {
                $first['cursor'] = $cursor;
                $orders[0] = $first;
            }
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    public function fetch_orders_by_status($status, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($status, $symbol, $since, $limit, $params) {
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'order_status' => $status,
            );
            if ($market !== null) {
                $request['product_id'] = $market['id'];
            }
            if ($limit === null) {
                $limit = 100;
            }
            $request['limit'] = $limit;
            if ($since !== null) {
                $request['start_date'] = $this->iso8601($since);
            }
            $until = $this->safe_integer_n($params, array( 'until' ));
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['end_date'] = $this->iso8601($until);
            }
            $response = Async\await($this->v3PrivateGetBrokerageOrdersHistoricalBatch ($this->extend($request, $params)));
            //
            //     {
            //         "orders" => array(
            //             {
            //                 "order_id" => "813a53c5-3e39-47bb-863d-2faf685d22d8",
            //                 "product_id" => "BTC-USDT",
            //                 "user_id" => "1111111-1111-1111-1111-111111111111",
            //                 "order_configuration" => array(
            //                     "market_market_ioc" => array(
            //                         "quote_size" => "6.36"
            //                     }
            //                 ),
            //                 "side" => "BUY",
            //                 "client_order_id" => "18eb9947-db49-4874-8e7b-39b8fe5f4317",
            //                 "status" => "FILLED",
            //                 "time_in_force" => "IMMEDIATE_OR_CANCEL",
            //                 "created_time" => "2023-01-18T01:37:37.975552Z",
            //                 "completion_percentage" => "100",
            //                 "filled_size" => "0.000297920684505",
            //                 "average_filled_price" => "21220.6399999973697697",
            //                 "fee" => "",
            //                 "number_of_fills" => "2",
            //                 "filled_value" => "6.3220675944333996",
            //                 "pending_cancel" => false,
            //                 "size_in_quote" => true,
            //                 "total_fees" => "0.0379324055666004",
            //                 "size_inclusive_of_fees" => true,
            //                 "total_value_after_fees" => "6.36",
            //                 "trigger_status" => "INVALID_ORDER_TYPE",
            //                 "order_type" => "MARKET",
            //                 "reject_reason" => "REJECT_REASON_UNSPECIFIED",
            //                 "settled" => true,
            //                 "product_type" => "SPOT",
            //                 "reject_message" => "",
            //                 "cancel_message" => "Internal error"
            //             ),
            //         ),
            //         "sequence" => "0",
            //         "has_next" => false,
            //         "cursor" => ""
            //     }
            //
            $orders = $this->safe_list($response, 'orders', array());
            $first = $this->safe_dict($orders, 0);
            $cursor = $this->safe_string($response, 'cursor');
            if (($cursor !== null) && ($cursor !== '')) {
                $first['cursor'] = $cursor;
                $orders[0] = $first;
            }
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on all currently open orders
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
             *
             * @param {string} $symbol unified market $symbol of the orders
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] the maximum number of open order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @param {int} [$params->until] the latest time in ms to fetch trades for
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOpenOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchOpenOrders', $symbol, $since, $limit, $params, 'cursor', 'cursor', null, 100));
            }
            return Async\await($this->fetch_orders_by_status('OPEN', $symbol, $since, $limit, $params));
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple closed orders made by the user
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
             *
             * @param {string} $symbol unified market $symbol of the orders
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] the maximum number of closed order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @param {int} [$params->until] the latest time in ms to fetch trades for
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchClosedOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchClosedOrders', $symbol, $since, $limit, $params, 'cursor', 'cursor', null, 100));
            }
            return Async\await($this->fetch_orders_by_status('FILLED', $symbol, $since, $limit, $params));
        }) ();
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple canceled orders made by the user
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
             *
             * @param {string} $symbol unified market $symbol of the orders
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] the maximum number of canceled order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            return Async\await($this->fetch_orders_by_status('CANCELLED', $symbol, $since, $limit, $params));
        }) ();
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpubliccandles
             *
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of $candles to fetch, not used by coinbase
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch trades for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @param {boolean} [$params->usePrivate] default false, when true will use the private endpoint to fetch the $candles
             * @return {int[][]} A list of $candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $maxLimit = 300;
            $limit = ($limit === null) ? $maxLimit : min ($limit, $maxLimit);
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate', false);
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params, $maxLimit - 1));
            }
            $market = $this->market($symbol);
            $request = array(
                'product_id' => $market['id'],
                'granularity' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            );
            $until = $this->safe_integer_n($params, array( 'until', 'end' ));
            $params = $this->omit($params, array( 'until' ));
            $duration = $this->parse_timeframe($timeframe);
            $requestedDuration = $limit * $duration;
            $sinceString = null;
            if ($since !== null) {
                $sinceString = $this->number_to_string($this->parse_to_int($since / 1000));
            } else {
                $now = (string) $this->seconds();
                $sinceString = Precise::string_sub($now, (string) $requestedDuration);
            }
            $request['start'] = $sinceString;
            if ($until !== null) {
                $request['end'] = $this->number_to_string($this->parse_to_int($until / 1000));
            } else {
                // 300 $candles max
                $request['end'] = Precise::string_add($sinceString, (string) $requestedDuration);
            }
            $response = null;
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'usePrivate', false);
            if ($usePrivate) {
                $response = Async\await($this->v3PrivateGetBrokerageProductsProductIdCandles ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PublicGetBrokerageMarketProductsProductIdCandles ($this->extend($request, $params)));
            }
            //
            //     {
            //         "candles" => array(
            //             array(
            //                 "start" => "1673391780",
            //                 "low" => "17414.36",
            //                 "high" => "17417.99",
            //                 "open" => "17417.74",
            //                 "close" => "17417.38",
            //                 "volume" => "1.87780853"
            //             ),
            //         )
            //     }
            //
            $candles = $this->safe_list($response, 'candles', array());
            return $this->parse_ohlcvs($candles, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         array(
        //             "start" => "1673391780",
        //             "low" => "17414.36",
        //             "high" => "17417.99",
        //             "open" => "17417.74",
        //             "close" => "17417.38",
        //             "volume" => "1.87780853"
        //         ),
        //     )
        //
        return array(
            $this->safe_timestamp($ohlcv, 'start'),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'volume'),
        );
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent $trades for a particular $symbol
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicmarkettrades
             *
             * @param {string} $symbol unified $market $symbol of the $trades
             * @param {int} [$since] not used by coinbase fetchTrades
             * @param {int} [$limit] the maximum number of trade structures to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->usePrivate] default false, when true will use the private endpoint to fetch the $trades
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'product_id' => $market['id'],
            );
            if ($since !== null) {
                $request['start'] = $this->number_to_string($this->parse_to_int($since / 1000));
            }
            if ($limit !== null) {
                $request['limit'] = min ($limit, 1000);
            }
            $until = null;
            list($until, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'until');
            if ($until !== null) {
                $request['end'] = $this->number_to_string($this->parse_to_int($until / 1000));
            } elseif ($since !== null) {
                throw new ArgumentsRequired($this->id . ' fetchTrades() requires a `$until` parameter when you use `$since` argument');
            }
            $response = null;
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'usePrivate', false);
            if ($usePrivate) {
                $response = Async\await($this->v3PrivateGetBrokerageProductsProductIdTicker ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PublicGetBrokerageMarketProductsProductIdTicker ($this->extend($request, $params)));
            }
            //
            //     {
            //         "trades" => array(
            //             array(
            //                 "trade_id" => "10092327",
            //                 "product_id" => "BTC-USDT",
            //                 "price" => "17488.12",
            //                 "size" => "0.0000623",
            //                 "time" => "2023-01-11T00:52:37.557001Z",
            //                 "side" => "BUY",
            //                 "bid" => "",
            //                 "ask" => ""
            //             ),
            //         )
            //     }
            //
            $trades = $this->safe_list($response, 'trades', array());
            return $this->parse_trades($trades, $market, $since, $limit);
        }) ();
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch all $trades made by the user
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfills
             *
             * @param {string} $symbol unified $market $symbol of the $trades
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] the maximum number of trade structures to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch $trades for
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_cursor('fetchMyTrades', $symbol, $since, $limit, $params, 'cursor', 'cursor', null, 250));
            }
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            if ($market !== null) {
                $request['product_id'] = $market['id'];
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            if ($since !== null) {
                $request['start_sequence_timestamp'] = $this->iso8601($since);
            }
            $until = $this->safe_integer_n($params, array( 'until' ));
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request['end_sequence_timestamp'] = $this->iso8601($until);
            }
            $response = Async\await($this->v3PrivateGetBrokerageOrdersHistoricalFills ($this->extend($request, $params)));
            //
            //     {
            //         "fills" => array(
            //             array(
            //                 "entry_id" => "b88b82cc89e326a2778874795102cbafd08dd979a2a7a3c69603fc4c23c2e010",
            //                 "trade_id" => "cdc39e45-bbd3-44ec-bf02-61742dfb16a1",
            //                 "order_id" => "813a53c5-3e39-47bb-863d-2faf685d22d8",
            //                 "trade_time" => "2023-01-18T01:37:38.091377090Z",
            //                 "trade_type" => "FILL",
            //                 "price" => "21220.64",
            //                 "size" => "0.0046830664333996",
            //                 "commission" => "0.0000280983986004",
            //                 "product_id" => "BTC-USDT",
            //                 "sequence_timestamp" => "2023-01-18T01:37:38.092520Z",
            //                 "liquidity_indicator" => "UNKNOWN_LIQUIDITY_INDICATOR",
            //                 "size_in_quote" => true,
            //                 "user_id" => "1111111-1111-1111-1111-111111111111",
            //                 "side" => "BUY"
            //             ),
            //         ),
            //         "cursor" => ""
            //     }
            //
            $trades = $this->safe_list($response, 'fills', array());
            $first = $this->safe_dict($trades, 0);
            $cursor = $this->safe_string($response, 'cursor');
            if (($cursor !== null) && ($cursor !== '')) {
                $first['cursor'] = $cursor;
                $trades[0] = $first;
            }
            return $this->parse_trades($trades, $market, $since, $limit);
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicproductbook
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->usePrivate] default false, when true will use the private endpoint to fetch the order book
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'product_id' => $market['id'],
            );
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = null;
            $usePrivate = false;
            list($usePrivate, $params) = $this->handle_option_and_params($params, 'fetchOrderBook', 'usePrivate', false);
            if ($usePrivate) {
                $response = Async\await($this->v3PrivateGetBrokerageProductBook ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v3PublicGetBrokerageMarketProductBook ($this->extend($request, $params)));
            }
            //
            //     {
            //         "pricebook" => {
            //             "product_id" => "BTC-USDT",
            //             "bids" => array(
            //                 array(
            //                     "price" => "30757.85",
            //                     "size" => "0.115"
            //                 ),
            //             ),
            //             "asks" => array(
            //                 array(
            //                     "price" => "30759.07",
            //                     "size" => "0.04877659"
            //                 ),
            //             ),
            //             "time" => "2023-06-30T04:02:40.533606Z"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'pricebook', array());
            $time = $this->safe_string($data, 'time');
            $timestamp = $this->parse8601($time);
            return $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks', 'price', 'size');
        }) ();
    }

    public function fetch_bids_asks(?array $symbols = null, $params = array ()) {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches the bid and ask price and volume for multiple markets
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getbestbidask
             *
             * @param {string[]} [$symbols] unified $symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $request = array();
            if ($symbols !== null) {
                $request['product_ids'] = $this->market_ids($symbols);
            }
            $response = Async\await($this->v3PrivateGetBrokerageBestBidAsk ($this->extend($request, $params)));
            //
            //     {
            //         "pricebooks" => array(
            //             {
            //                 "product_id" => "TRAC-EUR",
            //                 "bids" => array(
            //                     {
            //                         "price" => "0.2384",
            //                         "size" => "386.1"
            //                     }
            //                 ),
            //                 "asks" => array(
            //                     array(
            //                         "price" => "0.2406",
            //                         "size" => "672"
            //                     }
            //                 ),
            //                 "time" => "2023-06-30T07:15:24.656044Z"
            //             ),
            //         )
            //     }
            //
            $tickers = $this->safe_list($response, 'pricebooks', array());
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $address, $tag, $params) {
            /**
             * make a withdrawal
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-transactions#send-money
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} [$tag] an optional $tag for the withdrawal
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            $this->check_address($address);
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $accountId = $this->safe_string_2($params, 'account_id', 'accountId');
            $params = $this->omit($params, array( 'account_id', 'accountId' ));
            if ($accountId === null) {
                if ($code === null) {
                    throw new ArgumentsRequired($this->id . ' withdraw() requires an account_id (or $accountId) parameter OR a $currency $code argument');
                }
                $accountId = Async\await($this->find_account_id($code, $params));
                if ($accountId === null) {
                    throw new ExchangeError($this->id . ' withdraw() could not find account id for ' . $code);
                }
            }
            $request = array(
                'account_id' => $accountId,
                'type' => 'send',
                'to' => $address,
                'amount' => $amount,
                'currency' => $currency['id'],
            );
            if ($tag !== null) {
                $request['destination_tag'] = $tag;
            }
            $response = Async\await($this->v2PrivatePostAccountsAccountIdTransactions ($this->extend($request, $params)));
            //
            //     {
            //         "data" => {
            //             "id" => "a1794ecf-5693-55fa-70cf-ef731748ed82",
            //             "type" => "send",
            //             "status" => "pending",
            //             "amount" => array(
            //                 "amount" => "-14.008308",
            //                 "currency" => "USDC"
            //             ),
            //             "native_amount" => array(
            //                 "amount" => "-18.74",
            //                 "currency" => "CAD"
            //             ),
            //             "description" => null,
            //             "created_at" => "2024-01-12T01:27:31Z",
            //             "updated_at" => "2024-01-12T01:27:31Z",
            //             "resource" => "transaction",
            //             "resource_path" => "/v2/accounts/a34bgfad-ed67-538b-bffc-730c98c10da0/transactions/a1794ecf-5693-55fa-70cf-ef731748ed82",
            //             "instant_exchange" => false,
            //             "network" => array(
            //                 "status" => "pending",
            //                 "status_description" => "Pending (est. less than 10 minutes)",
            //                 "transaction_fee" => array(
            //                     "amount" => "4.008308",
            //                     "currency" => "USDC"
            //                 ),
            //                 "transaction_amount" => array(
            //                     "amount" => "10.000000",
            //                     "currency" => "USDC"
            //                 ),
            //                 "confirmations" => 0
            //             ),
            //             "to" => {
            //                 "resource" => "ethereum_address",
            //                 "address" => "0x9...",
            //                 "currency" => "USDC",
            //                 "address_info" => array(
            //                     "address" => "0x9..."
            //                 }
            //             ),
            //             "idem" => "748d8591-dg9a-7831-a45b-crd61dg78762",
            //             "details" => array(
            //                 "title" => "Sent USDC",
            //                 "subtitle" => "To USDC $address on Ethereum network",
            //                 "header" => "Sent 14.008308 USDC ($18.74)",
            //                 "health" => "warning"
            //             ),
            //             "hide_native_amount" => false
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_transaction($data, $currency);
        }) ();
    }

    public function fetch_deposit_addresses_by_network(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the deposit address for a $currency associated with this account
             *
             * @see https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_postcoinbaseaccountaddresses
             *
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = null;
            list($request, $params) = Async\await($this->prepare_account_request_with_currency_code($currency['code'], null, $params));
            $response = Async\await($this->v2PrivateGetAccountsAccountIdAddresses ($this->extend($request, $params)));
            //
            //    {
            //        pagination => array(
            //            ending_before => null,
            //            starting_after => null,
            //            previous_ending_before => null,
            //            next_starting_after => null,
            //            limit => '25',
            //            order => 'desc',
            //            previous_uri => null,
            //            next_uri => null
            //        ),
            //        $data => array(
            //            {
            //                id => '64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
            //                address => '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
            //                address_info => array( address => '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk' ),
            //                name => null,
            //                created_at => '2023-05-29T21:12:12Z',
            //                updated_at => '2023-05-29T21:12:12Z',
            //                network => 'solana',
            //                uri_scheme => 'solana',
            //                resource => 'address',
            //                resource_path => '/v2/accounts/a7b3d387-bfb8-5ce7-b8da-1f507e81cf25/addresses/64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
            //                warnings => array(
            //                    {
            //                    type => 'correct_address_warning',
            //                    title => 'This is an ERC20 USDC address.',
            //                    details => 'Only send ERC20 USD Coin (USDC) to this address.',
            //                    image_url => 'https://www.coinbase.com/assets/addresses/global-receive-warning-a3d91807e61c717e5a38d270965003dcc025ca8a3cea40ec3d7835b7c86087fa.png',
            //                    options => array( array( text => 'I understand', style => 'primary', id => 'dismiss' ) )
            //                    }
            //                ),
            //                qr_code_image_url => 'https://static-assets.coinbase.com/p2p/l2/asset_network_combinations/v5/usdc-solana.png',
            //                address_label => 'USDC address (Solana)',
            //                default_receive => true,
            //                deposit_uri => 'solana:5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk?spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
            //                callback_url => null,
            //                share_address_copy => array(
            //                    line1 => '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
            //                    line2 => 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.'
            //                ),
            //                receive_subtitle => 'ERC-20',
            //                inline_warning => {
            //                    text => 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.',
            //                    tooltip => array(
            //                    title => 'USDC (Solana)',
            //                    subtitle => 'This address can only receive USDC-SPL from Solana network.'
            //                    }
            //                }
            //            ),
            //            ...
            //        )
            //    }
            //
            $data = $this->safe_list($response, 'data', array());
            $addressStructures = $this->parse_deposit_addresses($data, null, false);
            return $this->index_by($addressStructures, 'network');
        }) ();
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //    {
        //        id => '64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
        //        $address => '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
        //        address_info => array(
        //            $address => 'GCF74576I7AQ56SLMKBQAP255EGUOWCRVII3S44KEXVNJEOIFVBDMXVL',
        //            destination_tag => '3722061866'
        //        ),
        //        name => null,
        //        created_at => '2023-05-29T21:12:12Z',
        //        updated_at => '2023-05-29T21:12:12Z',
        //        network => 'solana',
        //        uri_scheme => 'solana',
        //        resource => 'address',
        //        resource_path => '/v2/accounts/a7b3d387-bfb8-5ce7-b8da-1f507e81cf25/addresses/64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
        //        warnings => array(
        //            {
        //            type => 'correct_address_warning',
        //            title => 'This is an ERC20 USDC $address->',
        //            details => 'Only send ERC20 USD Coin (USDC) to this $address->',
        //            image_url => 'https://www.coinbase.com/assets/addresses/global-receive-warning-a3d91807e61c717e5a38d270965003dcc025ca8a3cea40ec3d7835b7c86087fa.png',
        //            options => array( array( text => 'I understand', style => 'primary', id => 'dismiss' ) )
        //            }
        //        ),
        //        qr_code_image_url => 'https://static-assets.coinbase.com/p2p/l2/asset_network_combinations/v5/usdc-solana.png',
        //        address_label => 'USDC $address (Solana)',
        //        default_receive => true,
        //        deposit_uri => 'solana:5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk?spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
        //        callback_url => null,
        //        share_address_copy => array(
        //            line1 => '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
        //            line2 => 'This $address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.'
        //        ),
        //        receive_subtitle => 'ERC-20',
        //        inline_warning => {
        //            text => 'This $address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.',
        //            tooltip => {
        //            title => 'USDC (Solana)',
        //            subtitle => 'This $address can only receive USDC-SPL from Solana network.'
        //            }
        //        }
        //    }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $this->check_address($address);
        $networkId = $this->safe_string($depositAddress, 'network');
        $code = $this->safe_currency_code(null, $currency);
        $addressLabel = $this->safe_string($depositAddress, 'address_label');
        $currencyId = null;
        if ($addressLabel !== null) {
            $splitAddressLabel = explode(' ', $addressLabel);
            $currencyId = $this->safe_string($splitAddressLabel, 0);
        }
        $addressInfo = $this->safe_dict($depositAddress, 'address_info');
        return array(
            'info' => $depositAddress,
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'network' => $this->network_id_to_code($networkId, $code),
            'address' => $address,
            'tag' => $this->safe_string($addressInfo, 'destination_tag'),
        );
    }

    public function deposit(string $code, float $amount, string $id, $params = array ()) {
        return Async\async(function () use ($code, $amount, $id, $params) {
            /**
             * make a deposit
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-deposits#deposit-funds
             *
             * @param {string} $code unified currency $code
             * @param {float} $amount the $amount to deposit
             * @param {string} $id the payment method $id to be used for the deposit, can be retrieved from v2PrivateGetPaymentMethods
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->accountId] the $id of the account to deposit into
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $accountId = $this->safe_string_2($params, 'account_id', 'accountId');
            $params = $this->omit($params, array( 'account_id', 'accountId' ));
            if ($accountId === null) {
                if ($code === null) {
                    throw new ArgumentsRequired($this->id . ' deposit() requires an account_id (or $accountId) parameter OR a currency $code argument');
                }
                $accountId = Async\await($this->find_account_id($code, $params));
                if ($accountId === null) {
                    throw new ExchangeError($this->id . ' deposit() could not find account $id for ' . $code);
                }
            }
            $request = array(
                'account_id' => $accountId,
                'amount' => $this->number_to_string($amount),
                'currency' => strtoupper($code), // need to use $code in case depositing USD etc.
                'payment_method' => $id,
                'commit' => true, // otheriwse the deposit does not go through
            );
            $response = Async\await($this->v2PrivatePostAccountsAccountIdDeposits ($this->extend($request, $params)));
            //
            //     {
            //         "data" => {
            //             "id" => "67e0eaec-07d7-54c4-a72c-2e92826897df",
            //             "status" => "created",
            //             "payment_method" => array(
            //                 "id" => "83562370-3e5c-51db-87da-752af5ab9559",
            //                 "resource" => "payment_method",
            //                 "resource_path" => "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
            //             ),
            //             "transaction" => array(
            //                 "id" => "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
            //                 "resource" => "transaction",
            //                 "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
            //             ),
            //             "amount" => array(
            //                 "amount" => "10.00",
            //                 "currency" => "USD"
            //             ),
            //             "subtotal" => array(
            //                 "amount" => "10.00",
            //                 "currency" => "USD"
            //             ),
            //             "created_at" => "2015-01-31T20:49:02Z",
            //             "updated_at" => "2015-02-11T16:54:02-08:00",
            //             "resource" => "deposit",
            //             "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/deposits/67e0eaec-07d7-54c4-a72c-2e92826897df",
            //             "committed" => true,
            //             "fee" => array(
            //                 "amount" => "0.00",
            //                 "currency" => "USD"
            //             ),
            //             "payout_at" => "2015-02-18T16:54:00-08:00"
            //         }
            //     }
            //
            // https://github.com/ccxt/ccxt/issues/25484
            $data = $this->safe_dict_2($response, 'data', 'transfer', array());
            return $this->parse_transaction($data);
        }) ();
    }

    public function fetch_deposit(string $id, ?string $code = null, $params = array ()) {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch information on a deposit, fiat only, for crypto transactions use fetchLedger
             *
             * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-deposits#show-deposit
             *
             * @param {string} $id deposit $id
             * @param {string} [$code] unified currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->accountId] the $id of the account that the funds were deposited into
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $accountId = $this->safe_string_2($params, 'account_id', 'accountId');
            $params = $this->omit($params, array( 'account_id', 'accountId' ));
            if ($accountId === null) {
                if ($code === null) {
                    throw new ArgumentsRequired($this->id . ' fetchDeposit() requires an account_id (or $accountId) parameter OR a currency $code argument');
                }
                $accountId = Async\await($this->find_account_id($code, $params));
                if ($accountId === null) {
                    throw new ExchangeError($this->id . ' fetchDeposit() could not find account $id for ' . $code);
                }
            }
            $request = array(
                'account_id' => $accountId,
                'deposit_id' => $id,
            );
            $response = Async\await($this->v2PrivateGetAccountsAccountIdDepositsDepositId ($this->extend($request, $params)));
            //
            //     {
            //         "data" => {
            //             "id" => "67e0eaec-07d7-54c4-a72c-2e92826897df",
            //             "status" => "completed",
            //             "payment_method" => array(
            //                 "id" => "83562370-3e5c-51db-87da-752af5ab9559",
            //                 "resource" => "payment_method",
            //                 "resource_path" => "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
            //             ),
            //             "transaction" => array(
            //                 "id" => "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
            //                 "resource" => "transaction",
            //                 "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
            //             ),
            //             "amount" => array(
            //                 "amount" => "10.00",
            //                 "currency" => "USD"
            //             ),
            //             "subtotal" => array(
            //                 "amount" => "10.00",
            //                 "currency" => "USD"
            //             ),
            //             "created_at" => "2015-01-31T20:49:02Z",
            //             "updated_at" => "2015-02-11T16:54:02-08:00",
            //             "resource" => "deposit",
            //             "resource_path" => "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/deposits/67e0eaec-07d7-54c4-a72c-2e92826897df",
            //             "committed" => true,
            //             "fee" => array(
            //                 "amount" => "0.00",
            //                 "currency" => "USD"
            //             ),
            //             "payout_at" => "2015-02-18T16:54:00-08:00"
            //         }
            //     }
            //
            // https://github.com/ccxt/ccxt/issues/25484
            $data = $this->safe_dict_2($response, 'data', 'transfer', array());
            return $this->parse_transaction($data);
        }) ();
    }

    public function fetch_deposit_method_ids($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * fetch the deposit id for a fiat currency associated with this account
             *
             * @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpaymentmethods
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=deposit-id-structure deposit id structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v3PrivateGetBrokeragePaymentMethods ($params));
            //
            //     {
            //         "payment_methods" => array(
            //             {
            //                 "id" => "21b39a5d-f7b46876fb2e",
            //                 "type" => "COINBASE_FIAT_ACCOUNT",
            //                 "name" => "CAD Wallet",
            //                 "currency" => "CAD",
            //                 "verified" => true,
            //                 "allow_buy" => false,
            //                 "allow_sell" => true,
            //                 "allow_deposit" => false,
            //                 "allow_withdraw" => false,
            //                 "created_at" => "2023-06-29T19:58:46Z",
            //                 "updated_at" => "2023-10-30T20:25:01Z"
            //             }
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'payment_methods', array());
            return $this->parse_deposit_method_ids($result);
        }) ();
    }

    public function fetch_deposit_method_id(string $id, $params = array ()) {
        return Async\async(function () use ($id, $params) {
            /**
             * fetch the deposit $id for a fiat currency associated with this account
             *
             * @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpaymentmethod
             *
             * @param {string} $id the deposit payment method $id
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=deposit-$id-structure deposit $id structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'payment_method_id' => $id,
            );
            $response = Async\await($this->v3PrivateGetBrokeragePaymentMethodsPaymentMethodId ($this->extend($request, $params)));
            //
            //     {
            //         "payment_method" => {
            //             "id" => "21b39a5d-f7b46876fb2e",
            //             "type" => "COINBASE_FIAT_ACCOUNT",
            //             "name" => "CAD Wallet",
            //             "currency" => "CAD",
            //             "verified" => true,
            //             "allow_buy" => false,
            //             "allow_sell" => true,
            //             "allow_deposit" => false,
            //             "allow_withdraw" => false,
            //             "created_at" => "2023-06-29T19:58:46Z",
            //             "updated_at" => "2023-10-30T20:25:01Z"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'payment_method', array());
            return $this->parse_deposit_method_id($result);
        }) ();
    }

    public function parse_deposit_method_ids($ids, $params = array ()) {
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $this->extend($this->parse_deposit_method_id($ids[$i]), $params);
            $result[] = $id;
        }
        return $result;
    }

    public function parse_deposit_method_id($depositId) {
        return array(
            'info' => $depositId,
            'id' => $this->safe_string($depositId, 'id'),
            'currency' => $this->safe_string($depositId, 'currency'),
            'verified' => $this->safe_bool($depositId, 'verified'),
            'tag' => $this->safe_string($depositId, 'name'),
        );
    }

    public function fetch_convert_quote(string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($fromCode, $toCode, $amount, $params) {
            /**
             * fetch a quote for converting from one currency to another
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_createconvertquote
             *
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {array} [$params->trade_incentive_metadata] an object to fill in user incentive $data
             * @param {string} [$params->trade_incentive_metadata.user_incentive_id] the id of the incentive
             * @param {string} [$params->trade_incentive_metadata.code_val] the code value of the incentive
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'from_account' => $fromCode,
                'to_account' => $toCode,
                'amount' => $this->number_to_string($amount),
            );
            $response = Async\await($this->v3PrivatePostBrokerageConvertQuote ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'trade', array());
            return $this->parse_conversion($data);
        }) ();
    }

    public function create_convert_trade(string $id, string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $fromCode, $toCode, $amount, $params) {
            /**
             * convert from one currency to another
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_commitconverttrade
             *
             * @param {string} $id the $id of the trade that you want to make
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'trade_id' => $id,
                'from_account' => $fromCode,
                'to_account' => $toCode,
            );
            $response = Async\await($this->v3PrivatePostBrokerageConvertTradeTradeId ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'trade', array());
            return $this->parse_conversion($data);
        }) ();
    }

    public function fetch_convert_trade(string $id, ?string $code = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch the $data for a conversion trade
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getconverttrade
             *
             * @param {string} $id the $id of the trade that you want to commit
             * @param {string} $code the unified currency $code that was converted from
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {strng} $params->toCode the unified currency $code that was converted into
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            if ($code === null) {
                throw new ArgumentsRequired($this->id . ' fetchConvertTrade() requires a $code argument');
            }
            $toCode = $this->safe_string($params, 'toCode');
            if ($toCode === null) {
                throw new ArgumentsRequired($this->id . ' fetchConvertTrade() requires a $toCode parameter');
            }
            $params = $this->omit($params, 'toCode');
            $request = array(
                'trade_id' => $id,
                'from_account' => $code,
                'to_account' => $toCode,
            );
            $response = Async\await($this->v3PrivateGetBrokerageConvertTradeTradeId ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'trade', array());
            return $this->parse_conversion($data);
        }) ();
    }

    public function parse_conversion(array $conversion, ?array $fromCurrency = null, ?array $toCurrency = null): array {
        $fromCoin = $this->safe_string($conversion, 'source_currency');
        $fromCode = $this->safe_currency_code($fromCoin, $fromCurrency);
        $to = $this->safe_string($conversion, 'target_currency');
        $toCode = $this->safe_currency_code($to, $toCurrency);
        $fromAmountStructure = $this->safe_dict($conversion, 'user_entered_amount');
        $feeStructure = $this->safe_dict($conversion, 'total_fee');
        $feeAmountStructure = $this->safe_dict($feeStructure, 'amount');
        return array(
            'info' => $conversion,
            'timestamp' => null,
            'datetime' => null,
            'id' => $this->safe_string($conversion, 'id'),
            'fromCurrency' => $fromCode,
            'fromAmount' => $this->safe_number($fromAmountStructure, 'value'),
            'toCurrency' => $toCode,
            'toAmount' => null,
            'price' => null,
            'fee' => $this->safe_number($feeAmountStructure, 'value'),
        );
    }

    public function close_position(string $symbol, ?string $side = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $side, $params) {
            /**
             * *futures only* closes open positions for a $market
             *
             * @see https://docs.cdp.coinbase.com/coinbase-app/trade/reference/retailbrokerageapi_closeposition
             *
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {string} [$side] not used by coinbase
             * @param {array} [$params] extra parameters specific to the coinbase api endpoint
             * @param {string}  $params->clientOrderId *mandatory* the client $order id of the position to close
             * @param {float} [$params->size] the size of the position to close, optional
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $clientOrderId = $this->safe_string_2($params, 'client_order_id', 'clientOrderId');
            $params = $this->omit($params, 'clientOrderId');
            $request = array(
                'product_id' => $market['id'],
            );
            if ($clientOrderId === null) {
                throw new ArgumentsRequired($this->id . ' closePosition() requires a $clientOrderId parameter');
            }
            $request['client_order_id'] = $clientOrderId;
            $response = Async\await($this->v3PrivatePostBrokerageOrdersClosePosition ($this->extend($request, $params)));
            $order = $this->safe_dict($response, 'success_response', array());
            return $this->parse_order($order);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmpositions
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getintxpositions
             *
             * @param {string[]} [$symbols] list of unified $market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->portfolio] the $portfolio UUID to fetch $positions for
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $market = null;
            if ($symbols !== null) {
                $market = $this->market($symbols[0]);
            }
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchPositions', $market, $params);
            $response = null;
            if ($type === 'future') {
                $response = Async\await($this->v3PrivateGetBrokerageCfmPositions ($params));
            } else {
                $portfolio = null;
                list($portfolio, $params) = $this->handle_option_and_params($params, 'fetchPositions', 'portfolio');
                if ($portfolio === null) {
                    throw new ArgumentsRequired($this->id . ' fetchPositions() requires a "portfolio" value in $params (eg => dbcb91e7-2bc9-515), or set.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
                }
                $request = array(
                    'portfolio_uuid' => $portfolio,
                );
                $response = Async\await($this->v3PrivateGetBrokerageIntxPositionsPortfolioUuid ($this->extend($request, $params)));
            }
            $positions = $this->safe_list($response, 'positions', array());
            return $this->parse_positions($positions, $symbols);
        }) ();
    }

    public function fetch_position(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch data on a single open contract trade $position
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getintxposition
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmposition
             *
             * @param {string} $symbol unified $market $symbol of the $market the $position is held in, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->product_id] *futures only* the product id of the $position to fetch, required for futures markets only
             * @param {string} [$params->portfolio] *perpetual/swaps only* the $portfolio UUID to fetch the $position for, required for perpetual/swaps markets only
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$position-structure $position structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $response = null;
            if ($market['future']) {
                $productId = $this->safe_string($market, 'product_id');
                if ($productId === null) {
                    throw new ArgumentsRequired($this->id . ' fetchPosition() requires a "product_id" in params');
                }
                $futureRequest = array(
                    'product_id' => $productId,
                );
                $response = Async\await($this->v3PrivateGetBrokerageCfmPositionsProductId ($this->extend($futureRequest, $params)));
            } else {
                $portfolio = null;
                list($portfolio, $params) = $this->handle_option_and_params($params, 'fetchPositions', 'portfolio');
                if ($portfolio === null) {
                    throw new ArgumentsRequired($this->id . ' fetchPosition() requires a "portfolio" value in $params (eg => dbcb91e7-2bc9-515), or set.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
                }
                $request = array(
                    'symbol' => $market['id'],
                    'portfolio_uuid' => $portfolio,
                );
                $response = Async\await($this->v3PrivateGetBrokerageIntxPositionsPortfolioUuidSymbol ($this->extend($request, $params)));
            }
            $position = $this->safe_dict($response, 'position', array());
            return $this->parse_position($position, $market);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        // {
        //     "product_id" => "1r4njf84-0-0",
        //     "product_uuid" => "cd34c18b-3665-4ed8-9305-3db277c49fc5",
        //     "symbol" => "ADA-PERP-INTX",
        //     "vwap" => array(
        //        "value" => "0.6171",
        //        "currency" => "USDC"
        //     ),
        //     "position_side" => "POSITION_SIDE_LONG",
        //     "net_size" => "20",
        //     "buy_order_size" => "0",
        //     "sell_order_size" => "0",
        //     "im_contribution" => "0.1",
        //     "unrealized_pnl" => array(
        //        "value" => "0.074",
        //        "currency" => "USDC"
        //     ),
        //     "mark_price" => array(
        //        "value" => "0.6208",
        //        "currency" => "USDC"
        //     ),
        //     "liquidation_price" => array(
        //        "value" => "0",
        //        "currency" => "USDC"
        //     ),
        //     "leverage" => "1",
        //     "im_notional" => array(
        //        "value" => "12.342",
        //        "currency" => "USDC"
        //     ),
        //     "mm_notional" => array(
        //        "value" => "0.814572",
        //        "currency" => "USDC"
        //     ),
        //     "position_notional" => array(
        //        "value" => "12.342",
        //        "currency" => "USDC"
        //     ),
        //     "margin_type" => "MARGIN_TYPE_CROSS",
        //     "liquidation_buffer" => "19.677828",
        //     "liquidation_percentage" => "4689.3506",
        //     "portfolio_summary" => {
        //        "portfolio_uuid" => "018ebd63-1f6d-7c8e-ada9-0761c5a2235f",
        //        "collateral" => "20.4184",
        //        "position_notional" => "12.342",
        //        "open_position_notional" => "12.342",
        //        "pending_fees" => "0",
        //        "borrow" => "0",
        //        "accrued_interest" => "0",
        //        "rolling_debt" => "0",
        //        "portfolio_initial_margin" => "0.1",
        //        "portfolio_im_notional" => array(
        //           "value" => "12.342",
        //           "currency" => "USDC"
        //        ),
        //        "portfolio_maintenance_margin" => "0.066",
        //        "portfolio_mm_notional" => array(
        //           "value" => "0.814572",
        //           "currency" => "USDC"
        //        ),
        //        "liquidation_percentage" => "4689.3506",
        //        "liquidation_buffer" => "19.677828",
        //        "margin_type" => "MARGIN_TYPE_CROSS",
        //        "margin_flags" => "PORTFOLIO_MARGIN_FLAGS_UNSPECIFIED",
        //        "liquidation_status" => "PORTFOLIO_LIQUIDATION_STATUS_NOT_LIQUIDATING",
        //        "unrealized_pnl" => array(
        //           "value" => "0.074",
        //           "currency" => "USDC"
        //        ),
        //        "buying_power" => array(
        //           "value" => "8.1504",
        //           "currency" => "USDC"
        //        ),
        //        "total_balance" => array(
        //           "value" => "20.4924",
        //           "currency" => "USDC"
        //        ),
        //        "max_withdrawal" => array(
        //           "value" => "8.0764",
        //           "currency" => "USDC"
        //        }
        //     ),
        //     "entry_vwap" => {
        //        "value" => "0.6091",
        //        "currency" => "USDC"
        //     }
        // }
        //
        $marketId = $this->safe_string($position, 'symbol', '');
        $market = $this->safe_market($marketId, $market);
        $rawMargin = $this->safe_string($position, 'margin_type');
        $marginMode = null;
        if ($rawMargin !== null) {
            $marginMode = ($rawMargin === 'MARGIN_TYPE_CROSS') ? 'cross' : 'isolated';
        }
        $notionalObject = $this->safe_dict($position, 'position_notional', array());
        $positionSide = $this->safe_string($position, 'position_side');
        $side = ($positionSide === 'POSITION_SIDE_LONG') ? 'long' : 'short';
        $unrealizedPNLObject = $this->safe_dict($position, 'unrealized_pnl', array());
        $liquidationPriceObject = $this->safe_dict($position, 'liquidation_price', array());
        $liquidationPrice = $this->safe_number($liquidationPriceObject, 'value');
        $vwapObject = $this->safe_dict($position, 'vwap', array());
        $summaryObject = $this->safe_dict($position, 'portfolio_summary', array());
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'product_id'),
            'symbol' => $this->safe_symbol($marketId, $market),
            'notional' => $this->safe_number($notionalObject, 'value'),
            'marginMode' => $marginMode,
            'liquidationPrice' => $liquidationPrice,
            'entryPrice' => $this->safe_number($vwapObject, 'value'),
            'unrealizedPnl' => $this->safe_number($unrealizedPNLObject, 'value'),
            'realizedPnl' => null,
            'percentage' => null,
            'contracts' => $this->safe_number($position, 'net_size'),
            'contractSize' => $market['contractSize'],
            'markPrice' => null,
            'lastPrice' => null,
            'side' => $side,
            'hedged' => null,
            'timestamp' => null,
            'datetime' => null,
            'lastUpdateTimestamp' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'collateral' => $this->safe_number($summaryObject, 'collateral'),
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'leverage' => $this->safe_number($position, 'leverage'),
            'marginRatio' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_trading_fees($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_gettransactionsummary/
             *
             * fetch the trading fees for multiple markets
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->type] 'spot' or 'swap'
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchTradingFees', null, $params);
            $isSpot = ($type === 'spot');
            $productType = $isSpot ? 'SPOT' : 'FUTURE';
            $request = array(
                'product_type' => $productType,
            );
            $response = Async\await($this->v3PrivateGetBrokerageTransactionSummary ($this->extend($request, $params)));
            //
            // {
            //     total_volume => '0',
            //     total_fees => '0',
            //     fee_tier => array(
            //       pricing_tier => 'Advanced 1',
            //       usd_from => '0',
            //       usd_to => '1000',
            //       taker_fee_rate => '0.008',
            //       maker_fee_rate => '0.006',
            //       aop_from => '',
            //       aop_to => ''
            //     ),
            //     margin_rate => null,
            //     goods_and_services_tax => null,
            //     advanced_trade_only_volume => '0',
            //     advanced_trade_only_fees => '0',
            //     coinbase_pro_volume => '0',
            //     coinbase_pro_fees => '0',
            //     total_balance => '',
            //     has_promo_fee => false
            // }
            //
            $data = $this->safe_dict($response, 'fee_tier', array());
            $taker_fee = $this->safe_number($data, 'taker_fee_rate');
            $marker_fee = $this->safe_number($data, 'maker_fee_rate');
            $result = array();
            for ($i = 0; $i < count($this->symbols); $i++) {
                $symbol = $this->symbols[$i];
                $market = $this->market($symbol);
                if (($isSpot && $market['spot']) || (!$isSpot && !$market['spot'])) {
                    $result[$symbol] = array(
                        'info' => $response,
                        'symbol' => $symbol,
                        'maker' => $taker_fee,
                        'taker' => $marker_fee,
                        'percentage' => true,
                    );
                }
            }
            return $result;
        }) ();
    }

    public function fetch_portfolio_details(string $portfolioUuid, $params = array ()): PromiseInterface {
        return Async\async(function () use ($portfolioUuid, $params) {
            /**
             * Fetch details for a specific portfolio by UUID
             *
             * @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getportfolios
             *
             * @param {string} $portfolioUuid The unique identifier of the portfolio to fetch
             * @param {Dict} [$params] Extra parameters specific to the exchange API endpoint
             * @return {any[]} An account structure <https://docs.ccxt.com/#/?id=account-structure>
             */
            Async\await($this->load_markets());
            $request = array(
                'portfolio_uuid' => $portfolioUuid,
            );
            $response = Async\await($this->v3PrivateGetBrokeragePortfoliosPortfolioUuid ($this->extend($request, $params)));
            $result = $this->parse_portfolio_details($response);
            return $result;
        }) ();
    }

    public function parse_portfolio_details(array $portfolioData) {
        $breakdown = $portfolioData['breakdown'];
        $portfolioInfo = $this->safe_dict($breakdown, 'portfolio', array());
        $portfolioName = $this->safe_string($portfolioInfo, 'name', 'Unknown');
        $portfolioUuid = $this->safe_string($portfolioInfo, 'uuid', '');
        $spotPositions = $this->safe_list($breakdown, 'spot_positions', array());
        $parsedPositions = array();
        for ($i = 0; $i < count($spotPositions); $i++) {
            $position = $spotPositions[$i];
            $currencyCode = $this->safe_string($position, 'asset', 'Unknown');
            $availableBalanceStr = $this->safe_string($position, 'available_to_trade_fiat', '0');
            $availableBalance = $this->parse_number($availableBalanceStr);
            $totalBalanceFiatStr = $this->safe_string($position, 'total_balance_fiat', '0');
            $totalBalanceFiat = $this->parse_number($totalBalanceFiatStr);
            $holdAmount = $totalBalanceFiat - $availableBalance;
            $costBasisDict = $this->safe_dict($position, 'cost_basis', array());
            $costBasisStr = $this->safe_string($costBasisDict, 'value', '0');
            $averageEntryPriceDict = $this->safe_dict($position, 'average_entry_price', array());
            $averageEntryPriceStr = $this->safe_string($averageEntryPriceDict, 'value', '0');
            $positionData = array(
                'currency' => $currencyCode,
                'available_balance' => $availableBalance,
                'hold_amount' => $holdAmount > 0 ? $holdAmount : 0,
                'wallet_name' => $portfolioName,
                'account_id' => $portfolioUuid,
                'account_uuid' => $this->safe_string($position, 'account_uuid', ''),
                'total_balance_fiat' => $totalBalanceFiat,
                'total_balance_crypto' => $this->parse_number($this->safe_string($position, 'total_balance_crypto', '0')),
                'available_to_trade_fiat' => $this->parse_number($this->safe_string($position, 'available_to_trade_fiat', '0')),
                'available_to_trade_crypto' => $this->parse_number($this->safe_string($position, 'available_to_trade_crypto', '0')),
                'available_to_transfer_fiat' => $this->parse_number($this->safe_string($position, 'available_to_transfer_fiat', '0')),
                'available_to_transfer_crypto' => $this->parse_number($this->safe_string($position, 'available_to_trade_crypto', '0')),
                'allocation' => $this->parse_number($this->safe_string($position, 'allocation', '0')),
                'cost_basis' => $this->parse_number($costBasisStr),
                'cost_basis_currency' => $this->safe_string($costBasisDict, 'currency', 'USD'),
                'is_cash' => $this->safe_bool($position, 'is_cash', false),
                'average_entry_price' => $this->parse_number($averageEntryPriceStr),
                'average_entry_price_currency' => $this->safe_string($averageEntryPriceDict, 'currency', 'USD'),
                'asset_uuid' => $this->safe_string($position, 'asset_uuid', ''),
                'unrealized_pnl' => $this->parse_number($this->safe_string($position, 'unrealized_pnl', '0')),
                'asset_color' => $this->safe_string($position, 'asset_color', ''),
                'account_type' => $this->safe_string($position, 'account_type', ''),
            );
            $parsedPositions[] = $positionData;
        }
        return $parsedPositions;
    }

    public function create_auth_token(?int $seconds, ?string $method = null, ?string $url = null, $useEddsa = false) {
        // v1 https://docs.cdp.coinbase.com/api-reference/authentication#php-2
        // v2  https://docs.cdp.coinbase.com/api-reference/v2/authentication
        $uri = null;
        if ($url !== null) {
            $uri = $method . ' ' . str_replace('https://', '', $url);
            $quesPos = mb_strpos($uri, '?');
            // Due to we use mb_strpos, $quesPos could be false in php. In that case, the $quesPos >= 0 is true
            // Also it's not possible that the question mark is first character, only check > 0 here.
            if ($quesPos > 0) {
                $uri = mb_substr($uri, 0, $quesPos - 0);
            }
        }
        // $this->eddsaarray("sub":"d2efa49a-369c-43d7-a60e-ae26e28853c2","iss":"cdp","aud":["cdp_service"],"uris":["GET api.coinbase.com/api/v3/brokerage/transaction_summary"])
        $nonce = $this->random_bytes(16);
        $aud = $useEddsa ? 'cdp_service' : 'retail_rest_api_proxy';
        $iss = $useEddsa ? 'cdp' : 'coinbase-cloud';
        $request = array(
            'aud' => array( $aud ),
            'iss' => $iss,
            'nbf' => $seconds,
            'exp' => $seconds + 120,
            'sub' => $this->apiKey,
            'iat' => $seconds,
        );
        if ($uri !== null) {
            if (!$useEddsa) {
                $request['uri'] = $uri;
            } else {
                $request['uris'] = array( $uri );
            }
        }
        if ($useEddsa) {
            $byteArray = base64_decode($this->secret);
            $seed = $this->array_slice($byteArray, 0, 32);
            return $this->jwt($request, $seed, 'sha256', false, array( 'kid' => $this->apiKey, 'nonce' => $nonce, 'alg' => 'EdDSA' ));
        } else {
            // $this->ecdsawith p256
            return $this->jwt($request, $this->encode($this->secret), 'sha256', false, array( 'kid' => $this->apiKey, 'nonce' => $nonce, 'alg' => 'ES256' ));
        }
    }

    public function nonce() {
        return $this->milliseconds() - $this->options['timeDifference'];
    }

    public function sign($path, $api = [], $method = 'GET', $params = array (), $headers = null, $body = null) {
        $version = $api[0];
        $signed = $api[1] === 'private';
        $isV3 = $version === 'v3';
        $pathPart = ($isV3) ? 'api/v3' : 'v2';
        $fullPath = '/' . $pathPart . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $savedPath = $fullPath;
        if ($method === 'GET') {
            if ($query) {
                $fullPath .= '?' . $this->urlencode_with_array_repeat($query);
            }
        }
        $url = $this->urls['api']['rest'] . $fullPath;
        if ($signed) {
            $authorization = $this->safe_string($this->headers, 'Authorization');
            $authorizationString = null;
            if ($authorization !== null) {
                $authorizationString = $authorization;
            } elseif ($this->token && !$this->check_required_credentials(false)) {
                $authorizationString = 'Bearer ' . $this->token;
            } else {
                $this->check_required_credentials();
                $seconds = $this->seconds();
                $payload = '';
                if ($method !== 'GET') {
                    if ($query) {
                        $body = $this->json($query);
                        $payload = $body;
                    }
                } else {
                    if (!$isV3) {
                        if ($query) {
                            $payload .= '?' . $this->urlencode($query);
                        }
                    }
                }
                // v3 => 'GET' doesn't need $payload in the $signature-> inside $url is enough
                // https://docs.cloud.coinbase.com/advanced-trade/docs/auth#example-$request
                // v2 => 'GET' require $payload in the $signature
                // https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-key-authentication
                $isCloudAPiKey = (mb_strpos($this->apiKey, 'organizations/') !== false) || (str_starts_with($this->secret, '-----BEGIN'));
                // using the size might be fragile, so we add an option to force v2 cloud $api key if needed
                $isV2CloudAPiKey = strlen($this->secret) === 88 || $this->safe_bool($this->options, 'v2CloudAPiKey', false) || str_ends_with($this->secret, '=');
                if ($isCloudAPiKey || $isV2CloudAPiKey) {
                    if ($isCloudAPiKey && str_starts_with($this->apiKey, '-----BEGIN')) {
                        throw new ArgumentsRequired($this->id . ' apiKey should contain the name (eg => organizations/3b910e93....) and not the public key');
                    }
                    // // it may not work for v2
                    // $uri = $method . ' ' . str_replace('https://', '', $url);
                    // $quesPos = mb_strpos($uri, '?');
                    // // Due to we use mb_strpos, $quesPos could be false in php. In that case, the $quesPos >= 0 is true
                    // // Also it's not possible that the question mark is first character, only check > 0 here.
                    // if ($quesPos > 0) {
                    //     $uri = mb_substr($uri, 0, $quesPos - 0);
                    // }
                    // $nonce = $this->random_bytes(16);
                    // $request = array(
                    //     'aud' => array( 'retail_rest_api_proxy' ),
                    //     'iss' => 'coinbase-cloud',
                    //     'nbf' => $seconds,
                    //     'exp' => $seconds + 120,
                    //     'sub' => $this->apiKey,
                    //     'uri' => $uri,
                    //     'iat' => $seconds,
                    // );
                    $token = $this->create_auth_token($seconds, $method, $url, $isV2CloudAPiKey);
                    // $token = $this->jwt($request, $this->encode($this->secret), 'sha256', false, array( 'kid' => $this->apiKey, 'nonce' => $nonce, 'alg' => 'ES256' ));
                    $authorizationString = 'Bearer ' . $token;
                } else {
                    $nonce = $this->nonce();
                    $timestamp = $this->parse_to_int($nonce / 1000);
                    $timestampString = (string) $timestamp;
                    $auth = $timestampString . $method . $savedPath . $payload;
                    $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
                    $headers = array(
                        'CB-ACCESS-KEY' => $this->apiKey,
                        'CB-ACCESS-SIGN' => $signature,
                        'CB-ACCESS-TIMESTAMP' => $timestampString,
                        'Content-Type' => 'application/json',
                    );
                }
            }
            if ($authorizationString !== null) {
                $headers = array(
                    'Authorization' => $authorizationString,
                    'Content-Type' => 'application/json',
                );
                if ($method !== 'GET') {
                    if ($query) {
                        $body = $this->json($query);
                    }
                }
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // fallback to default error handler
        }
        $feedback = $this->id . ' ' . $body;
        //
        //    array("error" => "invalid_request", "error_description" => "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.")
        //
        // or
        //
        //    {
        //      "errors" => array(
        //        {
        //          "id" => "not_found",
        //          "message" => "Not found"
        //        }
        //      )
        //    }
        // or
        // {
        //     "success" => false,
        //     "error_response" => array(
        //       "error" => "UNKNOWN_FAILURE_REASON",
        //       "message" => "",
        //       "error_details" => "",
        //       "preview_failure_reason" => "PREVIEW_STOP_PRICE_ABOVE_LAST_TRADE_PRICE"
        //     ),
        //     "order_configuration" => {
        //       "stop_limit_stop_limit_gtc" => {
        //         "base_size" => "0.0001",
        //         "limit_price" => "2000",
        //         "stop_price" => "2005",
        //         "stop_direction" => "STOP_DIRECTION_STOP_DOWN",
        //         "reduce_only" => false
        //       }
        //     }
        // }
        //
        $errorCode = $this->safe_string($response, 'error');
        if ($errorCode !== null) {
            $errorMessage = $this->safe_string_2($response, 'error_description', 'error');
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorMessage, $feedback);
            throw new ExchangeError($feedback);
        }
        $errorResponse = $this->safe_dict($response, 'error_response');
        if ($errorResponse !== null) {
            $errorMessageInner = $this->safe_string_2($errorResponse, 'preview_failure_reason', 'preview_failure_reason');
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorMessageInner, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorMessageInner, $feedback);
            throw new ExchangeError($feedback);
        }
        $errors = $this->safe_list($response, 'errors');
        if ($errors !== null) {
            if (gettype($errors) === 'array' && array_keys($errors) === array_keys(array_keys($errors))) {
                $numErrors = count($errors);
                if ($numErrors > 0) {
                    $errorCode = $this->safe_string($errors[0], 'id');
                    $errorMessage = $this->safe_string($errors[0], 'message');
                    if ($errorCode !== null) {
                        $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
                        $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorMessage, $feedback);
                        throw new ExchangeError($feedback);
                    }
                }
            }
        }
        $advancedTrade = $this->options['advanced'];
        if (!(is_array($response) && array_key_exists('data', $response)) && (!$advancedTrade)) {
            throw new ExchangeError($this->id . ' failed due to a malformed $response ' . $this->json($response));
        }
        return null;
    }
}
