<?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\AuthenticationError;
use ccxt\UnsubscribeError;
use \React\Async;
use \React\Promise\PromiseInterface;

class derive extends \ccxt\async\derive {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => false,
                'watchBalance' => false,
                'watchMyTrades' => true,
                'watchOHLCV' => false,
                'watchOrderBook' => true,
                'watchOrders' => true,
                'watchTicker' => true,
                'watchTickers' => false,
                'watchBidsAsks' => false,
                'watchTrades' => true,
                'watchTradesForSymbols' => false,
                'watchPositions' => false,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://api.lyra.finance/ws',
                ),
                'test' => array(
                    'ws' => 'wss://api-demo.lyra.finance/ws',
                ),
            ),
            'options' => array(
                'tradesLimit' => 1000,
                'ordersLimit' => 1000,
                'requestId' => array(),
            ),
            'streaming' => array(
                'keepAlive' => 9000,
            ),
            'exceptions' => array(
                'ws' => array(
                    'exact' => array(),
                ),
            ),
        ));
    }

    public function request_id($url) {
        $options = $this->safe_value($this->options, 'requestId', array());
        $previousValue = $this->safe_integer($options, $url, 0);
        $newValue = $this->sum($previousValue, 1);
        $this->options['requestId'][$url] = $newValue;
        return $newValue;
    }

    public function watch_public($messageHash, $message, $subscription) {
        return Async\async(function () use ($messageHash, $message, $subscription) {
            $url = $this->urls['api']['ws'];
            $requestId = $this->request_id($url);
            $request = $this->extend($message, array(
                'id' => $requestId,
            ));
            $subscription = $this->extend($subscription, array(
                'id' => $requestId,
                'method' => 'subscribe',
            ));
            return Async\await($this->watch($url, $messageHash, $request, $messageHash, $subscription));
        }) ();
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://docs.derive.xyz/reference/orderbook-instrument_name-group-depth
             *
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             * @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
             */
            Async\await($this->load_markets());
            if ($limit === null) {
                $limit = 10;
            }
            $market = $this->market($symbol);
            $topic = 'orderbook.' . $market['id'] . '.10.' . $this->number_to_string($limit);
            $request = array(
                'method' => 'subscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
                'symbol' => $symbol,
                'limit' => $limit,
                'params' => $params,
            );
            $orderbook = Async\await($this->watch_public($topic, $request, $subscription));
            return $orderbook->limit ();
        }) ();
    }

    public function handle_order_book(Client $client, $message) {
        //
        // {
        //     method => 'subscription',
        //     $params => {
        //       channel => 'orderbook.BTC-PERP.10.1',
        //       $data => {
        //         $timestamp => 1738331231506,
        //         instrument_name => 'BTC-PERP',
        //         publish_id => 628419,
        //         bids => array( array( '104669', '40' ) ),
        //         asks => array( array( '104736', '40' ) )
        //       }
        //     }
        // }
        //
        $params = $this->safe_dict($message, 'params');
        $data = $this->safe_dict($params, 'data');
        $marketId = $this->safe_string($data, 'instrument_name');
        $market = $this->safe_market($marketId);
        $symbol = $market['symbol'];
        $topic = $this->safe_string($params, 'channel');
        if (!(is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks))) {
            $defaultLimit = $this->safe_integer($this->options, 'watchOrderBookLimit', 1000);
            $subscription = $client->subscriptions[$topic];
            $limit = $this->safe_integer($subscription, 'limit', $defaultLimit);
            $this->orderbooks[$symbol] = $this->order_book(array(), $limit);
        }
        $orderbook = $this->orderbooks[$symbol];
        $timestamp = $this->safe_integer($data, 'timestamp');
        $snapshot = $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks');
        $orderbook->reset ($snapshot);
        $client->resolve ($orderbook, $topic);
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://docs.derive.xyz/reference/ticker-instrument_name-interval
             *
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             * @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~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $topic = 'ticker.' . $market['id'] . '.100';
            $request = array(
                'method' => 'subscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
                'symbol' => $symbol,
                'params' => $params,
            );
            return Async\await($this->watch_public($topic, $request, $subscription));
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        //
        // {
        //     method => 'subscription',
        //     $params => {
        //       channel => 'ticker.BTC-PERP.100',
        //       $data => {
        //         timestamp => 1738485104439,
        //         instrument_ticker => {
        //           instrument_type => 'perp',
        //           instrument_name => 'BTC-PERP',
        //           scheduled_activation => 1701840228,
        //           scheduled_deactivation => '9223372036854775807',
        //           is_active => true,
        //           tick_size => '0.1',
        //           minimum_amount => '0.01',
        //           maximum_amount => '10000',
        //           amount_step => '0.001',
        //           mark_price_fee_rate_cap => '0',
        //           maker_fee_rate => '0.0001',
        //           taker_fee_rate => '0.0003',
        //           base_fee => '0.1',
        //           base_currency => 'BTC',
        //           quote_currency => 'USD',
        //           option_details => null,
        //           perp_details => array(
        //             index => 'BTC-USD',
        //             max_rate_per_hour => '0.004',
        //             min_rate_per_hour => '-0.004',
        //             static_interest_rate => '0.0000125',
        //             aggregate_funding => '10581.779418721074588722',
        //             funding_rate => '0.000024792239208858'
        //           ),
        //           erc20_details => null,
        //           base_asset_address => '0xDBa83C0C654DB1cd914FA2710bA743e925B53086',
        //           base_asset_sub_id => '0',
        //           pro_rata_fraction => '0',
        //           fifo_min_allocation => '0',
        //           pro_rata_amount_step => '0.1',
        //           best_ask_amount => '0.131',
        //           best_ask_price => '99898.6',
        //           best_bid_amount => '0.056',
        //           best_bid_price => '99889.1',
        //           five_percent_bid_depth => '11.817',
        //           five_percent_ask_depth => '9.116',
        //           option_pricing => null,
        //           index_price => '99883.8',
        //           mark_price => '99897.52408421244763303548098',
        //           stats => array(
        //             contract_volume => '92.395',
        //             num_trades => '2924',
        //             open_interest => '33.743468027373780786',
        //             high => '102320.4',
        //             low => '99064.3',
        //             percent_change => '-0.021356',
        //             usd_change => '-2178'
        //           ),
        //           timestamp => 1738485165881,
        //           min_price => '97939.1',
        //           max_price => '101895.2'
        //         }
        //       }
        //     }
        // }
        //
        $params = $this->safe_dict($message, 'params');
        $rawData = $this->safe_dict($params, 'data');
        $data = $this->safe_dict($rawData, 'instrument_ticker');
        $topic = $this->safe_value($params, 'channel');
        $ticker = $this->parse_ticker($data);
        $this->tickers[$ticker['symbol']] = $ticker;
        $client->resolve ($ticker, $topic);
        return $message;
    }

    public function un_watch_order_book(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * unsubscribe from the orderbook channel
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->limit] orderbook $limit, default is null
             * @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());
            $limit = $this->safe_integer($params, 'limit');
            if ($limit === null) {
                $limit = 10;
            }
            $market = $this->market($symbol);
            $topic = 'orderbook.' . $market['id'] . '.10.' . $this->number_to_string($limit);
            $messageHash = 'unwatch' . $topic;
            $request = array(
                'method' => 'unsubscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
            );
            return Async\await($this->un_watch_public($messageHash, $request, $subscription));
        }) ();
    }

    public function un_watch_trades(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * unsubscribe from the trades channel
             * @param {string} $symbol unified $symbol of the $market to unwatch the trades for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {any} status of the unwatch $request
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $topic = 'trades.' . $market['id'];
            $messageHah = 'unwatch' . $topic;
            $request = array(
                'method' => 'unsubscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
            );
            return Async\await($this->un_watch_public($messageHah, $request, $subscription));
        }) ();
    }

    public function un_watch_public($messageHash, $message, $subscription) {
        return Async\async(function () use ($messageHash, $message, $subscription) {
            $url = $this->urls['api']['ws'];
            $requestId = $this->request_id($url);
            $request = $this->extend($message, array(
                'id' => $requestId,
            ));
            $subscription = $this->extend($subscription, array(
                'id' => $requestId,
                'method' => 'unsubscribe',
            ));
            return Async\await($this->watch($url, $messageHash, $request, $messageHash, $subscription));
        }) ();
    }

    public function handle_order_book_un_subscription(Client $client, $topic) {
        $parsedTopic = explode('.', $topic);
        $marketId = $this->safe_string($parsedTopic, 1);
        $market = $this->safe_market($marketId);
        $symbol = $market['symbol'];
        if (is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks)) {
            unset($this->orderbooks[$symbol]);
        }
        if (is_array($client->subscriptions) && array_key_exists($topic, $client->subscriptions)) {
            unset($client->subscriptions[$topic]);
        }
        $error = new UnsubscribeError ($this->id . ' orderbook ' . $symbol);
        $client->reject ($error, $topic);
        $client->resolve ($error, 'unwatch' . $topic);
    }

    public function handle_trades_un_subscription(Client $client, $topic) {
        $parsedTopic = explode('.', $topic);
        $marketId = $this->safe_string($parsedTopic, 1);
        $market = $this->safe_market($marketId);
        $symbol = $market['symbol'];
        if (is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks)) {
            unset($this->trades[$symbol]);
        }
        if (is_array($client->subscriptions) && array_key_exists($topic, $client->subscriptions)) {
            unset($client->subscriptions[$topic]);
        }
        $error = new UnsubscribeError ($this->id . ' trades ' . $symbol);
        $client->reject ($error, $topic);
        $client->resolve ($error, 'unwatch' . $topic);
    }

    public function handle_un_subscribe(Client $client, $message) {
        //
        // {
        //     id => 1,
        //     $result => {
        //       $status => array( 'orderbook.BTC-PERP.10.10' => 'ok' ),
        //       remaining_subscriptions => array()
        //     }
        // }
        //
        $result = $this->safe_dict($message, 'result');
        $status = $this->safe_dict($result, 'status');
        if ($status !== null) {
            $topics = is_array($status) ? array_keys($status) : array();
            for ($i = 0; $i < count($topics); $i++) {
                $topic = $topics[$i];
                if (mb_strpos($topic, 'orderbook') !== false) {
                    $this->handle_order_book_un_subscription($client, $topic);
                } elseif (mb_strpos($topic, 'trades') !== false) {
                    $this->handle_trades_un_subscription($client, $topic);
                }
            }
        }
        return $message;
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * watches information on multiple $trades made in a $market
             *
             * @see https://docs.derive.xyz/reference/trades-instrument_name
             *
             * @param {string} $symbol unified $market $symbol of the $market $trades were made in
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of trade 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=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $topic = 'trades.' . $market['id'];
            $request = array(
                'method' => 'subscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
                'symbol' => $symbol,
                'params' => $params,
            );
            $trades = Async\await($this->watch_public($topic, $request, $subscription));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($market['symbol'], $limit);
            }
            return $this->filter_by_symbol_since_limit($trades, $symbol, $since, $limit, true);
        }) ();
    }

    public function handle_trade(Client $client, $message) {
        //
        //
        $params = $this->safe_dict($message, 'params');
        $data = $this->safe_dict($params, 'data');
        $topic = $this->safe_value($params, 'channel');
        $parsedTopic = explode('.', $topic);
        $marketId = $this->safe_string($parsedTopic, 1);
        $market = $this->safe_market($marketId);
        $symbol = $market['symbol'];
        $tradesArray = $this->safe_value($this->trades, $symbol);
        if ($tradesArray === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $tradesArray = new ArrayCache ($limit);
        }
        for ($i = 0; $i < count($data); $i++) {
            $trade = $this->parse_trade($data[$i]);
            $tradesArray->append ($trade);
        }
        $this->trades[$symbol] = $tradesArray;
        $client->resolve ($tradesArray, $topic);
    }

    public function authenticate($params = array ()) {
        return Async\async(function () use ($params) {
            $this->check_required_credentials();
            $url = $this->urls['api']['ws'];
            $client = $this->client($url);
            $messageHash = 'authenticated';
            $future = $client->reusableFuture ($messageHash);
            $authenticated = $this->safe_value($client->subscriptions, $messageHash);
            if ($authenticated === null) {
                $requestId = $this->request_id($url);
                $now = (string) $this->milliseconds();
                $signature = $this->signMessage ($now, $this->privateKey);
                $deriveWalletAddress = $this->safe_string($this->options, 'deriveWalletAddress');
                $request = array(
                    'id' => $requestId,
                    'method' => 'public/login',
                    'params' => array(
                        'wallet' => $deriveWalletAddress,
                        'timestamp' => $now,
                        'signature' => $signature,
                    ),
                );
                // $subscription = array(
                //     'name' => topic,
                //     'symbol' => symbol,
                //     'params' => $params,
                // );
                $message = $this->extend($request, $params);
                $this->watch($url, $messageHash, $message, $messageHash, $message);
            }
            return Async\await($future);
        }) ();
    }

    public function watch_private($messageHash, $message, $subscription) {
        return Async\async(function () use ($messageHash, $message, $subscription) {
            Async\await($this->authenticate());
            $url = $this->urls['api']['ws'];
            $requestId = $this->request_id($url);
            $request = $this->extend($message, array(
                'id' => $requestId,
            ));
            $subscription = $this->extend($subscription, array(
                'id' => $requestId,
                'method' => 'subscribe',
            ));
            return Async\await($this->watch($url, $messageHash, $request, $messageHash, $subscription));
        }) ();
    }

    public function watch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs.derive.xyz/reference/subaccount_id-$orders
             *
             * watches information on multiple $orders made by the user
             * @param {string} $symbol unified $market $symbol of the $market $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->subaccount_id] *required* the subaccount id
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $subaccountId = null;
            list($subaccountId, $params) = $this->handleDeriveSubaccountId ('watchOrders', $params);
            $topic = $this->number_to_string($subaccountId) . '.orders';
            $messageHash = $topic;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $messageHash .= ':' . $symbol;
            }
            $request = array(
                'method' => 'subscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
                'params' => $params,
            );
            $message = $this->extend($request, $params);
            $orders = Async\await($this->watch_private($messageHash, $message, $subscription));
            if ($this->newUpdates) {
                $limit = $orders->getLimit ($symbol, $limit);
            }
            return $this->filter_by_symbol_since_limit($orders, $symbol, $since, $limit, true);
        }) ();
    }

    public function handle_order(Client $client, $message) {
        //
        // {
        //     method => 'subscription',
        //     $params => {
        //         channel => '130837.orders',
        //         $data => array(
        //             {
        //                 subaccount_id => 130837,
        //                 order_id => '1f44c564-5658-4b69-b8c4-4019924207d5',
        //                 instrument_name => 'BTC-PERP',
        //                 direction => 'buy',
        //                 label => 'test1234',
        //                 quote_id => null,
        //                 creation_timestamp => 1738578974146,
        //                 last_update_timestamp => 1738578974146,
        //                 limit_price => '10000',
        //                 amount => '0.01',
        //                 filled_amount => '0',
        //                 average_price => '0',
        //                 order_fee => '0',
        //                 order_type => 'limit',
        //                 time_in_force => 'post_only',
        //                 order_status => 'untriggered',
        //                 max_fee => '219',
        //                 signature_expiry_sec => 1746354973,
        //                 nonce => 1738578973570,
        //                 signer => '0x30CB7B06AdD6749BbE146A6827502B8f2a79269A',
        //                 signature => '0xc6927095f74a0d3b1aeef8c0579d120056530479f806e9d2e6616df742a8934c69046361beae833b32b25c0145e318438d7d1624bb835add956f63aa37192f571c',
        //                 cancel_reason => '',
        //                 mmp => false,
        //                 is_transfer => false,
        //                 replaced_order_id => null,
        //                 trigger_type => 'stoploss',
        //                 trigger_price_type => 'mark',
        //                 trigger_price => '102800',
        //                 trigger_reject_message => null
        //             }
        //         )
        //     }
        // }
        //
        $params = $this->safe_dict($message, 'params');
        $topic = $this->safe_string($params, 'channel');
        $rawOrders = $this->safe_list($params, 'data');
        for ($i = 0; $i < count($rawOrders); $i++) {
            $data = $rawOrders[$i];
            $parsed = $this->parse_order($data);
            $symbol = $this->safe_string($parsed, 'symbol');
            $orderId = $this->safe_string($parsed, 'id');
            if ($symbol !== null) {
                if ($this->orders === null) {
                    $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
                    $this->orders = new ArrayCacheBySymbolById ($limit);
                }
                $cachedOrders = $this->orders;
                $orders = $this->safe_value($cachedOrders->hashmap, $symbol, array());
                $order = $this->safe_value($orders, $orderId);
                if ($order !== null) {
                    $fee = $this->safe_value($order, 'fee');
                    if ($fee !== null) {
                        $parsed['fee'] = $fee;
                    }
                    $fees = $this->safe_value($order, 'fees');
                    if ($fees !== null) {
                        $parsed['fees'] = $fees;
                    }
                    $parsed['trades'] = $this->safe_value($order, 'trades');
                    $parsed['timestamp'] = $this->safe_integer($order, 'timestamp');
                    $parsed['datetime'] = $this->safe_string($order, 'datetime');
                }
                $cachedOrders->append ($parsed);
                $messageHashSymbol = $topic . ':' . $symbol;
                $client->resolve ($this->orders, $messageHashSymbol);
            }
        }
        $client->resolve ($this->orders, $topic);
    }

    public function watch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs.derive.xyz/reference/subaccount_id-$trades
             *
             * watches information on multiple $trades made by the user
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->subaccount_id] *required* the subaccount id
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $subaccountId = null;
            list($subaccountId, $params) = $this->handleDeriveSubaccountId ('watchMyTrades', $params);
            $topic = $this->number_to_string($subaccountId) . '.trades';
            $messageHash = $topic;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $messageHash .= ':' . $symbol;
            }
            $request = array(
                'method' => 'subscribe',
                'params' => array(
                    'channels' => array(
                        $topic,
                    ),
                ),
            );
            $subscription = array(
                'name' => $topic,
                'params' => $params,
            );
            $message = $this->extend($request, $params);
            $trades = Async\await($this->watch_private($messageHash, $message, $subscription));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($symbol, $limit);
            }
            return $this->filter_by_symbol_since_limit($trades, $symbol, $since, $limit, true);
        }) ();
    }

    public function handle_my_trade(Client $client, $message) {
        //
        //
        $myTrades = $this->myTrades;
        if ($myTrades === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $myTrades = new ArrayCacheBySymbolById ($limit);
        }
        $params = $this->safe_dict($message, 'params');
        $topic = $this->safe_string($params, 'channel');
        $rawTrades = $this->safe_list($params, 'data');
        for ($i = 0; $i < count($rawTrades); $i++) {
            $trade = $this->parse_trade($message);
            $myTrades->append ($trade);
            $client->resolve ($myTrades, $topic);
            $messageHash = $topic . $trade['symbol'];
            $client->resolve ($myTrades, $messageHash);
        }
    }

    public function handle_error_message(Client $client, $message): Bool {
        //
        // {
        //     id => '690c6276-0fc6-4121-aafa-f28bf5adedcb',
        //     $error => array( code => -32600, $message => 'Invalid Request' )
        // }
        //
        if (!(is_array($message) && array_key_exists('error', $message))) {
            return false;
        }
        $errorMessage = $this->safe_dict($message, 'error');
        $errorCode = $this->safe_string($errorMessage, 'code');
        try {
            if ($errorCode !== null) {
                $feedback = $this->id . ' ' . $this->json($message);
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
                throw new ExchangeError($feedback);
            }
            return false;
        } catch (Exception $error) {
            if ($error instanceof AuthenticationError) {
                $messageHash = 'authenticated';
                $client->reject ($error, $messageHash);
                if (is_array($client->subscriptions) && array_key_exists($messageHash, $client->subscriptions)) {
                    unset($client->subscriptions[$messageHash]);
                }
            } else {
                $client->reject ($error);
            }
            return true;
        }
    }

    public function handle_message(Client $client, $message) {
        if ($this->handle_error_message($client, $message)) {
            return;
        }
        $methods = array(
            'orderbook' => array($this, 'handle_order_book'),
            'ticker' => array($this, 'handle_ticker'),
            'trades' => array($this, 'handle_trade'),
            'orders' => array($this, 'handle_order'),
            'mytrades' => array($this, 'handle_my_trade'),
        );
        $event = null;
        $params = $this->safe_dict($message, 'params');
        if ($params !== null) {
            $channel = $this->safe_string($params, 'channel');
            if ($channel !== null) {
                $parsedChannel = explode('.', $channel);
                if ((mb_strpos($channel, 'orders') !== false) || mb_strpos($channel, 'trades') > 0) {
                    $event = $this->safe_string($parsedChannel, 1);
                    // {subaccounr_id}.trades
                    if ($event === 'trades') {
                        $event = 'mytrades';
                    }
                } else {
                    $event = $this->safe_string($parsedChannel, 0);
                }
            }
        }
        $method = $this->safe_value($methods, $event);
        if ($method !== null) {
            $method($client, $message);
            return;
        }
        if (is_array($message) && array_key_exists('id', $message)) {
            $id = $this->safe_string($message, 'id');
            $subscriptionsById = $this->index_by($client->subscriptions, 'id');
            $subscription = $this->safe_value($subscriptionsById, $id, array());
            if (is_array($subscription) && array_key_exists('method', $subscription)) {
                if ($subscription['method'] === 'public/login') {
                    $this->handle_auth($client, $message);
                } elseif ($subscription['method'] === 'unsubscribe') {
                    $this->handle_un_subscribe($client, $message);
                }
                // could handleSubscribe
            }
        }
    }

    public function handle_auth(Client $client, $message) {
        //
        // {
        //     id => 1,
        //     result => array( 130837 )
        // }
        //
        $messageHash = 'authenticated';
        $ids = $this->safe_list($message, 'result');
        if (strlen($ids) > 0) {
            // $client->resolve ($message, $messageHash);
            $future = $this->safe_value($client->futures, 'authenticated');
            $future->resolve (true);
        } else {
            $error = new AuthenticationError ($this->json($message));
            $client->reject ($error, $messageHash);
            // allows further authentication attempts
            if (is_array($client->subscriptions) && array_key_exists($messageHash, $client->subscriptions)) {
                unset($client->subscriptions['authenticated']);
            }
        }
    }
}
