<?php

namespace ccxt;

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

use Exception; // a common import
use ccxt\abstract\coinmate as Exchange;

class coinmate extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'coinmate',
            'name' => 'CoinMate',
            'countries' => array( 'GB', 'CZ', 'EU' ), // UK, Czech Republic
            'rateLimit' => 600,
            'has' => array(
                'CORS' => true,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelOrder' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => false,
                'createOrderWithTakeProfitAndStopLossWs' => false,
                'createPostOnlyOrder' => false,
                'createReduceOnlyOrder' => false,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchDepositsWithdrawals' => true,
                'fetchFundingHistory' => false,
                'fetchFundingInterval' => false,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchLeverage' => false,
                'fetchLeverages' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarginModes' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMarkPrices' => false,
                'fetchMyLiquidations' => false,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => false,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => false,
                'fetchTransactions' => 'emulated',
                'fetchVolatilityHistory' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'repayMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg',
                'api' => array(
                    'rest' => 'https://coinmate.io/api',
                ),
                'www' => 'https://coinmate.io',
                'fees' => 'https://coinmate.io/fees',
                'doc' => array(
                    'https://coinmate.docs.apiary.io',
                    'https://coinmate.io/developers',
                ),
                'referral' => 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0',
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'uid' => true,
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'orderBook',
                        'ticker',
                        'tickerAll',
                        'products',
                        'transactions',
                        'tradingPairs',
                    ),
                ),
                'private' => array(
                    'post' => array(
                        'balances',
                        'bitcoinCashWithdrawal',
                        'bitcoinCashDepositAddresses',
                        'bitcoinDepositAddresses',
                        'bitcoinWithdrawal',
                        'bitcoinWithdrawalFees',
                        'buyInstant',
                        'buyLimit',
                        'cancelOrder',
                        'cancelOrderWithInfo',
                        'createVoucher',
                        'dashDepositAddresses',
                        'dashWithdrawal',
                        'ethereumWithdrawal',
                        'ethereumDepositAddresses',
                        'litecoinWithdrawal',
                        'litecoinDepositAddresses',
                        'openOrders',
                        'order',
                        'orderHistory',
                        'orderById',
                        'pusherAuth',
                        'redeemVoucher',
                        'replaceByBuyLimit',
                        'replaceByBuyInstant',
                        'replaceBySellLimit',
                        'replaceBySellInstant',
                        'rippleDepositAddresses',
                        'rippleWithdrawal',
                        'sellInstant',
                        'sellLimit',
                        'transactionHistory',
                        'traderFees',
                        'tradeHistory',
                        'transfer',
                        'transferHistory',
                        'unconfirmedBitcoinDeposits',
                        'unconfirmedBitcoinCashDeposits',
                        'unconfirmedDashDeposits',
                        'unconfirmedEthereumDeposits',
                        'unconfirmedLitecoinDeposits',
                        'unconfirmedRippleDeposits',
                        'cancelAllOpenOrders',
                        'withdrawVirtualCurrency',
                        'virtualCurrencyDepositAddresses',
                        'unconfirmedVirtualCurrencyDeposits',
                        'adaWithdrawal',
                        'adaDepositAddresses',
                        'unconfirmedAdaDeposits',
                        'solWithdrawal',
                        'solDepositAddresses',
                        'unconfirmedSolDeposits',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.006'),
                    'maker' => $this->parse_number('0.004'),
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.006') ),
                            array( $this->parse_number('10000'), $this->parse_number('0.003') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0023') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.0021') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0018') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('3000000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('15000000'), $this->parse_number('0.001') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.004') ),
                            array( $this->parse_number('10000'), $this->parse_number('0.002') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.0009') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0005') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0003') ),
                            array( $this->parse_number('3000000'), $this->parse_number('0.0002') ),
                            array( $this->parse_number('15000000'), $this->parse_number('-0.0004') ),
                        ),
                    ),
                ),
            ),
            'options' => array(
                'withdraw' => array(
                    'fillResponsefromRequest' => true,
                    'methods' => array(
                        'BTC' => 'privatePostBitcoinWithdrawal',
                        'LTC' => 'privatePostLitecoinWithdrawal',
                        'BCH' => 'privatePostBitcoinCashWithdrawal',
                        'ETH' => 'privatePostEthereumWithdrawal',
                        'XRP' => 'privatePostRippleWithdrawal',
                        'DASH' => 'privatePostDashWithdrawal',
                        'DAI' => 'privatePostDaiWithdrawal',
                        'ADA' => 'privatePostAdaWithdrawal',
                        'SOL' => 'privatePostSolWithdrawal',
                    ),
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true, // todo implement
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => false,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => true, // todo implement
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => true, // todo
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'untilDays' => 100000, // todo implement
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => null,
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'No order with given ID' => '\\ccxt\\OrderNotFound',
                ),
                'broad' => array(
                    'Not enough account balance available' => '\\ccxt\\InsufficientFunds',
                    'Incorrect order ID' => '\\ccxt\\InvalidOrder',
                    'Minimum Order Size ' => '\\ccxt\\InvalidOrder',
                    'max allowed precision' => '\\ccxt\\InvalidOrder', // array("error":true,"errorMessage":"USDT_EUR - max allowed precision is 4 decimal places","data":null)
                    'TOO MANY REQUESTS' => '\\ccxt\\RateLimitExceeded',
                    'Access denied.' => '\\ccxt\\AuthenticationError', // array("error":true,"errorMessage":"Access denied.","data":null)
                ),
            ),
            'precisionMode' => TICK_SIZE,
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves $data on all markets for coinmate
         *
         * @see https://coinmate.docs.apiary.io/#reference/trading-pairs/get-trading-pairs/get
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market $data
         */
        $response = $this->publicGetTradingPairs ($params);
        //
        //     {
        //         "error":false,
        //         "errorMessage":null,
        //         "data" => array(
        //             array(
        //                 "name":"BTC_EUR",
        //                 "firstCurrency":"BTC",
        //                 "secondCurrency":"EUR",
        //                 "priceDecimals":2,
        //                 "lotDecimals":8,
        //                 "minAmount":0.0002,
        //                 "tradesWebSocketChannelId":"trades-BTC_EUR",
        //                 "orderBookWebSocketChannelId":"order_book-BTC_EUR",
        //                 "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR"
        //             ),
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $market = $data[$i];
            $id = $this->safe_string($market, 'name');
            $baseId = $this->safe_string($market, 'firstCurrency');
            $quoteId = $this->safe_string($market, 'secondCurrency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $base . '/' . $quote;
            $result[] = array(
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'settle' => null,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'settleId' => null,
                'type' => 'spot',
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'active' => null,
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->parse_number($this->parse_precision($this->safe_string($market, 'lotDecimals'))),
                    'price' => $this->parse_number($this->parse_precision($this->safe_string($market, 'priceDecimals'))),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => $this->safe_number($market, 'minAmount'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $market,
            );
        }
        return $result;
    }

    public function parse_balance($response): array {
        $balances = $this->safe_value($response, 'data', array());
        $result = array( 'info' => $response );
        $currencyIds = is_array($balances) ? array_keys($balances) : array();
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $code = $this->safe_currency_code($currencyId);
            $balance = $this->safe_value($balances, $currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_string($balance, 'available');
            $account['used'] = $this->safe_string($balance, 'reserved');
            $account['total'] = $this->safe_string($balance, 'balance');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://coinmate.docs.apiary.io/#reference/balance/get-balances/post
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $response = $this->privatePostBalances ($params);
        return $this->parse_balance($response);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://coinmate.docs.apiary.io/#reference/order-book/get-order-book/get
         *
         * @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
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
            'groupByPriceLimit' => 'False',
        );
        $response = $this->publicGetOrderBook ($this->extend($request, $params));
        $orderbook = $response['data'];
        $timestamp = $this->safe_timestamp($orderbook, 'timestamp');
        return $this->parse_order_book($orderbook, $market['symbol'], $timestamp, 'bids', 'asks', 'price', 'amount');
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://coinmate.docs.apiary.io/#reference/ticker/get-ticker/get
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
        );
        $response = $this->publicGetTicker ($this->extend($request, $params));
        //
        //     {
        //         "error" => false,
        //         "errorMessage" => null,
        //         "data" => {
        //             "last" => 0.55105,
        //             "high" => 0.56439,
        //             "low" => 0.54358,
        //             "amount" => 37038.993381,
        //             "bid" => 0.54595,
        //             "ask" => 0.55324,
        //             "change" => 3.03659243,
        //             "open" => 0.53481,
        //             "timestamp" => 1708074779
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data');
        return $this->parse_ticker($data, $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://coinmate.docs.apiary.io/#reference/ticker/get-$ticker-all/get
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the $ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structures~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $response = $this->publicGetTickerAll ($params);
        //
        //     {
        //         "error" => false,
        //         "errorMessage" => null,
        //         "data" => {
        //             "LTC_BTC" => {
        //                 "last" => "0.001337",
        //                 "high" => "0.001348",
        //                 "low" => "0.001332",
        //                 "amount" => "34.75472959",
        //                 "bid" => "0.001348",
        //                 "ask" => "0.001356",
        //                 "change" => "-0.74239050",
        //                 "open" => "0.001347",
        //                 "timestamp" => "1708074485"
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $keys = is_array($data) ? array_keys($data) : array();
        $result = array();
        for ($i = 0; $i < count($keys); $i++) {
            $market = $this->market($keys[$i]);
            $ticker = $this->parse_ticker($this->safe_value($data, $keys[$i]), $market);
            $result[$market['symbol']] = $ticker;
        }
        return $this->filter_by_array_tickers($result, 'symbol', $symbols);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "last" => "0.001337",
        //         "high" => "0.001348",
        //         "low" => "0.001332",
        //         "amount" => "34.75472959",
        //         "bid" => "0.001348",
        //         "ask" => "0.001356",
        //         "change" => "-0.74239050",
        //         "open" => "0.001347",
        //         "timestamp" => "1708074485"
        //     }
        //
        $timestamp = $this->safe_timestamp($ticker, 'timestamp');
        $last = $this->safe_number($ticker, 'last');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_number($ticker, 'high'),
            'low' => $this->safe_number($ticker, 'low'),
            'bid' => $this->safe_number($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_number($ticker, 'ask'),
            'vwap' => null,
            'askVolume' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_number($ticker, 'amount'),
            'quoteVolume' => null,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch history of deposits and withdrawals
         *
         * @see https://coinmate.docs.apiary.io/#reference/transfers/get-transfer-history/post
         *
         * @param {string} [$code] unified $currency $code for the $currency of the deposit/withdrawals, default is null
         * @param {int} [$since] timestamp in ms of the earliest deposit/withdrawal, default is null
         * @param {int} [$limit] max number of deposit/withdrawals to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $this->load_markets();
        $request = array(
            'limit' => 1000,
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        if ($since !== null) {
            $request['timestampFrom'] = $since;
        }
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['currency'] = $currency['id'];
        }
        $response = $this->privatePostTransferHistory ($this->extend($request, $params));
        $items = $response['data'];
        return $this->parse_transactions($items, null, $since, $limit);
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'COMPLETED' => 'ok',
            'WAITING' => 'pending',
            'SENT' => 'pending',
            'CREATED' => 'pending',
            'OK' => 'ok',
            'NEW' => 'pending',
            'CANCELED' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // deposits
        //
        //     {
        //         "transactionId" => 1862815,
        //         "timestamp" => 1516803982388,
        //         "amountCurrency" => "LTC",
        //         "amount" => 1,
        //         "fee" => 0,
        //         "walletType" => "LTC",
        //         "transferType" => "DEPOSIT",
        //         "transferStatus" => "COMPLETED",
        //         "txid":
        //         "ccb9255dfa874e6c28f1a64179769164025329d65e5201849c2400abd6bce245",
        //         "destination" => "LQrtSKA6LnhcwRrEuiborQJnjFF56xqsFn",
        //         "destinationTag" => null
        //     }
        //
        // withdrawals
        //
        //     {
        //         "transactionId" => 2140966,
        //         "timestamp" => 1519314282976,
        //         "amountCurrency" => "EUR",
        //         "amount" => 8421.7228,
        //         "fee" => 16.8772,
        //         "walletType" => "BANK_WIRE",
        //         "transferType" => "WITHDRAWAL",
        //         "transferStatus" => "COMPLETED",
        //         "txid" => null,
        //         "destination" => null,
        //         "destinationTag" => null
        //     }
        //
        // withdraw
        //
        //     {
        //         "id" => 2132583,
        //     }
        //
        $timestamp = $this->safe_integer($transaction, 'timestamp');
        $currencyId = $this->safe_string($transaction, 'amountCurrency');
        $code = $this->safe_currency_code($currencyId, $currency);
        return array(
            'info' => $transaction,
            'id' => $this->safe_string_2($transaction, 'transactionId', 'id'),
            'txid' => $this->safe_string($transaction, 'txid'),
            'type' => $this->safe_string_lower($transaction, 'transferType'),
            'currency' => $code,
            'network' => $this->safe_string($transaction, 'walletType'),
            'amount' => $this->safe_number($transaction, 'amount'),
            'status' => $this->parse_transaction_status($this->safe_string($transaction, 'transferStatus')),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $this->safe_string($transaction, 'destination'),
            'addressFrom' => null,
            'addressTo' => null,
            'tag' => $this->safe_string($transaction, 'destinationTag'),
            'tagFrom' => null,
            'tagTo' => null,
            'updated' => null,
            'comment' => null,
            'internal' => null,
            'fee' => array(
                'cost' => $this->safe_number($transaction, 'fee'),
                'currency' => $code,
                'rate' => null,
            ),
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://coinmate.docs.apiary.io/#reference/bitcoin-withdrawal-and-deposit/withdraw-bitcoins/post
         * @see https://coinmate.docs.apiary.io/#reference/litecoin-withdrawal-and-deposit/withdraw-litecoins/post
         * @see https://coinmate.docs.apiary.io/#reference/ethereum-withdrawal-and-deposit/withdraw-ethereum/post
         * @see https://coinmate.docs.apiary.io/#reference/ripple-withdrawal-and-deposit/withdraw-ripple/post
         * @see https://coinmate.docs.apiary.io/#reference/cardano-withdrawal-and-deposit/withdraw-cardano/post
         * @see https://coinmate.docs.apiary.io/#reference/solana-withdrawal-and-deposit/withdraw-solana/post
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transaction-structure $transaction structure~
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->check_address($address);
        $this->load_markets();
        $currency = $this->currency($code);
        $withdrawOptions = $this->safe_value($this->options, 'withdraw', array());
        $methods = $this->safe_value($withdrawOptions, 'methods', array());
        $method = $this->safe_string($methods, $code);
        if ($method === null) {
            $allowedCurrencies = is_array($methods) ? array_keys($methods) : array();
            throw new ExchangeError($this->id . ' withdraw() only allows withdrawing the following currencies => ' . implode(', ', $allowedCurrencies));
        }
        $request = array(
            'amount' => $this->currency_to_precision($code, $amount),
            'address' => $address,
        );
        if ($tag !== null) {
            $request['destinationTag'] = $tag;
        }
        $response = $this->$method ($this->extend($request, $params));
        //
        //     {
        //         "error" => false,
        //         "errorMessage" => null,
        //         "data" => {
        //             "id" => "9e0a37fc-4ab4-4b9d-b9e7-c9c8f7c4c8e0"
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $transaction = $this->parse_transaction($data, $currency);
        $fillResponseFromRequest = $this->safe_bool($withdrawOptions, 'fillResponseFromRequest', true);
        if ($fillResponseFromRequest) {
            $transaction['amount'] = $amount;
            $transaction['currency'] = $code;
            $transaction['address'] = $address;
            $transaction['tag'] = $tag;
            $transaction['type'] = 'withdrawal';
            $transaction['status'] = 'pending';
        }
        return $transaction;
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://coinmate.docs.apiary.io/#reference/trade-history/get-trade-history/post
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        if ($limit === null) {
            $limit = 1000;
        }
        $request = array(
            'limit' => $limit,
        );
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['currencyPair'] = $market['id'];
        }
        if ($since !== null) {
            $request['timestampFrom'] = $since;
        }
        $response = $this->privatePostTradeHistory ($this->extend($request, $params));
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, null, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchMyTrades (private)
        //
        //     {
        //         "transactionId" => 2671819,
        //         "createdTimestamp" => 1529649127605,
        //         "currencyPair" => "LTC_BTC",
        //         "type" => "BUY",
        //         "orderType" => "LIMIT",
        //         "orderId" => 101810227,
        //         "amount" => 0.01,
        //         "price" => 0.01406,
        //         "fee" => 0,
        //         "feeType" => "MAKER"
        //     }
        //
        // fetchTrades (public)
        //
        //     {
        //         "timestamp":1561598833416,
        //         "transactionId":"4156303",
        //         "price":10950.41,
        //         "amount":0.004,
        //         "currencyPair":"BTC_EUR",
        //         "tradeType":"BUY"
        //     }
        //
        $marketId = $this->safe_string($trade, 'currencyPair');
        $market = $this->safe_market($marketId, $market, '_');
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'amount');
        $side = $this->safe_string_lower_2($trade, 'type', 'tradeType');
        $type = $this->safe_string_lower($trade, 'orderType');
        $orderId = $this->safe_string($trade, 'orderId');
        $id = $this->safe_string($trade, 'transactionId');
        $timestamp = $this->safe_integer_2($trade, 'timestamp', 'createdTimestamp');
        $fee = null;
        $feeCostString = $this->safe_string($trade, 'fee');
        if ($feeCostString !== null) {
            $fee = array(
                'cost' => $feeCostString,
                'currency' => $market['quote'],
            );
        }
        $takerOrMaker = $this->safe_string($trade, 'feeType');
        $takerOrMaker = ($takerOrMaker === 'MAKER') ? 'maker' : 'taker';
        return $this->safe_trade(array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'type' => $type,
            'side' => $side,
            'order' => $orderId,
            'takerOrMaker' => $takerOrMaker,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent trades for a particular $symbol
         *
         * @see https://coinmate.docs.apiary.io/#reference/transactions/transactions/get
         *
         * @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 {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
            'minutesIntoHistory' => 10,
        );
        $response = $this->publicGetTransactions ($this->extend($request, $params));
        //
        //     {
        //         "error":false,
        //         "errorMessage":null,
        //         "data":array(
        //             {
        //                 "timestamp":1561598833416,
        //                 "transactionId":"4156303",
        //                 "price":10950.41,
        //                 "amount":0.004,
        //                 "currencyPair":"BTC_EUR",
        //                 "tradeType":"BUY"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): array {
        /**
         * fetch the trading fees for a $market
         *
         * @see https://coinmate.docs.apiary.io/#reference/trader-fees/get-trading-fees/post
         *
         * @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=fee-structure fee structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
        );
        $response = $this->privatePostTraderFees ($this->extend($request, $params));
        //
        //     {
        //         "error" => false,
        //         "errorMessage" => null,
        //         "data" => array( $maker => '0.3', $taker => "0.35", timestamp => "1646253217815" )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $makerString = $this->safe_string($data, 'maker');
        $takerString = $this->safe_string($data, 'taker');
        $maker = $this->parse_number(Precise::string_div($makerString, '100'));
        $taker = $this->parse_number(Precise::string_div($takerString, '100'));
        return array(
            'info' => $data,
            'symbol' => $market['symbol'],
            'maker' => $maker,
            'taker' => $taker,
            'percentage' => true,
            'tierBased' => true,
        );
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open orders
         *
         * @see https://coinmate.docs.apiary.io/#reference/order/get-open-orders/post
         *
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of  open orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $response = $this->privatePostOpenOrders ($this->extend(array(), $params));
        $extension = array( 'status' => 'open' );
        return $this->parse_orders($response['data'], null, $since, $limit, $extension);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple orders made by the user
         *
         * @see https://coinmate.docs.apiary.io/#reference/order/order-history/post
         *
         * @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
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
        );
        // offset param that appears in other parts of the API doesn't appear to be supported here
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privatePostOrderHistory ($this->extend($request, $params));
        return $this->parse_orders($response['data'], $market, $since, $limit);
    }

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

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

    public function parse_order(array $order, ?array $market = null): array {
        //
        // limit sell
        //
        //     {
        //         "id" => 781246605,
        //         "timestamp" => 1584480015133,
        //         "trailingUpdatedTimestamp" => null,
        //         "type" => "SELL",
        //         "currencyPair" => "ETH_BTC",
        //         "price" => 0.0345,
        //         "amount" => 0.01,
        //         "stopPrice" => null,
        //         "originalStopPrice" => null,
        //         "marketPriceAtLastUpdate" => null,
        //         "marketPriceAtOrderCreation" => null,
        //         "orderTradeType" => "LIMIT",
        //         "hidden" => false,
        //         "trailing" => false,
        //         "clientOrderId" => null
        //     }
        //
        // limit buy
        //
        //     {
        //         "id" => 67527001,
        //         "timestamp" => 1517931722613,
        //         "trailingUpdatedTimestamp" => null,
        //         "type" => "BUY",
        //         "price" => 5897.24,
        //         "remainingAmount" => 0.002367,
        //         "originalAmount" => 0.1,
        //         "stopPrice" => null,
        //         "originalStopPrice" => null,
        //         "marketPriceAtLastUpdate" => null,
        //         "marketPriceAtOrderCreation" => null,
        //         "status" => "CANCELLED",
        //         "orderTradeType" => "LIMIT",
        //         "hidden" => false,
        //         "avgPrice" => null,
        //         "trailing" => false,
        //     }
        //
        // cancelOrder
        //
        //    {
        //        "success" => true,
        //        "remainingAmount" => 0.1
        //    }
        //
        $id = $this->safe_string($order, 'id');
        $timestamp = $this->safe_integer($order, 'timestamp');
        $side = $this->safe_string_lower($order, 'type');
        $priceString = $this->safe_string($order, 'price');
        $amountString = $this->safe_string($order, 'originalAmount');
        $remainingString = $this->safe_string_2($order, 'remainingAmount', 'amount');
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $type = $this->parse_order_type($this->safe_string($order, 'orderTradeType'));
        $averageString = $this->safe_string($order, 'avgPrice');
        $marketId = $this->safe_string($order, 'currencyPair');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        $clientOrderId = $this->safe_string($order, 'clientOrderId');
        return $this->safe_order(array(
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $priceString,
            'triggerPrice' => $this->safe_number($order, 'stopPrice'),
            'amount' => $amountString,
            'cost' => null,
            'average' => $averageString,
            'filled' => null,
            'remaining' => $remainingString,
            'status' => $status,
            'trades' => null,
            'info' => $order,
            'fee' => null,
        ), $market);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://coinmate.docs.apiary.io/#reference/order/buy-limit-order/post
         * @see https://coinmate.docs.apiary.io/#reference/order/sell-limit-order/post
         * @see https://coinmate.docs.apiary.io/#reference/order/buy-instant-order/post
         * @see https://coinmate.docs.apiary.io/#reference/order/sell-instant-order/post
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $method = 'privatePost' . $this->capitalize($side);
        $market = $this->market($symbol);
        $request = array(
            'currencyPair' => $market['id'],
        );
        if ($type === 'market') {
            if ($side === 'buy') {
                $request['total'] = $this->amount_to_precision($symbol, $amount); // $amount in fiat
            } else {
                $request['amount'] = $this->amount_to_precision($symbol, $amount); // $amount in fiat
            }
            $method .= 'Instant';
        } else {
            $request['amount'] = $this->amount_to_precision($symbol, $amount); // $amount in crypto
            $request['price'] = $this->price_to_precision($symbol, $price);
            $method .= $this->capitalize($type);
        }
        $response = $this->$method ($this->extend($request, $params));
        $id = $this->safe_string($response, 'data');
        return $this->safe_order(array(
            'info' => $response,
            'id' => $id,
        ), $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://coinmate.docs.apiary.io/#reference/order/get-order-by-orderid/post
         * @see https://coinmate.docs.apiary.io/#reference/order/get-order-by-clientorderid/post
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $request = array(
            'orderId' => $id,
        );
        $market = null;
        if ($symbol) {
            $market = $this->market($symbol);
        }
        $response = $this->privatePostOrderById ($this->extend($request, $params));
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://coinmate.docs.apiary.io/#reference/order/cancel-order/post
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by coinmate cancelOrder ()
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        //   array("error":false,"errorMessage":null,"data":array("success":true,"remainingAmount":0.01))
        $request = array( 'orderId' => $id );
        $response = $this->privatePostCancelOrderWithInfo ($this->extend($request, $params));
        //
        //    {
        //        "error" => false,
        //        "errorMessage" => null,
        //        "data" => {
        //          "success" => true,
        //          "remainingAmount" => 0.1
        //        }
        //    }
        //
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data);
    }

    public function nonce() {
        return $this->milliseconds();
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api']['rest'] . '/' . $path;
        if ($api === 'public') {
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $this->check_required_credentials();
            $nonce = (string) $this->nonce();
            $auth = $nonce . $this->uid . $this->apiKey;
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
            $body = $this->urlencode($this->extend(array(
                'clientId' => $this->uid,
                'nonce' => $nonce,
                'publicKey' => $this->apiKey,
                'signature' => strtoupper($signature),
            ), $params));
            $headers = array(
                'Content-Type' => 'application/x-www-form-urlencoded',
            );
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // fallback to default error handler
        }
        //
        //     array("error":true,"errorMessage":"Api internal error","data":null)
        //     array("error":true,"errorMessage":"Access denied.","data":null)
        //
        $errorMessage = $this->safe_string($response, 'errorMessage');
        if ($errorMessage !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorMessage, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorMessage, $feedback);
            throw new ExchangeError($feedback); // unknown message
        }
        return null;
    }
}
