# -*- coding: utf-8 -*-

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

from ccxt_zagrava.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.coinbaseinternational import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, Market, Order, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade, Transaction, TransferEntry
from typing import List
from typing import Any
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import DuplicateOrderId
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class coinbaseinternational(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(coinbaseinternational, self).describe(), {
            'id': 'coinbaseinternational',
            'name': 'Coinbase International',
            'countries': ['US'],
            'certified': True,
            'pro': True,
            'rateLimit': 100,  # 10 requests per second
            'version': 'v1',
            'userAgent': self.userAgents['chrome'],
            'headers': {
                'CB-VERSION': '2018-05-30',
            },
            'has': {
                'CORS': True,
                'spot': True,
                'margin': True,
                'swap': True,
                'future': True,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': False,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': True,
                'createLimitBuyOrder': True,
                'createLimitSellOrder': True,
                'createMarketBuyOrder': True,
                'createMarketBuyOrderWithCost': False,
                'createMarketOrderWithCost': False,
                'createMarketSellOrder': True,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createPostOnlyOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'editOrder': True,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchBidsAsks': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCanceledOrders': False,
                'fetchClosedOrders': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchL2OrderBook': False,
                'fetchLedger': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginAdjustmentHistory': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyBuys': True,
                'fetchMySells': True,
                'fetchMyTrades': True,
                'fetchOHLCV': False,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': False,
                'fetchOrders': False,
                'fetchPosition': True,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': True,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': False,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'sandbox': True,
                'setLeverage': False,
                'setMargin': True,
                'setMarginMode': False,
                'setPositionMode': False,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://github.com/ccxt_zagrava/ccxt_zagrava/assets/43336371/866ae638-6ab5-4ebf-ab2c-cdcce9545625',
                'api': {
                    'rest': 'https://api.international.coinbase.com/api',
                },
                'test': {
                    'rest': 'https://api-n5e1.coinbase.com/api',
                },
                'www': 'https://international.coinbase.com',
                'doc': [
                    'https://docs.cloud.coinbase.com/intx/docs',
                ],
                'fees': [
                    'https://help.coinbase.com/en/international-exchange/trading-deposits-withdrawals/international-exchange-fees',
                ],
                'referral': '',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'password': True,
            },
            'api': {
                'v1': {
                    'public': {
                        'get': [
                            'assets',
                            'assets/{assets}',
                            'assets/{asset}/networks',
                            'instruments',
                            'instruments/{instrument}',
                            'instruments/{instrument}/quote',
                            'instruments/{instrument}/funding',
                            '',
                        ],
                    },
                    'private': {
                        'get': [
                            'orders',
                            'orders/{id}',
                            'portfolios',
                            'portfolios/{portfolio}',
                            'portfolios/{portfolio}/detail',
                            'portfolios/{portfolio}/summary',
                            'portfolios/{portfolio}/balances',
                            'portfolios/{portfolio}/balances/{asset}',
                            'portfolios/{portfolio}/positions',
                            'portfolios/{portfolio}/positions/{instrument}',
                            'portfolios/fills',
                            'portfolios/{portfolio}/fills',
                            'transfers',
                            'transfers/{transfer_uuid}',
                        ],
                        'post': [
                            'orders',
                            'portfolios',
                            'portfolios/margin',
                            'portfolios/transfer',
                            'transfers/withdraw',
                            'transfers/address',
                            'transfers/create-counterparty-id',
                            'transfers/validate-counterparty-id',
                            'transfers/withdraw/counterparty',
                        ],
                        'put': [
                            'orders/{id}',
                            'portfolios/{portfolio}',
                        ],
                        'delete': [
                            'orders',
                            'orders/{id}',
                        ],
                    },
                },
            },
            'fees': {
                'trading': {
                    'taker': self.parse_number('0.004'),
                    'maker': self.parse_number('0.002'),
                    'tierBased': True,
                    'percentage': True,
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.004')],
                            [self.parse_number('1000000'), self.parse_number('0.004')],
                            [self.parse_number('5000000'), self.parse_number('0.0035')],
                            [self.parse_number('10000000'), self.parse_number('0.0035')],
                            [self.parse_number('50000000'), self.parse_number('0.003')],
                            [self.parse_number('250000000'), self.parse_number('0.0025')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.002')],
                            [self.parse_number('1000000'), self.parse_number('0.0016')],
                            [self.parse_number('5000000'), self.parse_number('0.001')],
                            [self.parse_number('10000000'), self.parse_number('0.0008')],
                            [self.parse_number('50000000'), self.parse_number('0.0005')],
                            [self.parse_number('250000000'), self.parse_number('0')],
                        ],
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {},
                'broad': {
                    'DUPLICATE_CLIENT_ORDER_ID': DuplicateOrderId,
                    'Order rejected': InvalidOrder,
                    'market orders must be IoC': InvalidOrder,
                    'tif is required': InvalidOrder,
                    'Invalid replace order request': InvalidOrder,
                    'Unauthorized': PermissionDenied,
                    'invalid result_limit': BadRequest,
                    'is a required field': BadRequest,
                    'Not Found': BadRequest,
                    'ip not allowed': AuthenticationError,
                },
            },
            'timeframes': {
                '1m': 'ONE_MINUTE',
                '5m': 'FIVE_MINUTE',
                '15m': 'FIFTEEN_MINUTE',
                '30m': 'THIRTY_MINUTE',
                '1h': 'ONE_HOUR',
                '2h': 'TWO_HOUR',
                '6h': 'SIX_HOUR',
                '1d': 'ONE_DAY',
            },
            'options': {
                'brokerId': 'nfqkvdjp',
                'portfolio': '',  # default portfolio id
                'withdraw': {
                    'method': 'v1PrivatePostTransfersWithdraw',  # use v1PrivatePostTransfersWithdrawCounterparty for counterparty withdrawals
                },
                'networksById': {
                    'ethereum': 'ETH',
                    'arbitrum': 'ARBITRUM',
                    'avacchain': 'AVAX',
                    'optimism': 'OPTIMISM',
                    'polygon': 'MATIC',
                    'solana': 'SOL',
                    'bitcoin': 'BTC',
                },
            },
        })

    async def handle_portfolio_and_params(self, methodName: str, params={}):
        portfolio = None
        portfolio, params = self.handle_option_and_params(params, methodName, 'portfolio')
        if (portfolio is not None) and (portfolio != ''):
            return [portfolio, params]
        defaultPortfolio = self.safe_string(self.options, 'portfolio')
        if (defaultPortfolio is not None) and (defaultPortfolio != ''):
            return [defaultPortfolio, params]
        accounts = await self.fetch_accounts()
        for i in range(0, len(accounts)):
            account = accounts[i]
            info = self.safe_dict(account, 'info', {})
            if self.safe_bool(info, 'is_default'):
                portfolioId = self.safe_string(info, 'portfolio_id')
                self.options['portfolio'] = portfolioId
                return [portfolioId, params]
        raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a portfolio parameter or set the default portfolio with self.options["portfolio"]')

    async def handle_network_id_and_params(self, currencyCode: str, methodName: str, params):
        networkId = None
        networkId, params = self.handle_option_and_params(params, methodName, 'network_arn_id')
        if networkId is None:
            await self.load_currency_networks(currencyCode)
            networks = self.currencies[currencyCode]['networks']
            network = self.safe_string_2(params, 'networkCode', 'network')
            if network is None:
                # find default network
                if self.is_empty(networks):
                    raise BadRequest(self.id + ' createDepositAddress network not found for currency ' + currencyCode + ' please specify networkId in params')
                defaultNetwork = self.find_default_network(networks)
                networkId = defaultNetwork['id']
            else:
                networkId = self.network_code_to_id(network, currencyCode)
        return [networkId, params]

    async def fetch_accounts(self, params={}):
        """
        fetch all the accounts associated with a profile
        :see: https://docs.cloud.coinbase.com/intx/reference/getportfolios
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `account structures <https://docs.ccxt_zagrava.com/#/?id=account-structure>` indexed by the account type
        """
        await self.load_markets()
        response = await self.v1PrivateGetPortfolios(params)
        #
        #    [
        #        {
        #           "portfolio_id":"1ap32qsc-1-0",
        #           "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
        #           "name":"ccxt_zagrava Portfolio 030624-17:16",
        #           "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
        #           "maker_fee_rate":"0",
        #           "taker_fee_rate":"0.0002",
        #           "trading_lock":false,
        #           "borrow_disabled":false,
        #           "is_lsp":false,
        #           "is_default":true,
        #           "cross_collateral_enabled":false
        #        }
        #    ]
        #
        return self.parse_accounts(response, params)

    def parse_account(self, account):
        #
        #    {
        #       "portfolio_id":"1ap32qsc-1-0",
        #       "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
        #       "name":"ccxt_zagrava Portfolio 030624-17:16",
        #       "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
        #       "maker_fee_rate":"0",
        #       "taker_fee_rate":"0.0002",
        #       "trading_lock":false,
        #       "borrow_disabled":false,
        #       "is_lsp":false,
        #       "is_default":true,
        #       "cross_collateral_enabled":false
        #    }
        #
        return {
            'id': self.safe_string_2(account, 'portfolio_id', 'portfolio_uuid'),
            'type': None,
            'code': None,
            'info': account,
        }

    async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches historical funding rate prices
        :see: https://docs.cloud.coinbase.com/intx/reference/getinstrumentfunding
        :param str symbol: unified symbol of the market to fetch the funding rate history for
        :param int [since]: timestamp in ms of the earliest funding rate to fetch
        :param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt_zagrava.com/#/?id=funding-rate-history-structure>` to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
        maxEntriesPerRequest = None
        maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'maxEntriesPerRequest', 100)
        pageKey = 'ccxt_zagravaPageKey'
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchFundingRateHistory', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
        market = self.market(symbol)
        page = self.safe_integer(params, pageKey, 1) - 1
        request: dict = {
            'instrument': market['id'],
            'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
        }
        if limit is not None:
            request['result_limit'] = limit
        response = await self.v1PublicGetInstrumentsInstrumentFunding(self.extend(request, params))
        #
        #    {
        #        "pagination":{
        #           "result_limit":"25",
        #           "result_offset":"0"
        #        },
        #        "results":[
        #           {
        #              "instrument_id":"149264167780483072",
        #              "funding_rate":"0.000011",
        #              "mark_price":"47388.1",
        #              "event_time":"2024-02-10T16:00:00Z"
        #           },
        #           ...
        #        ]
        #    }
        #
        rawRates = self.safe_list(response, 'results', [])
        return self.parse_funding_rate_histories(rawRates, market, since, limit)

    def parse_funding_rate_history(self, info, market: Market = None):
        return self.parse_funding_rate(info, market)

    def parse_funding_rate(self, contract, market: Market = None):
        #
        #    {
        #       "instrument_id":"149264167780483072",
        #       "funding_rate":"0.000011",
        #       "mark_price":"47388.1",
        #       "event_time":"2024-02-10T16:00:00Z"
        #    }
        #
        fundingDatetime = self.safe_string_2(contract, 'event_time', 'time')
        return {
            'info': contract,
            'symbol': self.safe_symbol(None, market),
            'markPrice': self.safe_number(contract, 'mark_price'),
            'indexPrice': None,
            'interestRate': None,
            'estimatedSettlePrice': None,
            'timestamp': self.parse8601(fundingDatetime),
            'datetime': fundingDatetime,
            'fundingRate': self.safe_number(contract, 'funding_rate'),
            'fundingTimestamp': self.parse8601(fundingDatetime),
            'fundingDatetime': fundingDatetime,
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
        }

    async def create_deposit_address(self, code: str, params={}):
        """
        create a currency deposit address
        :see: https://docs.cloud.coinbase.com/intx/reference/createaddress
        :see: https://docs.cloud.coinbase.com/intx/reference/createcounterpartyid
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.network_arn_id]: Identifies the blockchain network(e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a) if not provided will pick default
        :param str [params.network]: unified network code to identify the blockchain network
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        await self.load_markets()
        method = None
        method, params = self.handle_option_and_params(params, 'createDepositAddress', 'method', 'v1PrivatePostTransfersAddress')
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('createDepositAddress', params)
        request: dict = {
            'portfolio': portfolio,
        }
        if method == 'v1PrivatePostTransfersAddress':
            currency = self.currency(code)
            request['asset'] = currency['id']
            networkId = None
            networkId, params = await self.handle_network_id_and_params(code, 'createDepositAddress', params)
            request['network_arn_id'] = networkId
        response = await getattr(self, method)(self.extend(request, params))
        #
        # v1PrivatePostTransfersAddress
        #    {
        #        address: "3LkwYscRyh6tUR1XTqXSJQoJnK7ucC1F4n",
        #        network_arn_id: "networks/bitcoin-mainnet/assets/6ecc0dcc-10a2-500e-b315-a3b9abae19ce",
        #        destination_tag: "",
        #    }
        # v1PrivatePostTransfersCreateCounterpartyId
        #    {
        #        "portfolio_uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
        #        "counterparty_id":"CB2ZPUCZBE"
        #    }
        #
        tag = self.safe_string(response, 'destination_tag')
        address = self.safe_string_2(response, 'address', 'counterparty_id')
        return {
            'currency': code,
            'tag': tag,
            'address': address,
            'info': response,
        }

    def find_default_network(self, networks):
        networksArray = self.to_array(networks)
        for i in range(0, len(networksArray)):
            info = networksArray[i]['info']
            is_default = self.safe_bool(info, 'is_default', False)
            if is_default is True:
                return networksArray[i]
        return networksArray[0]

    async def load_currency_networks(self, code, params={}):
        currency = self.currency(code)
        networks = self.safe_dict(currency, 'networks')
        if networks is not None:
            return
        request: dict = {
            'asset': currency['id'],
        }
        rawNetworks = await self.v1PublicGetAssetsAssetNetworks(request)
        #
        #    [
        #        {
        #            "asset_id" = self.parse_networks(rawNetworks)

    def parse_networks(self, networks, params={}):
        result: dict = {}
        for i in range(0, len(networks)):
            network = self.extend(self.parse_network(networks[i]), params)
            result[network['network']] = network
        return result

    def parse_network(self, network, params={}):
        #
        #    {
        #        "asset_id":"1",
        #        "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #        "asset_name":"USDC",
        #        "network_arn_id":"networks/ethereum-mainnet/assets/9bc140b4-69c3-5fc9-bd0d-b041bcf40039",
        #        "min_withdrawal_amt":"1",
        #        "max_withdrawal_amt":"100000000",
        #        "network_confirms":35,
        #        "processing_time":485,
        #        "is_default":true,
        #        "network_name":"ethereum",
        #        "display_name":"Ethereum"
        #    }
        #
        currencyId = self.safe_string(network, 'asset_name')
        currencyCode = self.safe_currency_code(currencyId)
        networkId = self.safe_string(network, 'network_arn_id')
        return self.safe_network({
            'info': network,
            'id': networkId,
            'name': self.safe_string(network, 'display_name'),
            'network': self.network_id_to_code(self.safe_string_n(network, ['network_name', 'display_name', 'network_arn_id'], ''), currencyCode),
            'active': None,
            'deposit': None,
            'withdraw': None,
            'precision': None,
            'fee': None,
            'limits': {
                'withdraw': {
                    'min': self.safe_number(network, 'min_withdrawal_amt'),
                    'max': self.safe_number(network, 'max_withdrawal_amt'),
                },
                'deposit': {
                    'min': None,
                    'max': None,
                },
            },
        })

    async def set_margin(self, symbol: str, amount: float, params={}) -> Any:
        """
        Either adds or reduces margin in order to set the margin to a specific value
        :see: https://docs.cloud.coinbase.com/intx/reference/setportfoliomarginoverride
        :param str symbol: unified market symbol of the market to set margin in
        :param float amount: the amount to set the margin to
        :param dict [params]: parameters specific to the exchange API endpoint
        :returns dict: A `margin structure <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#add-margin-structure>`
        """
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('setMargin', params)
        if symbol is not None:
            raise BadRequest(self.id + ' setMargin() only allows setting margin to full portfolio')
        request: dict = {
            'portfolio': portfolio,
            'margin_override': amount,
        }
        return await self.v1PrivatePostPortfoliosMargin(self.extend(request, params))

    async def fetch_deposits_withdrawals(self, code: str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals
        :see: https://docs.cloud.coinbase.com/intx/reference/gettransfers
        :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
        :param int [params.until]: Only find transfers updated before self time. Use timestamp format
        :param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
        :param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        :returns dict: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        paginate = None
        paginate, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'paginate')
        maxEntriesPerRequest = None
        maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'maxEntriesPerRequest', 100)
        pageKey = 'ccxt_zagravaPageKey'
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchDepositsWithdrawals', code, since, limit, params, pageKey, maxEntriesPerRequest)
        page = self.safe_integer(params, pageKey, 1) - 1
        request: dict = {
            'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
        }
        if since is not None:
            request['time_from'] = self.iso8601(since)
        if limit is not None:
            newLimit = min(limit, 100)
            request['result_limit'] = newLimit
        portfolios = None
        portfolios, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'portfolios')
        if portfolios is not None:
            request['portfolios'] = portfolios
        until = None
        until, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'until')
        if until is not None:
            request['time_to'] = self.iso8601(until)
        response = await self.v1PrivateGetTransfers(self.extend(request, params))
        #
        #    {
        #        "pagination":{
        #           "result_limit":25,
        #           "result_offset":0
        #        },
        #        "results":[
        #           {
        #              "transfer_uuid":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3",
        #              "transfer_type":"WITHDRAW",
        #              "amount":"1.000000",
        #              "asset":"USDC",
        #              "status":"PROCESSED",
        #              "network_name":"ethereum",
        #              "created_at":"2024-03-14T02:32:18.497795Z",
        #              "updated_at":"2024-03-14T02:35:38.514588Z",
        #              "from_portfolio":{
        #                 "id":"1yun54bb-1-6",
        #                 "uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
        #                 "name":"fungus technology o?Portfolio"
        #              },
        #              "to_address":"0xcdcE79F820BE9d6C5033db5c31d1AE3A8c2399bB"
        #           }
        #        ]
        #    }
        #
        rawTransactions = self.safe_list(response, 'results', [])
        return self.parse_transactions(rawTransactions)

    async def fetch_position(self, symbol: str, params={}):
        """
        :see: https://docs.cloud.coinbase.com/intx/reference/getportfolioposition
        fetch data on an open position
        :param str symbol: unified market symbol of the market the position is held in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `position structure <https://docs.ccxt_zagrava.com/#/?id=position-structure>`
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('fetchPosition', params)
        request: dict = {
            'portfolio': portfolio,
            'instrument': self.market_id(symbol),
        }
        position = await self.v1PrivateGetPortfoliosPortfolioPositionsInstrument(self.extend(request, params))
        #
        #    {
        #        "symbol":"BTC-PERP",
        #        "instrument_id":"114jqr89-0-0",
        #        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #        "vwap":"52482.3",
        #        "net_size":"0",
        #        "buy_order_size":"0.001",
        #        "sell_order_size":"0",
        #        "im_contribution":"0.2",
        #        "unrealized_pnl":"0",
        #        "mark_price":"52406.8",
        #        "entry_vwap":"52472.9"
        #    }
        #
        return self.parse_position(position)

    def parse_position(self, position: dict, market: Market = None):
        #
        #    {
        #       "symbol":"BTC-PERP",
        #       "instrument_id":"114jqr89-0-0",
        #       "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #       "vwap":"52482.3",
        #       "net_size":"0",
        #       "buy_order_size":"0.001",
        #       "sell_order_size":"0",
        #       "im_contribution":"0.2",
        #       "unrealized_pnl":"0",
        #       "mark_price":"52406.8",
        #       "entry_vwap":"52472.9"
        #    }
        #
        marketId = self.safe_string(position, 'symbol')
        quantity = self.safe_string(position, 'net_size')
        market = self.safe_market(marketId, market, '-')
        side = 'long'
        if Precise.string_le(quantity, '0'):
            side = 'short'
            quantity = Precise.string_mul('-1', quantity)
        return self.safe_position({
            'info': position,
            'id': self.safe_string(position, 'id'),
            'symbol': market['symbol'],
            'entryPrice': None,
            'markPrice': self.safe_number(position, 'mark_price'),
            'notional': None,
            'collateral': None,
            'unrealizedPnl': self.safe_number(position, 'unrealized_pnl'),
            'side': side,
            'contracts': self.parse_number(quantity),
            'contractSize': self.safe_number(market, 'contractSize'),
            'timestamp': None,
            'datetime': None,
            'hedged': None,
            'maintenanceMargin': None,
            'maintenanceMarginPercentage': None,
            'initialMargin': self.safe_number(position, 'im_contribution'),
            'initialMarginPercentage': None,
            'leverage': None,
            'liquidationPrice': None,
            'marginRatio': None,
            'marginMode': None,
            'percentage': None,
        })

    async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
        """
        :see: https://docs.cloud.coinbase.com/intx/reference/getportfoliopositions
        fetch all open positions
        :param str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [method]: method name to call, "positionRisk", "account" or "option", default is "positionRisk"
        :returns dict[]: a list of `position structure <https://docs.ccxt_zagrava.com/#/?id=position-structure>`
        """
        await self.load_markets()
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('fetchPositions', params)
        request: dict = {
            'portfolio': portfolio,
        }
        response = await self.v1PrivateGetPortfoliosPortfolioPositions(self.extend(request, params))
        #
        #    [
        #        {
        #           "symbol":"BTC-PERP",
        #           "instrument_id":"114jqr89-0-0",
        #           "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #           "vwap":"52482.3",
        #           "net_size":"0",
        #           "buy_order_size":"0.001",
        #           "sell_order_size":"0",
        #           "im_contribution":"0.2",
        #           "unrealized_pnl":"0",
        #           "mark_price":"52406.8",
        #           "entry_vwap":"52472.9"
        #        }
        #    ]
        #
        positions = self.parse_positions(response)
        if self.is_empty(symbols):
            return positions
        symbols = self.market_symbols(symbols)
        return self.filter_by_array_positions(positions, 'symbol', symbols, False)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://docs.cloud.coinbase.com/intx/reference/gettransfers
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
        :param int [params.until]: Only find transfers updated before self time. Use timestamp format
        :param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
        :param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        params['type'] = 'WITHDRAW'
        return await self.fetch_deposits_withdrawals(code, since, limit, params)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
        :param int [params.until]: Only find transfers updated before self time. Use timestamp format
        :param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
        :param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        params['type'] = 'DEPOSIT'
        return await self.fetch_deposits_withdrawals(code, since, limit, params)

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'PROCESSED': 'ok',
            'NEW': 'pending',
            'STARTED': 'pending',
            'FAILED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        #    {
        #        "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
        #    }
        # transactionType = self.safe_string(transaction, 'type')
        datetime = self.safe_string(transaction, 'updated_at')
        fromPorfolio = self.safe_dict(transaction, 'from_portfolio', {})
        addressFrom = self.safe_string_n(transaction, ['from_address', 'from_cb_account', self.safe_string_n(fromPorfolio, ['id', 'uuid', 'name']), 'from_counterparty_id'])
        toPorfolio = self.safe_dict(transaction, 'from_portfolio', {})
        addressTo = self.safe_string_n(transaction, ['to_address', 'to_cb_account', self.safe_string_n(toPorfolio, ['id', 'uuid', 'name']), 'to_counterparty_id'])
        return {
            'info': transaction,
            'id': self.safe_string(transaction, 'transfer_uuid'),
            'txid': self.safe_string(transaction, 'transaction_uuid'),
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'network': self.network_id_to_code(self.safe_string(transaction, 'network_name')),
            'address': None,  # TODO check if withdraw or deposit and populate
            'addressTo': addressTo,
            'addressFrom': addressFrom,
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'type': self.safe_string(transaction, 'resource'),
            'amount': self.safe_number(transaction, 'amount'),
            'currency': self.safe_currency_code(self.safe_string(transaction, 'asset'), currency),
            'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
            'updated': self.parse8601(datetime),
            'fee': {
                'cost': None,
                'currency': None,
            },
        }

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        #    {
        #       "portfolio_id":"1wp37qsc-1-0",
        #       "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #       "portfolio_name":"ccxt_zagrava Portfolio 020624-17:16",
        #       "fill_id":"1xbfy19y-1-184",
        #       "exec_id":"280841526207070392",
        #       "order_id":"1xbfv8yw-1-0",
        #       "instrument_id":"114jqr89-0-0",
        #       "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #       "symbol":"BTC-PERP",
        #       "match_id":"280841526207053840",
        #       "fill_price":"52500",
        #       "fill_qty":"0.01",
        #       "client_id":"1x59ctku-1-1",
        #       "client_order_id":"ccxt_zagrava3e4e2a5f-4a89-",
        #       "order_qty":"0.01",
        #       "limit_price":"52500",
        #       "total_filled":"0.01",
        #       "filled_vwap":"52500",
        #       "expire_time":"",
        #       "stop_price":"",
        #       "side":"BUY",
        #       "tif":"GTC",
        #       "stp_mode":"BOTH",
        #       "flags":"",
        #       "fee":"0.105",
        #       "fee_asset":"USDC",
        #       "order_status":"DONE",
        #       "event_time":"2024-02-15T00:43:57.631Z"
        #    }
        #
        marketId = self.safe_string(trade, 'symbol')
        datetime = self.safe_string(trade, 'event_time')
        return self.safe_trade({
            'info': trade,
            'id': self.safe_string_2(trade, 'fill_id', 'exec_id'),
            'order': self.safe_string(trade, 'order_id'),
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'symbol': self.safe_symbol(marketId, market),
            'type': None,
            'side': self.safe_string_lower(trade, 'side'),
            'takerOrMaker': None,
            'price': self.safe_number(trade, 'fill_price'),
            'amount': self.safe_number(trade, 'fill_qty'),
            'cost': None,
            'fee': {
                'cost': self.safe_number(trade, 'fee'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'fee_asset')),
            },
        })

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        :see: https://docs.cloud.coinbase.com/intx/reference/getinstruments
        retrieves data on all markets for coinbaseinternational
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await self.v1PublicGetInstruments(params)
        #
        #    [
        #        {
        #           "instrument_id":"149264164756389888",
        #           "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
        #           "symbol":"ETH-PERP",
        #           "type":"PERP",
        #           "base_asset_id":"118059611793145856",
        #           "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
        #           "base_asset_name":"ETH",
        #           "quote_asset_id":"1",
        #           "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #           "quote_asset_name":"USDC",
        #           "base_increment":"0.0001",
        #           "quote_increment":"0.01",
        #           "price_band_percent":"0.02",
        #           "market_order_percent":"0.0075",
        #           "qty_24hr":"44434.8131",
        #           "notional_24hr":"110943454.279785",
        #           "avg_daily_qty":"1099171.6025",
        #           "avg_daily_notional":"2637240145.456987",
        #           "previous_day_qty":"78909.3939",
        #           "open_interest":"1270.749",
        #           "position_limit_qty":"1831.9527",
        #           "position_limit_adq_pct":"0.05",
        #           "replacement_cost":"0.23",
        #           "base_imf":"0.1",
        #           "min_notional_value":"10",
        #           "funding_interval":"3600000000000",
        #           "trading_state":"TRADING",
        #           "quote":{
        #              "best_bid_price":"2490.8",
        #              "best_bid_size":"9.0515",
        #              "best_ask_price":"2490.81",
        #              "best_ask_size":"4.8486",
        #              "trade_price":"2490.39",
        #              "trade_qty":"0.9508",
        #              "index_price":"2490.5",
        #              "mark_price":"2490.8",
        #              "settlement_price":"2490.81",
        #              "limit_up":"2615.42",
        #              "limit_down":"2366.34",
        #              "predicted_funding":"0.000009",
        #              "timestamp":"2024-02-10T16:07:39.454Z"
        #           }
        #        },
        #        ...
        #    ]
        #
        return self.parse_markets(response)

    def parse_market(self, market: dict) -> Market:
        #
        #   {
        #       "instrument_id":"149264164756389888",
        #       "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
        #       "symbol":"ETH-PERP",
        #       "type":"PERP",
        #       "base_asset_id":"118059611793145856",
        #       "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
        #       "base_asset_name":"ETH",
        #       "quote_asset_id":"1",
        #       "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #       "quote_asset_name":"USDC",
        #       "base_increment":"0.0001",
        #       "quote_increment":"0.01",
        #       "price_band_percent":"0.02",
        #       "market_order_percent":"0.0075",
        #       "qty_24hr":"44434.8131",
        #       "notional_24hr":"110943454.279785",
        #       "avg_daily_qty":"1099171.6025",
        #       "avg_daily_notional":"2637240145.456987",
        #       "previous_day_qty":"78909.3939",
        #       "open_interest":"1270.749",
        #       "position_limit_qty":"1831.9527",
        #       "position_limit_adq_pct":"0.05",
        #       "replacement_cost":"0.23",
        #       "base_imf":"0.1",
        #       "min_notional_value":"10",
        #       "funding_interval":"3600000000000",
        #       "trading_state":"TRADING",
        #       "quote":{
        #          "best_bid_price":"2490.8",
        #          "best_bid_size":"9.0515",
        #          "best_ask_price":"2490.81",
        #          "best_ask_size":"4.8486",
        #          "trade_price":"2490.39",
        #          "trade_qty":"0.9508",
        #          "index_price":"2490.5",
        #          "mark_price":"2490.8",
        #          "settlement_price":"2490.81",
        #          "limit_up":"2615.42",
        #          "limit_down":"2366.34",
        #          "predicted_funding":"0.000009",
        #          "timestamp":"2024-02-10T16:07:39.454Z"
        #       }
        #    }
        #
        marketId = self.safe_string(market, 'symbol')
        baseId = self.safe_string(market, 'base_asset_name')
        quoteId = self.safe_string(market, 'quote_asset_name')
        typeId = self.safe_string(market, 'type')  # 'SPOT', 'PERP'
        isSpot = (typeId == 'SPOT')
        fees = self.fees
        symbol = baseId + '/' + quoteId
        settleId = None
        if not isSpot:
            settleId = quoteId
            symbol += ':' + quoteId
        return {
            'id': marketId,
            'lowercaseId': marketId.lower(),
            'symbol': symbol,
            'base': baseId,
            'quote': quoteId,
            'settle': settleId if settleId else None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleId if settleId else None,
            'type': 'spot' if isSpot else 'swap',
            'spot': isSpot,
            'margin': False,
            'swap': not isSpot,
            'future': False,
            'option': False,
            'active': self.safe_string(market, 'trading_state') == 'TRADING',
            'contract': not isSpot,
            'linear': None if isSpot else (settleId == quoteId),
            'inverse': None if isSpot else (settleId != quoteId),
            'taker': fees['trading']['taker'],
            'maker': fees['trading']['maker'],
            'contractSize': None if isSpot else 1,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.safe_number(market, 'base_increment'),
                'price': self.safe_number(market, 'quote_increment'),
                'cost': self.safe_number(market, 'quote_increment'),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': self.safe_number(market, 'base_imf'),
                },
                'amount': {
                    'min': None,
                    'max': None if isSpot else self.safe_number(market, 'position_limit_qty'),
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_number(market, 'min_notional_value'),
                    'max': None,
                },
            },
            'info': market,
            'created': None,
        }

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://docs.cloud.coinbase.com/intx/reference/getassets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        currencies = await self.v1PublicGetAssets(params)
        #
        #    [
        #        {
        #           "asset_id":"1",
        #           "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #           "asset_name":"USDC",
        #           "status":"ACTIVE",
        #           "collateral_weight":1.0,
        #           "supported_networks_enabled":true
        #        },
        #        ...
        #    ]
        #
        result: dict = {}
        for i in range(0, len(currencies)):
            currency = self.parse_currency(currencies[i])
            result[currency['code']] = currency
        return result

    def parse_currency(self, currency: dict):
        #
        #    {
        #       "asset_id":"1",
        #       "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #       "asset_name":"USDC",
        #       "status":"ACTIVE",
        #       "collateral_weight":1.0,
        #       "supported_networks_enabled":true
        #    }
        #
        id = self.safe_string(currency, 'asset_name')
        code = self.safe_currency_code(id)
        statusId = self.safe_string(currency, 'status')
        return {
            'id': id,
            'name': code,
            'code': code,
            'precision': None,
            'info': currency,
            'active': (statusId == 'ACTIVE'),
            'deposit': None,
            'withdraw': None,
            'networks': None,
            'fee': None,
            'fees': None,
            'limits': self.limits,
        }

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://docs.cloud.coinbase.com/intx/reference/getinstruments
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        instruments = await self.v1PublicGetInstruments(params)
        tickers: dict = {}
        for i in range(0, len(instruments)):
            instrument = instruments[i]
            marketId = self.safe_string(instrument, 'symbol')
            symbol = self.safe_symbol(marketId)
            quote = self.safe_dict(instrument, 'quote', {})
            tickers[symbol] = self.parse_ticker(quote, self.safe_market(marketId))
        return self.filter_by_array(tickers, 'symbol', symbols, True)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://docs.cloud.coinbase.com/intx/reference/getinstrumentquote
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instrument': self.market_id(symbol),
        }
        ticker = await self.v1PublicGetInstrumentsInstrumentQuote(self.extend(request, params))
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker: object, market: Market = None) -> Ticker:
        #
        #    {
        #        "best_bid_price":"2490.8",
        #        "best_bid_size":"9.0515",
        #        "best_ask_price":"2490.81",
        #        "best_ask_size":"4.8486",
        #        "trade_price":"2490.39",
        #        "trade_qty":"0.9508",
        #        "index_price":"2490.5",
        #        "mark_price":"2490.8",
        #        "settlement_price":"2490.81",
        #        "limit_up":"2615.42",
        #        "limit_down":"2366.34",
        #        "predicted_funding":"0.000009",
        #        "timestamp":"2024-02-10T16:07:39.454Z"
        #    }
        #
        datetime = self.safe_string(ticker, 'timestamp')
        return self.safe_ticker({
            'info': ticker,
            'symbol': self.safe_symbol(None, market),
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'bid': self.safe_number(ticker, 'best_bid_price'),
            'bidVolume': self.safe_number(ticker, 'best_bid_size'),
            'ask': self.safe_number(ticker, 'best_ask_price'),
            'askVolume': self.safe_number(ticker, 'best_ask_size'),
            'high': None,
            'low': None,
            'open': None,
            'close': None,
            'last': None,
            'change': None,
            'percentage': None,
            'average': None,
            'vwap': None,
            'baseVolume': None,
            'quoteVolume': None,
            'previousClose': None,
        })

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://docs.cloud.coinbase.com/intx/reference/getportfoliobalances
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.v3]: default False, set True to use v3 api endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('fetchBalance', params)
        request: dict = {
            'portfolio': portfolio,
        }
        balances = await self.v1PrivateGetPortfoliosPortfolioBalances(self.extend(request, params))
        #
        #    [
        #        {
        #           "asset_id":"0-0-1",
        #           "asset_name":"USDC",
        #           "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #           "quantity":"500000.0000000000",
        #           "hold":"0",
        #           "hold_available_for_collateral":"0",
        #           "transfer_hold":"0",
        #           "collateral_value":"500000.0",
        #           "max_withdraw_amount":"500000.0000000000",
        #           "loan":"0",
        #           "loan_collateral_requirement":"0.0"
        #        }
        #    ]
        #
        return self.parse_balance(balances)

    def parse_balance(self, response) -> Balances:
        #
        #    {
        #       "asset_id":"0-0-1",
        #       "asset_name":"USDC",
        #       "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
        #       "quantity":"500000.0000000000",
        #       "hold":"0",
        #       "hold_available_for_collateral":"0",
        #       "transfer_hold":"0",
        #       "collateral_value":"500000.0",
        #       "max_withdraw_amount":"500000.0000000000",
        #       "loan":"0",
        #       "loan_collateral_requirement":"0.0"
        #    }
        #
        result: dict = {
            'info': response,
        }
        for i in range(0, len(response)):
            rawBalance = response[i]
            currencyId = self.safe_string(rawBalance, 'asset_name')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(rawBalance, 'quantity')
            account['used'] = self.safe_string(rawBalance, 'hold')
            result[code] = account
        return self.safe_balance(result)

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        Transfer an amount of asset from one portfolio to another.
        :see: https://docs.cloud.coinbase.com/intx/reference/createportfolioassettransfer
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#transfer-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'asset': currency['id'],
            'ammount': amount,
            'from': fromAccount,
            'to': toAccount,
        }
        response = await self.v1PrivatePostPortfoliosTransfer(self.extend(request, params))
        success = self.safe_bool(response, 'success')
        return {
            'info': response,
            'id': None,
            'timestamp': None,
            'datetime': None,
            'currency': code,
            'amount': amount,
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'status': 'ok' if success else 'failed',
        }

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: float = None, params={}):
        """
        create a trade order
        :see: https://docs.cloud.coinbase.com/intx/reference/createorder
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much you want to trade in units of the base currency, quote currency for 'market' 'buy' orders
        :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.stopPrice]: price to trigger stop orders
        :param float [params.triggerPrice]: price to trigger stop orders
        :param float [params.stopLossPrice]: price to trigger stop-loss orders
        :param bool [params.postOnly]: True or False
        :param str [params.tif]: 'GTC', 'IOC', 'GTD' default is 'GTC' for limit orders and 'IOC' for market orders
        :param str [params.expire_time]: The expiration time required for orders with the time in force set to GTT. Must not go beyond 30 days of the current time. Uses ISO-8601 format(e.g., 2023-03-16T23:59:53Z)
        :param str [params.stp_mode]: Possible values: [NONE, AGGRESSING, BOTH] Specifies the behavior for self match handling. None disables the functionality, new cancels the newest order, and both cancels both orders.
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        typeId = type.upper()
        stopPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'stop_price'])
        clientOrderIdprefix = self.safe_string(self.options, 'brokerId', 'nfqkvdjp')
        clientOrderId = clientOrderIdprefix + '-' + self.uuid()
        clientOrderId = clientOrderId[0:17]
        request: dict = {
            'client_order_id': clientOrderId,
            'side': side.upper(),
            'instrument': market['id'],
            'size': self.amount_to_precision(market['symbol'], amount),
        }
        if stopPrice is not None:
            if type == 'limit':
                typeId = 'STOP_LIMIT'
            else:
                typeId = 'STOP'
            request['stop_price'] = stopPrice
        request['type'] = typeId
        if type == 'limit':
            if price is None:
                raise InvalidOrder(self.id + 'createOrder() requires a price parameter for a limit order types')
            request['price'] = price
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('createOrder', params)
        if portfolio is not None:
            request['portfolio'] = portfolio
        postOnly = self.safe_bool_2(params, 'postOnly', 'post_only')
        tif = self.safe_string_2(params, 'tif', 'timeInForce')
        # market orders must be IOC
        if typeId == 'MARKET':
            if tif is not None and tif != 'IOC':
                raise InvalidOrder(self.id + 'createOrder() market orders must have tif set to "IOC"')
            tif = 'IOC'
        else:
            tif = 'GTC' if (tif is None) else tif
        if postOnly is not None:
            request['post_only'] = postOnly
        request['tif'] = tif
        params = self.omit(params, ['client_order_id', 'user', 'postOnly', 'timeInForce'])
        response = await self.v1PrivatePostOrders(self.extend(request, params))
        #
        #    {
        #        "order_id":"1x96skvg-1-0",
        #        "client_order_id":"ccxt_zagrava",
        #        "side":"BUY",
        #        "instrument_id":"114jqr89-0-0",
        #        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #        "symbol":"BTC-PERP",
        #        "portfolio_id":"1wp37qsc-1-0",
        #        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #        "type":"LIMIT",
        #        "price":"10000",
        #        "size":"0.001",
        #        "tif":"GTC",
        #        "stp_mode":"BOTH",
        #        "event_type":"NEW",
        #        "order_status":"WORKING",
        #        "leaves_qty":"0.001",
        #        "exec_qty":"0",
        #        "avg_price":"0",
        #        "fee":"0"
        #    }
        #
        return self.parse_order(response, market)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #    {
        #        "order_id":"1x96skvg-1-0",
        #        "client_order_id":"ccxt_zagrava",
        #        "side":"BUY",
        #        "instrument_id":"114jqr89-0-0",
        #        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #        "symbol":"BTC-PERP",
        #        "portfolio_id":"1wp37qsc-1-0",
        #        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #        "type":"LIMIT",
        #        "price":"10000",
        #        "size":"0.001",
        #        "tif":"GTC",
        #        "stp_mode":"BOTH",
        #        "event_type":"NEW",
        #        "order_status":"WORKING",
        #        "leaves_qty":"0.001",
        #        "exec_qty":"0",
        #        "avg_price":"0",
        #        "fee":"0"
        #    }
        #
        marketId = self.safe_string(order, 'symbol')
        feeCost = self.safe_number(order, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
            }
        datetime = self.safe_string_2(order, 'submit_time', 'event_time')
        return self.safe_order({
            'info': order,
            'id': self.safe_string(order, 'order_id'),
            'clientOrderId': self.safe_string(order, 'client_order_id'),
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'lastTradeTimestamp': None,
            'symbol': self.safe_symbol(marketId, market),
            'type': self.parse_order_type(self.safe_string(order, 'type')),
            'timeInForce': self.safe_string(order, 'tif'),
            'postOnly': None,
            'side': self.safe_string_lower(order, 'side'),
            'price': self.safe_string(order, 'price'),
            'stopPrice': self.safe_string(order, 'stop_price'),
            'triggerPrice': self.safe_string(order, 'stop_price'),
            'amount': self.safe_string(order, 'size'),
            'filled': self.safe_string(order, 'exec_qty'),
            'remaining': self.safe_string(order, 'leaves_qty'),
            'cost': None,
            'average': self.safe_string(order, 'avg_price'),
            'status': self.parse_order_status(self.safe_string(order, 'order_status')),
            'fee': fee,
            'trades': None,
        }, market)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'NEW': 'open',
            'PARTIAL_FILLED': 'open',
            'FILLED': 'closed',
            'CANCELED': 'canceled',
            'REPLACED': 'canceled',
            'PENDING_CANCEL': 'open',
            'REJECTED': 'rejected',
            'PENDING_NEW': 'open',
            'EXPIRED': 'expired',
            'PENDING_REPLACE': 'open',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_type(self, type: Str):
        if type == 'UNKNOWN_ORDER_TYPE':
            return None
        types: dict = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'STOP': 'limit',
            'STOP_LIMIT': 'limit',
        }
        return self.safe_string(types, type, type)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://docs.cloud.coinbase.com/intx/reference/cancelorder
        :param str id: order id
        :param str symbol: not used by coinbaseinternational cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('cancelOrder', params)
        request: dict = {
            'portfolio': portfolio,
            'id': id,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
        orders = await self.v1PrivateDeleteOrdersId(self.extend(request, params))
        #
        #    {
        #        "order_id":"1x96skvg-1-0",
        #        "client_order_id":"ccxt_zagrava",
        #        "side":"BUY",
        #        "instrument_id":"114jqr89-0-0",
        #        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #        "symbol":"BTC-PERP",
        #        "portfolio_id":"1wp37qsc-1-0",
        #        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #        "type":"LIMIT",
        #        "price":"10000",
        #        "size":"0.001",
        #        "tif":"GTC",
        #        "stp_mode":"BOTH",
        #        "event_type":"CANCELED",
        #        "order_status":"DONE",
        #        "leaves_qty":"0.001",
        #        "exec_qty":"0",
        #        "avg_price":"0",
        #        "fee":"0"
        #    }
        #
        return self.parse_order(orders, market)

    async def cancel_all_orders(self, symbol: str = None, params={}):
        """
        cancel all open orders
        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('cancelAllOrders', params)
        request: dict = {
            'portfolio': portfolio,
        }
        market = None
        if symbol:
            market = self.market(symbol)
            request['instrument'] = market['id']
        orders = await self.v1PrivateDeleteOrders(self.extend(request, params))
        return self.parse_orders(orders, market)

    async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float = None, price: float = None, params={}):
        """
        edit a trade order
        :see: https://docs.cloud.coinbase.com/intx/reference/modifyorder
        :param str id: cancel order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str params['clientOrderId']: client order id
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'id': id,
        }
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('editOrder', params)
        if portfolio is not None:
            request['portfolio'] = portfolio
        if amount is not None:
            request['size'] = self.amount_to_precision(symbol, amount)
        if price is not None:
            request['price'] = self.price_to_precision(symbol, price)
        stopPrice = self.safe_number_n(params, ['stopPrice', 'stop_price', 'triggerPrice'])
        if stopPrice is not None:
            request['stop_price'] = stopPrice
        clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId')
        if clientOrderId is None:
            raise BadRequest(self.id + ' editOrder() requires a clientOrderId parameter')
        request['client_order_id'] = clientOrderId
        order = await self.v1PrivatePutOrdersId(self.extend(request, params))
        return self.parse_order(order, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://docs.cloud.coinbase.com/intx/reference/modifyorder
        :param str id: the order id
        :param str symbol: unified market symbol that the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('fetchOrder', params)
        request: dict = {
            'id': id,
            'portfolio': portfolio,
        }
        order = await self.v1PrivateGetOrdersId(self.extend(request, params))
        #
        #    {
        #        "order_id":"1x96skvg-1-0",
        #        "client_order_id":"ccxt_zagrava",
        #        "side":"BUY",
        #        "instrument_id":"114jqr89-0-0",
        #        "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #        "symbol":"BTC-PERP",
        #        "portfolio_id":"1wp37qsc-1-0",
        #        "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #        "type":"LIMIT",
        #        "price":"10000",
        #        "size":"0.001",
        #        "tif":"GTC",
        #        "stp_mode":"BOTH",
        #        "event_type":"NEW",
        #        "event_time":"2024-02-14T03:25:14Z",
        #        "submit_time":"2024-02-14T03:25:13.999Z",
        #        "order_status":"WORKING",
        #        "leaves_qty":"0.001",
        #        "exec_qty":"0",
        #        "avg_price":"0",
        #        "fee":"0"
        #    }
        #
        return self.parse_order(order, market)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on all currently open orders
        :see: https://docs.cloud.coinbase.com/intx/reference/getorders
        :param str symbol: unified market symbol of the orders
        :param int [since]: timestamp in ms of the earliest order, default is None
        :param int [limit]: the maximum number of open order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        :param int [params.offset]: offset
        :param str [params.event_type]: The most recent type of event that happened to the order. Allowed values: NEW, TRADE, REPLACED
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('fetchOpenOrders', params)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'paginate')
        maxEntriesPerRequest = None
        maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'maxEntriesPerRequest', 100)
        pageKey = 'ccxt_zagravaPageKey'
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchOpenOrders', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
        page = self.safe_integer(params, pageKey, 1) - 1
        request: dict = {
            'portfolio': portfolio,
            'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
        }
        market = None
        if symbol:
            market = self.market(symbol)
            request['instrument'] = symbol
        if limit is not None:
            if limit > 100:
                raise BadRequest(self.id + ' fetchOpenOrders() maximum limit is 100')
            request['result_limit'] = limit
        if since is not None:
            request['ref_datetime'] = self.iso8601(since)
        response = await self.v1PrivateGetOrders(self.extend(request, params))
        #
        #    {
        #        "pagination":{
        #           "result_limit":25,
        #           "result_offset":0
        #        },
        #        "results":[
        #           {
        #              "order_id":"1y4cm6b4-1-0",
        #              "client_order_id":"ccxt_zagravad0dd4b5d-8e5f-",
        #              "side":"SELL",
        #              "instrument_id":"114jqr89-0-0",
        #              "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #              "symbol":"BTC-PERP",
        #              "portfolio_id":"1wp37qsc-1-0",
        #              "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #              "type":"LIMIT",
        #              "price":"54000",
        #              "size":"0.01",
        #              "tif":"GTC",
        #              "stp_mode":"BOTH",
        #              "event_type":"NEW",
        #              "event_time":"2024-02-24T16:46:37.413Z",
        #              "submit_time":"2024-02-24T16:46:37.412Z",
        #              "order_status":"WORKING",
        #              "leaves_qty":"0.01",
        #              "exec_qty":"0",
        #              "avg_price":"0",
        #              "fee":"0"
        #           },
        #           ...
        #        ]
        #    }
        #
        rawOrders = self.safe_list(response, 'results', [])
        return self.parse_orders(rawOrders, market, since, limit)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://docs.cloud.coinbase.com/intx/reference/getmultiportfoliofills
        :param str symbol: unified market symbol of the trades
        :param int [since]: timestamp in ms of the earliest order, default is None
        :param int [limit]: the maximum number of trade structures to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch trades for
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#pagination-params)
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
        pageKey = 'ccxt_zagravaPageKey'
        maxEntriesPerRequest = None
        maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchMyTrades', 'maxEntriesPerRequest', 100)
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchMyTrades', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
        market = None
        if symbol is not None:
            market = self.market(symbol)
        page = self.safe_integer(params, pageKey, 1) - 1
        request: dict = {
            'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
        }
        if limit is not None:
            if limit > 100:
                raise BadRequest(self.id + ' fetchMyTrades() maximum limit is 100. Consider setting paginate to True to fetch more trades.')
            request['result_limit'] = limit
        if since is not None:
            request['time_from'] = self.iso8601(since)
        until = self.safe_string_n(params, ['until'])
        if until is not None:
            params = self.omit(params, ['until'])
            request['ref_datetime'] = self.iso8601(until)
        response = await self.v1PrivateGetPortfoliosFills(self.extend(request, params))
        #
        #    {
        #        "pagination":{
        #           "result_limit":25,
        #           "result_offset":0
        #        },
        #        "results":[
        #           {
        #              "portfolio_id":"1wp37qsc-1-0",
        #              "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
        #              "portfolio_name":"ccxt_zagrava Portfolio 020624-17:16",
        #              "fill_id":"1xbfy19y-1-184",
        #              "exec_id":"280841526207070392",
        #              "order_id":"1xbfv8yw-1-0",
        #              "instrument_id":"114jqr89-0-0",
        #              "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
        #              "symbol":"BTC-PERP",
        #              "match_id":"280841526207053840",
        #              "fill_price":"52500",
        #              "fill_qty":"0.01",
        #              "client_id":"1x59ctku-1-1",
        #              "client_order_id":"ccxt_zagrava3e4e2a5f-4a89-",
        #              "order_qty":"0.01",
        #              "limit_price":"52500",
        #              "total_filled":"0.01",
        #              "filled_vwap":"52500",
        #              "expire_time":"",
        #              "stop_price":"",
        #              "side":"BUY",
        #              "tif":"GTC",
        #              "stp_mode":"BOTH",
        #              "flags":"",
        #              "fee":"0.105",
        #              "fee_asset":"USDC",
        #              "order_status":"DONE",
        #              "event_time":"2024-02-15T00:43:57.631Z"
        #           },
        #        ]
        #    }
        #
        trades = self.safe_list(response, 'results', [])
        return self.parse_trades(trades, market, since, limit)

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://docs.cloud.coinbase.com/intx/reference/withdraw
        :see: https://docs.cloud.coinbase.com/intx/reference/counterpartywithdraw
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str [tag]: an optional tag for the withdrawal
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.add_network_fee_to_total]: if True, deducts network fee from the portfolio, otherwise deduct fee from the withdrawal
        :param str [params.network_arn_id]: Identifies the blockchain network(e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a)
        :param str [params.nonce]: a unique integer representing the withdrawal request
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        portfolio = None
        portfolio, params = await self.handle_portfolio_and_params('withdraw', params)
        method = None
        method, params = self.handle_option_and_params(params, 'withdraw', 'method', 'v1PrivatePostTransfersWithdraw')
        networkId = None
        networkId, params = await self.handle_network_id_and_params(code, 'withdraw', params)
        request: dict = {
            'portfolio': portfolio,
            'type': 'send',
            'asset': currency['id'],
            'address': address,
            'amount': amount,
            'currency': currency['id'],
            'network_arn_id': networkId,
            'nonce': self.nonce(),
        }
        response = await getattr(self, method)(self.extend(request, params))
        #
        #    {
        #        "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
        #    }
        #
        return self.parse_transaction(response, currency)

    def safe_network(self, network):
        withdrawEnabled = self.safe_bool(network, 'withdraw')
        depositEnabled = self.safe_bool(network, 'deposit')
        limits = self.safe_dict(network, 'limits')
        withdraw = self.safe_dict(limits, 'withdraw')
        withdrawMax = self.safe_number(withdraw, 'max')
        deposit = self.safe_dict(limits, 'deposit')
        depositMax = self.safe_number(deposit, 'max')
        if withdrawEnabled is None and withdrawMax is not None:
            withdrawEnabled = (withdrawMax > 0)
        if depositEnabled is None and depositMax is not None:
            depositEnabled = (depositMax > 0)
        networkId = self.safe_string(network, 'id')
        isEnabled = (withdrawEnabled and depositEnabled)
        return {
            'info': network['info'],
            'id': networkId,
            'name': self.safe_string(network, 'name'),
            'network': self.safe_string(network, 'network'),
            'active': self.safe_bool(network, 'active', isEnabled),
            'deposit': depositEnabled,
            'withdraw': withdrawEnabled,
            'fee': self.safe_number(network, 'fee'),
            'precision': self.safe_number(network, 'precision'),
            'limits': {
                'withdraw': {
                    'min': self.safe_number(withdraw, 'min'),
                    'max': withdrawMax,
                },
                'deposit': {
                    'min': self.safe_number(deposit, 'min'),
                    'max': depositMax,
                },
            },
        }

    def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
        version = api[0]
        signed = api[1] == 'private'
        fullPath = '/' + version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        savedPath = '/api' + fullPath
        if method == 'GET' or method == 'DELETE':
            if query:
                fullPath += '?' + self.urlencode_with_array_repeat(query)
        url = self.urls['api']['rest'] + fullPath
        if signed:
            self.check_required_credentials()
            nonce = str(self.nonce())
            payload = ''
            if method != 'GET':
                if query:
                    body = self.json(query)
                    payload = body
            auth = nonce + method + savedPath + payload
            signature = self.hmac(self.encode(auth), self.base64_to_binary(self.secret), hashlib.sha256, 'base64')
            headers = {
                'CB-ACCESS-TIMESTAMP': nonce,
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-PASSPHRASE': self.password,
                'CB-ACCESS-KEY': self.apiKey,
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        #
        #    {
        #        "title":"io.javalin.http.BadRequestResponse: Order rejected(DUPLICATE_CLIENT_ORDER_ID - duplicate client order id detected)",
        #        "status":400
        #    }
        #
        if response is None:
            return None  # fallback to default error handler
        feedback = self.id + ' ' + body
        errMsg = self.safe_string(response, 'title')
        if errMsg is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], errMsg, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], errMsg, feedback)
            raise ExchangeError(feedback)
        return None
