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

class defx extends \ccxt\async\defx {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchBalance' => true,
                'watchTicker' => true,
                'watchTickers' => true,
                'watchBidsAsks' => true,
                'watchTrades' => true,
                'watchTradesForSymbols' => true,
                'watchMyTrades' => false,
                'watchOrders' => true,
                'watchOrderBook' => true,
                'watchOrderBookForSymbols' => true,
                'watchOHLCV' => true,
                'watchOHLCVForSymbols' => true,
            ),
            'urls' => array(
                'test' => array(
                    'ws' => array(
                        'public' => 'wss://stream.testnet.defx.com/pricefeed',
                        'private' => 'wss://ws.testnet.defx.com/user',
                    ),
                ),
                'api' => array(
                    'ws' => array(
                        'public' => 'wss://marketfeed.api.defx.com/pricefeed',
                        'private' => 'wss://userfeed.api.defx.com/user',
                    ),
                ),
            ),
            'options' => array(
                'listenKeyRefreshRate' => 3540000, // 1 hour (59 mins so we have 1min to renew the token)
                'ws' => array(
                    'timeframes' => array(
                        '1m' => '1m',
                        '3m' => '3m',
                        '5m' => '5m',
                        '15m' => '15m',
                        '30m' => '30m',
                        '1h' => '1h',
                        '2h' => '2h',
                        '4h' => '4h',
                        '12h' => '12h',
                        '1d' => '1d',
                        '1w' => '1w',
                        '1M' => '1M',
                    ),
                ),
            ),
            'streaming' => array(
            ),
            'exceptions' => array(
            ),
        ));
    }

    public function watch_public($topics, $messageHashes, $params = array ()) {
        return Async\async(function () use ($topics, $messageHashes, $params) {
            Async\await($this->load_markets());
            $url = $this->urls['api']['ws']['public'];
            $request = array(
                'method' => 'SUBSCRIBE',
                'topics' => $topics,
            );
            $message = $this->extend($request, $params);
            return Async\await($this->watch_multiple($url, $messageHashes, $message, $messageHashes));
        }) ();
    }

    public function un_watch_public($topics, $messageHashes, $params = array ()) {
        return Async\async(function () use ($topics, $messageHashes, $params) {
            Async\await($this->load_markets());
            $url = $this->urls['api']['ws']['public'];
            $request = array(
                'method' => 'UNSUBSCRIBE',
                'topics' => $topics,
            );
            $message = $this->extend($request, $params);
            return Async\await($this->watch_multiple($url, $messageHashes, $message, $messageHashes));
        }) ();
    }

    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://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @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
             */
            $result = Async\await($this->watch_ohlcv_for_symbols(array( array( $symbol, $timeframe ) ), $since, $limit, $params));
            return $result[$symbol][$timeframe];
        }) ();
    }

    public function un_watch_ohlcv(string $symbol, $timeframe = '1m', $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $params) {
            /**
             * watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string} $symbol unified $symbol of the market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            return Async\await($this->un_watch_ohlcv_for_symbols(array( array( $symbol, $timeframe ) ), $params));
        }) ();
    }

    public function watch_ohlcv_for_symbols(array $symbolsAndTimeframes, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbolsAndTimeframes, $since, $limit, $params) {
            /**
             * watches historical candlestick data containing the open, high, low, close price, and the volume of a $market
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[][]} $symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
             * @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
             */
            $symbolsLength = count($symbolsAndTimeframes);
            if ($symbolsLength === 0 || gettype($symbolsAndTimeframes[0]) !== 'array' || array_keys($symbolsAndTimeframes[0]) !== array_keys(array_keys($symbolsAndTimeframes[0]))) {
                throw new ArgumentsRequired($this->id . " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like  [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]");
            }
            Async\await($this->load_markets());
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbolsAndTimeframes); $i++) {
                $symbolAndTimeframe = $symbolsAndTimeframes[$i];
                $marketId = $this->safe_string($symbolAndTimeframe, 0);
                $market = $this->market($marketId);
                $tf = $this->safe_string($symbolAndTimeframe, 1);
                $interval = $this->safe_string($this->timeframes, $tf, $tf);
                $topics[] = 'symbol:' . $market['id'] . ':ohlc:' . $interval;
                $messageHashes[] = 'candles:' . $interval . ':' . $market['symbol'];
            }
            list($symbol, $timeframe, $candles) = Async\await($this->watch_public($topics, $messageHashes, $params));
            if ($this->newUpdates) {
                $limit = $candles->getLimit ($symbol, $limit);
            }
            $filtered = $this->filter_by_since_limit($candles, $since, $limit, 0, true);
            return $this->create_ohlcv_object($symbol, $timeframe, $filtered);
        }) ();
    }

    public function un_watch_ohlcv_for_symbols(array $symbolsAndTimeframes, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbolsAndTimeframes, $params) {
            /**
             * unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[][]} $symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            $symbolsLength = count($symbolsAndTimeframes);
            if ($symbolsLength === 0 || gettype($symbolsAndTimeframes[0]) !== 'array' || array_keys($symbolsAndTimeframes[0]) !== array_keys(array_keys($symbolsAndTimeframes[0]))) {
                throw new ArgumentsRequired($this->id . " unWatchOHLCVForSymbols() requires a an array of symbols and timeframes, like  [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]");
            }
            Async\await($this->load_markets());
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbolsAndTimeframes); $i++) {
                $symbolAndTimeframe = $symbolsAndTimeframes[$i];
                $marketId = $this->safe_string($symbolAndTimeframe, 0);
                $market = $this->market($marketId);
                $tf = $this->safe_string($symbolAndTimeframe, 1);
                $interval = $this->safe_string($this->timeframes, $tf, $tf);
                $topics[] = 'symbol:' . $market['id'] . ':ohlc:' . $interval;
                $messageHashes[] = 'candles:' . $interval . ':' . $market['symbol'];
            }
            return Async\await($this->un_watch_public($topics, $messageHashes, $params));
        }) ();
    }

    public function handle_ohlcv(Client $client, $message) {
        //
        // {
        //     "topic" => "symbol:BTC_USDC:ohlc:3m",
        //     "event" => "ohlc",
        //     "timestamp" => 1730794277104,
        //     "data" => {
        //         "symbol" => "BTC_USDC",
        //         "window" => "3m",
        //         "open" => "57486.90000000",
        //         "high" => "57486.90000000",
        //         "low" => "57486.90000000",
        //         "close" => "57486.90000000",
        //         "volume" => "0.000",
        //         "quoteAssetVolume" => "0.00000000",
        //         "takerBuyAssetVolume" => "0.000",
        //         "takerBuyQuoteAssetVolume" => "0.00000000",
        //         "numberOfTrades" => 0,
        //         "start" => 1730794140000,
        //         "end" => 1730794320000,
        //         "isClosed" => false
        //     }
        // }
        //
        $data = $this->safe_dict($message, 'data', array());
        $marketId = $this->safe_string($data, 'symbol');
        $market = $this->market($marketId);
        $symbol = $market['symbol'];
        $timeframe = $this->safe_string($data, 'window');
        if (!(is_array($this->ohlcvs) && array_key_exists($symbol, $this->ohlcvs))) {
            $this->ohlcvs[$symbol] = array();
        }
        if (!(is_array($this->ohlcvs[$symbol]) && array_key_exists($timeframe, $this->ohlcvs[$symbol]))) {
            $limit = $this->safe_integer($this->options, 'OHLCVLimit', 1000);
            $stored = new ArrayCacheByTimestamp ($limit);
            $this->ohlcvs[$symbol][$timeframe] = $stored;
        }
        $ohlcv = $this->ohlcvs[$symbol][$timeframe];
        $parsed = $this->parse_ohlcv($data);
        $ohlcv->append ($parsed);
        $messageHash = 'candles:' . $timeframe . ':' . $symbol;
        $client->resolve (array( $symbol, $timeframe, $ohlcv ), $messageHash);
    }

    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://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @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);
            $symbol = $market['symbol'];
            $topic = 'symbol:' . $market['id'] . ':24hrTicker';
            $messageHash = 'ticker:' . $symbol;
            return Async\await($this->watch_public(array( $topic ), array( $messageHash ), $params));
        }) ();
    }

    public function un_watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @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 subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            return Async\await($this->un_watch_tickers(array( $symbol ), $params));
        }) ();
    }

    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 all markets of a specific list
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} [$symbols] 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());
            $symbols = $this->market_symbols($symbols, null, false);
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':24hrTicker';
                $messageHashes[] = 'ticker:' . $symbol;
            }
            Async\await($this->watch_public($topics, $messageHashes, $params));
            return $this->filter_by_array($this->tickers, 'symbol', $symbols);
        }) ();
    }

    public function un_watch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} [$symbols] 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());
            $symbols = $this->market_symbols($symbols, null, false);
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':24hrTicker';
                $messageHashes[] = 'ticker:' . $symbol;
            }
            return Async\await($this->un_watch_public($topics, $messageHashes, $params));
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        //
        // {
        //     "topic" => "symbol:BTC_USDC:24hrTicker",
        //     "event" => "24hrTicker",
        //     "timestamp" => 1730862543095,
        //     "data" => {
        //         "symbol" => "BTC_USDC",
        //         "priceChange" => "17114.70000000",
        //         "priceChangePercent" => "29.77",
        //         "weightedAvgPrice" => "6853147668",
        //         "lastPrice" => "74378.90000000",
        //         "lastQty" => "0.107",
        //         "bestBidPrice" => "61987.60000000",
        //         "bestBidQty" => "0.005",
        //         "bestAskPrice" => "84221.60000000",
        //         "bestAskQty" => "0.015",
        //         "openPrice" => "57486.90000000",
        //         "highPrice" => "88942.60000000",
        //         "lowPrice" => "47364.20000000",
        //         "volume" => "28.980",
        //         "quoteVolume" => "1986042.19424035",
        //         "openTime" => 1730776080000,
        //         "closeTime" => 1730862540000,
        //         "openInterestBase" => "67.130",
        //         "openInterestQuote" => "5008005.40800000"
        //     }
        // }
        //
        $this->handle_bid_ask($client, $message);
        $data = $this->safe_dict($message, 'data', array());
        $parsedTicker = $this->parse_ticker($data);
        $symbol = $parsedTicker['symbol'];
        $timestamp = $this->safe_integer($message, 'timestamp');
        $parsedTicker['timestamp'] = $timestamp;
        $parsedTicker['datetime'] = $this->iso8601($timestamp);
        $this->tickers[$symbol] = $parsedTicker;
        $messageHash = 'ticker:' . $symbol;
        $client->resolve ($parsedTicker, $messageHash);
    }

    public function watch_bids_asks(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * watches best bid & ask for $symbols
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} $symbols 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());
            $symbols = $this->market_symbols($symbols, null, false);
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':24hrTicker';
                $messageHashes[] = 'bidask:' . $symbol;
            }
            Async\await($this->watch_public($topics, $messageHashes, $params));
            return $this->filter_by_array($this->bidsasks, 'symbol', $symbols);
        }) ();
    }

    public function handle_bid_ask(Client $client, $message) {
        $data = $this->safe_dict($message, 'data', array());
        $parsedTicker = $this->parse_ws_bid_ask($data);
        $symbol = $parsedTicker['symbol'];
        $timestamp = $this->safe_integer($message, 'timestamp');
        $parsedTicker['timestamp'] = $timestamp;
        $parsedTicker['datetime'] = $this->iso8601($timestamp);
        $this->bidsasks[$symbol] = $parsedTicker;
        $messageHash = 'bidask:' . $symbol;
        $client->resolve ($parsedTicker, $messageHash);
    }

    public function parse_ws_bid_ask($ticker, $market = null) {
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $this->safe_string($market, 'symbol');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => null,
            'datetime' => null,
            'ask' => $this->safe_string($ticker, 'bestAskPrice'),
            'askVolume' => $this->safe_string($ticker, 'bestAskQty'),
            'bid' => $this->safe_string($ticker, 'bestBidPrice'),
            'bidVolume' => $this->safe_string($ticker, 'bestBidQty'),
            'info' => $ticker,
        ), $market);
    }

    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://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string} $symbol unified $symbol of the market to fetch the ticker for
             * @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 ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            return Async\await($this->watch_trades_for_symbols(array( $symbol ), $since, $limit, $params));
        }) ();
    }

    public function un_watch_trades(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * unWatches from the stream channel
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string} $symbol unified $symbol of the market to fetch trades for
             * @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->un_watch_trades_for_symbols(array( $symbol ), $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) {
            /**
             * watches information on multiple $trades made in a market
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} $symbols unified $symbol of the market to fetch $trades for
             * @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 ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $symbolsLength = count($symbols);
            if ($symbolsLength === 0) {
                throw new ArgumentsRequired($this->id . ' watchTradesForSymbols() requires a non-empty array of symbols');
            }
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':trades';
                $messageHashes[] = 'trade:' . $symbol;
            }
            $trades = Async\await($this->watch_public($topics, $messageHashes, $params));
            if ($this->newUpdates) {
                $first = $this->safe_value($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 un_watch_trades_for_symbols(array $symbols, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * unWatches from the stream channel
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} $symbols unified $symbol of the market to fetch trades for
             * @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);
            $symbolsLength = count($symbols);
            if ($symbolsLength === 0) {
                throw new ArgumentsRequired($this->id . ' unWatchTradesForSymbols() requires a non-empty array of symbols');
            }
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':trades';
                $messageHashes[] = 'trade:' . $symbol;
            }
            return Async\await($this->un_watch_public($topics, $messageHashes, $params));
        }) ();
    }

    public function handle_trades(Client $client, $message) {
        //
        // {
        //     "topic" => "symbol:SOL_USDC:$trades",
        //     "event" => "trades",
        //     "timestamp" => 1730967426331,
        //     "data" => {
        //         "buyerMaker" => true,
        //         "price" => "188.38700000",
        //         "qty" => "1.00",
        //         "symbol" => "SOL_USDC",
        //         "timestamp" => 1730967426328
        //     }
        // }
        //
        $data = $this->safe_dict($message, 'data', array());
        $parsedTrade = $this->parse_trade($data);
        $symbol = $parsedTrade['symbol'];
        if (!(is_array($this->trades) && array_key_exists($symbol, $this->trades))) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $stored = new ArrayCache ($limit);
            $this->trades[$symbol] = $stored;
        }
        $trades = $this->trades[$symbol];
        $trades->append ($parsedTrade);
        $messageHash = 'trade:' . $symbol;
        $client->resolve ($trades, $messageHash);
    }

    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://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @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 un_watch_order_book(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * unWatches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string} $symbol unified array of symbols
             * @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->un_watch_order_book_for_symbols(array( $symbol ), $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://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} $symbols unified array of $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());
            $symbolsLength = count($symbols);
            if ($symbolsLength === 0) {
                throw new ArgumentsRequired($this->id . ' watchOrderBookForSymbols() requires a non-empty array of symbols');
            }
            $symbols = $this->market_symbols($symbols);
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':depth:20:0.001';
                $messageHashes[] = 'orderbook:' . $symbol;
            }
            $orderbook = Async\await($this->watch_public($topics, $messageHashes, $params));
            return $orderbook->limit ();
        }) ();
    }

    public function un_watch_order_book_for_symbols(array $symbols, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * unWatches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/collection/667939a1b5d8069c13d614e9
             *
             * @param {string[]} $symbols unified array of $symbols
             * @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());
            $symbolsLength = count($symbols);
            if ($symbolsLength === 0) {
                throw new ArgumentsRequired($this->id . ' unWatchOrderBookForSymbols() requires a non-empty array of symbols');
            }
            $symbols = $this->market_symbols($symbols);
            $topics = array();
            $messageHashes = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $marketId = $this->market_id($symbol);
                $topics[] = 'symbol:' . $marketId . ':depth:20:0.001';
                $messageHashes[] = 'orderbook:' . $symbol;
            }
            return Async\await($this->un_watch_public($topics, $messageHashes, $params));
        }) ();
    }

    public function handle_order_book(Client $client, $message) {
        //
        // {
        //     "topic" => "symbol:SOL_USDC:depth:20:0.01",
        //     "event" => "depth",
        //     "timestamp" => 1731030695319,
        //     "data" => {
        //         "symbol" => "SOL_USDC",
        //         "timestamp" => 1731030695319,
        //         "lastTradeTimestamp" => 1731030275258,
        //         "level" => "20",
        //         "slab" => "0.01",
        //         "bids" => array(
        //             {
        //                 "price" => "198.27000000",
        //                 "qty" => "1.52"
        //             }
        //         ),
        //         "asks" => array(
        //             {
        //                 "price" => "198.44000000",
        //                 "qty" => "6.61"
        //             }
        //         )
        //     }
        // }
        //
        $data = $this->safe_dict($message, 'data', array());
        $marketId = $this->safe_string($data, 'symbol');
        $market = $this->market($marketId);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($data, 'timestamp');
        $snapshot = $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks', 'price', 'qty');
        if (!(is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks))) {
            $ob = $this->order_book($snapshot);
            $this->orderbooks[$symbol] = $ob;
        }
        $orderbook = $this->orderbooks[$symbol];
        $orderbook->reset ($snapshot);
        $messageHash = 'orderbook:' . $symbol;
        $client->resolve ($orderbook, $messageHash);
    }

    public function keep_alive_listen_key($params = array ()) {
        return Async\async(function () use ($params) {
            $listenKey = $this->safe_string($this->options, 'listenKey');
            if ($listenKey === null) {
                // A network $error happened => we can't renew a listen key that does not exist.
                return;
            }
            try {
                Async\await($this->v1PrivatePutApiUsersSocketListenKeysListenKey (array( 'listenKey' => $listenKey ))); // extend the expiry
            } catch (Exception $error) {
                $url = $this->urls['api']['ws']['private'] . '?$listenKey=' . $listenKey;
                $client = $this->client($url);
                $messageHashes = is_array($client->futures) ? array_keys($client->futures) : array();
                for ($j = 0; $j < count($messageHashes); $j++) {
                    $messageHash = $messageHashes[$j];
                    $client->reject ($error, $messageHash);
                }
                $this->options['listenKey'] = null;
                $this->options['lastAuthenticatedTime'] = 0;
                return;
            }
            // whether or not to schedule another $listenKey keepAlive request
            $listenKeyRefreshRate = $this->safe_integer($this->options, 'listenKeyRefreshRate', 3540000);
            $this->delay($listenKeyRefreshRate, array($this, 'keep_alive_listen_key'), $params);
        }) ();
    }

    public function authenticate($params = array ()) {
        return Async\async(function () use ($params) {
            $time = $this->milliseconds();
            $lastAuthenticatedTime = $this->safe_integer($this->options, 'lastAuthenticatedTime', 0);
            $listenKeyRefreshRate = $this->safe_integer($this->options, 'listenKeyRefreshRate', 3540000); // 1 hour
            if ($time - $lastAuthenticatedTime > $listenKeyRefreshRate) {
                $response = Async\await($this->v1PrivatePostApiUsersSocketListenKeys ());
                $this->options['listenKey'] = $this->safe_string($response, 'listenKey');
                $this->options['lastAuthenticatedTime'] = $time;
                $this->delay($listenKeyRefreshRate, array($this, 'keep_alive_listen_key'), $params);
            }
        }) ();
    }

    public function watch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/ws-raw-request/667939b2f00f79161bb47809
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            Async\await($this->authenticate());
            $baseUrl = $this->urls['api']['ws']['private'];
            $messageHash = 'WALLET_BALANCE_UPDATE';
            $url = $baseUrl . '?listenKey=' . $this->options['listenKey'];
            return Async\await($this->watch($url, $messageHash, null, $messageHash));
        }) ();
    }

    public function handle_balance(Client $client, $message) {
        //
        // {
        //     "event" => "WALLET_BALANCE_UPDATE",
        //     "timestamp" => 1711015961397,
        //     "data" => {
        //         "asset" => "USDC", "balance" => "27.64712963"
        //     }
        // }
        //
        $messageHash = $this->safe_string($message, 'event');
        $data = $this->safe_dict($message, 'data', array());
        $timestamp = $this->safe_integer($message, 'timestamp');
        if ($this->balance === null) {
            $this->balance = array();
        }
        $this->balance['info'] = $data;
        $this->balance['timestamp'] = $timestamp;
        $this->balance['datetime'] = $this->iso8601($timestamp);
        $currencyId = $this->safe_string($data, 'asset');
        $code = $this->safe_currency_code($currencyId);
        $account = (is_array($this->balance) && array_key_exists($code, $this->balance)) ? $this->balance[$code] : $this->account();
        $account['free'] = $this->safe_string($data, 'balance');
        $this->balance[$code] = $account;
        $this->balance = $this->safe_balance($this->balance);
        $client->resolve ($this->balance, $messageHash);
    }

    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) {
            /**
             * watches information on multiple $orders made by the user
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/ws-raw-request/667939b2f00f79161bb47809
             *
             * @param {string} [$symbol] unified $market $symbol of the $market the $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
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            Async\await($this->authenticate());
            $baseUrl = $this->urls['api']['ws']['private'];
            $messageHash = 'orders';
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $messageHash .= ':' . $market['symbol'];
            }
            $url = $baseUrl . '?listenKey=' . $this->options['listenKey'];
            $orders = Async\await($this->watch($url, $messageHash, null, $messageHash));
            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) {
        //
        // {
        //     "event" => "ORDER_UPDATE",
        //     "timestamp" => 1731417961446,
        //     "data" => {
        //         "orderId" => "766738557656630928",
        //         "symbol" => "SOL_USDC",
        //         "side" => "SELL",
        //         "type" => "MARKET",
        //         "status" => "FILLED",
        //         "clientOrderId" => "0193208d-717b-7811-a80e-c036e220ad9b",
        //         "reduceOnly" => false,
        //         "postOnly" => false,
        //         "timeInForce" => "GTC",
        //         "isTriggered" => false,
        //         "createdAt" => "2024-11-12T13:26:00.829Z",
        //         "updatedAt" => "2024-11-12T13:26:01.436Z",
        //         "avgPrice" => "209.60000000",
        //         "cumulativeQuote" => "104.80000000",
        //         "totalFee" => "0.05764000",
        //         "executedQty" => "0.50",
        //         "origQty" => "0.50",
        //         "role" => "TAKER",
        //         "pnl" => "0.00000000",
        //         "lastFillPnL" => "0.00000000",
        //         "lastFillPrice" => "209.60000000",
        //         "lastFillQty" => "0.50",
        //         "linkedOrderParentType" => null,
        //         "workingType" => null
        //     }
        // }
        //
        $channel = 'orders';
        $data = $this->safe_dict($message, 'data', array());
        if ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $this->orders = new ArrayCacheBySymbolById ($limit);
        }
        $orders = $this->orders;
        $parsedOrder = $this->parse_order($data);
        $orders->append ($parsedOrder);
        $messageHash = $channel . ':' . $parsedOrder['symbol'];
        $client->resolve ($orders, $channel);
        $client->resolve ($orders, $messageHash);
    }

    public function watch_positions(?array $symbols = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $since, $limit, $params) {
            /**
             * watch all open positions
             *
             * @see https://www.postman.com/defxcode/defx-public-apis/ws-raw-request/667939b2f00f79161bb47809
             *
             * @param {string[]|null} $symbols list of unified market $symbols
             * @param {number} [$since] $since timestamp
             * @param {number} [$limit] $limit
             * @param {array} $params extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of {@link https://docs.ccxt.com/en/latest/manual.html#position-structure position structure}
             */
            Async\await($this->load_markets());
            Async\await($this->authenticate());
            $symbols = $this->market_symbols($symbols);
            $baseUrl = $this->urls['api']['ws']['private'];
            $channel = 'positions';
            $url = $baseUrl . '?listenKey=' . $this->options['listenKey'];
            $newPosition = null;
            if ($symbols !== null) {
                $messageHashes = array();
                for ($i = 0; $i < count($symbols); $i++) {
                    $symbol = $symbols[$i];
                    $messageHashes[] = $channel . ':' . $symbol;
                }
                $newPosition = Async\await($this->watch_multiple($url, $messageHashes, null, $messageHashes));
            } else {
                $newPosition = Async\await($this->watch($url, $channel, null, $channel));
            }
            if ($this->newUpdates) {
                return $newPosition;
            }
            return $this->filter_by_symbols_since_limit($this->positions, $symbols, $since, $limit, true);
        }) ();
    }

    public function handle_positions($client, $message) {
        //
        // {
        //     "event" => "POSITION_UPDATE",
        //     "timestamp" => 1731417961456,
        //     "data" => {
        //         "positionId" => "0193208d-735d-7fe9-90bd-8bc6d6bc1eda",
        //         "createdAt" => 1289847904328,
        //         "symbol" => "SOL_USDC",
        //         "positionSide" => "SHORT",
        //         "entryPrice" => "209.60000000",
        //         "quantity" => "0.50",
        //         "status" => "ACTIVE",
        //         "marginAsset" => "USDC",
        //         "marginAmount" => "15.17475649",
        //         "realizedPnL" => "0.00000000"
        //     }
        // }
        //
        $channel = 'positions';
        $data = $this->safe_dict($message, 'data', array());
        if ($this->positions === null) {
            $this->positions = new ArrayCacheBySymbolById ();
        }
        $cache = $this->positions;
        $parsedPosition = $this->parse_position($data);
        $timestamp = $this->safe_integer($message, 'timestamp');
        $parsedPosition['timestamp'] = $timestamp;
        $parsedPosition['datetime'] = $this->iso8601($timestamp);
        $cache->append ($parsedPosition);
        $messageHash = $channel . ':' . $parsedPosition['symbol'];
        $client->resolve (array( $parsedPosition ), $channel);
        $client->resolve (array( $parsedPosition ), $messageHash);
    }

    public function handle_message(Client $client, $message) {
        $error = $this->safe_string($message, 'code');
        if ($error !== null) {
            $errorMsg = $this->safe_string($message, 'msg');
            throw new ExchangeError($this->id . ' ' . $errorMsg);
        }
        $event = $this->safe_string($message, 'event');
        if ($event !== null) {
            $methods = array(
                'ohlc' => array($this, 'handle_ohlcv'),
                '24hrTicker' => array($this, 'handle_ticker'),
                'trades' => array($this, 'handle_trades'),
                'depth' => array($this, 'handle_order_book'),
                'WALLET_BALANCE_UPDATE' => array($this, 'handle_balance'),
                'ORDER_UPDATE' => array($this, 'handle_order'),
                'POSITION_UPDATE' => array($this, 'handle_positions'),
            );
            $exacMethod = $this->safe_value($methods, $event);
            if ($exacMethod !== null) {
                $exacMethod($client, $message);
            }
        }
    }
}
