<?php

namespace ccxt\pro;

// 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\ExchangeError;
use ccxt\NotSupported;
use \React\Async;
use \React\Promise\PromiseInterface;

class coinbaseinternational extends \ccxt\async\coinbaseinternational {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchTrades' => true,
                'watchTradesForSymbols' => true,
                'watchOrderBook' => true,
                'watchOrderBookForSymbols' => true,
                'watchTicker' => true,
                'watchBalance' => false,
                'watchMyTrades' => false,
                'watchOHLCV' => true,
                'watchOHLCVForSymbols' => false,
                'watchOrders' => false,
                'watchOrdersForSymbols' => false,
                'watchPositions' => false,
                'watchTickers' => true,
                'createOrderWs' => false,
                'editOrderWs' => false,
                'cancelOrderWs' => false,
                'cancelOrdersWs' => false,
                'cancelAllOrdersWs' => false,
                'fetchOrderWs' => false,
                'fetchOrdersWs' => false,
                'fetchBalanceWs' => false,
                'fetchMyTradesWs' => false,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://ws-md.international.coinbase.com',
                ),
                'test' => array(
                    'ws' => 'wss://ws-md.n5e2.coinbase.com',
                ),
            ),
            'options' => array(
                'watchTicker' => array(
                    'channel' => 'LEVEL1',  // 'INSTRUMENTS' or 'RISK'
                ),
                'tradesLimit' => 1000,
                'ordersLimit' => 1000,
                'myTradesLimit' => 1000,
                'timeframes' => array(
                    '1m' => 'CANDLES_ONE_MINUTE',
                    '5m' => 'CANDLES_FIVE_MINUTES',
                    '30m' => 'CANDLES_THIRTY_MINUTES',
                    '1h' => 'CANDLES_ONE_HOUR',
                    '2h' => 'CANDLES_TWO_HOURS',
                    '1d' => 'CANDLES_ONE_DAY',
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'Unable to authenticate' => '\\ccxt\\AuthenticationError',
                ),
            ),
        ));
    }

    public function subscribe(string $name, ?array $symbols = null, $params = array ()) {
        return Async\async(function () use ($name, $symbols, $params) {
            /**
             * @ignore
             * subscribes to a websocket channel
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-overview#$subscribe
             *
             * @param {string} $name the $name of the channel
             * @param {string[]} [$symbols] unified $market symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} subscription to a websocket channel
             */
            Async\await($this->load_markets());
            $this->check_required_credentials();
            $market = null;
            $messageHash = $name;
            $productIds = null;
            if ($symbols === null) {
                $symbols = $this->get_active_symbols();
            }
            $symbolsLength = count($symbols);
            $messageHashes = array();
            if ($symbolsLength > 1) {
                $parsedSymbols = $this->market_symbols($symbols);
                $marketIds = $this->market_ids($parsedSymbols);
                $productIds = $marketIds;
                for ($i = 0; $i < count($parsedSymbols); $i++) {
                    $messageHashes[] = $name . '::' . $parsedSymbols[$i];
                }
                // $messageHash = $messageHash . '::' . implode(',', $parsedSymbols);
            } elseif ($symbolsLength === 1) {
                $market = $this->market($symbols[0]);
                $messageHash = $name . '::' . $market['symbol'];
                $productIds = [ $market['id'] ];
            }
            $url = $this->urls['api']['ws'];
            if ($url === null) {
                throw new NotSupported($this->id . ' is not supported in sandbox environment');
            }
            $timestamp = (string) $this->nonce();
            $auth = $timestamp . $this->apiKey . 'CBINTLMD' . $this->password;
            $signature = $this->hmac($this->encode($auth), base64_decode($this->secret), 'sha256', 'base64');
            $subscribe = array(
                'type' => 'SUBSCRIBE',
                // 'product_ids' => $productIds,
                'channels' => array( $name ),
                'time' => $timestamp,
                'key' => $this->apiKey,
                'passphrase' => $this->password,
                'signature' => $signature,
            );
            if ($productIds !== null) {
                $subscribe['product_ids'] = $productIds;
            }
            if ($symbolsLength > 1) {
                return Async\await($this->watch_multiple($url, $messageHashes, $this->extend($subscribe, $params), $messageHashes));
            }
            return Async\await($this->watch($url, $messageHash, $this->extend($subscribe, $params), $messageHash));
        }) ();
    }

    public function subscribe_multiple(string $name, ?array $symbols = null, $params = array ()) {
        return Async\async(function () use ($name, $symbols, $params) {
            /**
             * @ignore
             * subscribes to a websocket channel using watchMultiple
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-overview#$subscribe
             *
             * @param {string} $name the $name of the channel
             * @param {string|string[]} [$symbols] unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} subscription to a websocket channel
             */
            Async\await($this->load_markets());
            $this->check_required_credentials();
            if ($this->is_empty($symbols)) {
                $symbols = $this->symbols;
            } else {
                $symbols = $this->market_symbols($symbols);
            }
            $messageHashes = array();
            $productIds = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $marketId = $this->market_id($symbols[$i]);
                $symbol = $this->symbol($marketId);
                $productIds[] = $marketId;
                $messageHashes[] = $name . '::' . $symbol;
            }
            $url = $this->urls['api']['ws'];
            if ($url === null) {
                throw new NotSupported($this->id . ' is not supported in sandbox environment');
            }
            $timestamp = $this->number_to_string($this->seconds());
            $auth = $timestamp . $this->apiKey . 'CBINTLMD' . $this->password;
            $signature = $this->hmac($this->encode($auth), base64_decode($this->secret), 'sha256', 'base64');
            $subscribe = array(
                'type' => 'SUBSCRIBE',
                'time' => $timestamp,
                'product_ids' => $productIds,
                'channels' => array( $name ),
                'key' => $this->apiKey,
                'passphrase' => $this->password,
                'signature' => $signature,
            );
            return Async\await($this->watch_multiple($url, $messageHashes, $this->extend($subscribe, $params), $messageHashes));
        }) ();
    }

    public function watch_funding_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * watch the current funding rate
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-channels#funding-channel
             *
             * @param {string} $symbol unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
             */
            Async\await($this->load_markets());
            return Async\await($this->subscribe('RISK', array( $symbol ), $params));
        }) ();
    }

    public function watch_funding_rates(array $symbols, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * watch the funding rate for multiple markets
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-channels#funding-channel
             *
             * @param {string[]|null} $symbols list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=funding-rates-structure funding rates structures~, indexe by market $symbols
             */
            Async\await($this->load_markets());
            $fundingRate = Async\await($this->subscribe_multiple('RISK', $symbols, $params));
            $symbol = $this->safe_string($fundingRate, 'symbol');
            if ($this->newUpdates) {
                $result = array();
                $result[$symbol] = $fundingRate;
                return $result;
            }
            return $this->filter_by_array($this->fundingRates, 'symbol', $symbols);
        }) ();
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * watches 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/docs/websocket-channels#instruments-$channel
             *
             * @param {string} [$symbol] unified $symbol of the market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->channel] the $channel to watch, 'LEVEL1' or 'INSTRUMENTS', default is 'LEVEL1'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $channel = null;
            list($channel, $params) = $this->handle_option_and_params($params, 'watchTicker', 'channel', 'LEVEL1');
            return Async\await($this->subscribe($channel, array( $symbol ), $params));
        }) ();
    }

    public function get_active_symbols() {
        $symbols = $this->symbols;
        $output = array();
        for ($i = 0; $i < count($symbols); $i++) {
            $symbol = $symbols[$i];
            $market = $this->markets[$symbol];
            if ($market['active']) {
                $output[] = $symbol;
            }
        }
        return $output;
    }

    public function watch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * watches 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/docs/websocket-channels#instruments-$channel
             *
             * @param {string[]} [$symbols] unified symbol of the market to fetch the $ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->channel] the $channel to watch, 'LEVEL1' or 'INSTRUMENTS', default is 'INSTLEVEL1UMENTS'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
             */
            Async\await($this->load_markets());
            $channel = null;
            list($channel, $params) = $this->handle_option_and_params($params, 'watchTickers', 'channel', 'LEVEL1');
            $ticker = Async\await($this->subscribe($channel, $symbols, $params));
            if ($this->newUpdates) {
                $result = array();
                $result[$ticker['symbol']] = $ticker;
                return $result;
            }
            return $this->filter_by_array($this->tickers, 'symbol', $symbols);
        }) ();
    }

    public function handle_instrument(Client $client, $message) {
        //
        //    {
        //        "sequence" => 1,
        //        "product_id" => "ETH-PERP",
        //        "instrument_type" => "PERP",
        //        "base_asset_name" => "ETH",
        //        "quote_asset_name" => "USDC",
        //        "base_increment" => "0.0001",
        //        "quote_increment" => "0.01",
        //        "avg_daily_quantity" => "43.0",
        //        "avg_daily_volume" => "80245.2",
        //        "total_30_day_quantity":"1443.0",
        //        "total_30_day_volume":"3040449.0",
        //        "total_24_hour_quantity":"48.1",
        //        "total_24_hour_volume":"101348.3",
        //        "base_imf" => "0.2",
        //        "min_quantity" => "0.0001",
        //        "position_size_limit" => "500",
        //        "funding_interval" => "60000000000",
        //        "trading_state" => "trading",
        //        "last_update_time" => "2023-05-04T11:16:33.016Z",
        //        "time" => "2023-05-10T14:58:47.000Z",
        //        "channel":"INSTRUMENTS",
        //        "type":"SNAPSHOT"
        //    }
        $ticker = $this->parse_ws_instrument($message);
        $channel = $this->safe_string($message, 'channel');
        $client->resolve ($ticker, $channel);
        $client->resolve ($ticker, $channel . '::' . $ticker['symbol']);
    }

    public function parse_ws_instrument(array $ticker, $market = null) {
        //
        //    {
        //        "sequence" => 1,
        //        "product_id" => "ETH-PERP",
        //        "instrument_type" => "PERP",
        //        "base_asset_name" => "ETH",
        //        "quote_asset_name" => "USDC",
        //        "base_increment" => "0.0001",
        //        "quote_increment" => "0.01",
        //        "avg_daily_quantity" => "43.0",
        //        "avg_daily_volume" => "80245.2",
        //        "total_30_day_quantity":"1443.0",
        //        "total_30_day_volume":"3040449.0",
        //        "total_24_hour_quantity":"48.1",
        //        "total_24_hour_volume":"101348.3",
        //        "base_imf" => "0.2",
        //        "min_quantity" => "0.0001",
        //        "position_size_limit" => "500",
        //        "funding_interval" => "60000000000",
        //        "trading_state" => "trading",
        //        "last_update_time" => "2023-05-04T11:16:33.016Z",
        //        "time" => "2023-05-10T14:58:47.000Z",
        //        "channel":"INSTRUMENTS",
        //        "type":"SNAPSHOT"
        //    }
        // instruments
        //   {
        //       sequence => 0,
        //       instrument_type => 'PERP',
        //       instrument_mode => 'standard',
        //       base_asset_name => 'BTC',
        //       quote_asset_name => 'USDC',
        //       base_increment => '0.0001',
        //       quote_increment => '0.1',
        //       avg_daily_quantity => '502.8845',
        //       avg_daily_volume => '3.1495242961566668E7',
        //       total30_day_quantity => '15086.535',
        //       total30_day_volume => '9.44857288847E8',
        //       total24_hour_quantity => '5.0',
        //       total24_hour_volume => '337016.5',
        //       base_imf => '0.1',
        //       min_quantity => '0.0001',
        //       position_size_limit => '800',
        //       funding_interval => '3600000000000',
        //       trading_state => 'trading',
        //       last_updated_time => '2024-07-30T15:00:00Z',
        //       default_initial_margin => '0.2',
        //       base_asset_multiplier => '1.0',
        //       channel => 'INSTRUMENTS',
        //       type => 'SNAPSHOT',
        //       time => '2024-07-30T15:26:56.766Z',
        //   }
        //
        $marketId = $this->safe_string($ticker, 'product_id');
        $datetime = $this->safe_string($ticker, 'time');
        return $this->safe_ticker(array(
            'info' => $ticker,
            'symbol' => $this->safe_symbol($marketId, $market, '-'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'high' => null,
            'low' => null,
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => null,
            'last' => null,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string_2($ticker, 'total_24_hour_quantity', 'total24_hour_quantity'),
            'quoteVolume' => $this->safe_string_2($ticker, 'total_24_hour_volume', 'total24_hour_volume'),
        ));
    }

    public function handle_ticker(Client $client, $message) {
        //
        // snapshot
        //    {
        //        "sequence" => 0,
        //        "product_id" => "BTC-PERP",
        //        "time" => "2023-05-10T14:58:47.000Z",
        //        "bid_price" => "28787.8",
        //        "bid_qty" => "0.466", // One side book
        //        "channel" => "LEVEL1",
        //        "type" => "SNAPSHOT"
        //    }
        // update
        //    {
        //       "sequence" => 1,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.547Z",
        //       "bid_price" => "28787.8",
        //       "bid_qty" => "0.466",
        //       "ask_price" => "28788.8",
        //       "ask_qty" => "1.566",
        //       "channel" => "LEVEL1",
        //       "type" => "UPDATE"
        //    }
        //
        $ticker = $this->parse_ws_ticker($message);
        $channel = $this->safe_string($message, 'channel');
        $client->resolve ($ticker, $channel);
        $client->resolve ($ticker, $channel . '::' . $ticker['symbol']);
    }

    public function parse_ws_ticker(array $ticker, ?array $market = null): array {
        //
        //    {
        //       "sequence" => 1,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.547Z",
        //       "bid_price" => "28787.8",
        //       "bid_qty" => "0.466",
        //       "ask_price" => "28788.8",
        //       "ask_qty" => "1.566",
        //       "channel" => "LEVEL1",
        //       "type" => "UPDATE"
        //    }
        //
        $datetime = $this->safe_string($ticker, 'time');
        $marketId = $this->safe_string($ticker, 'product_id');
        return $this->safe_ticker(array(
            'info' => $ticker,
            'symbol' => $this->safe_symbol($marketId, $market),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'bid' => $this->safe_number($ticker, 'bid_price'),
            'bidVolume' => $this->safe_number($ticker, 'bid_qty'),
            'ask' => $this->safe_number($ticker, 'ask_price'),
            'askVolume' => $this->safe_number($ticker, 'ask_qty'),
            'high' => null,
            'low' => null,
            'open' => null,
            'close' => null,
            'last' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'vwap' => null,
            'baseVolume' => null,
            'quoteVolume' => null,
            'previousClose' => null,
        ));
    }

    public function watch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * watches historical candlestick data containing the open, high, low, close price, and the volume of a $market
             *
             * @see https://docs.cdp.coinbase.com/intx/docs/websocket-channels#candles-channel
             *
             * @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
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $options = $this->safe_dict($this->options, 'timeframes', array());
            $interval = $this->safe_string($options, $timeframe, $timeframe);
            $ohlcv = Async\await($this->subscribe($interval, array( $symbol ), $params));
            if ($this->newUpdates) {
                $limit = $ohlcv->getLimit ($symbol, $limit);
            }
            return $this->filter_by_since_limit($ohlcv, $since, $limit, 0, true);
        }) ();
    }

    public function handle_ohlcv(Client $client, $message) {
        //
        // {
        //     "sequence" => 0,
        //     "product_id" => "BTC-PERP",
        //     "channel" => "CANDLES_ONE_MINUTE",
        //     "type" => "SNAPSHOT",
        //     "candles" => array(
        //       array(
        //           "time" => "2023-05-10T14:58:47.000Z",
        //           "low" => "28787.8",
        //           "high" => "28788.8",
        //           "open" => "28788.8",
        //           "close" => "28787.8",
        //           "volume" => "0.466"
        //        ),
        //     )
        //  }
        //
        $messageHash = $this->safe_string($message, 'channel');
        $marketId = $this->safe_string($message, 'product_id');
        $market = $this->safe_market($marketId);
        $symbol = $market['symbol'];
        $timeframe = $this->find_timeframe($messageHash);
        $this->ohlcvs[$symbol] = $this->safe_value($this->ohlcvs, $symbol, array());
        if ($this->safe_value($this->ohlcvs[$symbol], $timeframe) === null) {
            $limit = $this->safe_integer($this->options, 'OHLCVLimit', 1000);
            $this->ohlcvs[$symbol][$timeframe] = new ArrayCacheByTimestamp ($limit);
        }
        $stored = $this->ohlcvs[$symbol][$timeframe];
        $data = $this->safe_list($message, 'candles', array());
        for ($i = 0; $i < count($data); $i++) {
            $tick = $data[$i];
            $parsed = $this->parse_ohlcv($tick, $market);
            $stored->append ($parsed);
        }
        $client->resolve ($stored, $messageHash . '::' . $symbol);
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-channels#match-channel
             *
             * @param {string} $symbol unified $symbol of the market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @param {int} [$limit] the maximum amount of trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            return Async\await($this->watch_trades_for_symbols(array( $symbol ), $since, $limit, $params));
        }) ();
    }

    public function watch_trades_for_symbols(array $symbols, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $since, $limit, $params) {
            /**
             * get the list of most recent $trades for a list of $symbols
             * @param {string[]} $symbols unified symbol of the market to fetch $trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @param {int} [$limit] the maximum amount of $trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols, null, false, true, true);
            $trades = Async\await($this->subscribe_multiple('MATCH', $symbols, $params));
            if ($this->newUpdates) {
                $first = $this->safe_dict($trades, 0);
                $tradeSymbol = $this->safe_string($first, 'symbol');
                $limit = $trades->getLimit ($tradeSymbol, $limit);
            }
            return $this->filter_by_since_limit($trades, $since, $limit, 'timestamp', true);
        }) ();
    }

    public function handle_trade($client, $message) {
        //
        //    {
        //       "sequence" => 0,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.002Z",
        //       "match_id" => "177101110052388865",
        //       "trade_qty" => "0.006",
        //       "aggressor_side" => "BUY",
        //       "trade_price" => "28833.1",
        //       "channel" => "MATCH",
        //       "type" => "UPDATE"
        //    }
        //
        $trade = $this->parse_ws_trade($message);
        $symbol = $trade['symbol'];
        $channel = $this->safe_string($message, 'channel');
        if (!(is_array($this->trades) && array_key_exists($symbol, $this->trades))) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $tradesArrayCache = new ArrayCache ($limit);
            $this->trades[$symbol] = $tradesArrayCache;
        }
        $tradesArray = $this->trades[$symbol];
        $tradesArray->append ($trade);
        $this->trades[$symbol] = $tradesArray;
        $client->resolve ($tradesArray, $channel);
        $client->resolve ($tradesArray, $channel . '::' . $trade['symbol']);
        return $message;
    }

    public function parse_ws_trade($trade, $market = null) {
        //
        //    {
        //       "sequence" => 0,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.002Z",
        //       "match_id" => "177101110052388865",
        //       "trade_qty" => "0.006",
        //       "aggressor_side" => "BUY",
        //       "trade_price" => "28833.1",
        //       "channel" => "MATCH",
        //       "type" => "UPDATE"
        //    }
        $marketId = $this->safe_string_2($trade, 'symbol', 'product_id');
        $datetime = $this->safe_string($trade, 'time');
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $this->safe_string($trade, 'match_id'),
            'order' => null,
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'symbol' => $this->safe_symbol($marketId, $market),
            'type' => null,
            'side' => $this->safe_string_lower($trade, 'agressor_side'),
            'takerOrMaker' => null,
            'price' => $this->safe_string($trade, 'trade_price'),
            'amount' => $this->safe_string($trade, 'trade_qty'),
            'cost' => null,
            'fee' => null,
        ));
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-channels#level2-channel
             *
             * @param {string} $symbol unified $symbol of the market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by market symbols
             */
            return Async\await($this->watch_order_book_for_symbols(array( $symbol ), $limit, $params));
        }) ();
    }

    public function watch_order_book_for_symbols(array $symbols, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $limit, $params) {
            /**
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://docs.cloud.coinbase.com/intx/docs/websocket-channels#level2-channel
             *
             * @param {string[]} $symbols
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by market $symbols
             */
            Async\await($this->load_markets());
            return Async\await($this->subscribe_multiple('LEVEL2', $symbols, $params));
        }) ();
    }

    public function handle_order_book($client, $message) {
        //
        // snapshot
        //    {
        //       "sequence" => 0,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.000Z",
        //       "bids" => [
        //           ["29100", "0.02"],
        //           ["28950", "0.01"],
        //           ["28900", "0.01"]
        //       ],
        //       "asks" => [
        //           ["29267.8", "18"],
        //           ["29747.6", "18"],
        //           ["30227.4", "9"]
        //       ],
        //       "channel" => "LEVEL2",
        //       "type" => "SNAPSHOT",
        //    }
        // update
        //    {
        //       "sequence" => 1,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.375Z",
        //       "changes" => array(
        //           array(
        //               "BUY",
        //               "28787.7",
        //               "6"
        //           )
        //       ),
        //       "channel" => "LEVEL2",
        //       "type" => "UPDATE"
        //    }
        //
        $type = $this->safe_string($message, 'type');
        $marketId = $this->safe_string($message, 'product_id');
        $symbol = $this->safe_symbol($marketId);
        $datetime = $this->safe_string($message, 'time');
        $channel = $this->safe_string($message, 'channel');
        if (!(is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks))) {
            $limit = $this->safe_integer($this->options, 'watchOrderBookLimit', 1000);
            $this->orderbooks[$symbol] = $this->order_book(array(), $limit);
        }
        $orderbook = $this->orderbooks[$symbol];
        if ($type === 'SNAPSHOT') {
            $parsedSnapshot = $this->parse_order_book($message, $symbol, null, 'bids', 'asks');
            $orderbook->reset ($parsedSnapshot);
            $orderbook['symbol'] = $symbol;
        } else {
            $changes = $this->safe_list($message, 'changes', array());
            $this->handle_deltas($orderbook, $changes);
        }
        $orderbook['nonce'] = $this->safe_integer($message, 'sequence');
        $orderbook['datetime'] = $datetime;
        $orderbook['timestamp'] = $this->parse8601($datetime);
        $this->orderbooks[$symbol] = $orderbook;
        $client->resolve ($orderbook, $channel . '::' . $symbol);
    }

    public function handle_delta($orderbook, $delta) {
        $rawSide = $this->safe_string_lower($delta, 0);
        $side = ($rawSide === 'buy') ? 'bids' : 'asks';
        $price = $this->safe_float($delta, 1);
        $amount = $this->safe_float($delta, 2);
        $bookside = $orderbook[$side];
        $bookside->store ($price, $amount);
    }

    public function handle_deltas($orderbook, $deltas) {
        for ($i = 0; $i < count($deltas); $i++) {
            $this->handle_delta($orderbook, $deltas[$i]);
        }
    }

    public function handle_subscription_status($client, $message) {
        //
        //    {
        //       "channels" => array(
        //           array(
        //               "name" => "MATCH",
        //               "product_ids" => array(
        //                   "BTC-PERP",
        //                   "ETH-PERP"
        //               )
        //           ),
        //           {
        //               "name" => "INSTRUMENTS",
        //               "product_ids" => array(
        //                   "BTC-PERP",
        //                   "ETH-PERP"
        //               )
        //           }
        //       ),
        //       "authenticated" => true,
        //       "channel" => "SUBSCRIPTIONS",
        //       "type" => "SNAPSHOT",
        //       "time" => "2023-05-30T16:53:46.847Z"
        //    }
        //
        return $message;
    }

    public function handle_funding_rate(Client $client, $message) {
        //
        // snapshot
        //    {
        //       "sequence" => 0,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T14:58:47.000Z",
        //       "funding_rate" => "0.001387",
        //       "is_final" => true,
        //       "channel" => "FUNDING",
        //       "type" => "SNAPSHOT"
        //    }
        // update
        //    {
        //       "sequence" => 1,
        //       "product_id" => "BTC-PERP",
        //       "time" => "2023-05-10T15:00:00.000Z",
        //       "funding_rate" => "0.001487",
        //       "is_final" => false,
        //       "channel" => "FUNDING",
        //       "type" => "UPDATE"
        //    }
        //
        $channel = $this->safe_string($message, 'channel');
        $fundingRate = $this->parse_funding_rate($message);
        $this->fundingRates[$fundingRate['symbol']] = $fundingRate;
        $client->resolve ($fundingRate, $channel . '::' . $fundingRate['symbol']);
    }

    public function handle_error_message(Client $client, $message): Bool {
        //
        //    {
        //        $message => 'Failed to subscribe',
        //        $reason => 'Unable to authenticate',
        //        channel => 'SUBSCRIPTIONS',
        //        $type => 'REJECT'
        //    }
        //
        $type = $this->safe_string($message, 'type');
        if ($type !== 'REJECT') {
            return false;
        }
        $reason = $this->safe_string($message, 'reason');
        $errMsg = $this->safe_string($message, 'message');
        try {
            $feedback = $this->id . ' ' . $errMsg . $reason;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $reason, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $reason, $feedback);
            throw new ExchangeError($feedback);
        } catch (Exception $e) {
            $client->reject ($e);
        }
        return true;
    }

    public function handle_message($client, $message) {
        if ($this->handle_error_message($client, $message)) {
            return;
        }
        $channel = $this->safe_string($message, 'channel', '');
        $methods = array(
            'SUBSCRIPTIONS' => array($this, 'handle_subscription_status'),
            'INSTRUMENTS' => array($this, 'handle_instrument'),
            'LEVEL1' => array($this, 'handle_ticker'),
            'MATCH' => array($this, 'handle_trade'),
            'LEVEL2' => array($this, 'handle_order_book'),
            'FUNDING' => array($this, 'handle_funding_rate'),
            'RISK' => array($this, 'handle_ticker'),
        );
        $type = $this->safe_string($message, 'type');
        if ($type === 'error') {
            $errorMessage = $this->safe_string($message, 'message');
            throw new ExchangeError($errorMessage);
        }
        if (mb_strpos($channel, 'CANDLES') > -1) {
            $this->handle_ohlcv($client, $message);
        }
        $method = $this->safe_value($methods, $channel);
        if ($method !== null) {
            $method($client, $message);
        }
    }
}
