<?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\kraken as Exchange;

class kraken extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'kraken',
            'name' => 'Kraken',
            'countries' => array( 'US' ),
            'version' => '0',
            // rate-limits => https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits-#1
            // for public => 1 req/s
            // for private => every second 0.33 weight added to your allowed capacity (some private endpoints need 1 weight, some need 2)
            'rateLimit' => 1000,
            'certified' => false,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'createDepositAddress' => true,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'createTrailingAmountOrder' => true,
                'createTrailingPercentOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLedger' => true,
                'fetchLedgerEntry' => true,
                'fetchLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderTrades' => 'emulated',
                'fetchPositions' => true,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => false,
                'fetchWithdrawals' => true,
                'setLeverage' => false,
                'setMarginMode' => false, // Kraken only supports cross margin
                'transfer' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => 1,
                '5m' => 5,
                '15m' => 15,
                '30m' => 30,
                '1h' => 60,
                '4h' => 240,
                '1d' => 1440,
                '1w' => 10080,
                '2w' => 21600,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg',
                'api' => array(
                    'public' => 'https://api.kraken.com',
                    'private' => 'https://api.kraken.com',
                    'zendesk' => 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', // use the public zendesk api to receive article bodies and bypass new anti-spam protections
                ),
                'www' => 'https://www.kraken.com',
                'doc' => 'https://docs.kraken.com/rest/',
                'fees' => 'https://www.kraken.com/en-us/features/fee-schedule',
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.0026'),
                    'maker' => $this->parse_number('0.0016'),
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0026') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0024') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0022') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.0020') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0018') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.0014') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0001') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0014') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.0010') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0006') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.0004') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0002') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0') ),
                        ),
                    ),
                ),
            ),
            'handleContentTypeApplicationZip' => true,
            'api' => array(
                'zendesk' => array(
                    'get' => array(
                        // we should really refrain from putting fixed fee numbers and stop hardcoding
                        // we will be using their web APIs to scrape all numbers from these articles
                        '360000292886', // -What-are-the-deposit-fees-
                        '201893608', // -What-are-the-withdrawal-fees-
                    ),
                ),
                'public' => array(
                    'get' => array(
                        // rate-limits explained in comment in the top of this file
                        'Assets' => 1,
                        'AssetPairs' => 1,
                        'Depth' => 1.2,
                        'OHLC' => 1.2, // 1.2 because 1 triggers too many requests immediately
                        'Spread' => 1,
                        'SystemStatus' => 1,
                        'Ticker' => 1,
                        'Time' => 1,
                        'Trades' => 1.2,
                    ),
                ),
                'private' => array(
                    'post' => array(
                        'AddOrder' => 0,
                        'AddOrderBatch' => 0,
                        'AddExport' => 3,
                        'AmendOrder' => 0,
                        'Balance' => 3,
                        'CancelAll' => 3,
                        'CancelAllOrdersAfter' => 3,
                        'CancelOrder' => 0,
                        'CancelOrderBatch' => 0,
                        'ClosedOrders' => 3,
                        'DepositAddresses' => 3,
                        'DepositMethods' => 3,
                        'DepositStatus' => 3,
                        'EditOrder' => 0,
                        'ExportStatus' => 3,
                        'GetWebSocketsToken' => 3,
                        'Ledgers' => 6,
                        'OpenOrders' => 3,
                        'OpenPositions' => 3,
                        'QueryLedgers' => 3,
                        'QueryOrders' => 3,
                        'QueryTrades' => 3,
                        'RetrieveExport' => 3,
                        'RemoveExport' => 3,
                        'BalanceEx' => 3,
                        'TradeBalance' => 3,
                        'TradesHistory' => 6,
                        'TradeVolume' => 3,
                        'Withdraw' => 3,
                        'WithdrawCancel' => 3,
                        'WithdrawInfo' => 3,
                        'WithdrawMethods' => 3,
                        'WithdrawAddresses' => 3,
                        'WithdrawStatus' => 3,
                        'WalletTransfer' => 3,
                        // sub accounts
                        'CreateSubaccount' => 3,
                        'AccountTransfer' => 3,
                        // earn
                        'Earn/Allocate' => 3,
                        'Earn/Deallocate' => 3,
                        'Earn/AllocateStatus' => 3,
                        'Earn/DeallocateStatus' => 3,
                        'Earn/Strategies' => 3,
                        'Earn/Allocations' => 3,
                    ),
                ),
            ),
            'commonCurrencies' => array(
                // about X & Z prefixes and .S & .M suffixes, see comment under fetchCurrencies
                'LUNA' => 'LUNC',
                'LUNA2' => 'LUNA',
                'REPV2' => 'REP',
                'REP' => 'REPV1',
                'UST' => 'USTC',
                'XBT' => 'BTC',
                'XDG' => 'DOGE',
                'FEE' => 'KFEE',
                'XETC' => 'ETC',
                'XETH' => 'ETH',
                'XLTC' => 'LTC',
                'XMLN' => 'MLN',
                'XREP' => 'REP',
                'XXBT' => 'BTC',
                'XXDG' => 'DOGE',
                'XXLM' => 'XLM',
                'XXMR' => 'XMR',
                'XXRP' => 'XRP',
                'XZEC' => 'ZEC',
                'ZAUD' => 'AUD',
                'ZCAD' => 'CAD',
                'ZEUR' => 'EUR',
                'ZGBP' => 'GBP',
                'ZJPY' => 'JPY',
                'ZUSD' => 'USD',
            ),
            'options' => array(
                'timeDifference' => 0, // the difference between system clock and Binance clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'marketsByAltname' => array(),
                'delistedMarketsById' => array(),
                // cannot withdraw/deposit these
                'inactiveCurrencies' => array( 'CAD', 'USD', 'JPY', 'GBP' ),
                'networks' => array(
                    'ETH' => 'ERC20',
                    'TRX' => 'TRC20',
                ),
                'depositMethods' => array(
                    '1INCH' => '1inch' . ' ' . '(1INCH)',
                    'AAVE' => 'Aave',
                    'ADA' => 'ADA',
                    'ALGO' => 'Algorand',
                    'ANKR' => 'ANKR' . ' ' . '(ANKR)',
                    'ANT' => 'Aragon' . ' ' . '(ANT)',
                    'ATOM' => 'Cosmos',
                    'AXS' => 'Axie Infinity Shards' . ' ' . '(AXS)',
                    'BADGER' => 'Bager DAO' . ' ' . '(BADGER)',
                    'BAL' => 'Balancer' . ' ' . '(BAL)',
                    'BAND' => 'Band Protocol' . ' ' . '(BAND)',
                    'BAT' => 'BAT',
                    'BCH' => 'Bitcoin Cash',
                    'BNC' => 'Bifrost' . ' ' . '(BNC)',
                    'BNT' => 'Bancor' . ' ' . '(BNT)',
                    'BTC' => 'Bitcoin',
                    'CHZ' => 'Chiliz' . ' ' . '(CHZ)',
                    'COMP' => 'Compound' . ' ' . '(COMP)',
                    'CQT' => '\tCovalent Query Token' . ' ' . '(CQT)',
                    'CRV' => 'Curve DAO Token' . ' ' . '(CRV)',
                    'CTSI' => 'Cartesi' . ' ' . '(CTSI)',
                    'DAI' => 'Dai',
                    'DASH' => 'Dash',
                    'DOGE' => 'Dogecoin',
                    'DOT' => 'Polkadot',
                    'DYDX' => 'dYdX' . ' ' . '(DYDX)',
                    'ENJ' => 'Enjin Coin' . ' ' . '(ENJ)',
                    'EOS' => 'EOS',
                    'ETC' => 'Ether Classic' . ' ' . '(Hex)',
                    'ETH' => 'Ether' . ' ' . '(Hex)',
                    'EWT' => 'Energy Web Token',
                    'FEE' => 'Kraken Fee Credit',
                    'FIL' => 'Filecoin',
                    'FLOW' => 'Flow',
                    'GHST' => 'Aavegotchi' . ' ' . '(GHST)',
                    'GNO' => 'GNO',
                    'GRT' => 'GRT',
                    'ICX' => 'Icon',
                    'INJ' => 'Injective Protocol' . ' ' . '(INJ)',
                    'KAR' => 'Karura' . ' ' . '(KAR)',
                    'KAVA' => 'Kava',
                    'KEEP' => 'Keep Token' . ' ' . '(KEEP)',
                    'KNC' => 'Kyber Network' . ' ' . '(KNC)',
                    'KSM' => 'Kusama',
                    'LINK' => 'Link',
                    'LPT' => 'Livepeer Token' . ' ' . '(LPT)',
                    'LRC' => 'Loopring' . ' ' . '(LRC)',
                    'LSK' => 'Lisk',
                    'LTC' => 'Litecoin',
                    'MANA' => 'MANA',
                    'MATIC' => 'Polygon' . ' ' . '(MATIC)',
                    'MINA' => 'Mina', // inspected from webui
                    'MIR' => 'Mirror Protocol' . ' ' . '(MIR)',
                    'MKR' => 'Maker' . ' ' . '(MKR)',
                    'MLN' => 'MLN',
                    'MOVR' => 'Moonriver' . ' ' . '(MOVR)',
                    'NANO' => 'NANO',
                    'OCEAN' => 'OCEAN',
                    'OGN' => 'Origin Protocol' . ' ' . '(OGN)',
                    'OMG' => 'OMG',
                    'OXT' => 'Orchid' . ' ' . '(OXT)',
                    'OXY' => 'Oxygen' . ' ' . '(OXY)',
                    'PAXG' => 'PAX' . ' ' . '(Gold)',
                    'PERP' => 'Perpetual Protocol' . ' ' . '(PERP)',
                    'PHA' => 'Phala' . ' ' . '(PHA)',
                    'QTUM' => 'QTUM',
                    'RARI' => 'Rarible' . ' ' . '(RARI)',
                    'RAY' => 'Raydium' . ' ' . '(RAY)',
                    'REN' => 'Ren Protocol' . ' ' . '(REN)',
                    'REP' => 'REPv2',
                    'REPV1' => 'REP',
                    'SAND' => 'The Sandbox' . ' ' . '(SAND)',
                    'SC' => 'Siacoin',
                    'SDN' => 'Shiden' . ' ' . '(SDN)',
                    'SOL' => 'Solana',  // their deposit method api doesn't work for SOL - was guessed
                    'SNX' => 'Synthetix  Network' . ' ' . '(SNX)',
                    'SRM' => 'Serum', // inspected from webui
                    'STORJ' => 'Storj' . ' ' . '(STORJ)',
                    'SUSHI' => 'Sushiswap' . ' ' . '(SUSHI)',
                    'TBTC' => 'tBTC',
                    'TRX' => 'Tron',
                    'UNI' => 'UNI',
                    'USDC' => 'USDC',
                    'USDT' => 'Tether USD' . ' ' . '(ERC20)',
                    'USDT-TRC20' => 'Tether USD' . ' ' . '(TRC20)',
                    'WAVES' => 'Waves',
                    'WBTC' => 'Wrapped Bitcoin' . ' ' . '(WBTC)',
                    'XLM' => 'Stellar XLM',
                    'XMR' => 'Monero',
                    'XRP' => 'Ripple XRP',
                    'XTZ' => 'XTZ',
                    'YFI' => 'YFI',
                    'ZEC' => 'Zcash' . ' ' . '(Transparent)',
                    'ZRX' => '0x' . ' ' . '(ZRX)',
                ),
                'withdrawMethods' => array(   // keeping it here because deposit and withdraw return different networks codes
                    'Lightning' => 'Lightning',
                    'Bitcoin' => 'BTC',
                    'Ripple' => 'XRP',
                    'Litecoin' => 'LTC',
                    'Dogecoin' => 'DOGE',
                    'Stellar' => 'XLM',
                    'Ethereum' => 'ERC20',
                    'Arbitrum One' => 'Arbitrum',
                    'Polygon' => 'MATIC',
                    'Arbitrum Nova' => 'Arbitrum',
                    'Optimism' => 'Optimism',
                    'zkSync Era' => 'zkSync',
                    'Ethereum Classic' => 'ETC',
                    'Zcash' => 'ZEC',
                    'Monero' => 'XMR',
                    'Tron' => 'TRC20',
                    'Solana' => 'SOL',
                    'EOS' => 'EOS',
                    'Bitcoin Cash' => 'BCH',
                    'Cardano' => 'ADA',
                    'Qtum' => 'QTUM',
                    'Tezos' => 'XTZ',
                    'Cosmos' => 'ATOM',
                    'Nano' => 'NANO',
                    'Siacoin' => 'SC',
                    'Lisk' => 'LSK',
                    'Waves' => 'WAVES',
                    'ICON' => 'ICX',
                    'Algorand' => 'ALGO',
                    'Polygon - USDC.e' => 'MATIC',
                    'Arbitrum One - USDC.e' => 'Arbitrum',
                    'Polkadot' => 'DOT',
                    'Kava' => 'KAVA',
                    'Filecoin' => 'FIL',
                    'Kusama' => 'KSM',
                    'Flow' => 'FLOW',
                    'Energy Web' => 'EW',
                    'Mina' => 'MINA',
                    'Centrifuge' => 'CFG',
                    'Karura' => 'KAR',
                    'Moonriver' => 'MOVR',
                    'Shiden' => 'SDN',
                    'Khala' => 'PHA',
                    'Bifrost Kusama' => 'BNC',
                    'Songbird' => 'SGB',
                    'Terra classic' => 'LUNC',
                    'KILT' => 'KILT',
                    'Basilisk' => 'BSX',
                    'Flare' => 'FLR',
                    'Avalanche C-Chain' => 'AVAX',
                    'Kintsugi' => 'KINT',
                    'Altair' => 'AIR',
                    'Moonbeam' => 'GLMR',
                    'Acala' => 'ACA',
                    'Astar' => 'ASTR',
                    'Akash' => 'AKT',
                    'Robonomics' => 'XRT',
                    'Fantom' => 'FTM',
                    'Elrond' => 'EGLD',
                    'THORchain' => 'RUNE',
                    'Secret' => 'SCRT',
                    'Near' => 'NEAR',
                    'Internet Computer Protocol' => 'ICP',
                    'Picasso' => 'PICA',
                    'Crust Shadow' => 'CSM',
                    'Integritee' => 'TEER',
                    'Parallel Finance' => 'PARA',
                    'HydraDX' => 'HDX',
                    'Interlay' => 'INTR',
                    'Fetch.ai' => 'FET',
                    'NYM' => 'NYM',
                    'Terra 2.0' => 'LUNA2',
                    'Juno' => 'JUNO',
                    'Nodle' => 'NODL',
                    'Stacks' => 'STX',
                    'Ethereum PoW' => 'ETHW',
                    'Aptos' => 'APT',
                    'Sui' => 'SUI',
                    'Genshiro' => 'GENS',
                    'Aventus' => 'AVT',
                    'Sei' => 'SEI',
                    'OriginTrail' => 'OTP',
                    'Celestia' => 'TIA',
                ),
                'marketHelperProps' => array( 'marketsByAltname', 'delistedMarketsById' ), // used by setMarketsFromExchange
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => false, // todo
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => true,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => true, // todo implement
                        'iceberg' => true, // todo implement
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'untilDays' => null,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 720,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    'EQuery:Invalid asset pair' => '\\ccxt\\BadSymbol', // array("error":["EQuery:Invalid asset pair"])
                    'EAPI:Invalid key' => '\\ccxt\\AuthenticationError',
                    'EFunding:Unknown withdraw key' => '\\ccxt\\InvalidAddress', // array("error":["EFunding:Unknown withdraw key"])
                    'EFunding:Invalid amount' => '\\ccxt\\InsufficientFunds',
                    'EService:Unavailable' => '\\ccxt\\ExchangeNotAvailable',
                    'EDatabase:Internal error' => '\\ccxt\\ExchangeNotAvailable',
                    'EService:Busy' => '\\ccxt\\ExchangeNotAvailable',
                    'EQuery:Unknown asset' => '\\ccxt\\BadSymbol', // array("error":["EQuery:Unknown asset"])
                    'EAPI:Rate limit exceeded' => '\\ccxt\\DDoSProtection',
                    'EOrder:Rate limit exceeded' => '\\ccxt\\DDoSProtection',
                    'EGeneral:Internal error' => '\\ccxt\\ExchangeNotAvailable',
                    'EGeneral:Temporary lockout' => '\\ccxt\\DDoSProtection',
                    'EGeneral:Permission denied' => '\\ccxt\\PermissionDenied',
                    'EGeneral:Invalid arguments:price' => '\\ccxt\\InvalidOrder',
                    'EOrder:Unknown order' => '\\ccxt\\InvalidOrder',
                    'EOrder:Invalid price:Invalid price argument' => '\\ccxt\\InvalidOrder',
                    'EOrder:Order minimum not met' => '\\ccxt\\InvalidOrder',
                    'EOrder:Insufficient funds' => '\\ccxt\\InsufficientFunds',
                    'EGeneral:Invalid arguments' => '\\ccxt\\BadRequest',
                    'ESession:Invalid session' => '\\ccxt\\AuthenticationError',
                    'EAPI:Invalid nonce' => '\\ccxt\\InvalidNonce',
                    'EFunding:No funding method' => '\\ccxt\\BadRequest', // array("error":"EFunding:No funding method")
                    'EFunding:Unknown asset' => '\\ccxt\\BadSymbol', // array("error":["EFunding:Unknown asset"])
                    'EService:Market in post_only mode' => '\\ccxt\\OnMaintenance', // array(is_array(post_only mode"]) && array_key_exists("error":["EService:Market, post_only mode"]))
                    'EGeneral:Too many requests' => '\\ccxt\\DDoSProtection', // array("error":["EGeneral:Too many requests"])
                    'ETrade:User Locked' => '\\ccxt\\AccountSuspended', // array("error":["ETrade:User Locked"])
                ),
                'broad' => array(
                    ':Invalid order' => '\\ccxt\\InvalidOrder',
                    ':Invalid arguments:volume' => '\\ccxt\\InvalidOrder',
                    ':Invalid arguments:viqc' => '\\ccxt\\InvalidOrder',
                    ':Invalid nonce' => '\\ccxt\\InvalidNonce',
                    ':IInsufficient funds' => '\\ccxt\\InsufficientFunds',
                    ':Cancel pending' => '\\ccxt\\CancelPending',
                    ':Rate limit exceeded' => '\\ccxt\\RateLimitExceeded',
                ),
            ),
        ));
    }

    public function fee_to_precision($symbol, $fee) {
        return $this->decimal_to_precision($fee, TRUNCATE, $this->markets[$symbol]['precision']['amount'], $this->precisionMode);
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all $markets for kraken
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTradableAssetPairs
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $promises = array();
        $promises[] = $this->publicGetAssetPairs ($params);
        if ($this->options['adjustForTimeDifference']) {
            $promises[] = $this->load_time_difference();
        }
        $responses = $promises;
        $assetsResponse = $responses[0];
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "ADAETH" => array(
        //                 "altname" => "ADAETH",
        //                 "wsname" => "ADA\/ETH",
        //                 "aclass_base" => "currency",
        //                 "base" => "ADA",
        //                 "aclass_quote" => "currency",
        //                 "quote" => "XETH",
        //                 "lot" => "unit",
        //                 "pair_decimals" => 7,
        //                 "lot_decimals" => 8,
        //                 "lot_multiplier" => 1,
        //                 "leverage_buy" => array(),
        //                 "leverage_sell" => array(),
        //                 "fees" => [
        //                     [0, 0.26],
        //                     [50000, 0.24],
        //                     [100000, 0.22],
        //                     [250000, 0.2],
        //                     [500000, 0.18],
        //                     [1000000, 0.16],
        //                     [2500000, 0.14],
        //                     [5000000, 0.12],
        //                     [10000000, 0.1]
        //                 ],
        //                 "fees_maker" => [
        //                     [0, 0.16],
        //                     [50000, 0.14],
        //                     [100000, 0.12],
        //                     [250000, 0.1],
        //                     [500000, 0.08],
        //                     [1000000, 0.06],
        //                     [2500000, 0.04],
        //                     [5000000, 0.02],
        //                     [10000000, 0]
        //                 ],
        //                 "fee_volume_currency" => "ZUSD",
        //                 "margin_call" => 80,
        //                 "margin_stop" => 40,
        //                 "ordermin" => "1"
        //             ),
        //         }
        //     }
        //
        $markets = $this->safe_dict($assetsResponse, 'result', array());
        $cachedCurrencies = $this->safe_dict($this->options, 'cachedCurrencies', array());
        $keys = is_array($markets) ? array_keys($markets) : array();
        $result = array();
        for ($i = 0; $i < count($keys); $i++) {
            $id = $keys[$i];
            $market = $markets[$id];
            $baseIdRaw = $this->safe_string($market, 'base');
            $quoteIdRaw = $this->safe_string($market, 'quote');
            $baseId = $this->safe_currency_code($baseIdRaw);
            $quoteId = $this->safe_currency_code($quoteIdRaw);
            $base = $baseId;
            $quote = $quoteId;
            $makerFees = $this->safe_list($market, 'fees_maker', array());
            $firstMakerFee = $this->safe_list($makerFees, 0, array());
            $firstMakerFeeRate = $this->safe_string($firstMakerFee, 1);
            $maker = null;
            if ($firstMakerFeeRate !== null) {
                $maker = $this->parse_number(Precise::string_div($firstMakerFeeRate, '100'));
            }
            $takerFees = $this->safe_list($market, 'fees', array());
            $firstTakerFee = $this->safe_list($takerFees, 0, array());
            $firstTakerFeeRate = $this->safe_string($firstTakerFee, 1);
            $taker = null;
            if ($firstTakerFeeRate !== null) {
                $taker = $this->parse_number(Precise::string_div($firstTakerFeeRate, '100'));
            }
            $leverageBuy = $this->safe_list($market, 'leverage_buy', array());
            $leverageBuyLength = count($leverageBuy);
            $precisionPrice = $this->parse_number($this->parse_precision($this->safe_string($market, 'pair_decimals')));
            $precisionAmount = $this->parse_number($this->parse_precision($this->safe_string($market, 'lot_decimals')));
            $spot = true;
            // fix https://github.com/freqtrade/freqtrade/issues/11765#issuecomment-2894224103
            if ($spot && (is_array($cachedCurrencies) && array_key_exists($base, $cachedCurrencies))) {
                $currency = $cachedCurrencies[$base];
                $currencyPrecision = $this->safe_number($currency, 'precision');
                // if $currency precision is greater (e.g. 0.01) than $market precision (e.g. 0.001)
                if ($currencyPrecision > $precisionAmount) {
                    $precisionAmount = $currencyPrecision;
                }
            }
            $status = $this->safe_string($market, 'status');
            $isActive = $status === 'online';
            $result[] = array(
                'id' => $id,
                'wsId' => $this->safe_string($market, 'wsname'),
                'symbol' => $base . '/' . $quote,
                'base' => $base,
                'quote' => $quote,
                'settle' => null,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'settleId' => null,
                'altname' => $market['altname'],
                'type' => 'spot',
                'spot' => $spot,
                'margin' => ($leverageBuyLength > 0),
                'swap' => false,
                'future' => false,
                'option' => false,
                'active' => $isActive,
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'taker' => $taker,
                'maker' => $maker,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $precisionAmount,
                    'price' => $precisionPrice,
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => $this->parse_number('1'),
                        'max' => $this->safe_number($leverageBuy, $leverageBuyLength - 1, 1),
                    ),
                    'amount' => array(
                        'min' => $this->safe_number($market, 'ordermin'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => $this->safe_number($market, 'costmin'),
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $market,
            );
        }
        $this->options['marketsByAltname'] = $this->index_by($result, 'altname');
        return $result;
    }

    public function fetch_status($params = array ()) {
        /**
         * the latest known information on the availability of the exchange API
         *
         * @see https://docs.kraken.com/api/docs/rest-api/get-system-status/
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-status-structure status structure~
         */
        $response = $this->publicGetSystemStatus ($params);
        //
        // {
        //     error => array(),
        //     $result => array( status => 'online', timestamp => '2024-07-22T16:34:44Z' )
        // }
        //
        $result = $this->safe_dict($response, 'result');
        $statusRaw = $this->safe_string($result, 'status');
        return array(
            'status' => ($statusRaw === 'online') ? 'ok' : 'maintenance',
            'updated' => null,
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available $currencies on an exchange
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getAssetInfo
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of $currencies
         */
        $response = $this->publicGetAssets ($params);
        //
        //     {
        //         "error" => array(),
        //         "result" => array(
        //             "ATOM" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "ATOM",
        //                 "collateral_value" => "0.7",
        //                 "decimals" => 8,
        //                 "display_decimals" => 6,
        //                 "margin_rate" => 0.02,
        //                 "status" => "enabled",
        //             ),
        //             "ATOM.S" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "ATOM.S",
        //                 "decimals" => 8,
        //                 "display_decimals" => 6,
        //                 "status" => "enabled",
        //             ),
        //             "XXBT" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "XBT",
        //                 "decimals" => 10,
        //                 "display_decimals" => 5,
        //                 "margin_rate" => 0.01,
        //                 "status" => "enabled",
        //             ),
        //             "XETH" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "ETH",
        //                 "decimals" => 10,
        //                 "display_decimals" => 5
        //                 "margin_rate" => 0.02,
        //                 "status" => "enabled",
        //             ),
        //             "XBT.M" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "XBT.M",
        //                 "decimals" => 10,
        //                 "display_decimals" => 5
        //                 "status" => "enabled",
        //             ),
        //             "ETH.M" => array(
        //                 "aclass" => "currency",
        //                 "altname" => "ETH.M",
        //                 "decimals" => 10,
        //                 "display_decimals" => 5
        //                 "status" => "enabled",
        //             ),
        //             ...
        //         ),
        //     }
        //
        $currencies = $this->safe_value($response, 'result', array());
        $ids = is_array($currencies) ? array_keys($currencies) : array();
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $ids[$i];
            $currency = $currencies[$id];
            // todo => will need to rethink the fees
            // see => https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees-
            // to add support for multiple withdrawal/deposit methods and
            // differentiated fees for each particular method
            //
            // Notes about abbreviations:
            // Z and X prefixes => https://support.kraken.com/hc/en-us/articles/360001206766-Bitcoin-$currency-$code-XBT-vs-BTC
            // S and M suffixes => https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M-
            //
            $code = $this->safe_currency_code($id);
            // the below can not be reliable done in `safeCurrencyCode`, so we have to do it here
            if (mb_strpos($id, '.') === false) {
                $altName = $this->safe_string($currency, 'altname');
                // handle cases like below:
                //
                //  $id   | altname
                // ---------------
                // XXBT  |  XBT
                // ZUSD  |  USD
                if ($id !== $altName && (str_starts_with($id, 'X') || str_starts_with($id, 'Z'))) {
                    $code = $this->safe_currency_code($altName);
                    // also, add map in commonCurrencies:
                    $this->commonCurrencies[$id] = $code;
                } else {
                    $code = $this->safe_currency_code($id);
                }
            }
            $isFiat = mb_strpos($code, '.HOLD') !== false;
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'name' => $this->safe_string($currency, 'altname'),
                'active' => $this->safe_string($currency, 'status') === 'enabled',
                'type' => $isFiat ? 'fiat' : 'crypto',
                'deposit' => null,
                'withdraw' => null,
                'fee' => null,
                'precision' => $this->parse_number($this->parse_precision($this->safe_string($currency, 'decimals'))),
                'limits' => array(
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'networks' => array(),
            ));
        }
        return $result;
    }

    public function safe_currency_code(?string $currencyId, ?array $currency = null): ?string {
        if ($currencyId === null) {
            return $currencyId;
        }
        if (mb_strpos($currencyId, '.') > 0) {
            // if ID contains .M, .S or .F, then it can't contain X or Z prefix. in such case, ID equals to ALTNAME
            $parts = explode('.', $currencyId);
            $firstPart = $this->safe_string($parts, 0);
            $secondPart = $this->safe_string($parts, 1);
            return parent::safe_currency_code($firstPart, $currency) . '.' . $secondPart;
        }
        return parent::safe_currency_code($currencyId, $currency);
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): array {
        /**
         * fetch the trading fees for a $market
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradeVolume
         *
         * @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(
            'pair' => $market['id'],
            'fee-info' => true,
        );
        $response = $this->privatePostTradeVolume ($this->extend($request, $params));
        //
        //     {
        //        "error" => array(),
        //        "result" => {
        //          "currency" => 'ZUSD',
        //          "volume" => '0.0000',
        //          "fees" => {
        //            "XXBTZUSD" => array(
        //              "fee" => '0.2600',
        //              "minfee" => '0.1000',
        //              "maxfee" => '0.2600',
        //              "nextfee" => '0.2400',
        //              "tiervolume" => '0.0000',
        //              "nextvolume" => '50000.0000'
        //            }
        //          ),
        //          "fees_maker" => {
        //            "XXBTZUSD" => {
        //              "fee" => '0.1600',
        //              "minfee" => '0.0000',
        //              "maxfee" => '0.1600',
        //              "nextfee" => '0.1400',
        //              "tiervolume" => '0.0000',
        //              "nextvolume" => '50000.0000'
        //            }
        //          }
        //        }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_trading_fee($result, $market);
    }

    public function parse_trading_fee($response, $market) {
        $makerFees = $this->safe_value($response, 'fees_maker', array());
        $takerFees = $this->safe_value($response, 'fees', array());
        $symbolMakerFee = $this->safe_value($makerFees, $market['id'], array());
        $symbolTakerFee = $this->safe_value($takerFees, $market['id'], array());
        return array(
            'info' => $response,
            'symbol' => $market['symbol'],
            'maker' => $this->parse_number(Precise::string_div($this->safe_string($symbolMakerFee, 'fee'), '100')),
            'taker' => $this->parse_number(Precise::string_div($this->safe_string($symbolTakerFee, 'fee'), '100')),
            'percentage' => true,
            'tierBased' => true,
        );
    }

    public function parse_bid_ask($bidask, int|string $priceKey = 0, int|string $amountKey = 1, int|string $countOrIdKey = 2) {
        $price = $this->safe_number($bidask, $priceKey);
        $amount = $this->safe_number($bidask, $amountKey);
        $timestamp = $this->safe_integer($bidask, 2);
        return array( $price, $amount, $timestamp );
    }

    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://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getOrderBook
         *
         * @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(
            'pair' => $market['id'],
        );
        if ($limit !== null) {
            $request['count'] = $limit; // 100
        }
        $response = $this->publicGetDepth ($this->extend($request, $params));
        //
        //     {
        //         "error":array(),
        //         "result":{
        //             "XETHXXBT":{
        //                 "asks":[
        //                     ["0.023480","4.000",1586321307],
        //                     ["0.023490","50.095",1586321306],
        //                     ["0.023500","28.535",1586321302],
        //                 ],
        //                 "bids":[
        //                     ["0.023470","59.580",1586321307],
        //                     ["0.023460","20.000",1586321301],
        //                     ["0.023440","67.832",1586321306],
        //                 ]
        //             }
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $orderbook = $this->safe_value($result, $market['id']);
        // sometimes kraken returns wsname instead of $market id
        // https://github.com/ccxt/ccxt/issues/8662
        $marketInfo = $this->safe_value($market, 'info', array());
        $wsName = $this->safe_value($marketInfo, 'wsname');
        if ($wsName !== null) {
            $orderbook = $this->safe_value($result, $wsName, $orderbook);
        }
        return $this->parse_order_book($orderbook, $symbol);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "a":["2432.77000","1","1.000"],
        //         "b":["2431.37000","2","2.000"],
        //         "c":["2430.58000","0.04408910"],
        //         "v":["4147.94474901","8896.96086304"],
        //         "p":["2456.22239","2568.63032"],
        //         "t":[3907,10056],
        //         "l":["2302.18000","2302.18000"],
        //         "h":["2621.14000","2860.01000"],
        //         "o":"2571.56000"
        //     }
        //
        $symbol = $this->safe_symbol(null, $market);
        $v = $this->safe_value($ticker, 'v', array());
        $baseVolume = $this->safe_string($v, 1);
        $p = $this->safe_value($ticker, 'p', array());
        $vwap = $this->safe_string($p, 1);
        $quoteVolume = Precise::string_mul($baseVolume, $vwap);
        $c = $this->safe_value($ticker, 'c', array());
        $last = $this->safe_string($c, 0);
        $high = $this->safe_value($ticker, 'h', array());
        $low = $this->safe_value($ticker, 'l', array());
        $bid = $this->safe_value($ticker, 'b', array());
        $ask = $this->safe_value($ticker, 'a', array());
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => null,
            'datetime' => null,
            'high' => $this->safe_string($high, 1),
            'low' => $this->safe_string($low, 1),
            'bid' => $this->safe_string($bid, 0),
            'bidVolume' => $this->safe_string($bid, 2),
            'ask' => $this->safe_string($ask, 0),
            'askVolume' => $this->safe_string($ask, 2),
            'vwap' => $vwap,
            'open' => $this->safe_string($ticker, 'o'),
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        ), $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://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation
         *
         * @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();
        $request = array();
        if ($symbols !== null) {
            $symbols = $this->market_symbols($symbols);
            $marketIds = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $market = $this->markets[$symbol];
                if ($market['active']) {
                    $marketIds[] = $market['id'];
                }
            }
            $request['pair'] = implode(',', $marketIds);
        }
        $response = $this->publicGetTicker ($this->extend($request, $params));
        $tickers = $response['result'];
        $ids = is_array($tickers) ? array_keys($tickers) : array();
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $ids[$i];
            $market = $this->safe_market($id);
            $symbol = $market['symbol'];
            $ticker = $tickers[$id];
            $result[$symbol] = $this->parse_ticker($ticker, $market);
        }
        return $this->filter_by_array_tickers($result, 'symbol', $symbols);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation
         *
         * @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(
            'pair' => $market['id'],
        );
        $response = $this->publicGetTicker ($this->extend($request, $params));
        $ticker = $response['result'][$market['id']];
        return $this->parse_ticker($ticker, $market);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         1591475640,
        //         "0.02500",
        //         "0.02500",
        //         "0.02500",
        //         "0.02500",
        //         "0.02500",
        //         "9.12201000",
        //         5
        //     )
        //
        return array(
            $this->safe_timestamp($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
            $this->safe_number($ohlcv, 6),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://docs.kraken.com/api/docs/rest-api/get-ohlc-data
         *
         * @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
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params, 720);
        }
        $market = $this->market($symbol);
        $parsedTimeframe = $this->safe_integer($this->timeframes, $timeframe);
        $request = array(
            'pair' => $market['id'],
        );
        if ($parsedTimeframe !== null) {
            $request['interval'] = $parsedTimeframe;
        } else {
            $request['interval'] = $timeframe;
        }
        if ($since !== null) {
            $scaledSince = $this->parse_to_int($since / 1000);
            $timeFrameInSeconds = $parsedTimeframe * 60;
            $request['since'] = $this->number_to_string($scaledSince - $timeFrameInSeconds); // expected to be in seconds
        }
        $response = $this->publicGetOHLC ($this->extend($request, $params));
        //
        //     {
        //         "error":array(),
        //         "result":{
        //             "XETHXXBT":[
        //                 [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0],
        //                 [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5],
        //                 [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2],
        //                 [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1],
        //             ],
        //             "last":1591517580
        //         }
        //     }
        $result = $this->safe_value($response, 'result', array());
        $ohlcvs = $this->safe_list($result, $market['id'], array());
        return $this->parse_ohlcvs($ohlcvs, $market, $timeframe, $since, $limit);
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'trade' => 'trade',
            'withdrawal' => 'transaction',
            'deposit' => 'transaction',
            'transfer' => 'transfer',
            'margin' => 'margin',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        //     {
        //         'LTFK7F-N2CUX-PNY4SX' => array(
        //             "refid" => "TSJTGT-DT7WN-GPPQMJ",
        //             "time" =>  1520102320.555,
        //             "type" => "trade",
        //             "aclass" => "currency",
        //             "asset" => "XETH",
        //             "amount" => "0.1087194600",
        //             "fee" => "0.0000000000",
        //             "balance" => "0.2855851000"
        //         ),
        //         ...
        //     }
        //
        $id = $this->safe_string($item, 'id');
        $direction = null;
        $account = null;
        $referenceId = $this->safe_string($item, 'refid');
        $referenceAccount = null;
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'type'));
        $currencyId = $this->safe_string($item, 'asset');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        $amount = $this->safe_string($item, 'amount');
        if (Precise::string_lt($amount, '0')) {
            $direction = 'out';
            $amount = Precise::string_abs($amount);
        } else {
            $direction = 'in';
        }
        $timestamp = $this->safe_integer_product($item, 'time', 1000);
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $id,
            'direction' => $direction,
            'account' => $account,
            'referenceId' => $referenceId,
            'referenceAccount' => $referenceAccount,
            'type' => $type,
            'currency' => $code,
            'amount' => $this->parse_number($amount),
            'before' => null,
            'after' => $this->safe_number($item, 'balance'),
            'status' => 'ok',
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'fee' => array(
                'cost' => $this->safe_number($item, 'fee'),
                'currency' => $code,
            ),
        ), $currency);
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch the history of changes, actions done by the user or operations that altered the balance of the user
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getLedgers
         *
         * @param {string} [$code] unified $currency $code, default is null
         * @param {int} [$since] timestamp in ms of the earliest $ledger entry, default is null
         * @param {int} [$limit] max number of $ledger entries to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest $ledger entry
         * @param {int} [$params->end] timestamp in seconds of the latest $ledger entry
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ledger ledger structure~
         */
        // https://www.kraken.com/features/api#get-ledgers-info
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['asset'] = $currency['id'];
        }
        if ($since !== null) {
            $request['start'] = $this->parse_to_int($since / 1000);
        }
        $until = $this->safe_string_n($params, array( 'until', 'till' ));
        if ($until !== null) {
            $params = $this->omit($params, array( 'until', 'till' ));
            $untilDivided = Precise::string_div($until, '1000');
            $request['end'] = $this->parse_to_int(Precise::string_add($untilDivided, '1'));
        }
        $response = $this->privatePostLedgers ($this->extend($request, $params));
        // {  error => array(),
        //   "result" => { $ledger => { 'LPUAIB-TS774-UKHP7X' => array(   refid => "A2B4HBV-L4MDIE-JU4N3N",
        //                                                   "time" =>  1520103488.314,
        //                                                   "type" => "withdrawal",
        //                                                 "aclass" => "currency",
        //                                                  "asset" => "XETH",
        //                                                 "amount" => "-0.2805800000",
        //                                                    "fee" => "0.0050000000",
        //                                                "balance" => "0.0000051000"           ),
        $result = $this->safe_value($response, 'result', array());
        $ledger = $this->safe_value($result, 'ledger', array());
        $keys = is_array($ledger) ? array_keys($ledger) : array();
        $items = array();
        for ($i = 0; $i < count($keys); $i++) {
            $key = $keys[$i];
            $value = $ledger[$key];
            $value['id'] = $key;
            $items[] = $value;
        }
        return $this->parse_ledger($items, $currency, $since, $limit);
    }

    public function fetch_ledger_entries_by_ids($ids, ?string $code = null, $params = array ()) {
        // https://www.kraken.com/features/api#query-ledgers
        $this->load_markets();
        $ids = implode(',', $ids);
        $request = $this->extend(array(
            'id' => $ids,
        ), $params);
        $response = $this->privatePostQueryLedgers ($request);
        // {  error => array(),
        //   "result" => { 'LPUAIB-TS774-UKHP7X' => {   refid => "A2B4HBV-L4MDIE-JU4N3N",
        //                                         "time" =>  1520103488.314,
        //                                         "type" => "withdrawal",
        //                                       "aclass" => "currency",
        //                                        "asset" => "XETH",
        //                                       "amount" => "-0.2805800000",
        //                                          "fee" => "0.0050000000",
        //                                      "balance" => "0.0000051000"           } } }
        $result = $response['result'];
        $keys = is_array($result) ? array_keys($result) : array();
        $items = array();
        for ($i = 0; $i < count($keys); $i++) {
            $key = $keys[$i];
            $value = $result[$key];
            $value['id'] = $key;
            $items[] = $value;
        }
        return $this->parse_ledger($items);
    }

    public function fetch_ledger_entry(string $id, ?string $code = null, $params = array ()): array {
        $items = $this->fetch_ledger_entries_by_ids(array( $id ), $code, $params);
        return $items[0];
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //     array(
        //         "0.032310", // $price
        //         "4.28169434", // $amount
        //         1541390792.763, // $timestamp
        //         "s", // sell or buy
        //         "l", // limit or $market
        //         ""
        //     )
        //
        // fetchOrderTrades (private)
        //
        //     {
        //         "id" => 'TIMIRG-WUNNE-RRJ6GT', // injected from outside
        //         "ordertxid" => 'OQRPN2-LRHFY-HIFA7D',
        //         "postxid" => 'TKH2SE-M7IF5-CFI7LT',
        //         "pair" => 'USDCUSDT',
        //         "time" => 1586340086.457,
        //         "type" => 'sell',
        //         "ordertype" => 'market',
        //         "price" => '0.99860000',
        //         "cost" => '22.16892001',
        //         "fee" => '0.04433784',
        //         "vol" => '22.20000000',
        //         "margin" => '0.00000000',
        //         "misc" => ''
        //     }
        //
        // fetchMyTrades
        //
        //     {
        //         "ordertxid" => "OSJVN7-A2AE-63WZV",
        //         "postxid" => "TBP7O6-PNXI-CONU",
        //         "pair" => "XXBTZUSD",
        //         "time" => 1710429248.3052235,
        //         "type" => "sell",
        //         "ordertype" => "liquidation $market",
        //         "price" => "72026.50000",
        //         "cost" => "7.20265",
        //         "fee" => "0.01873",
        //         "vol" => "0.00010000",
        //         "margin" => "1.44053",
        //         "leverage" => "5",
        //         "misc" => "closing",
        //         "trade_id" => 68230622,
        //         "maker" => false
        //     }
        //
        // watchTrades
        //
        //     {
        //         "symbol" => "BTC/USD",
        //         "side" => "buy",
        //         "price" => 109601.2,
        //         "qty" => 0.04561994,
        //         "ord_type" => "market",
        //         "trade_id" => 83449369,
        //         "timestamp" => "2025-05-27T11:24:03.847761Z"
        //     }
        //
        $timestamp = null;
        $datetime = null;
        $side = null;
        $type = null;
        $price = null;
        $amount = null;
        $id = null;
        $orderId = null;
        $fee = null;
        $symbol = null;
        if (gettype($trade) === 'array' && array_keys($trade) === array_keys(array_keys($trade))) {
            $timestamp = $this->safe_timestamp($trade, 2);
            $side = ($trade[3] === 's') ? 'sell' : 'buy';
            $type = ($trade[4] === 'l') ? 'limit' : 'market';
            $price = $this->safe_string($trade, 0);
            $amount = $this->safe_string($trade, 1);
            $tradeLength = count($trade);
            if ($tradeLength > 6) {
                $id = $this->safe_string($trade, 6); // artificially added #1794
            }
        } elseif (gettype($trade) === 'string') {
            $id = $trade;
        } elseif (is_array($trade) && array_key_exists('ordertxid', $trade)) {
            $marketId = $this->safe_string($trade, 'pair');
            $foundMarket = $this->find_market_by_altname_or_id($marketId);
            if ($foundMarket !== null) {
                $market = $foundMarket;
            } elseif ($marketId !== null) {
                // delisted $market ids go here
                $market = $this->get_delisted_market_by_id($marketId);
            }
            $orderId = $this->safe_string($trade, 'ordertxid');
            $id = $this->safe_string_2($trade, 'id', 'postxid');
            $timestamp = $this->safe_timestamp($trade, 'time');
            $side = $this->safe_string($trade, 'type');
            $type = $this->safe_string($trade, 'ordertype');
            $price = $this->safe_string($trade, 'price');
            $amount = $this->safe_string($trade, 'vol');
            if (is_array($trade) && array_key_exists('fee', $trade)) {
                $currency = null;
                if ($market !== null) {
                    $currency = $market['quote'];
                }
                $fee = array(
                    'cost' => $this->safe_string($trade, 'fee'),
                    'currency' => $currency,
                );
            }
        } else {
            $symbol = $this->safe_string($trade, 'symbol');
            $datetime = $this->safe_string($trade, 'timestamp');
            $id = $this->safe_string($trade, 'trade_id');
            $side = $this->safe_string($trade, 'side');
            $type = $this->safe_string($trade, 'ord_type');
            $price = $this->safe_string($trade, 'price');
            $amount = $this->safe_string($trade, 'qty');
        }
        if ($market !== null) {
            $symbol = $market['symbol'];
        }
        $cost = $this->safe_string($trade, 'cost');
        $maker = $this->safe_bool($trade, 'maker');
        $takerOrMaker = null;
        if ($maker !== null) {
            $takerOrMaker = $maker ? 'maker' : 'taker';
        }
        if ($datetime === null) {
            $datetime = $this->iso8601($timestamp);
        } else {
            $timestamp = $this->parse8601($datetime);
        }
        return $this->safe_trade(array(
            'id' => $id,
            'order' => $orderId,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $datetime,
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            '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://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getRecentTrades
         *
         * @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);
        $id = $market['id'];
        $request = array(
            'pair' => $id,
        );
        // https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API
        // https://github.com/ccxt/ccxt/issues/5677
        if ($since !== null) {
            $request['since'] = $this->number_to_string($this->parse_to_int($since / 1000)); // expected to be in seconds
        }
        if ($limit !== null) {
            $request['count'] = $limit;
        }
        $response = $this->publicGetTrades ($this->extend($request, $params));
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "XETHXXBT" => [
        //                 ["0.032310","4.28169434",1541390792.763,"s","l",""]
        //             ],
        //             "last" => "1541439421200678657"
        //         }
        //     }
        //
        $result = $response['result'];
        $trades = $result[$id];
        // $trades is a sorted array => last (most recent trade) goes last
        $length = count($trades);
        if ($length <= 0) {
            return array();
        }
        $lastTrade = $trades[$length - 1];
        $lastTradeId = $this->safe_string($result, 'last');
        $lastTrade[] = $lastTradeId;
        $trades[$length - 1] = $lastTrade;
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_balance($response): array {
        $balances = $this->safe_value($response, 'result', array());
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        $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, array());
            $account = $this->account();
            $account['used'] = $this->safe_string($balance, 'hold_trade');
            $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://docs.kraken.com/rest/#tag/Account-Data/operation/getExtendedBalance
         *
         * @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->privatePostBalanceEx ($params);
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "ZUSD" => array(
        //                 "balance" => 25435.21,
        //                 "hold_trade" => 8249.76
        //             ),
        //             "XXBT" => {
        //                 "balance" => 1.2435,
        //                 "hold_trade" => 0.8423
        //             }
        //         }
        //     }
        //
        return $this->parse_balance($response);
    }

    public function create_market_order_with_cost(string $symbol, string $side, float $cost, $params = array ()) {
        /**
         * create a market order by providing the $symbol, $side and $cost
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder
         *
         * @param {string} $symbol unified $symbol of the market to create an order in (only USD markets are supported)
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $cost how much you want to trade in units of the quote currency
         * @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();
        // only buy orders are supported by the endpoint
        $req = array(
            'cost' => $cost,
        );
        return $this->create_order($symbol, 'market', $side, $cost, null, $this->extend($req, $params));
    }

    public function create_market_buy_order_with_cost(string $symbol, float $cost, $params = array ()) {
        /**
         * create a market buy order by providing the $symbol, side and $cost
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder
         *
         * @param {string} $symbol unified $symbol of the market to create an order in
         * @param {float} $cost how much you want to trade in units of the quote currency
         * @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();
        return $this->create_market_order_with_cost($symbol, 'buy', $cost, $params);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://docs.kraken.com/api/docs/rest-api/add-order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {bool} [$params->postOnly] if true, the order will only be posted to the order book and not executed immediately
         * @param {bool} [$params->reduceOnly] *margin only* indicates if this order is to reduce the size of a position
         * @param {float} [$params->stopLossPrice] *margin only* the $price that a stop loss order is triggered at
         * @param {float} [$params->takeProfitPrice] *margin only* the $price that a take profit order is triggered at
         * @param {string} [$params->trailingAmount] *margin only* the quote $amount to trail away from the current $market $price
         * @param {string} [$params->trailingPercent] *margin only* the percent to trail away from the current $market $price
         * @param {string} [$params->trailingLimitAmount] *margin only* the quote $amount away from the trailingAmount
         * @param {string} [$params->trailingLimitPercent] *margin only* the percent away from the trailingAmount
         * @param {string} [$params->offset] *margin only* '+' or '-' whether you want the trailingLimitAmount value to be positive or negative, default is negative '-'
         * @param {string} [$params->trigger] *margin only* the activation $price $type, 'last' or 'index', default is 'last'
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
            'type' => $side,
            'ordertype' => $type,
            'volume' => $this->amount_to_precision($symbol, $amount),
        );
        $orderRequest = $this->order_request('createOrder', $symbol, $type, $request, $amount, $price, $params);
        $flags = $this->safe_string($orderRequest[0], 'oflags', '');
        $isUsingCost = mb_strpos($flags, 'viqc') > -1;
        $response = $this->privatePostAddOrder ($this->extend($orderRequest[0], $orderRequest[1]));
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "descr" => array( order => 'buy 0.02100000 ETHUSDT @ limit 330.00' ), // see more examples in "parseOrder"
        //             "txid" => array( 'OEKVV2-IH52O-TPL6GZ' )
        //         }
        //     }
        //
        $result = $this->safe_dict($response, 'result');
        $result['usingCost'] = $isUsingCost;
        // it's impossible to know if the order was created using cost or base currency
        // becuase kraken only returns something like this => array( order => 'buy 10.00000000 LTCUSD @ market' )
        // this usingCost flag is used to help the parsing but omited from the order
        return $this->parse_order($result);
    }

    public function find_market_by_altname_or_id($id) {
        $marketsByAltname = $this->safe_value($this->options, 'marketsByAltname', array());
        if (is_array($marketsByAltname) && array_key_exists($id, $marketsByAltname)) {
            return $marketsByAltname[$id];
        } else {
            return $this->safe_market($id);
        }
    }

    public function get_delisted_market_by_id($id) {
        if ($id === null) {
            return $id;
        }
        $market = $this->safe_value($this->options['delistedMarketsById'], $id);
        if ($market !== null) {
            return $market;
        }
        $baseIdStart = 0;
        $baseIdEnd = 3;
        $quoteIdStart = 3;
        $quoteIdEnd = 6;
        if (strlen($id) === 8) {
            $baseIdEnd = 4;
            $quoteIdStart = 4;
            $quoteIdEnd = 8;
        } elseif (strlen($id) === 7) {
            $baseIdEnd = 4;
            $quoteIdStart = 4;
            $quoteIdEnd = 7;
        }
        $baseId = mb_substr($id, $baseIdStart, $baseIdEnd - $baseIdStart);
        $quoteId = mb_substr($id, $quoteIdStart, $quoteIdEnd - $quoteIdStart);
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $market = array(
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
        );
        $this->options['delistedMarketsById'][$id] = $market;
        return $market;
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'pending' => 'open', // order pending book entry
            'open' => 'open',
            'closed' => 'closed',
            'canceled' => 'canceled',
            'expired' => 'expired',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($status) {
        $statuses = array(
            // we dont add "space" delimited orders here (eg. stop loss) because they need separate parsing
            'take-profit' => 'market',
            'stop-loss' => 'market',
            'stop-loss-limit' => 'limit',
            'take-profit-limit' => 'limit',
            'trailing-stop-limit' => 'limit',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder
        //
        //     {
        //         "descr" => array(
        //            "order" => "buy 0.02100000 ETHUSDT @ limit 330.00" // limit orders
        //                     "buy 0.12345678 ETHUSDT @ $market" // $market $order
        //                     "sell 0.28002676 ETHUSDT @ stop loss 0.0123 -> limit 0.0.1222" // stop $order
        //                     "sell 0.00100000 ETHUSDT @ stop loss 2677.00 -> limit 2577.00 with 5:1 leverage"
        //                     "buy 0.10000000 LTCUSDT @ take profit 75.00000 -> limit 74.00000"
        //                     "sell 10.00000000 XRPEUR @ trailing stop +50.0000%" // trailing stop
        //         ),
        //         "txid" => array( 'OEKVV2-IH52O-TPL6GZ' )
        //     }
        //
        // editOrder
        //
        //     {
        //         "amend_id" => "TJSMEH-AA67V-YUSQ6O"
        //     }
        //
        //  ws - createOrder
        //     {
        //         "order_id" => "OXM2QD-EALR2-YBAVEU"
        //     }
        //
        //  ws - editOrder
        //     {
        //         "amend_id" => "TJSMEH-AA67V-YUSQ6O",
        //         "order_id" => "OXM2QD-EALR2-YBAVEU"
        //     }
        //
        //  {
        //      "error" => array(),
        //      "result" => {
        //          "open" => {
        //              "OXVPSU-Q726F-L3SDEP" => {
        //                  "refid" => null,
        //                  "userref" => 0,
        //                  "status" => "open",
        //                  "opentm" => 1706893367.4656649,
        //                  "starttm" => 0,
        //                  "expiretm" => 0,
        //                  "descr" => array(
        //                      "pair" => "XRPEUR",
        //                      "type" => "sell",
        //                      "ordertype" => "trailing-stop",
        //                      "price" => "+50.0000%",
        //                      "price2" => "0",
        //                      "leverage" => "none",
        //                      "order" => "sell 10.00000000 XRPEUR @ trailing stop +50.0000%",
        //                      "close" => ""
        //                  ),
        //                  "vol" => "10.00000000",
        //                  "vol_exec" => "0.00000000",
        //                  "cost" => "0.00000000",
        //                  "fee" => "0.00000000",
        //                  "price" => "0.00000000",
        //                  "stopprice" => "0.23424000",
        //                  "limitprice" => "0.46847000",
        //                  "misc" => "",
        //                  "oflags" => "fciq",
        //                  "trigger" => "index"
        //              }
        //      }
        //  }
        //
        // fetchOpenOrders
        //
        //      {
        //         "refid" => null,
        //         "userref" => null,
        //         "cl_ord_id" => "1234",
        //         "status" => "open",
        //         "opentm" => 1733815269.370054,
        //         "starttm" => 0,
        //         "expiretm" => 0,
        //         "descr" => array(
        //             "pair" => "XBTUSD",
        //             "type" => "buy",
        //             "ordertype" => "limit",
        //             "price" => "70000.0",
        //             "price2" => "0",
        //             "leverage" => "none",
        //             "order" => "buy 0.00010000 XBTUSD @ limit 70000.0",
        //             "close" => ""
        //         ),
        //         "vol" => "0.00010000",
        //         "vol_exec" => "0.00000000",
        //         "cost" => "0.00000",
        //         "fee" => "0.00000",
        //         "price" => "0.00000",
        //         "stopprice" => "0.00000",
        //         "limitprice" => "0.00000",
        //         "misc" => "",
        //         "oflags" => "fciq"
        //     }
        //
        $isUsingCost = $this->safe_bool($order, 'usingCost', false);
        $order = $this->omit($order, 'usingCost');
        $description = $this->safe_dict($order, 'descr', array());
        $orderDescriptionObj = $this->safe_dict($order, 'descr'); // can be null
        $orderDescription = null;
        if ($orderDescriptionObj !== null) {
            $orderDescription = $this->safe_string($orderDescriptionObj, 'order');
        } else {
            $orderDescription = $this->safe_string($order, 'descr');
        }
        $side = null;
        $rawType = null;
        $marketId = null;
        $price = null;
        $amount = null;
        $cost = null;
        $triggerPrice = null;
        if ($orderDescription !== null) {
            $parts = explode(' ', $orderDescription);
            $side = $this->safe_string($parts, 0);
            if (!$isUsingCost) {
                $amount = $this->safe_string($parts, 1);
            } else {
                $cost = $this->safe_string($parts, 1);
            }
            $marketId = $this->safe_string($parts, 2);
            $part4 = $this->safe_string($parts, 4);
            $part5 = $this->safe_string($parts, 5);
            if ($part4 === 'limit' || $part4 === 'market') {
                $rawType = $part4; // eg, limit, $market
            } else {
                $rawType = $part4 . ' ' . $part5; // eg. stop loss, take profit, trailing stop
            }
            if ($rawType === 'stop loss' || $rawType === 'take profit') {
                $triggerPrice = $this->safe_string($parts, 6);
                $price = $this->safe_string($parts, 9);
            } elseif ($rawType === 'limit') {
                $price = $this->safe_string($parts, 5);
            }
        }
        $side = $this->safe_string($description, 'type', $side);
        $rawType = $this->safe_string($description, 'ordertype', $rawType); // orderType has dash, e.g. trailing-stop
        $marketId = $this->safe_string($description, 'pair', $marketId);
        $foundMarket = $this->find_market_by_altname_or_id($marketId);
        $symbol = null;
        if ($foundMarket !== null) {
            $market = $foundMarket;
        } elseif ($marketId !== null) {
            // delisted $market ids go here
            $market = $this->get_delisted_market_by_id($marketId);
        }
        $timestamp = $this->safe_timestamp($order, 'opentm');
        $amount = $this->safe_string($order, 'vol', $amount);
        $filled = $this->safe_string($order, 'vol_exec');
        $fee = null;
        // kraken truncates the $cost in the api response so we will ignore it and calculate it from $average & $filled
        // $cost = $this->safe_string($order, 'cost');
        $price = $this->safe_string($description, 'price', $price);
        // when type = trailing stop returns $price = '+50.0000%'
        if (($price !== null) && (str_ends_with($price, '%') || Precise::string_equals($price, '0.00000') || Precise::string_equals($price, '0'))) {
            $price = null; // this is not the $price we want
        }
        if ($price === null) {
            $price = $this->safe_string($description, 'price2');
            $price = $this->safe_string_2($order, 'limitprice', 'price', $price);
        }
        $flags = $this->safe_string($order, 'oflags', '');
        $isPostOnly = mb_strpos($flags, 'post') > -1;
        $average = $this->safe_number($order, 'price');
        if ($market !== null) {
            $symbol = $market['symbol'];
            if (is_array($order) && array_key_exists('fee', $order)) {
                $feeCost = $this->safe_string($order, 'fee');
                $fee = array(
                    'cost' => $feeCost,
                    'rate' => null,
                );
                if (mb_strpos($flags, 'fciq') !== false) {
                    $fee['currency'] = $market['quote'];
                } elseif (mb_strpos($flags, 'fcib') !== false) {
                    $fee['currency'] = $market['base'];
                }
            }
        }
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $id = $this->safe_string_n($order, array( 'id', 'txid', 'order_id', 'amend_id' ));
        if (($id === null) || (str_starts_with($id, '['))) {
            $txid = $this->safe_list($order, 'txid');
            $id = $this->safe_string($txid, 0);
        }
        $userref = $this->safe_string($order, 'userref');
        $clientOrderId = $this->safe_string($order, 'cl_ord_id', $userref);
        $rawTrades = $this->safe_value($order, 'trades', array());
        $trades = array();
        for ($i = 0; $i < count($rawTrades); $i++) {
            $rawTrade = $rawTrades[$i];
            if (gettype($rawTrade) === 'string') {
                $trades[] = $this->safe_trade(array( 'id' => $rawTrade, 'orderId' => $id, 'symbol' => $symbol, 'info' => array()));
            } else {
                $trades[] = $rawTrade;
            }
        }
        // in #24192 PR, this field is not something consistent/actual
        // $triggerPrice = $this->omit_zero($this->safe_string($order, 'stopprice', $triggerPrice));
        $stopLossPrice = null;
        $takeProfitPrice = null;
        // the dashed strings are not provided from fields (eg. fetch $order)
        // while spaced strings from "order" sentence (when other fields not available)
        if ($rawType !== null) {
            if (str_starts_with($rawType, 'take-profit')) {
                $takeProfitPrice = $this->safe_string($description, 'price');
                $price = $this->omit_zero($this->safe_string($description, 'price2'));
            } elseif (str_starts_with($rawType, 'stop-loss')) {
                $stopLossPrice = $this->safe_string($description, 'price');
                $price = $this->omit_zero($this->safe_string($description, 'price2'));
            } elseif ($rawType === 'take profit') {
                $takeProfitPrice = $triggerPrice;
            } elseif ($rawType === 'stop loss') {
                $stopLossPrice = $triggerPrice;
            }
        }
        $finalType = $this->parse_order_type($rawType);
        // unlike from endpoints which provide eg => "take-profit-limit"
        // for "space-delimited" orders we dont have market/limit suffixes, their format is
        // eg => `stop loss > limit 123`, so we need to parse them manually
        if ($this->in_array($finalType, array( 'stop loss', 'take profit' ))) {
            $finalType = ($price === null) ? 'market' : 'limit';
        }
        $amendId = $this->safe_string($order, 'amend_id');
        if ($amendId !== null) {
            $isPostOnly = null;
        }
        return $this->safe_order(array(
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'info' => $order,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'status' => $status,
            'symbol' => $symbol,
            'type' => $finalType,
            'timeInForce' => null,
            'postOnly' => $isPostOnly,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'takeProfitPrice' => $takeProfitPrice,
            'stopLossPrice' => $stopLossPrice,
            'cost' => $cost,
            'amount' => $amount,
            'filled' => $filled,
            'average' => $average,
            'remaining' => null,
            'reduceOnly' => $this->safe_bool_2($order, 'reduceOnly', 'reduce_only'),
            'fee' => $fee,
            'trades' => $trades,
        ), $market);
    }

    public function order_request(string $method, string $symbol, string $type, array $request, ?float $amount, ?float $price = null, $params = array ()) {
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        $params = $this->omit($params, array( 'clientOrderId' ));
        if ($clientOrderId !== null) {
            $request['cl_ord_id'] = $clientOrderId;
        }
        $stopLossTriggerPrice = $this->safe_string($params, 'stopLossPrice');
        $takeProfitTriggerPrice = $this->safe_string($params, 'takeProfitPrice');
        $isStopLossTriggerOrder = $stopLossTriggerPrice !== null;
        $isTakeProfitTriggerOrder = $takeProfitTriggerPrice !== null;
        $isStopLossOrTakeProfitTrigger = $isStopLossTriggerOrder || $isTakeProfitTriggerOrder;
        $trailingAmount = $this->safe_string($params, 'trailingAmount');
        $trailingPercent = $this->safe_string($params, 'trailingPercent');
        $trailingLimitAmount = $this->safe_string($params, 'trailingLimitAmount');
        $trailingLimitPercent = $this->safe_string($params, 'trailingLimitPercent');
        $isTrailingAmountOrder = $trailingAmount !== null;
        $isTrailingPercentOrder = $trailingPercent !== null;
        $isLimitOrder = str_ends_with($type, 'limit'); // supporting limit, stop-loss-limit, take-profit-limit, etc
        $isMarketOrder = $type === 'market';
        $cost = $this->safe_string($params, 'cost');
        $flags = $this->safe_string($params, 'oflags');
        $params = $this->omit($params, array( 'cost', 'oflags' ));
        $isViqcOrder = ($flags !== null) && (mb_strpos($flags, 'viqc') > -1); // volume in quote currency
        if ($isMarketOrder && ($cost !== null || $isViqcOrder)) {
            if ($cost === null && ($amount !== null)) {
                $request['volume'] = $this->cost_to_precision($symbol, $this->number_to_string($amount));
            } else {
                $request['volume'] = $this->cost_to_precision($symbol, $cost);
            }
            $extendedOflags = ($flags !== null) ? $flags . ',viqc' : 'viqc';
            $request['oflags'] = $extendedOflags;
        } elseif ($isLimitOrder && !$isTrailingAmountOrder && !$isTrailingPercentOrder) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $reduceOnly = $this->safe_bool_2($params, 'reduceOnly', 'reduce_only');
        if ($isStopLossOrTakeProfitTrigger) {
            if ($isStopLossTriggerOrder) {
                $request['price'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                if ($isLimitOrder) {
                    $request['ordertype'] = 'stop-loss-limit';
                } else {
                    $request['ordertype'] = 'stop-loss';
                }
            } elseif ($isTakeProfitTriggerOrder) {
                $request['price'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                if ($isLimitOrder) {
                    $request['ordertype'] = 'take-profit-limit';
                } else {
                    $request['ordertype'] = 'take-profit';
                }
            }
            if ($isLimitOrder) {
                $request['price2'] = $this->price_to_precision($symbol, $price);
            }
        } elseif ($isTrailingAmountOrder || $isTrailingPercentOrder) {
            $trailingPercentString = null;
            if ($trailingPercent !== null) {
                $trailingPercentString = (str_ends_with($trailingPercent, '%')) ? ('+' . $trailingPercent) : ('+' . $trailingPercent . '%');
            }
            $trailingAmountString = ($trailingAmount !== null) ? '+' . $trailingAmount : null; // must use . for this
            $offset = $this->safe_string($params, 'offset', '-'); // can use . or - for this
            $trailingLimitAmountString = ($trailingLimitAmount !== null) ? $offset . $this->number_to_string($trailingLimitAmount) : null;
            $trailingActivationPriceType = $this->safe_string($params, 'trigger', 'last');
            $request['trigger'] = $trailingActivationPriceType;
            if ($isLimitOrder || ($trailingLimitAmount !== null) || ($trailingLimitPercent !== null)) {
                $request['ordertype'] = 'trailing-stop-limit';
                if ($trailingLimitPercent !== null) {
                    $trailingLimitPercentString = (str_ends_with($trailingLimitPercent, '%')) ? ($offset . $trailingLimitPercent) : ($offset . $trailingLimitPercent . '%');
                    $request['price'] = $trailingPercentString;
                    $request['price2'] = $trailingLimitPercentString;
                } elseif ($trailingLimitAmount !== null) {
                    $request['price'] = $trailingAmountString;
                    $request['price2'] = $trailingLimitAmountString;
                }
            } else {
                $request['ordertype'] = 'trailing-stop';
                if ($trailingPercent !== null) {
                    $request['price'] = $trailingPercentString;
                } else {
                    $request['price'] = $trailingAmountString;
                }
            }
        }
        if ($reduceOnly) {
            if ($method === 'createOrderWs') {
                $request['reduce_only'] = true; // ws $request can't have stringified bool
            } else {
                $request['reduce_only'] = 'true'; // not using property_exists($this, boolean) case, because the urlencodedNested transforms it into 'True' string
            }
        }
        $close = $this->safe_dict($params, 'close');
        if ($close !== null) {
            $close = $this->extend(array(), $close);
            $closePrice = $this->safe_value($close, 'price');
            if ($closePrice !== null) {
                $close['price'] = $this->price_to_precision($symbol, $closePrice);
            }
            $closePrice2 = $this->safe_value($close, 'price2'); // stopPrice
            if ($closePrice2 !== null) {
                $close['price2'] = $this->price_to_precision($symbol, $closePrice2);
            }
            $request['close'] = $close;
        }
        $timeInForce = $this->safe_string_2($params, 'timeInForce', 'timeinforce');
        if ($timeInForce !== null) {
            $request['timeinforce'] = $timeInForce;
        }
        $isMarket = ($type === 'market');
        $postOnly = null;
        list($postOnly, $params) = $this->handle_post_only($isMarket, false, $params);
        if ($postOnly) {
            $extendedPostFlags = ($flags !== null) ? $flags . ',post' : 'post';
            $request['oflags'] = $extendedPostFlags;
        }
        if (($flags !== null) && !(is_array($request) && array_key_exists('oflags', $request))) {
            $request['oflags'] = $flags;
        }
        $params = $this->omit($params, array( 'timeInForce', 'reduceOnly', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent', 'offset' ));
        return array( $request, $params );
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        /**
         * edit a trade order
         *
         * @see https://docs.kraken.com/api/docs/rest-api/amend-order
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} [$amount] how much of the currency you want to trade in units of the base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {float} [$params->stopLossPrice] the $price that a stop loss order is triggered at
         * @param {float} [$params->takeProfitPrice] the $price that a take profit order is triggered at
         * @param {string} [$params->trailingAmount] the quote $amount to trail away from the current $market $price
         * @param {string} [$params->trailingPercent] the percent to trail away from the current $market $price
         * @param {string} [$params->trailingLimitAmount] the quote $amount away from the trailingAmount
         * @param {string} [$params->trailingLimitPercent] the percent away from the trailingAmount
         * @param {string} [$params->offset] '+' or '-' whether you want the trailingLimitAmount value to be positive or negative
         * @param {boolean} [$params->postOnly] if true, the order will only be posted to the order book and not executed immediately
         * @param {string} [$params->clientOrderId] the orders client order $id
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['spot']) {
            throw new NotSupported($this->id . ' editOrder() does not support ' . $market['type'] . ' orders, only spot orders are accepted');
        }
        $request = array(
            'txid' => $id,
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'cl_ord_id');
        if ($clientOrderId !== null) {
            $request['cl_ord_id'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientOrderId', 'cl_ord_id' ));
            $request = $this->omit($request, 'txid');
        }
        $isMarket = ($type === 'market');
        $postOnly = null;
        list($postOnly, $params) = $this->handle_post_only($isMarket, false, $params);
        if ($postOnly) {
            $request['post_only'] = 'true'; // not using property_exists($this, boolean) case, because the urlencodedNested transforms it into 'True' string
        }
        if ($amount !== null) {
            $request['order_qty'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($price !== null) {
            $request['limit_price'] = $this->price_to_precision($symbol, $price);
        }
        $allTriggerPrices = $this->safe_string_n($params, array( 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent' ));
        if ($allTriggerPrices !== null) {
            $offset = $this->safe_string($params, 'offset');
            $params = $this->omit($params, array( 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent', 'offset' ));
            if ($offset !== null) {
                $allTriggerPrices = $offset . $allTriggerPrices;
                $request['trigger_price'] = $allTriggerPrices;
            } else {
                $request['trigger_price'] = $this->price_to_precision($symbol, $allTriggerPrices);
            }
        }
        $response = $this->privatePostAmendOrder ($this->extend($request, $params));
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "amend_id" => "TJSMEH-AA67V-YUSQ6O"
        //         }
        //     }
        //
        $result = $this->safe_dict($response, 'result', array());
        return $this->parse_order($result, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getOrdersInfo
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by kraken fetchOrder
         * @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();
        $clientOrderId = $this->safe_value_2($params, 'userref', 'clientOrderId');
        $request = array(
            'trades' => true, // whether or not to include trades in output (optional, default false)
            'txid' => $id, // do not comma separate a list of ids - use fetchOrdersByIds instead
            // 'userref' => 'optional', // restrict results to given user reference $id (optional)
        );
        $query = $params;
        if ($clientOrderId !== null) {
            $request['userref'] = $clientOrderId;
            $query = $this->omit($params, array( 'userref', 'clientOrderId' ));
        }
        $response = $this->privatePostQueryOrders ($this->extend($request, $query));
        //
        //     {
        //         "error":array(),
        //         "result":{
        //             "OTLAS3-RRHUF-NDWH5A":{
        //                 "refid":null,
        //                 "userref":null,
        //                 "status":"closed",
        //                 "reason":null,
        //                 "opentm":1586822919.3342,
        //                 "closetm":1586822919.365,
        //                 "starttm":0,
        //                 "expiretm":0,
        //                 "descr":array(
        //                     "pair":"XBTUSDT",
        //                     "type":"sell",
        //                     "ordertype":"market",
        //                     "price":"0",
        //                     "price2":"0",
        //                     "leverage":"none",
        //                     "order":"sell 0.21804000 XBTUSDT @ market",
        //                     "close":""
        //                 ),
        //                 "vol":"0.21804000",
        //                 "vol_exec":"0.21804000",
        //                 "cost":"1493.9",
        //                 "fee":"3.8",
        //                 "price":"6851.5",
        //                 "stopprice":"0.00000",
        //                 "limitprice":"0.00000",
        //                 "misc":"",
        //                 "oflags":"fciq",
        //                 "trades":["TT5UC3-GOIRW-6AZZ6R"]
        //             }
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        if (!(is_array($result) && array_key_exists($id, $result))) {
            throw new OrderNotFound($this->id . ' fetchOrder() could not find order $id ' . $id);
        }
        return $this->parse_order($this->extend(array( 'id' => $id ), $result[$id]));
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all the $trades made from a single order
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradesInfo
         *
         * @param {string} $id order $id
         * @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 to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
         */
        $orderTrades = $this->safe_value($params, 'trades');
        $tradeIds = array();
        if ($orderTrades === null) {
            throw new ArgumentsRequired($this->id . " fetchOrderTrades() requires a unified order structure in the $params argument or a 'trades' param (an array of trade $id strings)");
        } else {
            for ($i = 0; $i < count($orderTrades); $i++) {
                $orderTrade = $orderTrades[$i];
                if (gettype($orderTrade) === 'string') {
                    $tradeIds[] = $orderTrade;
                } else {
                    $tradeIds[] = $orderTrade['id'];
                }
            }
        }
        $this->load_markets();
        if ($symbol !== null) {
            $symbol = $this->symbol($symbol);
        }
        $options = $this->safe_value($this->options, 'fetchOrderTrades', array());
        $batchSize = $this->safe_integer($options, 'batchSize', 20);
        $numTradeIds = count($tradeIds);
        $numBatches = $this->parse_to_int($numTradeIds / $batchSize);
        $numBatches = $this->sum($numBatches, 1);
        $result = array();
        for ($j = 0; $j < $numBatches; $j++) {
            $requestIds = array();
            for ($k = 0; $k < $batchSize; $k++) {
                $index = $this->sum($j * $batchSize, $k);
                if ($index < $numTradeIds) {
                    $requestIds[] = $tradeIds[$index];
                }
            }
            $request = array(
                'txid' => implode(',', $requestIds),
            );
            $response = $this->privatePostQueryTrades ($request);
            //
            //     {
            //         "error" => array(),
            //         "result" => {
            //             'TIMIRG-WUNNE-RRJ6GT' => {
            //                 "ordertxid" => 'OQRPN2-LRHFY-HIFA7D',
            //                 "postxid" => 'TKH2SE-M7IF5-CFI7LT',
            //                 "pair" => 'USDCUSDT',
            //                 "time" => 1586340086.457,
            //                 "type" => 'sell',
            //                 "ordertype" => 'market',
            //                 "price" => '0.99860000',
            //                 "cost" => '22.16892001',
            //                 "fee" => '0.04433784',
            //                 "vol" => '22.20000000',
            //                 "margin" => '0.00000000',
            //                 "misc" => ''
            //             }
            //         }
            //     }
            //
            $rawTrades = $this->safe_value($response, 'result');
            $ids = is_array($rawTrades) ? array_keys($rawTrades) : array();
            for ($i = 0; $i < count($ids); $i++) {
                $rawTrades[$ids[$i]]['id'] = $ids[$i];
            }
            $trades = $this->parse_trades($rawTrades, null, $since, $limit);
            $tradesFilteredBySymbol = $this->filter_by_symbol($trades, $symbol);
            $result = $this->array_concat($result, $tradesFilteredBySymbol);
        }
        return $result;
    }

    public function fetch_orders_by_ids($ids, ?string $symbol = null, $params = array ()) {
        /**
         * fetch $orders by the list of $order $id
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getClosedOrders
         *
         * @param {string[]} [$ids] list of $order $id
         * @param {string} [$symbol] unified ccxt market $symbol
         * @param {array} [$params] extra parameters specific to the kraken api endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        $this->load_markets();
        $response = $this->privatePostQueryOrders ($this->extend(array(
            'trades' => true, // whether or not to include trades in output (optional, default false)
            'txid' => implode(',', $ids), // comma delimited list of transaction $ids to query info about (20 maximum)
        ), $params));
        $result = $this->safe_value($response, 'result', array());
        $orders = array();
        $orderIds = is_array($result) ? array_keys($result) : array();
        for ($i = 0; $i < count($orderIds); $i++) {
            $id = $orderIds[$i];
            $item = $result[$id];
            $order = $this->parse_order($this->extend(array( 'id' => $id ), $item));
            $orders[] = $order;
        }
        return $orders;
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $trades made by the user
         *
         * @see https://docs.kraken.com/api/docs/rest-api/get-trade-history
         *
         * @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
         * @param {int} [$params->until] timestamp in ms of the latest trade entry
         * @param {int} [$params->end] timestamp in seconds of the latest trade entry
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        $request = array(
            // 'type' => 'all', // any position, closed position, closing position, no position
            // 'trades' => false, // whether or not to include $trades related to position in output
            // 'start' => 1234567890, // starting unix timestamp or trade tx id of results (exclusive)
            // 'end' => 1234567890, // ending unix timestamp or trade tx id of results (inclusive)
            // 'ofs' = result offset
        );
        if ($since !== null) {
            $request['start'] = $this->parse_to_int($since / 1000);
        }
        $until = $this->safe_string_n($params, array( 'until', 'till' ));
        if ($until !== null) {
            $params = $this->omit($params, array( 'until', 'till' ));
            $untilDivided = Precise::string_div($until, '1000');
            $request['end'] = $this->parse_to_int(Precise::string_add($untilDivided, '1'));
        }
        $response = $this->privatePostTradesHistory ($this->extend($request, $params));
        //
        //     {
        //         "error" => array(),
        //         "result" => array(
        //             "trades" => array(
        //                 "GJ3NYQ-XJRTF-THZABF" => array(
        //                     "ordertxid" => "TKH2SE-ZIF5E-CFI7LT",
        //                     "postxid" => "OEN3VX-M7IF5-JNBJAM",
        //                     "pair" => "XICNXETH",
        //                     "time" => 1527213229.4491,
        //                     "type" => "sell",
        //                     "ordertype" => "limit",
        //                     "price" => "0.001612",
        //                     "cost" => "0.025792",
        //                     "fee" => "0.000026",
        //                     "vol" => "16.00000000",
        //                     "margin" => "0.000000",
        //                     "leverage" => "5",
        //                     "misc" => ""
        //                     "trade_id" => 68230622,
        //                     "maker" => false
        //                 ),
        //                 ...
        //             ),
        //             "count" => 9760,
        //         ),
        //     }
        //
        $trades = $response['result']['trades'];
        $ids = is_array($trades) ? array_keys($trades) : array();
        for ($i = 0; $i < count($ids); $i++) {
            $trades[$ids[$i]]['id'] = $ids[$i];
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://docs.kraken.com/api/docs/rest-api/cancel-order
         *
         * @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
         * @param {string} [$params->clientOrderId] the orders client order $id
         * @param {int} [$params->userref] the orders user reference $id
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $response = null;
        $requestId = $this->safe_value($params, 'userref', $id); // string or integer
        $params = $this->omit($params, 'userref');
        $request = array(
            'txid' => $requestId, // order $id or userref
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'cl_ord_id');
        if ($clientOrderId !== null) {
            $request['cl_ord_id'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientOrderId', 'cl_ord_id' ));
            $request = $this->omit($request, 'txid');
        }
        try {
            $response = $this->privatePostCancelOrder ($this->extend($request, $params));
            //
            //    {
            //        error => array(),
            //        result => {
            //            count => '1'
            //        }
            //    }
            //
        } catch (Exception $e) {
            if ($this->last_http_response) {
                if (mb_strpos($this->last_http_response, 'EOrder:Unknown order') !== false) {
                    throw new OrderNotFound($this->id . ' cancelOrder() error ' . $this->last_http_response);
                }
            }
            throw $e;
        }
        return $this->safe_order(array(
            'info' => $response,
        ));
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple orders
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Trading/operation/cancelOrderBatch
         *
         * @param {string[]} $ids open orders transaction ID (txid) or user reference (userref)
         * @param {string} $symbol unified market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $request = array(
            'orders' => $ids,
        );
        $response = $this->privatePostCancelOrderBatch ($this->extend($request, $params));
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //           "count" => 2
        //         }
        //     }
        //
        return array(
            $this->safe_order(array(
                'info' => $response,
            )),
        );
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Trading/operation/cancelAllOrders
         *
         * @param {string} $symbol unified market $symbol, not used by kraken cancelAllOrders (all open orders are cancelled)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $response = $this->privatePostCancelAll ($params);
        //
        //    {
        //        error => array(),
        //        result => {
        //            count => '1'
        //        }
        //    }
        //
        return array(
            $this->safe_order(array(
                'info' => $response,
            )),
        );
    }

    public function cancel_all_orders_after(?int $timeout, $params = array ()) {
        /**
         * dead man's switch, cancel all orders after the given $timeout
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Trading/operation/cancelAllOrdersAfter
         *
         * @param {number} $timeout time in milliseconds, 0 represents cancel the timer
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} the api result
         */
        if ($timeout > 86400000) {
            throw new BadRequest($this->id . ' cancelAllOrdersAfter $timeout should be less than 86400000 milliseconds');
        }
        $this->load_markets();
        $request = array(
            'timeout' => ($timeout > 0) ? ($this->parse_to_int($timeout / 1000)) : 0,
        );
        $response = $this->privatePostCancelAllOrdersAfter ($this->extend($request, $params));
        //
        //     {
        //         "error" => [ ],
        //         "result" => {
        //             "currentTime" => "2023-03-24T17:41:56Z",
        //             "triggerTime" => "2023-03-24T17:42:56Z"
        //         }
        //     }
        //
        return $response;
    }

    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://docs.kraken.com/api/docs/rest-api/get-$open-$orders
         *
         * @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
         * @param {string} [$params->clientOrderId] the $orders client order $id
         * @param {int} [$params->userref] the $orders user reference $id
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?$id=order-structure order structures~
         */
        $this->load_markets();
        $request = array();
        if ($since !== null) {
            $request['start'] = $this->parse_to_int($since / 1000);
        }
        $userref = $this->safe_integer($params, 'userref');
        if ($userref !== null) {
            $request['userref'] = $userref;
            $params = $this->omit($params, 'userref');
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['cl_ord_id'] = $clientOrderId;
            $params = $this->omit($params, 'clientOrderId');
        }
        $response = $this->privatePostOpenOrders ($this->extend($request, $params));
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             "open" => {
        //                 "O45M52-BFD5S-YXKQOU" => {
        //                     "refid" => null,
        //                     "userref" => null,
        //                     "cl_ord_id" => "1234",
        //                     "status" => "open",
        //                     "opentm" => 1733815269.370054,
        //                     "starttm" => 0,
        //                     "expiretm" => 0,
        //                     "descr" => array(
        //                         "pair" => "XBTUSD",
        //                         "type" => "buy",
        //                         "ordertype" => "limit",
        //                         "price" => "70000.0",
        //                         "price2" => "0",
        //                         "leverage" => "none",
        //                         "order" => "buy 0.00010000 XBTUSD @ $limit 70000.0",
        //                         "close" => ""
        //                     ),
        //                     "vol" => "0.00010000",
        //                     "vol_exec" => "0.00000000",
        //                     "cost" => "0.00000",
        //                     "fee" => "0.00000",
        //                     "price" => "0.00000",
        //                     "stopprice" => "0.00000",
        //                     "limitprice" => "0.00000",
        //                     "misc" => "",
        //                     "oflags" => "fciq"
        //                 }
        //             }
        //         }
        //     }
        //
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $result = $this->safe_dict($response, 'result', array());
        $open = $this->safe_dict($result, 'open', array());
        $orders = array();
        $orderIds = is_array($open) ? array_keys($open) : array();
        for ($i = 0; $i < count($orderIds); $i++) {
            $id = $orderIds[$i];
            $item = $open[$id];
            $orders[] = $this->extend(array( 'id' => $id ), $item);
        }
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple $closed $orders made by the user
         *
         * @see https://docs.kraken.com/api/docs/rest-api/get-$closed-$orders
         *
         * @param {string} [$symbol] unified $market $symbol of the $market $orders were made in
         * @param {int} [$since] the earliest time in ms to fetch $orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest entry
         * @param {string} [$params->clientOrderId] the $orders client order $id
         * @param {int} [$params->userref] the $orders user reference $id
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?$id=order-structure order structures~
         */
        $this->load_markets();
        $request = array();
        if ($since !== null) {
            $request['start'] = $this->parse_to_int($since / 1000);
        }
        $userref = $this->safe_integer($params, 'userref');
        if ($userref !== null) {
            $request['userref'] = $userref;
            $params = $this->omit($params, 'userref');
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['cl_ord_id'] = $clientOrderId;
            $params = $this->omit($params, 'clientOrderId');
        }
        list($request, $params) = $this->handle_until_option('end', $request, $params);
        $response = $this->privatePostClosedOrders ($this->extend($request, $params));
        //
        //     {
        //         "error":array(),
        //         "result":{
        //             "closed":array(
        //                 "OETZYO-UL524-QJMXCT":array(
        //                     "refid":null,
        //                     "userref":null,
        //                     "status":"canceled",
        //                     "reason":"User requested",
        //                     "opentm":1601489313.3898,
        //                     "closetm":1601489346.5507,
        //                     "starttm":0,
        //                     "expiretm":0,
        //                     "descr":array(
        //                         "pair":"ETHUSDT",
        //                         "type":"buy",
        //                         "ordertype":"limit",
        //                         "price":"330.00",
        //                         "price2":"0",
        //                         "leverage":"none",
        //                         "order":"buy 0.02100000 ETHUSDT @ $limit 330.00",
        //                         "close":""
        //                     ),
        //                     "vol":"0.02100000",
        //                     "vol_exec":"0.00000000",
        //                     "cost":"0.00000",
        //                     "fee":"0.00000",
        //                     "price":"0.00000",
        //                     "stopprice":"0.00000",
        //                     "limitprice":"0.00000",
        //                     "misc":"",
        //                     "oflags":"fciq"
        //                 ),
        //             ),
        //             "count":16
        //         }
        //     }
        //
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $result = $this->safe_dict($response, 'result', array());
        $closed = $this->safe_dict($result, 'closed', array());
        $orders = array();
        $orderIds = is_array($closed) ? array_keys($closed) : array();
        for ($i = 0; $i < count($orderIds); $i++) {
            $id = $orderIds[$i];
            $item = $closed[$id];
            $orders[] = $this->extend(array( 'id' => $id ), $item);
        }
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function parse_transaction_status(?string $status) {
        // IFEX transaction states
        $statuses = array(
            'Initial' => 'pending',
            'Pending' => 'pending',
            'Success' => 'ok',
            'Settled' => 'pending',
            'Failure' => 'failed',
            'Partial' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_network($network) {
        $withdrawMethods = $this->safe_value($this->options, 'withdrawMethods', array());
        return $this->safe_string($withdrawMethods, $network, $network);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fetchDeposits
        //
        //     {
        //         "method" => "Ether (Hex)",
        //         "aclass" => "currency",
        //         "asset" => "XETH",
        //         "refid" => "Q2CANKL-LBFVEE-U4Y2WQ",
        //         "txid" => "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
        //         "info" => "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
        //         "amount" => "7.9999257900",
        //         "fee" => "0.0000000000",
        //         "time" =>  1529223212,
        //         "status" => "Success"
        //     }
        //
        // there can be an additional 'status-prop' field present
        // deposit pending review by exchange => 'on-hold'
        // the deposit is initiated by the exchange => 'return'
        //
        //      {
        //          "type" => 'deposit',
        //          "method" => 'Fidor Bank AG (Wire Transfer)',
        //          "aclass" => 'currency',
        //          "asset" => 'ZEUR',
        //          "refid" => 'xxx-xxx-xxx',
        //          "txid" => '12341234',
        //          "info" => 'BANKCODEXXX',
        //          "amount" => '38769.08',
        //          "fee" => '0.0000',
        //          "time" => 1644306552,
        //          "status" => 'Success',
        //          $status-prop => 'on-hold'
        //      }
        //
        //
        // fetchWithdrawals
        //
        //     {
        //         "method" => "Ether",
        //         "aclass" => "currency",
        //         "asset" => "XETH",
        //         "refid" => "A2BF34S-O7LBNQ-UE4Y4O",
        //         "txid" => "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d",
        //         "info" => "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
        //         "amount" => "9.9950000000",
        //         "fee" => "0.0050000000",
        //         "time" =>  1530481750,
        //         "status" => "Success"
        //         "key":"Huobi wallet",
        //         "network":"Tron"
        //         $status-prop => 'on-hold' // this field might not be present in some cases
        //     }
        //
        // withdraw
        //
        //     {
        //         "refid" => "AGBSO6T-UFMTTQ-I7KGS6"
        //     }
        //
        $id = $this->safe_string($transaction, 'refid');
        $txid = $this->safe_string($transaction, 'txid');
        $timestamp = $this->safe_timestamp($transaction, 'time');
        $currencyId = $this->safe_string($transaction, 'asset');
        $code = $this->safe_currency_code($currencyId, $currency);
        $address = $this->safe_string($transaction, 'info');
        $amount = $this->safe_number($transaction, 'amount');
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $statusProp = $this->safe_string($transaction, 'status-prop');
        $isOnHoldDeposit = $statusProp === 'on-hold';
        $isCancellationRequest = $statusProp === 'cancel-pending';
        $isOnHoldWithdrawal = $statusProp === 'onhold';
        if ($isOnHoldDeposit || $isCancellationRequest || $isOnHoldWithdrawal) {
            $status = 'pending';
        }
        $type = $this->safe_string($transaction, 'type'); // injected from the outside
        $feeCost = $this->safe_number($transaction, 'fee');
        if ($feeCost === null) {
            if ($type === 'deposit') {
                $feeCost = 0;
            }
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'currency' => $code,
            'amount' => $amount,
            'network' => $this->parse_network($this->safe_string($transaction, 'network')),
            'address' => $address,
            'addressTo' => null,
            'addressFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'status' => $status,
            'type' => $type,
            'updated' => null,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'comment' => null,
            'internal' => null,
            'fee' => array(
                'currency' => $code,
                'cost' => $feeCost,
            ),
        );
    }

    public function parse_transactions_by_type($type, $transactions, ?string $code = null, ?int $since = null, ?int $limit = null) {
        $result = array();
        for ($i = 0; $i < count($transactions); $i++) {
            $transaction = $this->parse_transaction($this->extend(array(
                'type' => $type,
            ), $transactions[$i]));
            $result[] = $transaction;
        }
        return $this->filter_by_currency_since_limit($result, $code, $since, $limit);
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/getStatusRecentDeposits
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest transaction entry
         * @param {int} [$params->end] timestamp in seconds of the latest transaction entry
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        // https://www.kraken.com/en-us/help/api#deposit-status
        $this->load_markets();
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['asset'] = $currency['id'];
        }
        if ($since !== null) {
            $sinceString = $this->number_to_string($since);
            $request['start'] = Precise::string_div($sinceString, '1000');
        }
        $until = $this->safe_string_n($params, array( 'until', 'till' ));
        if ($until !== null) {
            $params = $this->omit($params, array( 'until', 'till' ));
            $untilDivided = Precise::string_div($until, '1000');
            $request['end'] = Precise::string_add($untilDivided, '1');
        }
        $response = $this->privatePostDepositStatus ($this->extend($request, $params));
        //
        //     {  error => array(),
        //       "result" => array( { "method" => "Ether (Hex)",
        //                     "aclass" => "currency",
        //                      "asset" => "XETH",
        //                      "refid" => "Q2CANKL-LBFVEE-U4Y2WQ",
        //                       "txid" => "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
        //                       "info" => "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
        //                     "amount" => "7.9999257900",
        //                        "fee" => "0.0000000000",
        //                       "time" =>  1529223212,
        //                     "status" => "Success"                                                       } ) }
        //
        return $this->parse_transactions_by_type('deposit', $response['result'], $code, $since, $limit);
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getServerTime
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        // https://www.kraken.com/en-us/features/api#get-server-time
        $response = $this->publicGetTime ($params);
        //
        //    {
        //        "error" => array(),
        //        "result" => {
        //            "unixtime" => 1591502873,
        //            "rfc1123" => "Sun,  7 Jun 20 04:07:53 +0000"
        //        }
        //    }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->safe_timestamp($result, 'unixtime');
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/getStatusRecentWithdrawals
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch withdrawals for
         * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest transaction entry
         * @param {int} [$params->end] timestamp in seconds of the latest transaction entry
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'paginate');
        if ($paginate) {
            $params['cursor'] = true;
            return $this->fetch_paginated_call_cursor('fetchWithdrawals', $code, $since, $limit, $params, 'next_cursor', 'cursor');
        }
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['asset'] = $currency['id'];
        }
        if ($since !== null) {
            $sinceString = $this->number_to_string($since);
            $request['start'] = Precise::string_div($sinceString, '1000');
        }
        $until = $this->safe_string_n($params, array( 'until', 'till' ));
        if ($until !== null) {
            $params = $this->omit($params, array( 'until', 'till' ));
            $untilDivided = Precise::string_div($until, '1000');
            $request['end'] = Precise::string_add($untilDivided, '1');
        }
        $response = $this->privatePostWithdrawStatus ($this->extend($request, $params));
        //
        // with no pagination
        //     {  error => array(),
        //       "result" => array( { "method" => "Ether",
        //                     "aclass" => "currency",
        //                      "asset" => "XETH",
        //                      "refid" => "A2BF34S-O7LBNQ-UE4Y4O",
        //                       "txid" => "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d",
        //                       "info" => "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
        //                     "amount" => "9.9950000000",
        //                        "fee" => "0.0050000000",
        //                       "time" =>  1530481750,
        //                     "status" => "Success"                                                             } ) }
        // with pagination
        //    {
        //        "error":array(),
        //        "result":{
        //           "withdrawals":array(
        //              array(
        //                 "method":"Tether USD (TRC20)",
        //                 "aclass":"currency",
        //                 "asset":"USDT",
        //                 "refid":"BSNFZU2-MEFN4G-J3NEZV",
        //                 "txid":"1c7a642fb7387bbc2c6a2c509fd1ae146937f4cf793b4079a4f0715e3a02615a",
        //                 "info":"TQmdxSuC16EhFg8FZWtYgrfFRosoRF7bCp",
        //                 "amount":"1996.50000000",
        //                 "fee":"2.50000000",
        //                 "time":1669126657,
        //                 "status":"Success",
        //                 "key":"poloniex",
        //                 "network":"Tron"
        //              ),
        //             ...
        //           ),
        //           "next_cursor":"HgAAAAAAAABGVFRSd3k1LVF4Y0JQY05Gd0xRY0NxenFndHpybkwBAQH2AwEBAAAAAQAAAAAAAAABAAAAAAAZAAAAAAAAAA=="
        //        }
        //     }
        //
        $rawWithdrawals = null;
        $result = $this->safe_value($response, 'result');
        if (gettype($result) !== 'array' || array_keys($result) !== array_keys(array_keys($result))) {
            $rawWithdrawals = $this->add_pagination_cursor_to_result($result);
        } else {
            $rawWithdrawals = $result;
        }
        return $this->parse_transactions_by_type('withdrawal', $rawWithdrawals, $code, $since, $limit);
    }

    public function add_pagination_cursor_to_result($result) {
        $cursor = $this->safe_string($result, 'next_cursor');
        $data = $this->safe_value($result, 'withdrawals');
        $dataLength = count($data);
        if ($cursor !== null && $dataLength > 0) {
            $last = $data[$dataLength - 1];
            $last['next_cursor'] = $cursor;
            $data[$dataLength - 1] = $last;
        }
        return $data;
    }

    public function create_deposit_address(string $code, $params = array ()): array {
        /**
         * create a currency deposit address
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/getDepositAddresses
         *
         * @param {string} $code unified currency $code of the currency for the deposit address
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $request = array(
            'new' => 'true',
        );
        return $this->fetch_deposit_address($code, $this->extend($request, $params));
    }

    public function fetch_deposit_methods(string $code, $params = array ()) {
        /**
         * fetch deposit methods for a $currency associated with this account
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/getDepositMethods
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the kraken api endpoint
         * @return {array} of deposit methods
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset' => $currency['id'],
        );
        $response = $this->privatePostDepositMethods ($this->extend($request, $params));
        //
        //     {
        //         "error":array(),
        //         "result":array(
        //             array("method":"Ether (Hex)","limit":false,"gen-address":true)
        //         )
        //     }
        //
        //     {
        //         "error":array(),
        //         "result":array(
        //             array("method":"Tether USD (ERC20)","limit":false,"address-setup-fee":"0.00000000","gen-address":true),
        //             array("method":"Tether USD (TRC20)","limit":false,"address-setup-fee":"0.00000000","gen-address":true)
        //         )
        //     }
        //
        //     {
        //         "error":array(),
        //         "result":array(
        //             array("method":"Bitcoin","limit":false,"fee":"0.0000000000","gen-address":true)
        //         )
        //     }
        //
        return $this->safe_value($response, 'result');
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/getDepositAddresses
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $network = $this->safe_string_upper($params, 'network');
        $networks = $this->safe_value($this->options, 'networks', array());
        $network = $this->safe_string($networks, $network, $network); // support ETH > ERC20 aliases
        $params = $this->omit($params, 'network');
        if (($code === 'USDT') && ($network === 'TRC20')) {
            $code = $code . '-' . $network;
        }
        $defaultDepositMethods = $this->safe_value($this->options, 'depositMethods', array());
        $defaultDepositMethod = $this->safe_string($defaultDepositMethods, $code);
        $depositMethod = $this->safe_string($params, 'method', $defaultDepositMethod);
        // if the user has specified an exchange-specific method in $params
        // we pass it, otherwise we take the 'network' unified param
        if ($depositMethod === null) {
            $depositMethods = $this->fetch_deposit_methods($code);
            if ($network !== null) {
                // find best matching deposit method, or fallback to the first one
                for ($i = 0; $i < count($depositMethods); $i++) {
                    $entry = $this->safe_string($depositMethods[$i], 'method');
                    if (mb_strpos($entry, $network) !== false) {
                        $depositMethod = $entry;
                        break;
                    }
                }
            }
            // if $depositMethod was not specified, fallback to the first available deposit method
            if ($depositMethod === null) {
                $firstDepositMethod = $this->safe_value($depositMethods, 0, array());
                $depositMethod = $this->safe_string($firstDepositMethod, 'method');
            }
        }
        $request = array(
            'asset' => $currency['id'],
            'method' => $depositMethod,
        );
        $response = $this->privatePostDepositAddresses ($this->extend($request, $params));
        //
        //     {
        //         "error":array(),
        //         "result":array(
        //             array("address":"0x77b5051f97efa9cc52c9ad5b023a53fc15c200d3","expiretm":"0")
        //         )
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $firstResult = $this->safe_value($result, 0, array());
        if ($firstResult === null) {
            throw new InvalidAddress($this->id . ' privatePostDepositAddresses() returned no addresses for ' . $code);
        }
        return $this->parse_deposit_address($firstResult, $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "address":"0x77b5051f97efa9cc52c9ad5b023a53fc15c200d3",
        //         "expiretm":"0"
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $tag = $this->safe_string($depositAddress, 'tag');
        $currency = $this->safe_currency(null, $currency);
        $code = $currency['code'];
        $this->check_address($address);
        return array(
            'info' => $depositAddress,
            'currency' => $code,
            'network' => null,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://docs.kraken.com/rest/#tag/Funding/operation/withdrawFunds
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to, not required can be '' or null/none/null
         * @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);
        if (is_array($params) && array_key_exists('key', $params)) {
            $this->load_markets();
            $currency = $this->currency($code);
            $request = array(
                'asset' => $currency['id'],
                'amount' => $amount,
                // 'address' => $address,
            );
            if ($address !== null && $address !== '') {
                $request['address'] = $address;
                $this->check_address($address);
            }
            $response = $this->privatePostWithdraw ($this->extend($request, $params));
            //
            //     {
            //         "error" => array(),
            //         "result" => {
            //             "refid" => "AGBSO6T-UFMTTQ-I7KGS6"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            return $this->parse_transaction($result, $currency);
        }
        throw new ExchangeError($this->id . " withdraw() requires a 'key' parameter (withdrawal key name, up on your account)");
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         *
         * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getOpenPositions
         *
         * @param {string[]} [$symbols] not used by kraken fetchPositions ()
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->load_markets();
        $request = array(
            // 'txid' => 'comma delimited list of transaction ids to restrict output to',
            'docalcs' => 'true', // whether or not to include profit/loss calculations
            'consolidation' => 'market', // what to consolidate the positions data around, market will consolidate positions based on market pair
        );
        $response = $this->privatePostOpenPositions ($this->extend($request, $params));
        //
        // no consolidation
        //
        //     {
        //         "error" => array(),
        //         "result" => {
        //             'TGUFMY-FLESJ-VYIX3J' => {
        //                 "ordertxid" => "O3LRNU-ZKDG5-XNCDFR",
        //                 "posstatus" => "open",
        //                 "pair" => "ETHUSDT",
        //                 "time" =>  1611557231.4584,
        //                 "type" => "buy",
        //                 "ordertype" => "market",
        //                 "cost" => "28.49800",
        //                 "fee" => "0.07979",
        //                 "vol" => "0.02000000",
        //                 "vol_closed" => "0.00000000",
        //                 "margin" => "14.24900",
        //                 "terms" => "0.0200% per 4 hours",
        //                 "rollovertm" => "1611571631",
        //                 "misc" => "",
        //                 "oflags" => ""
        //             }
        //         }
        //     }
        //
        // consolidation by market
        //
        //     {
        //         "error" => array(),
        //         "result" => array(
        //             {
        //                 "pair" => "ETHUSDT",
        //                 "positions" => "1",
        //                 "type" => "buy",
        //                 "leverage" => "2.00000",
        //                 "cost" => "28.49800",
        //                 "fee" => "0.07979",
        //                 "vol" => "0.02000000",
        //                 "vol_closed" => "0.00000000",
        //                 "margin" => "14.24900"
        //             }
        //         )
        //     }
        //
        $symbols = $this->market_symbols($symbols);
        $result = $this->safe_list($response, 'result');
        $results = $this->parse_positions($result, $symbols);
        return $this->filter_by_array_positions($results, 'symbol', $symbols, false);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //             {
        //                 "pair" => "ETHUSDT",
        //                 "positions" => "1",
        //                 "type" => "buy",
        //                 "leverage" => "2.00000",
        //                 "cost" => "28.49800",
        //                 "fee" => "0.07979",
        //                 "vol" => "0.02000000",
        //                 "vol_closed" => "0.00000000",
        //                 "margin" => "14.24900"
        //             }
        //
        $marketId = $this->safe_string($position, 'pair');
        $rawSide = $this->safe_string($position, 'type');
        $side = ($rawSide === 'buy') ? 'long' : 'short';
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $this->safe_symbol($marketId, $market),
            'notional' => null,
            'marginMode' => null,
            'liquidationPrice' => null,
            'entryPrice' => null,
            'unrealizedPnl' => $this->safe_number($position, 'net'),
            'realizedPnl' => null,
            'percentage' => null,
            'contracts' => $this->safe_number($position, 'vol'),
            'contractSize' => null,
            'markPrice' => null,
            'lastPrice' => null,
            'side' => $side,
            'hedged' => null,
            'timestamp' => null,
            'datetime' => null,
            'lastUpdateTimestamp' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'collateral' => null,
            'initialMargin' => $this->safe_number($position, 'margin'),
            'initialMarginPercentage' => null,
            'leverage' => $this->safe_number($position, 'leverage'),
            'marginRatio' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function parse_account_type($account) {
        $accountByType = array(
            'spot' => 'Spot Wallet',
            'swap' => 'Futures Wallet',
            'future' => 'Futures Wallet',
        );
        return $this->safe_string($accountByType, $account, $account);
    }

    public function transfer_out(string $code, $amount, $params = array ()) {
        /**
         * transfer from spot wallet to futures wallet
         *
         * @see https://docs.kraken.com/rest/#tag/User-Funding/operation/walletTransfer
         *
         * @param {str} $code Unified currency $code
         * @param {float} $amount Size of the transfer
         * @param {dict} [$params] Exchange specific parameters
         * @return a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
         */
        return $this->transfer($code, $amount, 'spot', 'swap', $params);
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         *
         * @see https://docs.kraken.com/rest/#tag/User-Funding/operation/walletTransfer
         *
         * transfers currencies between sub-accounts (only spot->swap direction is supported)
         * @param {string} $code Unified $currency $code
         * @param {float} $amount Size of the $transfer
         * @param {string} $fromAccount 'spot' or 'Spot Wallet'
         * @param {string} $toAccount 'swap' or 'Futures Wallet'
         * @param {array} [$params] Exchange specific parameters
         * @return a ~@link https://docs.ccxt.com/#/?id=$transfer-structure $transfer structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $fromAccount = $this->parse_account_type($fromAccount);
        $toAccount = $this->parse_account_type($toAccount);
        $request = array(
            'amount' => $this->currency_to_precision($code, $amount),
            'from' => $fromAccount,
            'to' => $toAccount,
            'asset' => $currency['id'],
        );
        if ($fromAccount !== 'Spot Wallet') {
            throw new BadRequest($this->id . ' $transfer cannot $transfer from ' . $fromAccount . ' to ' . $toAccount . '. Use krakenfutures instead to $transfer from the futures account.');
        }
        $response = $this->privatePostWalletTransfer ($this->extend($request, $params));
        //
        //   {
        //       "error":array(
        //       ),
        //       "result":{
        //          "refid":"BOIUSIF-M7DLMN-UXZ3P5"
        //       }
        //   }
        //
        $transfer = $this->parse_transfer($response, $currency);
        return $this->extend($transfer, array(
            'amount' => $amount,
            'fromAccount' => $fromAccount,
            'toAccount' => $toAccount,
        ));
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // $transfer
        //
        //    {
        //        "error":array(
        //        ),
        //        "result":{
        //           "refid":"BOIUSIF-M7DLMN-UXZ3P5"
        //        }
        //    }
        //
        $result = $this->safe_value($transfer, 'result', array());
        $refid = $this->safe_string($result, 'refid');
        return array(
            'info' => $transfer,
            'id' => $refid,
            'timestamp' => null,
            'datetime' => null,
            'currency' => $this->safe_string($currency, 'code'),
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => 'sucess',
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = '/' . $this->version . '/' . $api . '/' . $path;
        if ($api === 'public') {
            if ($params) {
                // urlencodeNested is used to address https://github.com/ccxt/ccxt/issues/12872
                $url .= '?' . $this->urlencode_nested($params);
            }
        } elseif ($api === 'private') {
            $price = $this->safe_string($params, 'price');
            $isTriggerPercent = false;
            if ($price !== null) {
                $isTriggerPercent = (str_ends_with($price, '%')) ? true : false;
            }
            $isCancelOrderBatch = ($path === 'CancelOrderBatch');
            $this->check_required_credentials();
            $nonce = (string) $this->nonce();
            // urlencodeNested is used to address https://github.com/ccxt/ccxt/issues/12872
            if ($isCancelOrderBatch || $isTriggerPercent) {
                $body = $this->json($this->extend(array( 'nonce' => $nonce ), $params));
            } else {
                $body = $this->urlencode_nested($this->extend(array( 'nonce' => $nonce ), $params));
            }
            $auth = $this->encode($nonce . $body);
            $hash = $this->hash($auth, 'sha256', 'binary');
            $binary = $this->encode($url);
            $binhash = $this->binary_concat($binary, $hash);
            $secret = base64_decode($this->secret);
            $signature = $this->hmac($binhash, $secret, 'sha512', 'base64');
            $headers = array(
                'API-Key' => $this->apiKey,
                'API-Sign' => $signature,
            );
            if ($isCancelOrderBatch || $isTriggerPercent) {
                $headers['Content-Type'] = 'application/json';
            } else {
                $headers['Content-Type'] = 'application/x-www-form-urlencoded';
            }
        } else {
            $url = '/' . $path;
        }
        $url = $this->urls['api'][$api] . $url;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function nonce() {
        return $this->milliseconds() - $this->options['timeDifference'];
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($code === 520) {
            throw new ExchangeNotAvailable($this->id . ' ' . (string) $code . ' ' . $reason);
        }
        if ($response === null) {
            return null;
        }
        if ($body[0] === '{') {
            if (gettype($response) !== 'string') {
                if (is_array($response) && array_key_exists('error', $response)) {
                    $numErrors = count($response['error']);
                    if ($numErrors) {
                        $message = $this->id . ' ' . $body;
                        for ($i = 0; $i < count($response['error']); $i++) {
                            $error = $response['error'][$i];
                            $this->throw_exactly_matched_exception($this->exceptions['exact'], $error, $message);
                            $this->throw_broadly_matched_exception($this->exceptions['broad'], $error, $message);
                        }
                        throw new ExchangeError($message);
                    }
                }
            }
        }
        return null;
    }
}
