<?php

namespace ccxt;

// 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\abstract\coinbaseinternational as Exchange;

class coinbaseinternational extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'coinbaseinternational',
            'name' => 'Coinbase International',
            'countries' => array( 'US' ),
            'certified' => false,
            'pro' => true,
            'rateLimit' => 100, // 10 requests per second
            'version' => 'v1',
            'userAgent' => $this->userAgents['chrome'],
            'headers' => array(
                'CB-VERSION' => '2018-05-30',
            ),
            'has' => array(
                'CORS' => true,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => true,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => false,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createDepositAddress' => true,
                'createLimitBuyOrder' => true,
                'createLimitSellOrder' => true,
                'createMarketBuyOrder' => true,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrder' => true,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'editOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrders' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposits' => true,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchL2OrderBook' => false,
                'fetchLedger' => false,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyBuys' => true,
                'fetchMySells' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => false,
                'fetchOrders' => false,
                'fetchPosition' => true,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => false,
                'fetchTrades' => false,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransfers' => true,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'sandbox' => true,
                'setLeverage' => false,
                'setMargin' => true,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'withdraw' => true,
            ),
            'urls' => array(
                'logo' => 'https://github.com/ccxt/ccxt/assets/43336371/866ae638-6ab5-4ebf-ab2c-cdcce9545625',
                'api' => array(
                    'rest' => 'https://api.international.coinbase.com/api',
                ),
                'test' => array(
                    'rest' => 'https://api-n5e1.coinbase.com/api',
                ),
                'www' => 'https://international.coinbase.com',
                'doc' => array(
                    'https://docs.cloud.coinbase.com/intx/docs',
                ),
                'fees' => array(
                    'https://help.coinbase.com/en/international-exchange/trading-deposits-withdrawals/international-exchange-fees',
                ),
                'referral' => '',
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'password' => true,
            ),
            'api' => array(
                'v1' => array(
                    'public' => array(
                        'get' => array(
                            'assets',
                            'assets/{assets}',
                            'assets/{asset}/networks',
                            'instruments',
                            'instruments/{instrument}',
                            'instruments/{instrument}/quote',
                            'instruments/{instrument}/funding',
                            'instruments/{instrument}/candles',
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'orders',
                            'orders/{id}',
                            'portfolios',
                            'portfolios/{portfolio}',
                            'portfolios/{portfolio}/detail',
                            'portfolios/{portfolio}/summary',
                            'portfolios/{portfolio}/balances',
                            'portfolios/{portfolio}/balances/{asset}',
                            'portfolios/{portfolio}/positions',
                            'portfolios/{portfolio}/positions/{instrument}',
                            'portfolios/fills',
                            'portfolios/{portfolio}/fills',
                            'transfers',
                            'transfers/{transfer_uuid}',
                        ),
                        'post' => array(
                            'orders',
                            'portfolios',
                            'portfolios/margin',
                            'portfolios/transfer',
                            'transfers/withdraw',
                            'transfers/address',
                            'transfers/create-counterparty-id',
                            'transfers/validate-counterparty-id',
                            'transfers/withdraw/counterparty',
                        ),
                        'put' => array(
                            'orders/{id}',
                            'portfolios/{portfolio}',
                        ),
                        'delete' => array(
                            'orders',
                            'orders/{id}',
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'taker' => $this->parse_number('0.004'),
                    'maker' => $this->parse_number('0.002'),
                    'tierBased' => true,
                    'percentage' => true,
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.004') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.004') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0035') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0035') ),
                            array( $this->parse_number('50000000'), $this->parse_number('0.003') ),
                            array( $this->parse_number('250000000'), $this->parse_number('0.0025') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.002') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.001') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('50000000'), $this->parse_number('0.0005') ),
                            array( $this->parse_number('250000000'), $this->parse_number('0') ),
                        ),
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(),
                'broad' => array(
                    'DUPLICATE_CLIENT_ORDER_ID' => '\\ccxt\\DuplicateOrderId',
                    'Order rejected' => '\\ccxt\\InvalidOrder',
                    'market orders must be IoC' => '\\ccxt\\InvalidOrder',
                    'tif is required' => '\\ccxt\\InvalidOrder',
                    'Invalid replace order request' => '\\ccxt\\InvalidOrder',
                    'Unauthorized' => '\\ccxt\\PermissionDenied',
                    'invalid result_limit' => '\\ccxt\\BadRequest',
                    'is a required field' => '\\ccxt\\BadRequest',
                    'Not Found' => '\\ccxt\\BadRequest',
                    'ip not allowed' => '\\ccxt\\AuthenticationError',
                ),
            ),
            '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',
            ),
            'options' => array(
                'brokerId' => 'nfqkvdjp',
                'portfolio' => '', // default portfolio id
                'withdraw' => array(
                    'method' => 'v1PrivatePostTransfersWithdraw', // use v1PrivatePostTransfersWithdrawCounterparty for counterparty withdrawals
                ),
                'networksById' => array(
                    'ethereum' => 'ETH',
                    'arbitrum' => 'ARBITRUM',
                    'avacchain' => 'AVAX',
                    'optimism' => 'OPTIMISM',
                    'polygon' => 'MATIC',
                    'solana' => 'SOL',
                    'bitcoin' => 'BTC',
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => true,
                        'stopLossPrice' => false, // todo implementation
                        'takeProfitPrice' => false, // todo implementation
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => true,
                            'GTC' => true, // has 30 days max
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => true,
                        'selfTradePrevention' => true, // todo => implement
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => null,
                        'untilDays' => 10000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => array(
                        'limit' => 300,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function handle_portfolio_and_params(string $methodName, $params = array ()) {
        $portfolio = null;
        list($portfolio, $params) = $this->handle_option_and_params($params, $methodName, 'portfolio');
        if (($portfolio !== null) && ($portfolio !== '')) {
            return array( $portfolio, $params );
        }
        $defaultPortfolio = $this->safe_string($this->options, 'portfolio');
        if (($defaultPortfolio !== null) && ($defaultPortfolio !== '')) {
            return array( $defaultPortfolio, $params );
        }
        $accounts = $this->fetch_accounts();
        for ($i = 0; $i < count($accounts); $i++) {
            $account = $accounts[$i];
            $info = $this->safe_dict($account, 'info', array());
            if ($this->safe_bool($info, 'is_default')) {
                $portfolioId = $this->safe_string($info, 'portfolio_id');
                $this->options['portfolio'] = $portfolioId;
                return array( $portfolioId, $params );
            }
        }
        throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $portfolio parameter or set the default $portfolio with $this->options["portfolio"]');
    }

    public function handle_network_id_and_params(string $currencyCode, string $methodName, $params) {
        $networkId = null;
        list($networkId, $params) = $this->handle_option_and_params($params, $methodName, 'network_arn_id');
        if ($networkId === null) {
            $this->load_currency_networks($currencyCode);
            $networks = $this->currencies[$currencyCode]['networks'];
            $network = $this->safe_string_2($params, 'networkCode', 'network');
            if ($network === null) {
                // find default $network
                if ($this->is_empty($networks)) {
                    throw new BadRequest($this->id . ' createDepositAddress $network not found for currency ' . $currencyCode . ' please specify $networkId in params');
                }
                $defaultNetwork = $this->find_default_network($networks);
                $networkId = $defaultNetwork['id'];
            } else {
                $networkId = $this->network_code_to_id($network, $currencyCode);
            }
        }
        return array( $networkId, $params );
    }

    public function fetch_accounts($params = array ()) {
        /**
         * fetch all the accounts associated with a profile
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/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
         */
        $this->load_markets();
        $response = $this->v1PrivateGetPortfolios ($params);
        //
        //    array(
        //        {
        //           "portfolio_id":"1ap32qsc-1-0",
        //           "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
        //           "name":"CCXT Portfolio 030624-17:16",
        //           "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
        //           "maker_fee_rate":"0",
        //           "taker_fee_rate":"0.0002",
        //           "trading_lock":false,
        //           "borrow_disabled":false,
        //           "is_lsp":false,
        //           "is_default":true,
        //           "cross_collateral_enabled":false
        //        }
        //    )
        //
        return $this->parse_accounts($response, $params);
    }

    public function parse_account($account) {
        //
        //    {
        //       "portfolio_id":"1ap32qsc-1-0",
        //       "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
        //       "name":"CCXT Portfolio 030624-17:16",
        //       "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
        //       "maker_fee_rate":"0",
        //       "taker_fee_rate":"0.0002",
        //       "trading_lock":false,
        //       "borrow_disabled":false,
        //       "is_lsp":false,
        //       "is_default":true,
        //       "cross_collateral_enabled":false
        //    }
        //
        return array(
            'id' => $this->safe_string_2($account, 'portfolio_id', 'portfolio_uuid'),
            'type' => null,
            'code' => null,
            'info' => $account,
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = 100, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://docs.cdp.coinbase.com/intx/reference/getinstrumentcandles
         *
         * @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, default 100 max 10000
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int[][]} A list of $candles ordered, open, high, low, close, volume
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @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)
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params, 10000);
        }
        $market = $this->market($symbol);
        $request = array(
            'instrument' => $market['id'],
            'granularity' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        if ($since !== null) {
            $request['start'] = $this->iso8601($since);
        } else {
            throw new ArgumentsRequired($this->id . ' fetchOHLCV() requires a $since argument');
        }
        $unitl = $this->safe_integer($params, 'until');
        if ($unitl !== null) {
            $params = $this->omit($params, 'until');
            $request['end'] = $this->iso8601($unitl);
        }
        $response = $this->v1PublicGetInstrumentsInstrumentCandles ($this->extend($request, $params));
        //
        //   {
        //       "aggregations" => array(
        //         {
        //           "start" => "2024-04-23T00:00:00Z",
        //           "open" => "62884.4",
        //           "high" => "64710.6",
        //           "low" => "62884.4",
        //           "close" => "63508.4",
        //           "volume" => "3253.9983"
        //         }
        //       )
        //   }
        //
        $candles = $this->safe_list($response, 'aggregations', array());
        return $this->parse_ohlcvs($candles, $market, $timeframe, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //   {
        //     "start" => "2024-04-23T00:00:00Z",
        //     "open" => "62884.4",
        //     "high" => "64710.6",
        //     "low" => "62884.4",
        //     "close" => "63508.4",
        //     "volume" => "3253.9983"
        //   }
        //
        return array(
            $this->parse8601($this->safe_string_2($ohlcv, 'start', 'time')),
            $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_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding rate prices
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getinstrumentfunding
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
         * @param {int} [$since] timestamp in ms of the earliest funding rate to fetch
         * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
         * @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 list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
        }
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
        $maxEntriesPerRequest = null;
        list($maxEntriesPerRequest, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'maxEntriesPerRequest', 100);
        $pageKey = 'ccxtPageKey';
        if ($paginate) {
            return $this->fetch_paginated_call_incremental('fetchFundingRateHistory', $symbol, $since, $limit, $params, $pageKey, $maxEntriesPerRequest);
        }
        $market = $this->market($symbol);
        $page = $this->safe_integer($params, $pageKey, 1) - 1;
        $request = array(
            'instrument' => $market['id'],
            'result_offset' => $this->safe_integer_2($params, 'offset', 'result_offset', $page * $maxEntriesPerRequest),
        );
        if ($limit !== null) {
            $request['result_limit'] = $limit;
        }
        $response = $this->v1PublicGetInstrumentsInstrumentFunding ($this->extend($request, $params));
        //
        //    {
        //        "pagination":array(
        //           "result_limit":"25",
        //           "result_offset":"0"
        //        ),
        //        "results":array(
        //           array(
        //              "instrument_id":"149264167780483072",
        //              "funding_rate":"0.000011",
        //              "mark_price":"47388.1",
        //              "event_time":"2024-02-10T16:00:00Z"
        //           ),
        //           ...
        //        )
        //    }
        //
        $rawRates = $this->safe_list($response, 'results', array());
        return $this->parse_funding_rate_histories($rawRates, $market, $since, $limit);
    }

    public function parse_funding_rate_history($info, ?array $market = null) {
        return $this->parse_funding_rate($info, $market);
    }

    public function parse_funding_rate($contract, ?array $market = null) {
        //
        //    {
        //       "instrument_id":"149264167780483072",
        //       "funding_rate":"0.000011",
        //       "mark_price":"47388.1",
        //       "event_time":"2024-02-10T16:00:00Z"
        //    }
        //
        $fundingDatetime = $this->safe_string_2($contract, 'event_time', 'time');
        return array(
            'info' => $contract,
            'symbol' => $this->safe_symbol(null, $market),
            'markPrice' => $this->safe_number($contract, 'mark_price'),
            'indexPrice' => null,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => $this->parse8601($fundingDatetime),
            'datetime' => $fundingDatetime,
            'fundingRate' => $this->safe_number($contract, 'funding_rate'),
            'fundingTimestamp' => $this->parse8601($fundingDatetime),
            'fundingDatetime' => $fundingDatetime,
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
        );
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of funding payments paid and received on this account
         *
         * @see https://docs.cdp.coinbase.com/intx/reference/gettransfers
         *
         * @param {string} [$symbol] unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch funding history for
         * @param {int} [$limit] the maximum number of funding history structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
         */
        $this->load_markets();
        $request = array(
            'type' => 'FUNDING',
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $portfolios = null;
        list($portfolios, $params) = $this->handle_option_and_params($params, 'fetchFundingHistory', 'portfolios');
        if ($portfolios !== null) {
            $request['portfolios'] = $portfolios;
        }
        if ($since !== null) {
            $request['time_from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['result_limit'] = $limit;
        } else {
            $request['result_limit'] = 100;
        }
        $response = $this->v1PrivateGetTransfers ($this->extend($request, $params));
        $fundings = $this->safe_list($response, 'results', array());
        return $this->parse_incomes($fundings, $market, $since, $limit);
    }

    public function parse_income($income, ?array $market = null) {
        //
        // {
        //     "amount":"0.0008",
        //     "asset":"USDC",
        //     "created_at":"2024-02-22T16:00:00Z",
        //     "from_portfolio":array(
        //        "id":"13yuk1fs-1-0",
        //        "name":"Eng Test Portfolio - 2",
        //        "uuid":"018712f2-5ff9-7de3-9010-xxxxxxxxx"
        //     ),
        //     "instrument_id":"149264164756389888",
        //     "instrument_symbol":"ETH-PERP",
        //     "position_id":"1xy4v51m-1-2",
        //     "status":"PROCESSED",
        //     "to_portfolio":array(
        //        "name":"CB_FUND"
        //     ),
        //     "transfer_type":"FUNDING",
        //     "transfer_uuid":"a6b708df-2c44-32c5-bb98-xxxxxxxxxx",
        //     "updated_at":"2024-02-22T16:00:00Z"
        // }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $market = $this->safe_market($marketId, $market, null, 'contract');
        $datetime = $this->safe_integer($income, 'created_at');
        $timestamp = $this->parse8601($datetime);
        $currencyId = $this->safe_string($income, 'asset');
        $code = $this->safe_currency_code($currencyId);
        return array(
            'info' => $income,
            'symbol' => $market['symbol'],
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string($income, 'transfer_uuid'),
            'amount' => $this->safe_number($income, 'amount'),
            'rate' => null,
        );
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch a history of internal $transfers made on an account
         *
         * @see https://docs.cdp.coinbase.com/intx/reference/gettransfers
         *
         * @param {string} $code unified $currency $code of the $currency transferred
         * @param {int} [$since] the earliest time in ms to fetch $transfers for
         * @param {int} [$limit] the maximum number of  $transfers 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=transfer-structure transfer structures~
         */
        $this->load_markets();
        $request = array(
            'type' => 'INTERNAL',
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $portfolios = null;
        list($portfolios, $params) = $this->handle_option_and_params($params, 'fetchTransfers', 'portfolios');
        if ($portfolios !== null) {
            $request['portfolios'] = $portfolios;
        }
        if ($since !== null) {
            $request['time_from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['result_limit'] = $limit;
        } else {
            $request['result_limit'] = 100;
        }
        $response = $this->v1PrivateGetTransfers ($this->extend($request, $params));
        $transfers = $this->safe_list($response, 'results', array());
        return $this->parse_transfers($transfers, $currency, $since, $limit);
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // {
        //     "amount":"0.0008",
        //     "asset":"USDC",
        //     "created_at":"2024-02-22T16:00:00Z",
        //     "from_portfolio":array(
        //        "id":"13yuk1fs-1-0",
        //        "name":"Eng Test Portfolio - 2",
        //        "uuid":"018712f2-5ff9-7de3-9010-xxxxxxxxx"
        //     ),
        //     "instrument_id":"149264164756389888",
        //     "instrument_symbol":"ETH-PERP",
        //     "position_id":"1xy4v51m-1-2",
        //     "status":"PROCESSED",
        //     "to_portfolio":array(
        //        "name":"CB_FUND"
        //     ),
        //     "transfer_type":"FUNDING",
        //     "transfer_uuid":"a6b708df-2c44-32c5-bb98-xxxxxxxxxx",
        //     "updated_at":"2024-02-22T16:00:00Z"
        // }
        //
        $datetime = $this->safe_integer($transfer, 'created_at');
        $timestamp = $this->parse8601($datetime);
        $currencyId = $this->safe_string($transfer, 'asset');
        $code = $this->safe_currency_code($currencyId);
        $fromPorfolio = $this->safe_dict($transfer, 'from_portfolio', array());
        $fromId = $this->safe_string($fromPorfolio, 'id');
        $toPorfolio = $this->safe_dict($transfer, 'to_portfolio', array());
        $toId = $this->safe_string($toPorfolio, 'id');
        return array(
            'info' => $transfer,
            'id' => $this->safe_string($transfer, 'transfer_uuid'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $code,
            'amount' => $this->safe_number($transfer, 'amount'),
            'fromAccount' => $fromId,
            'toAccount' => $toId,
            'status' => $this->parse_transfer_status($this->safe_string($transfer, 'status')),
        );
    }

    public function parse_transfer_status(?string $status): ?string {
        $statuses = array(
            'FAILED' => 'failed',
            'PROCESSED' => 'ok',
            'NEW' => 'pending',
            'STARTED' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function create_deposit_address(string $code, $params = array ()): array {
        /**
         * create a $currency deposit $address
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/createaddress
         * @see https://docs.cloud.coinbase.com/intx/reference/createcounterpartyid
         *
         * @param {string} $code unified $currency $code of the $currency for the deposit $address
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network_arn_id] Identifies the blockchain network (e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a) if not provided will pick default
         * @param {string} [$params->network] unified network $code to identify the blockchain network
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $this->load_markets();
        $method = null;
        list($method, $params) = $this->handle_option_and_params($params, 'createDepositAddress', 'method', 'v1PrivatePostTransfersAddress');
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('createDepositAddress', $params);
        $request = array(
            'portfolio' => $portfolio,
        );
        if ($method === 'v1PrivatePostTransfersAddress') {
            $currency = $this->currency($code);
            $request['asset'] = $currency['id'];
            $networkId = null;
            list($networkId, $params) = $this->handle_network_id_and_params($code, 'createDepositAddress', $params);
            $request['network_arn_id'] = $networkId;
        }
        $response = $this->$method ($this->extend($request, $params));
        //
        // v1PrivatePostTransfersAddress
        //    {
        //        $address => "3LkwYscRyh6tUR1XTqXSJQoJnK7ucC1F4n",
        //        network_arn_id => "networks/bitcoin-mainnet/assets/6ecc0dcc-10a2-500e-b315-a3b9abae19ce",
        //        destination_tag => "",
        //    }
        // v1PrivatePostTransfersCreateCounterpartyId
        //    {
        //        "portfolio_uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
        //        "counterparty_id":"CB2ZPUCZBE"
        //    }
        //
        $tag = $this->safe_string($response, 'destination_tag');
        $address = $this->safe_string_2($response, 'address', 'counterparty_id');
        return array(
            'currency' => $code,
            'tag' => $tag,
            'address' => $address,
            'network' => null,
            'info' => $response,
        );
    }

    public function find_default_network($networks) {
        $networksArray = $this->to_array($networks);
        for ($i = 0; $i < count($networksArray); $i++) {
            $info = $networksArray[$i]['info'];
            $is_default = $this->safe_bool($info, 'is_default', false);
            if ($is_default === true) {
                return $networksArray[$i];
            }
        }
        return $networksArray[0];
    }

    public function load_currency_networks($code, $params = array ()) {
        $currency = $this->currency($code);
        $networks = $this->safe_dict($currency, 'networks');
        if ($networks !== null) {
            return false;
        }
        $request = array(
            'asset' => $currency['id'],
        );
        $rawNetworks = $this->v1PublicGetAssetsAssetNetworks ($request);
        //
        //    array(
        //        array(
        //            "asset_id":"1",
        //            "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //            "asset_name":"USDC",
        //            "network_arn_id":"networks/ethereum-mainnet/assets/9bc140b4-69c3-5fc9-bd0d-b041bcf40039",
        //            "min_withdrawal_amt":"1",
        //            "max_withdrawal_amt":"100000000",
        //            "network_confirms":35,
        //            "processing_time":485,
        //            "is_default":true,
        //            "network_name":"ethereum",
        //            "display_name":"Ethereum"
        //        ),
        //        ....
        //    )
        //
        $currency['networks'] = $this->parse_networks($rawNetworks);
        return true;
    }

    public function parse_networks($networks, $params = array ()) {
        $result = array();
        for ($i = 0; $i < count($networks); $i++) {
            $network = $this->extend($this->parse_network($networks[$i]), $params);
            $result[$network['network']] = $network;
        }
        return $result;
    }

    public function parse_network($network, $params = array ()) {
        //
        //    {
        //        "asset_id":"1",
        //        "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //        "asset_name":"USDC",
        //        "network_arn_id":"networks/ethereum-mainnet/assets/9bc140b4-69c3-5fc9-bd0d-b041bcf40039",
        //        "min_withdrawal_amt":"1",
        //        "max_withdrawal_amt":"100000000",
        //        "network_confirms":35,
        //        "processing_time":485,
        //        "is_default":true,
        //        "network_name":"ethereum",
        //        "display_name":"Ethereum"
        //    }
        //
        $currencyId = $this->safe_string($network, 'asset_name');
        $currencyCode = $this->safe_currency_code($currencyId);
        $networkId = $this->safe_string($network, 'network_arn_id');
        $networkIdForCode = $this->safe_string_n($network, array( 'network_name', 'display_name', 'network_arn_id' ), '');
        return $this->safe_network(array(
            'info' => $network,
            'id' => $networkId,
            'name' => $this->safe_string($network, 'display_name'),
            'network' => $this->network_id_to_code($networkIdForCode, $currencyCode),
            'active' => null,
            'deposit' => null,
            'withdraw' => null,
            'precision' => null,
            'fee' => null,
            'limits' => array(
                'withdraw' => array(
                    'min' => $this->safe_number($network, 'min_withdrawal_amt'),
                    'max' => $this->safe_number($network, 'max_withdrawal_amt'),
                ),
                'deposit' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
        ));
    }

    public function set_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * Either adds or reduces margin in order to set the margin to a specific value
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/setportfoliomarginoverride
         *
         * @param {string} $symbol unified market $symbol of the market to set margin in
         * @param {float} $amount the $amount to set the margin to
         * @param {array} [$params] parameters specific to the exchange API endpoint
         * @return {array} A {@link https://github.com/ccxt/ccxt/wiki/Manual#add-margin-structure margin structure}
         */
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('setMargin', $params);
        if ($symbol !== null) {
            throw new BadRequest($this->id . ' setMargin() only allows setting margin to full portfolio');
        }
        $request = array(
            'portfolio' => $portfolio,
            'margin_override' => $amount,
        );
        return $this->v1PrivatePostPortfoliosMargin ($this->extend($request, $params));
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch history of deposits and withdrawals
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/gettransfers
         *
         * @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 is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->portfolios] Identifies the $portfolios by UUID (e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID (e.g., 5189861793641175). Can provide single or multiple $portfolios to filter by or fetches transfers for all $portfolios if none are provided.
         * @param {int} [$params->until] Only find transfers updated before this time. Use timestamp format
         * @param {string} [$params->status] The current status of transfer. Possible values => [PROCESSED, NEW, FAILED, STARTED]
         * @param {string} [$params->type] The type of transfer Possible values => [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
         * @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 list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $paginate = null;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'paginate');
        $maxEntriesPerRequest = null;
        list($maxEntriesPerRequest, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'maxEntriesPerRequest', 100);
        $pageKey = 'ccxtPageKey';
        if ($paginate) {
            return $this->fetch_paginated_call_incremental('fetchDepositsWithdrawals', $code, $since, $limit, $params, $pageKey, $maxEntriesPerRequest);
        }
        $page = $this->safe_integer($params, $pageKey, 1) - 1;
        $request = array(
            'result_offset' => $this->safe_integer_2($params, 'offset', 'result_offset', $page * $maxEntriesPerRequest),
        );
        if ($since !== null) {
            $request['time_from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $newLimit = min ($limit, 100);
            $request['result_limit'] = $newLimit;
        }
        $portfolios = null;
        list($portfolios, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'portfolios');
        if ($portfolios !== null) {
            $request['portfolios'] = $portfolios;
        }
        $until = null;
        list($until, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'until');
        if ($until !== null) {
            $request['time_to'] = $this->iso8601($until);
        }
        $response = $this->v1PrivateGetTransfers ($this->extend($request, $params));
        //
        //    {
        //        "pagination":array(
        //           "result_limit":25,
        //           "result_offset":0
        //        ),
        //        "results":array(
        //           {
        //              "transfer_uuid":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3",
        //              "transfer_type":"WITHDRAW",
        //              "amount":"1.000000",
        //              "asset":"USDC",
        //              "status":"PROCESSED",
        //              "network_name":"ethereum",
        //              "created_at":"2024-03-14T02:32:18.497795Z",
        //              "updated_at":"2024-03-14T02:35:38.514588Z",
        //              "from_portfolio":array(
        //                 "id":"1yun54bb-1-6",
        //                 "uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
        //                 "name":"fungus technology o?Portfolio"
        //              ),
        //              "to_address":"0xcdcE79F820BE9d6C5033db5c31d1AE3A8c2399bB"
        //           }
        //        )
        //    }
        //
        $rawTransactions = $this->safe_list($response, 'results', array());
        return $this->parse_transactions($rawTransactions);
    }

    public function fetch_position(string $symbol, $params = array ()) {
        /**
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getportfolioposition
         *
         * fetch data on an open $position
         * @param {string} $symbol unified market $symbol of the market the $position is held in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$position-structure $position structure~
         */
        $this->load_markets();
        $symbol = $this->symbol($symbol);
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('fetchPosition', $params);
        $request = array(
            'portfolio' => $portfolio,
            'instrument' => $this->market_id($symbol),
        );
        $position = $this->v1PrivateGetPortfoliosPortfolioPositionsInstrument ($this->extend($request, $params));
        //
        //    {
        //        "symbol":"BTC-PERP",
        //        "instrument_id":"114jqr89-0-0",
        //        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //        "vwap":"52482.3",
        //        "net_size":"0",
        //        "buy_order_size":"0.001",
        //        "sell_order_size":"0",
        //        "im_contribution":"0.2",
        //        "unrealized_pnl":"0",
        //        "mark_price":"52406.8",
        //        "entry_vwap":"52472.9"
        //    }
        //
        return $this->parse_position($position);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //    {
        //       "symbol":"BTC-PERP",
        //       "instrument_id":"114jqr89-0-0",
        //       "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //       "vwap":"52482.3",
        //       "net_size":"0",
        //       "buy_order_size":"0.001",
        //       "sell_order_size":"0",
        //       "im_contribution":"0.2",
        //       "unrealized_pnl":"0",
        //       "mark_price":"52406.8",
        //       "entry_vwap":"52472.9"
        //    }
        //
        $marketId = $this->safe_string($position, 'symbol');
        $quantity = $this->safe_string($position, 'net_size');
        $market = $this->safe_market($marketId, $market, '-');
        $side = 'long';
        if (Precise::string_le($quantity, '0')) {
            $side = 'short';
            $quantity = Precise::string_mul('-1', $quantity);
        }
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'id'),
            'symbol' => $market['symbol'],
            'entryPrice' => null,
            'markPrice' => $this->safe_number($position, 'mark_price'),
            'notional' => null,
            'collateral' => null,
            'unrealizedPnl' => $this->safe_number($position, 'unrealized_pnl'),
            'side' => $side,
            'contracts' => $this->parse_number($quantity),
            'contractSize' => $this->safe_number($market, 'contractSize'),
            'timestamp' => null,
            'datetime' => null,
            'hedged' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'initialMargin' => $this->safe_number($position, 'im_contribution'),
            'initialMarginPercentage' => null,
            'leverage' => null,
            'liquidationPrice' => null,
            'marginRatio' => null,
            'marginMode' => null,
            'percentage' => null,
        ));
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getportfoliopositions
         *
         * fetch all open $positions
         * @param {string[]} [$symbols] list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->load_markets();
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('fetchPositions', $params);
        $request = array(
            'portfolio' => $portfolio,
        );
        $response = $this->v1PrivateGetPortfoliosPortfolioPositions ($this->extend($request, $params));
        //
        //    array(
        //        {
        //           "symbol":"BTC-PERP",
        //           "instrument_id":"114jqr89-0-0",
        //           "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //           "vwap":"52482.3",
        //           "net_size":"0",
        //           "buy_order_size":"0.001",
        //           "sell_order_size":"0",
        //           "im_contribution":"0.2",
        //           "unrealized_pnl":"0",
        //           "mark_price":"52406.8",
        //           "entry_vwap":"52472.9"
        //        }
        //    )
        //
        $positions = $this->parse_positions($response);
        if ($this->is_empty($symbols)) {
            return $positions;
        }
        $symbols = $this->market_symbols($symbols);
        return $this->filter_by_array_positions($positions, 'symbol', $symbols, false);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/gettransfers
         *
         * @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->portfolios] Identifies the portfolios by UUID (e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID (e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
         * @param {int} [$params->until] Only find transfers updated before this time. Use timestamp format
         * @param {string} [$params->status] The current status of transfer. Possible values => [PROCESSED, NEW, FAILED, STARTED]
         * @param {string} [$params->type] The type of transfer Possible values => [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
         * @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 list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $params['type'] = 'WITHDRAW';
        return $this->fetch_deposits_withdrawals($code, $since, $limit, $params);
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         * @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->portfolios] Identifies the portfolios by UUID (e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID (e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
         * @param {int} [$params->until] Only find transfers updated before this time. Use timestamp format
         * @param {string} [$params->status] The current status of transfer. Possible values => [PROCESSED, NEW, FAILED, STARTED]
         * @param {string} [$params->type] The type of transfer Possible values => [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
         * @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 list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $params['type'] = 'DEPOSIT';
        return $this->fetch_deposits_withdrawals($code, $since, $limit, $params);
    }

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

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //    {
        //        "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
        //    }
        // $transactionType = $this->safe_string($transaction, 'type');
        $datetime = $this->safe_string($transaction, 'updated_at');
        $fromPorfolio = $this->safe_dict($transaction, 'from_portfolio', array());
        $addressFrom = $this->safe_string_n($transaction, array( 'from_address', 'from_cb_account', $this->safe_string_n($fromPorfolio, array( 'id', 'uuid', 'name' )), 'from_counterparty_id' ));
        $toPorfolio = $this->safe_dict($transaction, 'from_portfolio', array());
        $addressTo = $this->safe_string_n($transaction, array( 'to_address', 'to_cb_account', $this->safe_string_n($toPorfolio, array( 'id', 'uuid', 'name' )), 'to_counterparty_id' ));
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'transfer_uuid'),
            'txid' => $this->safe_string($transaction, 'transaction_uuid'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'network' => $this->network_id_to_code($this->safe_string($transaction, 'network_name')),
            'address' => null, // TODO check if withdraw or deposit and populate
            'addressTo' => $addressTo,
            'addressFrom' => $addressFrom,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => $this->safe_string($transaction, 'resource'),
            'amount' => $this->safe_number($transaction, 'amount'),
            'currency' => $this->safe_currency_code($this->safe_string($transaction, 'asset'), $currency),
            'status' => $this->parse_transaction_status($this->safe_string($transaction, 'status')),
            'updated' => $this->parse8601($datetime),
            'fee' => array(
                'cost' => null,
                'currency' => null,
            ),
        );
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        //    {
        //       "portfolio_id":"1wp37qsc-1-0",
        //       "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //       "portfolio_name":"CCXT Portfolio 020624-17:16",
        //       "fill_id":"1xbfy19y-1-184",
        //       "exec_id":"280841526207070392",
        //       "order_id":"1xbfv8yw-1-0",
        //       "instrument_id":"114jqr89-0-0",
        //       "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //       "symbol":"BTC-PERP",
        //       "match_id":"280841526207053840",
        //       "fill_price":"52500",
        //       "fill_qty":"0.01",
        //       "client_id":"1x59ctku-1-1",
        //       "client_order_id":"ccxt3e4e2a5f-4a89-",
        //       "order_qty":"0.01",
        //       "limit_price":"52500",
        //       "total_filled":"0.01",
        //       "filled_vwap":"52500",
        //       "expire_time":"",
        //       "stop_price":"",
        //       "side":"BUY",
        //       "tif":"GTC",
        //       "stp_mode":"BOTH",
        //       "flags":"",
        //       "fee":"0.105",
        //       "fee_asset":"USDC",
        //       "order_status":"DONE",
        //       "event_time":"2024-02-15T00:43:57.631Z"
        //    }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $datetime = $this->safe_string($trade, 'event_time');
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $this->safe_string_2($trade, 'fill_id', 'exec_id'),
            'order' => $this->safe_string($trade, 'order_id'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'symbol' => $this->safe_symbol($marketId, $market),
            'type' => null,
            'side' => $this->safe_string_lower($trade, 'side'),
            'takerOrMaker' => null,
            'price' => $this->safe_number($trade, 'fill_price'),
            'amount' => $this->safe_number($trade, 'fill_qty'),
            'cost' => null,
            'fee' => array(
                'cost' => $this->safe_number($trade, 'fee'),
                'currency' => $this->safe_currency_code($this->safe_string($trade, 'fee_asset')),
            ),
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getinstruments
         *
         * retrieves data on all markets for coinbaseinternational
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $response = $this->v1PublicGetInstruments ($params);
        //
        //    array(
        //        {
        //           "instrument_id":"149264164756389888",
        //           "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
        //           "symbol":"ETH-PERP",
        //           "type":"PERP",
        //           "base_asset_id":"118059611793145856",
        //           "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
        //           "base_asset_name":"ETH",
        //           "quote_asset_id":"1",
        //           "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //           "quote_asset_name":"USDC",
        //           "base_increment":"0.0001",
        //           "quote_increment":"0.01",
        //           "price_band_percent":"0.02",
        //           "market_order_percent":"0.0075",
        //           "qty_24hr":"44434.8131",
        //           "notional_24hr":"110943454.279785",
        //           "avg_daily_qty":"1099171.6025",
        //           "avg_daily_notional":"2637240145.456987",
        //           "previous_day_qty":"78909.3939",
        //           "open_interest":"1270.749",
        //           "position_limit_qty":"1831.9527",
        //           "position_limit_adq_pct":"0.05",
        //           "replacement_cost":"0.23",
        //           "base_imf":"0.1",
        //           "min_notional_value":"10",
        //           "funding_interval":"3600000000000",
        //           "trading_state":"TRADING",
        //           "quote":array(
        //              "best_bid_price":"2490.8",
        //              "best_bid_size":"9.0515",
        //              "best_ask_price":"2490.81",
        //              "best_ask_size":"4.8486",
        //              "trade_price":"2490.39",
        //              "trade_qty":"0.9508",
        //              "index_price":"2490.5",
        //              "mark_price":"2490.8",
        //              "settlement_price":"2490.81",
        //              "limit_up":"2615.42",
        //              "limit_down":"2366.34",
        //              "predicted_funding":"0.000009",
        //              "timestamp":"2024-02-10T16:07:39.454Z"
        //           }
        //        ),
        //        ...
        //    )
        //
        return $this->parse_markets($response);
    }

    public function parse_market(array $market): array {
        //
        //   {
        //       "instrument_id":"149264164756389888",
        //       "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
        //       "symbol":"ETH-PERP",
        //       "type":"PERP",
        //       "base_asset_id":"118059611793145856",
        //       "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
        //       "base_asset_name":"ETH",
        //       "quote_asset_id":"1",
        //       "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //       "quote_asset_name":"USDC",
        //       "base_increment":"0.0001",
        //       "quote_increment":"0.01",
        //       "price_band_percent":"0.02",
        //       "market_order_percent":"0.0075",
        //       "qty_24hr":"44434.8131",
        //       "notional_24hr":"110943454.279785",
        //       "avg_daily_qty":"1099171.6025",
        //       "avg_daily_notional":"2637240145.456987",
        //       "previous_day_qty":"78909.3939",
        //       "open_interest":"1270.749",
        //       "position_limit_qty":"1831.9527",
        //       "position_limit_adq_pct":"0.05",
        //       "replacement_cost":"0.23",
        //       "base_imf":"0.1",
        //       "min_notional_value":"10",
        //       "funding_interval":"3600000000000",
        //       "trading_state":"TRADING",
        //       "quote":{
        //          "best_bid_price":"2490.8",
        //          "best_bid_size":"9.0515",
        //          "best_ask_price":"2490.81",
        //          "best_ask_size":"4.8486",
        //          "trade_price":"2490.39",
        //          "trade_qty":"0.9508",
        //          "index_price":"2490.5",
        //          "mark_price":"2490.8",
        //          "settlement_price":"2490.81",
        //          "limit_up":"2615.42",
        //          "limit_down":"2366.34",
        //          "predicted_funding":"0.000009",
        //          "timestamp":"2024-02-10T16:07:39.454Z"
        //       }
        //    }
        //
        $marketId = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'base_asset_name');
        $quoteId = $this->safe_string($market, 'quote_asset_name');
        $typeId = $this->safe_string($market, 'type'); // 'SPOT', 'PERP'
        $isSpot = ($typeId === 'SPOT');
        $fees = $this->fees;
        $symbol = $baseId . '/' . $quoteId;
        $settleId = null;
        if (!$isSpot) {
            $settleId = $quoteId;
            $symbol .= ':' . $quoteId;
        }
        return array(
            'id' => $marketId,
            'lowercaseId' => strtolower($marketId),
            'symbol' => $symbol,
            'base' => $baseId,
            'quote' => $quoteId,
            'settle' => $settleId ? $settleId : null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId ? $settleId : null,
            'type' => $isSpot ? 'spot' : 'swap',
            'spot' => $isSpot,
            'margin' => false,
            'swap' => !$isSpot,
            'future' => false,
            'option' => false,
            'active' => $this->safe_string($market, 'trading_state') === 'TRADING',
            'contract' => !$isSpot,
            'linear' => $isSpot ? null : ($settleId === $quoteId),
            'inverse' => $isSpot ? null : ($settleId !== $quoteId),
            'taker' => $fees['trading']['taker'],
            'maker' => $fees['trading']['maker'],
            'contractSize' => $isSpot ? null : 1,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'base_increment'),
                'price' => $this->safe_number($market, 'quote_increment'),
                'cost' => $this->safe_number($market, 'quote_increment'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => $this->safe_number($market, 'base_imf'),
                ),
                'amount' => array(
                    'min' => null,
                    'max' => $isSpot ? null : $this->safe_number($market, 'position_limit_qty'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'min_notional_value'),
                    'max' => null,
                ),
            ),
            'info' => $market,
            'created' => null,
        );
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available $currencies on an exchange
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getassets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of $currencies
         */
        $currencies = $this->v1PublicGetAssets ($params);
        //
        //    array(
        //        array(
        //           "asset_id":"1",
        //           "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //           "asset_name":"USDC",
        //           "status":"ACTIVE",
        //           "collateral_weight":1.0,
        //           "supported_networks_enabled":true
        //        ),
        //        ...
        //    )
        //
        return $this->parse_currencies($currencies);
    }

    public function parse_currency(array $currency): array {
        //
        //    {
        //       "asset_id":"1",
        //       "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //       "asset_name":"USDC",
        //       "status":"ACTIVE",
        //       "collateral_weight":1.0,
        //       "supported_networks_enabled":true
        //    }
        //
        $id = $this->safe_string($currency, 'asset_name');
        $code = $this->safe_currency_code($id);
        $statusId = $this->safe_string($currency, 'status');
        return $this->safe_currency_structure(array(
            'id' => $id,
            'name' => $code,
            'code' => $code,
            'precision' => null,
            'info' => $currency,
            'active' => ($statusId === 'ACTIVE'),
            'deposit' => null,
            'withdraw' => null,
            'networks' => null,
            'fee' => null,
            'fees' => null,
            'limits' => $this->limits,
        ));
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price $tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getinstruments
         *
         * @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
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $instruments = $this->v1PublicGetInstruments ($params);
        $tickers = array();
        for ($i = 0; $i < count($instruments); $i++) {
            $instrument = $instruments[$i];
            $marketId = $this->safe_string($instrument, 'symbol');
            $symbol = $this->safe_symbol($marketId);
            $quote = $this->safe_dict($instrument, 'quote', array());
            $tickers[$symbol] = $this->parse_ticker($quote, $this->safe_market($marketId));
        }
        return $this->filter_by_array($tickers, 'symbol', $symbols, true);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * 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/intx/reference/getinstrumentquote
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the $ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'instrument' => $this->market_id($symbol),
        );
        $ticker = $this->v1PublicGetInstrumentsInstrumentQuote ($this->extend($request, $params));
        return $this->parse_ticker($ticker, $market);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //    {
        //        "best_bid_price":"2490.8",
        //        "best_bid_size":"9.0515",
        //        "best_ask_price":"2490.81",
        //        "best_ask_size":"4.8486",
        //        "trade_price":"2490.39",
        //        "trade_qty":"0.9508",
        //        "index_price":"2490.5",
        //        "mark_price":"2490.8",
        //        "settlement_price":"2490.81",
        //        "limit_up":"2615.42",
        //        "limit_down":"2366.34",
        //        "predicted_funding":"0.000009",
        //        "timestamp":"2024-02-10T16:07:39.454Z"
        //    }
        //
        $datetime = $this->safe_string($ticker, 'timestamp');
        return $this->safe_ticker(array(
            'info' => $ticker,
            'symbol' => $this->safe_symbol(null, $market),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'bid' => $this->safe_number($ticker, 'best_bid_price'),
            'bidVolume' => $this->safe_number($ticker, 'best_bid_size'),
            'ask' => $this->safe_number($ticker, 'best_ask_price'),
            'askVolume' => $this->safe_number($ticker, 'best_ask_size'),
            'high' => null,
            'low' => null,
            'open' => null,
            'close' => null,
            'last' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'vwap' => null,
            'baseVolume' => null,
            'quoteVolume' => null,
            'previousClose' => null,
            'markPrice' => $this->safe_number($ticker, 'mark_price'),
            'indexPrice' => $this->safe_number($ticker, 'index_price'),
        ));
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getportfoliobalances
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {boolean} [$params->v3] default false, set true to use v3 api endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('fetchBalance', $params);
        $request = array(
            'portfolio' => $portfolio,
        );
        $balances = $this->v1PrivateGetPortfoliosPortfolioBalances ($this->extend($request, $params));
        //
        //    array(
        //        {
        //           "asset_id":"0-0-1",
        //           "asset_name":"USDC",
        //           "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //           "quantity":"500000.0000000000",
        //           "hold":"0",
        //           "hold_available_for_collateral":"0",
        //           "transfer_hold":"0",
        //           "collateral_value":"500000.0",
        //           "max_withdraw_amount":"500000.0000000000",
        //           "loan":"0",
        //           "loan_collateral_requirement":"0.0"
        //        }
        //    )
        //
        return $this->parse_balance($balances);
    }

    public function parse_balance($response): array {
        //
        //    {
        //       "asset_id":"0-0-1",
        //       "asset_name":"USDC",
        //       "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        //       "quantity":"500000.0000000000",
        //       "hold":"0",
        //       "hold_available_for_collateral":"0",
        //       "transfer_hold":"0",
        //       "collateral_value":"500000.0",
        //       "max_withdraw_amount":"500000.0000000000",
        //       "loan":"0",
        //       "loan_collateral_requirement":"0.0"
        //    }
        //
        $result = array(
            'info' => $response,
        );
        for ($i = 0; $i < count($response); $i++) {
            $rawBalance = $response[$i];
            $currencyId = $this->safe_string($rawBalance, 'asset_name');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_string($rawBalance, 'quantity');
            $account['used'] = $this->safe_string($rawBalance, 'hold');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * Transfer an $amount of asset from one portfolio to another.
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/createportfolioassettransfer
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount amount to transfer
         * @param {string} $fromAccount account to transfer from
         * @param {string} $toAccount account to transfer to
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a {@link https://github.com/ccxt/ccxt/wiki/Manual#transfer-structure transfer structure}
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset' => $currency['id'],
            'ammount' => $amount,
            'from' => $fromAccount,
            'to' => $toAccount,
        );
        $response = $this->v1PrivatePostPortfoliosTransfer ($this->extend($request, $params));
        $success = $this->safe_bool($response, 'success');
        return array(
            'info' => $response,
            'id' => null,
            'timestamp' => null,
            'datetime' => null,
            'currency' => $code,
            'amount' => $amount,
            'fromAccount' => $fromAccount,
            'toAccount' => $toAccount,
            'status' => $success ? 'ok' : 'failed',
        );
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/createorder
         *
         * @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] alias for $triggerPrice
         * @param {float} [$params->triggerPrice] $price to trigger stop orders
         * @param {float} [$params->stopLossPrice] $price to trigger stop-loss orders
         * @param {bool} [$params->postOnly] true or false
         * @param {string} [$params->tif] 'GTC', 'IOC', 'GTD' default is 'GTC' for limit orders and 'IOC' for $market orders
         * @param {string} [$params->expire_time] The expiration time required for orders with the time in force set to GTT. Must not go beyond 30 days of the current time. Uses ISO-8601 format (e.g., 2023-03-16T23:59:53Z)
         * @param {string} [$params->stp_mode] Possible values => [NONE, AGGRESSING, BOTH] Specifies the behavior for self match handling. None disables the functionality, new cancels the newest order, and both cancels both orders.
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $typeId = strtoupper($type);
        $triggerPrice = $this->safe_number_n($params, array( 'triggerPrice', 'stopPrice', 'stop_price' ));
        $clientOrderIdprefix = $this->safe_string($this->options, 'brokerId', 'nfqkvdjp');
        $clientOrderId = $clientOrderIdprefix . '-' . $this->uuid();
        $clientOrderId = mb_substr($clientOrderId, 0, 17 - 0);
        $request = array(
            'client_order_id' => $clientOrderId,
            'side' => strtoupper($side),
            'instrument' => $market['id'],
            'size' => $this->amount_to_precision($market['symbol'], $amount),
        );
        if ($triggerPrice !== null) {
            if ($type === 'limit') {
                $typeId = 'STOP_LIMIT';
            } else {
                $typeId = 'STOP';
            }
            $request['stop_price'] = $triggerPrice;
        }
        $request['type'] = $typeId;
        if ($type === 'limit') {
            if ($price === null) {
                throw new InvalidOrder($this->id . ' createOrder() requires a $price parameter for a limit order types');
            }
            $request['price'] = $price;
        }
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('createOrder', $params);
        if ($portfolio !== null) {
            $request['portfolio'] = $portfolio;
        }
        $postOnly = $this->safe_bool_2($params, 'postOnly', 'post_only');
        $tif = $this->safe_string_2($params, 'tif', 'timeInForce');
        // $market orders must be IOC
        if ($typeId === 'MARKET') {
            if ($tif !== null && $tif !== 'IOC') {
                throw new InvalidOrder($this->id . ' createOrder() $market orders must have $tif set to "IOC"');
            }
            $tif = 'IOC';
        } else {
            $tif = ($tif === null) ? 'GTC' : $tif;
        }
        if ($postOnly !== null) {
            $request['post_only'] = $postOnly;
        }
        $request['tif'] = $tif;
        $params = $this->omit($params, array( 'client_order_id', 'user', 'postOnly', 'timeInForce' ));
        $response = $this->v1PrivatePostOrders ($this->extend($request, $params));
        //
        //    {
        //        "order_id":"1x96skvg-1-0",
        //        "client_order_id":"ccxt",
        //        "side":"BUY",
        //        "instrument_id":"114jqr89-0-0",
        //        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //        "symbol":"BTC-PERP",
        //        "portfolio_id":"1wp37qsc-1-0",
        //        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //        "type":"LIMIT",
        //        "price":"10000",
        //        "size":"0.001",
        //        "tif":"GTC",
        //        "stp_mode":"BOTH",
        //        "event_type":"NEW",
        //        "order_status":"WORKING",
        //        "leaves_qty":"0.001",
        //        "exec_qty":"0",
        //        "avg_price":"0",
        //        "fee":"0"
        //    }
        //
        return $this->parse_order($response, $market);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        //    {
        //        "order_id":"1x96skvg-1-0",
        //        "client_order_id":"ccxt",
        //        "side":"BUY",
        //        "instrument_id":"114jqr89-0-0",
        //        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //        "symbol":"BTC-PERP",
        //        "portfolio_id":"1wp37qsc-1-0",
        //        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //        "type":"LIMIT",
        //        "price":"10000",
        //        "size":"0.001",
        //        "tif":"GTC",
        //        "stp_mode":"BOTH",
        //        "event_type":"NEW",
        //        "order_status":"WORKING",
        //        "leaves_qty":"0.001",
        //        "exec_qty":"0",
        //        "avg_price":"0",
        //        "fee":"0"
        //    }
        //
        $marketId = $this->safe_string($order, 'symbol');
        $feeCost = $this->safe_number($order, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
            );
        }
        $datetime = $this->safe_string_2($order, 'submit_time', 'event_time');
        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' => $this->safe_symbol($marketId, $market),
            'type' => $this->parse_order_type($this->safe_string($order, 'type')),
            'timeInForce' => $this->safe_string($order, 'tif'),
            'postOnly' => null,
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $this->safe_string($order, 'price'),
            'triggerPrice' => $this->safe_string($order, 'stop_price'),
            'amount' => $this->safe_string($order, 'size'),
            'filled' => $this->safe_string($order, 'exec_qty'),
            'remaining' => $this->safe_string($order, 'leaves_qty'),
            'cost' => null,
            'average' => $this->safe_string($order, 'avg_price'),
            'status' => $this->parse_order_status($this->safe_string($order, 'order_status')),
            'fee' => $fee,
            'trades' => null,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'NEW' => 'open',
            'PARTIAL_FILLED' => 'open',
            'FILLED' => 'closed',
            'CANCELED' => 'canceled',
            'REPLACED' => 'canceled',
            'PENDING_CANCEL' => 'open',
            'REJECTED' => 'rejected',
            'PENDING_NEW' => 'open',
            'EXPIRED' => 'expired',
            'PENDING_REPLACE' => 'open',
        );
        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 cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/cancelorder
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by coinbaseinternational 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~
         */
        $this->load_markets();
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('cancelOrder', $params);
        $request = array(
            'portfolio' => $portfolio,
            'id' => $id,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $orders = $this->v1PrivateDeleteOrdersId ($this->extend($request, $params));
        //
        //    {
        //        "order_id":"1x96skvg-1-0",
        //        "client_order_id":"ccxt",
        //        "side":"BUY",
        //        "instrument_id":"114jqr89-0-0",
        //        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //        "symbol":"BTC-PERP",
        //        "portfolio_id":"1wp37qsc-1-0",
        //        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //        "type":"LIMIT",
        //        "price":"10000",
        //        "size":"0.001",
        //        "tif":"GTC",
        //        "stp_mode":"BOTH",
        //        "event_type":"CANCELED",
        //        "order_status":"DONE",
        //        "leaves_qty":"0.001",
        //        "exec_qty":"0",
        //        "avg_price":"0",
        //        "fee":"0"
        //    }
        //
        return $this->parse_order($orders, $market);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open $orders
         * @param {string} $symbol unified $market $symbol, only $orders in the $market of this $symbol are cancelled when $symbol is not 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~
         */
        $this->load_markets();
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('cancelAllOrders', $params);
        $request = array(
            'portfolio' => $portfolio,
        );
        $market = null;
        if ($symbol) {
            $market = $this->market($symbol);
            $request['instrument'] = $market['id'];
        }
        $orders = $this->v1PrivateDeleteOrders ($this->extend($request, $params));
        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 ()) {
        /**
         * edit a trade $order
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/modifyorder
         *
         * @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 {string} $params->clientOrderId client $order $id
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'id' => $id,
        );
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('editOrder', $params);
        if ($portfolio !== null) {
            $request['portfolio'] = $portfolio;
        }
        if ($amount !== null) {
            $request['size'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $triggerPrice = $this->safe_number_n($params, array( 'stopPrice', 'stop_price', 'triggerPrice' ));
        if ($triggerPrice !== null) {
            $request['stop_price'] = $triggerPrice;
        }
        $clientOrderId = $this->safe_string_2($params, 'client_order_id', 'clientOrderId');
        if ($clientOrderId === null) {
            throw new BadRequest($this->id . ' editOrder() requires a $clientOrderId parameter');
        }
        $request['client_order_id'] = $clientOrderId;
        $order = $this->v1PrivatePutOrdersId ($this->extend($request, $params));
        return $this->parse_order($order, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an $order made by the user
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/modifyorder
         *
         * @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~
         */
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('fetchOrder', $params);
        $request = array(
            'id' => $id,
            'portfolio' => $portfolio,
        );
        $order = $this->v1PrivateGetOrdersId ($this->extend($request, $params));
        //
        //    {
        //        "order_id":"1x96skvg-1-0",
        //        "client_order_id":"ccxt",
        //        "side":"BUY",
        //        "instrument_id":"114jqr89-0-0",
        //        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //        "symbol":"BTC-PERP",
        //        "portfolio_id":"1wp37qsc-1-0",
        //        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //        "type":"LIMIT",
        //        "price":"10000",
        //        "size":"0.001",
        //        "tif":"GTC",
        //        "stp_mode":"BOTH",
        //        "event_type":"NEW",
        //        "event_time":"2024-02-14T03:25:14Z",
        //        "submit_time":"2024-02-14T03:25:13.999Z",
        //        "order_status":"WORKING",
        //        "leaves_qty":"0.001",
        //        "exec_qty":"0",
        //        "avg_price":"0",
        //        "fee":"0"
        //    }
        //
        return $this->parse_order($order, $market);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on all currently open orders
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getorders
         *
         * @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->offset] offset
         * @param {string} [$params->event_type] The most recent type of event that happened to the order. Allowed values => NEW, TRADE, REPLACED
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('fetchOpenOrders', $params);
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOpenOrders', 'paginate');
        $maxEntriesPerRequest = null;
        list($maxEntriesPerRequest, $params) = $this->handle_option_and_params($params, 'fetchOpenOrders', 'maxEntriesPerRequest', 100);
        $pageKey = 'ccxtPageKey';
        if ($paginate) {
            return $this->fetch_paginated_call_incremental('fetchOpenOrders', $symbol, $since, $limit, $params, $pageKey, $maxEntriesPerRequest);
        }
        $page = $this->safe_integer($params, $pageKey, 1) - 1;
        $request = array(
            'portfolio' => $portfolio,
            'result_offset' => $this->safe_integer_2($params, 'offset', 'result_offset', $page * $maxEntriesPerRequest),
        );
        $market = null;
        if ($symbol) {
            $market = $this->market($symbol);
            $request['instrument'] = $symbol;
        }
        if ($limit !== null) {
            if ($limit > 100) {
                throw new BadRequest($this->id . ' fetchOpenOrders() maximum $limit is 100');
            }
            $request['result_limit'] = $limit;
        }
        if ($since !== null) {
            $request['ref_datetime'] = $this->iso8601($since);
        }
        $response = $this->v1PrivateGetOrders ($this->extend($request, $params));
        //
        //    {
        //        "pagination":array(
        //           "result_limit":25,
        //           "result_offset":0
        //        ),
        //        "results":array(
        //           array(
        //              "order_id":"1y4cm6b4-1-0",
        //              "client_order_id":"ccxtd0dd4b5d-8e5f-",
        //              "side":"SELL",
        //              "instrument_id":"114jqr89-0-0",
        //              "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //              "symbol":"BTC-PERP",
        //              "portfolio_id":"1wp37qsc-1-0",
        //              "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //              "type":"LIMIT",
        //              "price":"54000",
        //              "size":"0.01",
        //              "tif":"GTC",
        //              "stp_mode":"BOTH",
        //              "event_type":"NEW",
        //              "event_time":"2024-02-24T16:46:37.413Z",
        //              "submit_time":"2024-02-24T16:46:37.412Z",
        //              "order_status":"WORKING",
        //              "leaves_qty":"0.01",
        //              "exec_qty":"0",
        //              "avg_price":"0",
        //              "fee":"0"
        //           ),
        //           ...
        //        )
        //    }
        //
        $rawOrders = $this->safe_list($response, 'results', array());
        return $this->parse_orders($rawOrders, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $trades made by the user
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/getmultiportfoliofills
         *
         * @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~
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
        $pageKey = 'ccxtPageKey';
        $maxEntriesPerRequest = null;
        list($maxEntriesPerRequest, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'maxEntriesPerRequest', 100);
        if ($paginate) {
            return $this->fetch_paginated_call_incremental('fetchMyTrades', $symbol, $since, $limit, $params, $pageKey, $maxEntriesPerRequest);
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $page = $this->safe_integer($params, $pageKey, 1) - 1;
        $request = array(
            'result_offset' => $this->safe_integer_2($params, 'offset', 'result_offset', $page * $maxEntriesPerRequest),
        );
        if ($limit !== null) {
            if ($limit > 100) {
                throw new BadRequest($this->id . ' fetchMyTrades() maximum $limit is 100. Consider setting $paginate to true to fetch more $trades->');
            }
            $request['result_limit'] = $limit;
        }
        if ($since !== null) {
            $request['time_from'] = $this->iso8601($since);
        }
        $until = $this->safe_string_n($params, array( 'until' ));
        if ($until !== null) {
            $params = $this->omit($params, array( 'until' ));
            $request['ref_datetime'] = $this->iso8601($until);
        }
        $response = $this->v1PrivateGetPortfoliosFills ($this->extend($request, $params));
        //
        //    {
        //        "pagination":array(
        //           "result_limit":25,
        //           "result_offset":0
        //        ),
        //        "results":array(
        //           array(
        //              "portfolio_id":"1wp37qsc-1-0",
        //              "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        //              "portfolio_name":"CCXT Portfolio 020624-17:16",
        //              "fill_id":"1xbfy19y-1-184",
        //              "exec_id":"280841526207070392",
        //              "order_id":"1xbfv8yw-1-0",
        //              "instrument_id":"114jqr89-0-0",
        //              "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        //              "symbol":"BTC-PERP",
        //              "match_id":"280841526207053840",
        //              "fill_price":"52500",
        //              "fill_qty":"0.01",
        //              "client_id":"1x59ctku-1-1",
        //              "client_order_id":"ccxt3e4e2a5f-4a89-",
        //              "order_qty":"0.01",
        //              "limit_price":"52500",
        //              "total_filled":"0.01",
        //              "filled_vwap":"52500",
        //              "expire_time":"",
        //              "stop_price":"",
        //              "side":"BUY",
        //              "tif":"GTC",
        //              "stp_mode":"BOTH",
        //              "flags":"",
        //              "fee":"0.105",
        //              "fee_asset":"USDC",
        //              "order_status":"DONE",
        //              "event_time":"2024-02-15T00:43:57.631Z"
        //           ),
        //        )
        //    }
        //
        $trades = $this->safe_list($response, 'results', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://docs.cloud.coinbase.com/intx/reference/withdraw
         * @see https://docs.cloud.coinbase.com/intx/reference/counterpartywithdraw
         *
         * @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
         * @param {boolean} [$params->add_network_fee_to_total] if true, deducts network fee from the $portfolio, otherwise deduct fee from the withdrawal
         * @param {string} [$params->network_arn_id] Identifies the blockchain network (e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a)
         * @param {string} [$params->nonce] a unique integer representing the withdrawal $request
         * @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);
        $this->load_markets();
        $currency = $this->currency($code);
        $portfolio = null;
        list($portfolio, $params) = $this->handle_portfolio_and_params('withdraw', $params);
        $method = null;
        list($method, $params) = $this->handle_option_and_params($params, 'withdraw', 'method', 'v1PrivatePostTransfersWithdraw');
        $networkId = null;
        list($networkId, $params) = $this->handle_network_id_and_params($code, 'withdraw', $params);
        $request = array(
            'portfolio' => $portfolio,
            'type' => 'send',
            'asset' => $currency['id'],
            'address' => $address,
            'amount' => $amount,
            'currency' => $currency['id'],
            'network_arn_id' => $networkId,
            'nonce' => $this->nonce(),
        );
        $response = $this->$method ($this->extend($request, $params));
        //
        //    {
        //        "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
        //    }
        //
        return $this->parse_transaction($response, $currency);
    }

    public function safe_network($network) {
        $withdrawEnabled = $this->safe_bool($network, 'withdraw');
        $depositEnabled = $this->safe_bool($network, 'deposit');
        $limits = $this->safe_dict($network, 'limits');
        $withdraw = $this->safe_dict($limits, 'withdraw');
        $withdrawMax = $this->safe_number($withdraw, 'max');
        $deposit = $this->safe_dict($limits, 'deposit');
        $depositMax = $this->safe_number($deposit, 'max');
        if ($withdrawEnabled === null && $withdrawMax !== null) {
            $withdrawEnabled = ($withdrawMax > 0);
        }
        if ($depositEnabled === null && $depositMax !== null) {
            $depositEnabled = ($depositMax > 0);
        }
        $networkId = $this->safe_string($network, 'id');
        $isEnabled = ($withdrawEnabled && $depositEnabled);
        return array(
            'info' => $network['info'],
            'id' => $networkId,
            'name' => $this->safe_string($network, 'name'),
            'network' => $this->safe_string($network, 'network'),
            'active' => $this->safe_bool($network, 'active', $isEnabled),
            'deposit' => $depositEnabled,
            'withdraw' => $withdrawEnabled,
            'fee' => $this->safe_number($network, 'fee'),
            'precision' => $this->safe_number($network, 'precision'),
            'limits' => array(
                'withdraw' => array(
                    'min' => $this->safe_number($withdraw, 'min'),
                    'max' => $withdrawMax,
                ),
                'deposit' => array(
                    'min' => $this->safe_number($deposit, 'min'),
                    'max' => $depositMax,
                ),
            ),
        );
    }

    public function sign($path, $api = [], $method = 'GET', $params = array (), $headers = null, $body = null) {
        $version = $api[0];
        $signed = $api[1] === 'private';
        $fullPath = '/' . $version . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $savedPath = '/api' . $fullPath;
        if ($method === 'GET' || $method === 'DELETE') {
            if ($query) {
                $fullPath .= '?' . $this->urlencode_with_array_repeat($query);
            }
        }
        $url = $this->urls['api']['rest'] . $fullPath;
        if ($signed) {
            $this->check_required_credentials();
            $nonce = (string) $this->nonce();
            $payload = '';
            if ($method !== 'GET') {
                if ($query) {
                    $body = $this->json($query);
                    $payload = $body;
                }
            }
            $auth = $nonce . $method . $savedPath . $payload;
            $signature = $this->hmac($this->encode($auth), base64_decode($this->secret), 'sha256', 'base64');
            $headers = array(
                'CB-ACCESS-TIMESTAMP' => $nonce,
                'CB-ACCESS-SIGN' => $signature,
                'CB-ACCESS-PASSPHRASE' => $this->password,
                'CB-ACCESS-KEY' => $this->apiKey,
            );
        }
        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) {
        //
        //    {
        //        "title":"io.javalin.http.BadRequestResponse => Order rejected (DUPLICATE_CLIENT_ORDER_ID - duplicate client order id detected)",
        //        "status":400
        //    }
        //
        if ($response === null) {
            return null; // fallback to default error handler
        }
        $feedback = $this->id . ' ' . $body;
        $errMsg = $this->safe_string($response, 'title');
        if ($errMsg !== null) {
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errMsg, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errMsg, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
