<?php

namespace ccxt\async;

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

use Exception; // a common import
use \ccxt\ExchangeError;
use \ccxt\AuthenticationError;
use \ccxt\ArgumentsRequired;
use \ccxt\BadRequest;
use \ccxt\InsufficientFunds;

class wavesexchange extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'wavesexchange',
            'name' => 'Waves.Exchange',
            'countries' => ['CH'], // Switzerland
            'rateLimit' => 500,
            'certified' => true,
            'pro' => false,
            'has' => array(
                'cancelOrder' => true,
                'createMarketOrder' => false,
                'createOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchDepositAddress' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTrades' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '3h' => '3h',
                '4h' => '4h',
                '6h' => '6h',
                '12h' => '12h',
                '1d' => '1d',
                '1w' => '1w',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg',
                'api' => array(
                    'matcher' => 'http://matcher.waves.exchange',
                    'node' => 'https://nodes.waves.exchange',
                    'public' => 'https://api.wavesplatform.com/v0',
                    'private' => 'https://api.waves.exchange/v1',
                    'forward' => 'https://waves.exchange/api/v1/forward/matcher',
                    'market' => 'https://marketdata.wavesplatform.com/api/v1',
                ),
                'doc' => 'https://docs.waves.exchange',
                'www' => 'https://waves.exchange',
            ),
            'api' => array(
                'matcher' => array(
                    'get' => array(
                        'matcher',
                        'matcher/settings',
                        'matcher/settings/rates',
                        'matcher/balance/reserved/{publicKey}',
                        'matcher/debug/allSnashotOffsets',
                        'matcher/debug/currentOffset',
                        'matcher/debug/lastOffset',
                        'matcher/debug/oldestSnapshotOffset',
                        'matcher/orderbook',
                        'matcher/orderbook/{amountAsset}/{priceAsset}',
                        'matcher/orderbook/{baseId}/{quoteId}/publicKey/{publicKey}',
                        'matcher/orderbook/{baseId}/{quoteId}/{orderId}',
                        'matcher/orderbook/{baseId}/{quoteId}/info',
                        'matcher/orderbook/{baseId}/{quoteId}/status',
                        'matcher/orderbook/{baseId}/{quoteId}/tradeableBalance/{address}',
                        'matcher/orderbook/{publicKey}',
                        'matcher/orderbook/{publicKey}/{orderId}',
                        'matcher/orders/{address}',
                        'matcher/orders/{address}/{orderId}',
                        'matcher/transactions/{orderId}',
                    ),
                    'post' => array(
                        'matcher/orderbook',
                        'matcher/orderbook/market',
                        'matcher/orderbook/cancel',
                        'matcher/orderbook/{baseId}/{quoteId}/cancel',
                        'matcher/debug/saveSnapshots',
                        'matcher/orders/{address}/cancel',
                        'matcher/orders/cancel/{orderId}',
                    ),
                    'delete' => array(
                        'matcher/orderbook/{baseId}/{quoteId}',
                        'matcher/settings/rates/{assetId}',
                    ),
                    'put' => array(
                        'matcher/settings/rates/{assetId}',
                    ),
                ),
                'node' => array(
                    'get' => array(
                        'addresses',
                        'addresses/balance/{address}',
                        'addresses/balance/{address}/{confirmations}',
                        'addresses/balance/details/{address}',
                        'addresses/data/{address}',
                        'addresses/data/{address}/{key}',
                        'addresses/effectiveBalance/{address}',
                        'addresses/effectiveBalance/{address}/{confirmations}',
                        'addresses/publicKey/{publicKey}',
                        'addresses/scriptInfo/{address}',
                        'addresses/scriptInfo/{address}/meta',
                        'addresses/seed/{address}',
                        'addresses/seq/{from}/{to}',
                        'addresses/validate/{address}',
                        'alias/by-address/{address}',
                        'alias/by-alias/{alias}',
                        'assets/{assetId}/distribution/{height}/{limit}',
                        'assets/balance/{address}',
                        'assets/balance/{address}/{assetId}',
                        'assets/details/{assetId}',
                        'assets/nft/{address}/limit/{limit}',
                        'blockchain/rewards',
                        'blockchain/rewards/height',
                        'blocks/address/{address}/{from}/{to}/',
                        'blocks/at/{height}',
                        'blocks/delay/{signature}/{blockNum}',
                        'blocks/first',
                        'blocks/headers/last',
                        'blocks/headers/seq/{from}/{to}',
                        'blocks/height',
                        'blocks/height/{signature}',
                        'blocks/last',
                        'blocks/seq/{from}/{to}',
                        'blocks/signature/{signature}',
                        'consensus/algo',
                        'consensus/basetarget',
                        'consensus/basetarget/{blockId}',
                        'consensus/{generatingbalance}/address',
                        'consensus/generationsignature',
                        'consensus/generationsignature/{blockId}',
                        'debug/balances/history/{address}',
                        'debug/blocks/{howMany}',
                        'debug/configInfo',
                        'debug/historyInfo',
                        'debug/info',
                        'debug/minerInfo',
                        'debug/portfolios/{address}',
                        'debug/state',
                        'debug/stateChanges/address/{address}',
                        'debug/stateChanges/info/{id}',
                        'debug/stateWaves/{height}',
                        'leasing/active/{address}',
                        'node/state',
                        'node/version',
                        'peers/all',
                        'peers/blacklisted',
                        'peers/connected',
                        'peers/suspended',
                        'transactions/address/{address}/limit/{limit}',
                        'transactions/info/{id}',
                        'transactions/status',
                        'transactions/unconfirmed',
                        'transactions/unconfirmed/info/{id}',
                        'transactions/unconfirmed/size',
                        'utils/seed',
                        'utils/seed/{length}',
                        'utils/time',
                        'wallet/seed',
                    ),
                    'post' => array(
                        'addresses',
                        'addresses/data/{address}',
                        'addresses/sign/{address}',
                        'addresses/signText/{address}',
                        'addresses/verify/{address}',
                        'addresses/verifyText/{address}',
                        'debug/blacklist',
                        'debug/print',
                        'debug/rollback',
                        'debug/validate',
                        'node/stop',
                        'peers/clearblacklist',
                        'peers/connect',
                        'transactions/broadcast',
                        'transactions/calculateFee',
                        'tranasctions/sign',
                        'transactions/sign/{signerAddress}',
                        'tranasctions/status',
                        'utils/hash/fast',
                        'utils/hash/secure',
                        'utils/script/compileCode',
                        'utils/script/compileWithImports',
                        'utils/script/decompile',
                        'utils/script/estimate',
                        'utils/sign/{privateKey}',
                        'utils/transactionsSerialize',
                    ),
                    'delete' => array(
                        'addresses/{address}',
                        'debug/rollback-to/{signature}',
                    ),
                ),
                'public' => array(
                    'get' => array(
                        'pairs',
                        'candles/{baseId}/{quoteId}',
                        'transactions/exchange',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'deposit/addresses/{code}',
                        'deposit/currencies',
                        'withdraw/currencies',
                        'withdraw/addresses/{currency}/{address}',
                    ),
                    'post' => array(
                        'oauth2/token',
                    ),
                ),
                'forward' => array(
                    'get' => array(
                        'matcher/orders/{address}',  // can't get the orders endpoint to work with the matcher api
                        'matcher/orders/{address}/{orderId}',
                    ),
                    'post' => array(
                        'matcher/orders/{wavesAddress}/cancel',
                    ),
                ),
                'market' => array(
                    'get' => array(
                        'tickers',
                    ),
                ),
            ),
            'options' => array(
                'allowedCandles' => 1440,
                'accessToken' => null,
                'matcherPublicKey' => null,
                'quotes' => null,
                'createOrderDefaultExpiry' => 2419200000, // 60 * 60 * 24 * 28 * 1000
                'wavesAddress' => null,
                'withdrawFeeUSDN' => 7420,
                'withdrawFeeWAVES' => 100000,
                'wavesPrecision' => 8,
            ),
            'requiresEddsa' => true,
            'exceptions' => array(
                '3147270' => '\\ccxt\\InsufficientFunds',  // https://github.com/wavesplatform/matcher/wiki/List-of-all-errors
                '112' => '\\ccxt\\InsufficientFunds',
                '4' => '\\ccxt\\ExchangeError',
                '13' => '\\ccxt\\ExchangeNotAvailable',
                '14' => '\\ccxt\\ExchangeNotAvailable',
                '3145733' => '\\ccxt\\AccountSuspended',
                '3148040' => '\\ccxt\\DuplicateOrderId',
                '3148801' => '\\ccxt\\AuthenticationError',
                '9440512' => '\\ccxt\\AuthenticationError',
                '9440771' => '\\ccxt\\BadSymbol',
                '9441026' => '\\ccxt\\InvalidOrder',
                '9441282' => '\\ccxt\\InvalidOrder',
                '9441286' => '\\ccxt\\InvalidOrder',
                '9441295' => '\\ccxt\\InvalidOrder',
                '9441540' => '\\ccxt\\InvalidOrder',
                '9441542' => '\\ccxt\\InvalidOrder',
                '106954752' => '\\ccxt\\AuthenticationError',
                '106954769' => '\\ccxt\\AuthenticationError',
                '106957828' => '\\ccxt\\AuthenticationError',
                '106960131' => '\\ccxt\\AuthenticationError',
                '106981137' => '\\ccxt\\AuthenticationError',
                '9437193' => '\\ccxt\\OrderNotFound',
                '1048577' => '\\ccxt\\BadRequest',
                '1051904' => '\\ccxt\\AuthenticationError',
            ),
        ));
    }

    public function get_quotes() {
        $quotes = $this->safe_value($this->options, 'quotes');
        if ($quotes) {
            return $quotes;
        } else {
            // currencies can have any name because you can create you own token
            // as a result someone can create a fake token called BTC
            // we use this mapping to determine the real tokens
            // https://docs.waves.exchange/en/waves-matcher/matcher-api#asset-pair
            $response = yield $this->matcherGetMatcherSettings ();
            // {
            //   "orderVersions" => array(
            //     1,
            //     2,
            //     3
            //   ),
            //   "success" => true,
            //   "matcherPublicKey" => "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5",
            //   "orderFee" => {
            //     "dynamic" => {
            //       "baseFee" => 300000,
            //       "rates" => array(
            //         "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ" => 1.22639597,
            //         "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH" => 0.00989643,
            //         "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk" => 0.0395674,
            //         "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" => 0.00018814,
            //         "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" => 26.19721262,
            //         "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu" => 0.00752978,
            //         "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p" => 1.84575,
            //         "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H" => 0.02330273,
            //         "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy" => 0.00721412,
            //         "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3" => 0.02659103,
            //         "WAVES" => 1,
            //         "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa" => 0.03433583
            //       }
            //     }
            //   ),
            //   "networkByte" => 87,
            //   "matcherVersion" => "2.1.3.5",
            //   "status" => "SimpleResponse",
            //   "$priceAssets" => array(
            //     "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck",
            //     "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
            //     "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ",
            //     "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU",
            //     "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN",
            //     "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS",
            //     "WAVES",
            //     "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu",
            //     "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy",
            //     "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH",
            //     "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk",
            //     "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H",
            //     "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3",
            //     "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa",
            //     "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8"
            //   )
            // }
            $quotes = array();
            $priceAssets = $this->safe_value($response, 'priceAssets');
            for ($i = 0; $i < count($priceAssets); $i++) {
                $quotes[$priceAssets[$i]] = true;
            }
            $this->options['quotes'] = $quotes;
            return $quotes;
        }
    }

    public function fetch_markets($params = array ()) {
        $response = yield $this->marketGetTickers ();
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $entry = $response[$i];
            $baseId = $this->safe_string($entry, 'amountAssetID');
            $quoteId = $this->safe_string($entry, 'priceAssetID');
            $id = $baseId . '/' . $quoteId;
            $marketId = $this->safe_string($entry, 'symbol');
            list($base, $quote) = explode('/', $marketId);
            $symbol = $this->safe_currency_code($base) . '/' . $this->safe_currency_code($quote);
            $precision = array(
                'amount' => $this->safe_integer($entry, 'amountAssetDecimals'),
                'price' => $this->safe_integer($entry, 'priceAssetDecimals'),
            );
            $result[] = array(
                'symbol' => $symbol,
                'id' => $id,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'info' => $entry,
                'precision' => $precision,
            );
        }
        return $result;
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array_merge(array(
            'amountAsset' => $market['baseId'],
            'priceAsset' => $market['quoteId'],
        ), $params);
        $response = yield $this->matcherGetMatcherOrderbookAmountAssetPriceAsset ($request);
        $timestamp = $this->safe_integer($response, 'timestamp');
        $bids = $this->parse_order_book_side($this->safe_value($response, 'bids'), $market, $limit);
        $asks = $this->parse_order_book_side($this->safe_value($response, 'asks'), $market, $limit);
        return array(
            'bids' => $bids,
            'asks' => $asks,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'nonce' => null,
        );
    }

    public function parse_order_book_side($bookSide, $market = null, $limit = null) {
        $precision = $market['precision'];
        $wavesPrecision = $this->safe_integer($this->options, 'wavesPrecision', 8);
        $amountPrecision = pow(10, $precision['amount']);
        $difference = $precision['amount'] - $precision['price'];
        $pricePrecision = pow(10, $wavesPrecision - $difference);
        $result = array();
        for ($i = 0; $i < count($bookSide); $i++) {
            $entry = $bookSide[$i];
            $price = $this->safe_integer($entry, 'price', 0) / $pricePrecision;
            $amount = $this->safe_integer($entry, 'amount', 0) / $amountPrecision;
            if (($limit !== null) && ($i > $limit)) {
                break;
            }
            $result[] = [$price, $amount];
        }
        return $result;
    }

    public function check_required_keys() {
        if ($this->apiKey === null) {
            throw new AuthenticationError($this->id . ' requires apiKey credential');
        }
        if ($this->secret === null) {
            throw new AuthenticationError($this->id . ' requires secret credential');
        }
        $apiKeyBytes = null;
        $secretKeyBytes = null;
        try {
            $apiKeyBytes = $this->base58_to_binary($this->apiKey);
        } catch (Exception $e) {
            throw new AuthenticationError($this->id . ' apiKey must be a base58 encoded public key');
        }
        try {
            $secretKeyBytes = $this->base58_to_binary($this->secret);
        } catch (Exception $e) {
            throw new AuthenticationError($this->id . ' secret must be a base58 encoded private key');
        }
        $hexApiKeyBytes = bin2hex($apiKeyBytes);
        $hexSecretKeyBytes = bin2hex($secretKeyBytes);
        if (strlen($hexApiKeyBytes) !== 64) {
            throw new AuthenticationError($this->id . ' apiKey must be a base58 encoded public key');
        }
        if (strlen($hexSecretKeyBytes) !== 64) {
            throw new AuthenticationError($this->id . ' secret must be a base58 encoded private key');
        }
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $query = $this->omit($params, $this->extract_params($path));
        $isCancelOrder = $path === 'matcher/orders/{wavesAddress}/cancel';
        $path = $this->implode_params($path, $params);
        $url = $this->urls['api'][$api] . '/' . $path;
        $queryString = $this->urlencode($query);
        if (($api === 'private') || ($api === 'forward')) {
            $headers = array(
                'Accept' => 'application/json',
            );
            $accessToken = $this->safe_string($this->options, 'accessToken');
            if ($accessToken) {
                $headers['Authorization'] = 'Bearer ' . $accessToken;
            }
            if ($method === 'POST') {
                $headers['content-type'] = 'application/json';
            } else {
                $headers['content-type'] = 'application/x-www-form-urlencoded';
            }
            if ($isCancelOrder) {
                $body = $this->json([$query['orderId']]);
                $queryString = '';
            }
            if (strlen($queryString) > 0) {
                $url .= '?' . $queryString;
            }
        } else if ($api === 'matcher') {
            if ($method === 'POST') {
                $headers = array(
                    'content-type' => 'application/json',
                );
                $body = $this->json($query);
            } else {
                $headers = $query;
            }
        } else {
            if ($method === 'POST') {
                $headers = array(
                    'content-type' => 'application/json',
                );
                $body = $this->json($query);
            } else {
                $headers = array(
                    'content-type' => 'application/x-www-form-urlencoded',
                );
                if (strlen($queryString) > 0) {
                    $url .= '?' . $queryString;
                }
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function get_access_token() {
        if (!$this->safe_string($this->options, 'accessToken')) {
            $prefix = 'ffffff01';
            $expiresDelta = 60 * 60 * 24 * 7;
            $seconds = $this->sum($this->seconds(), $expiresDelta);
            $seconds = (string) $seconds;
            $clientId = 'waves.exchange';
            $message = 'W:' . $clientId . ':' . $seconds;
            $messageHex = bin2hex($this->encode($message));
            $payload = $prefix . $messageHex;
            $hexKey = bin2hex($this->base58_to_binary($this->secret));
            $signature = $this->eddsa($payload, $hexKey, 'ed25519');
            $request = array(
                'grant_type' => 'password',
                'scope' => 'general',
                'username' => $this->apiKey,
                'password' => $seconds . ':' . $signature,
                'client_id' => $clientId,
            );
            $response = yield $this->privatePostOauth2Token ($request);
            // { access_token => 'eyJhbGciOXJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5MTk3NTA1NywiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkOWI5IiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.B-XwexBnUAzbWknVN68RKT0ZP5w6Qk1SKJ8usL3OIwDEzCUUX9PjW-5TQHmiCRcA4oft8lqXEiCwEoNfsblCo_jTpRo518a1vZkIbHQk0-13Dm1K5ewGxfxAwBk0g49odcbKdjl64TN1yM_PO1VtLVuiTeZP-XF-S42Uj-7fcO-r7AulyQLuTE0uo-Qdep8HDCk47rduZwtJOmhFbCCnSgnLYvKWy3CVTeldsR77qxUY-vy8q9McqeP7Id-_MWnsob8vWXpkeJxaEsw1Fke1dxApJaJam09VU8EB3ZJWpkT7V8PdafIrQGeexx3jhKKxo7rRb4hDV8kfpVoCgkvFan',
            //   token_type => 'bearer',
            //   refresh_token => 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwiYXRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkXWI5IiwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5Mzk2MjI1OCwiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiM2MzZWRlMTktNjI5My00MTNlLWJmMWUtZTRlZDZlYzUzZTgzIiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.gD1Qj0jfqayfZpBvNY0t3ccMyK5hdbT7dY-_5L6LxwV0Knan4ndEtvygxlTOczmJUKtnA4T1r5GBFgNMZTvtViKZIbqZNysEg2OY8UxwDaF4VPeGJLg_QXEnn8wBeBQdyMafh9UQdwD2ci7x-saM4tOAGmncAygfTDxy80201gwDhfAkAGerb9kL00oWzSJScldxu--pNLDBUEHZt52MSEel10HGrzvZkkvvSh67vcQo5TOGb5KG6nh65UdJCwr41AVz4fbQPP-N2Nkxqy0TE_bqVzZxExXgvcS8TS0Z82T3ijJa_ct7B9wblpylBnvmyj3VycUzufD6uy8MUGq32D',
            //   expires_in => 604798,
            //   scope => 'general' }
            $this->options['accessToken'] = $this->safe_string($response, 'access_token');
            return $this->options['accessToken'];
        }
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     {
        //         "__type":"pair",
        //         "$data":array(
        //             "firstPrice":0.00012512,
        //             "lastPrice":0.00012441,
        //             "$low":0.00012167,
        //             "$high":0.00012768,
        //             "weightedAveragePrice":0.000124710697407246,
        //             "volume":209554.26356614,
        //             "$quoteVolume":26.1336583539951,
        //             "volumeWaves":209554.26356614,
        //             "txsCount":6655
        //         ),
        //         "amountAsset":"WAVES",
        //         "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS"
        //     }
        //
        $timestamp = null;
        $baseId = $this->safe_string($ticker, 'amountAsset');
        $quoteId = $this->safe_string($ticker, 'priceAsset');
        $symbol = null;
        if (($baseId !== null) && ($quoteId !== null)) {
            $marketId = $baseId . '/' . $quoteId;
            if (is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id)) {
                $market = $this->markets_by_id[$marketId];
            } else {
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $symbol = $base . '/' . $quote;
            }
        }
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        $data = $this->safe_value($ticker, 'data', array());
        $last = $this->safe_float($data, 'lastPrice');
        $low = $this->safe_float($data, 'low');
        $high = $this->safe_float($data, 'high');
        $vwap = $this->safe_float($data, 'weightedAveragePrice');
        $baseVolume = $this->safe_float($data, 'volume');
        $quoteVolume = $this->safe_float($data, 'quoteVolume');
        $open = $this->safe_value($data, 'firstPrice');
        $change = null;
        $average = null;
        $percentage = null;
        if ($last !== null && $open !== null) {
            $change = $last - $open;
            $average = $this->sum($last, $open) / 2;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
        }
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $high,
            'low' => $low,
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pairs' => $market['id'],
        );
        $response = yield $this->publicGetPairs (array_merge($request, $params));
        //
        //     {
        //         "__type":"list",
        //         "$data":array(
        //             {
        //                 "__type":"pair",
        //                 "$data":array(
        //                     "firstPrice":0.00012512,
        //                     "lastPrice":0.00012441,
        //                     "low":0.00012167,
        //                     "high":0.00012768,
        //                     "weightedAveragePrice":0.000124710697407246,
        //                     "volume":209554.26356614,
        //                     "quoteVolume":26.1336583539951,
        //                     "volumeWaves":209554.26356614,
        //                     "txsCount":6655
        //                 ),
        //                 "amountAsset":"WAVES",
        //                 "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $ticker = $this->safe_value($data, 0, array());
        return $this->parse_ticker($ticker, $market);
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'baseId' => $market['baseId'],
            'quoteId' => $market['quoteId'],
            'interval' => $this->timeframes[$timeframe],
        );
        if ($since !== null) {
            $request['timeStart'] = (string) $since;
        } else {
            $allowedCandles = $this->safe_integer($this->options, 'allowedCandles', 1440);
            $timeframeUnix = $this->parse_timeframe($timeframe) * 1000;
            $currentTime = (int) floor($this->milliseconds() / $timeframeUnix) * $timeframeUnix;
            $delta = ($allowedCandles - 1) * $timeframeUnix;
            $timeStart = $currentTime - $delta;
            $request['timeStart'] = (string) $timeStart;
        }
        $response = yield $this->publicGetCandlesBaseIdQuoteId (array_merge($request, $params));
        //
        //     {
        //         "__type" => "list",
        //         "$data" => array(
        //             {
        //                 "__type" => "candle",
        //                 "$data" => {
        //                     "time" => "2020-06-09T14:47:00.000Z",
        //                     "$open" => 0.0250385,
        //                     "close" => 0.0250385,
        //                     "high" => 0.0250385,
        //                     "low" => 0.0250385,
        //                     "volume" => 0.01033012,
        //                     "quoteVolume" => 0.00025865,
        //                     "weightedAveragePrice" => 0.0250385,
        //                     "maxHeight" => 2099399,
        //                     "txsCount" => 5,
        //                     "timeClose" => "2020-06-09T14:47:59.999Z"
        //                 }
        //             }
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $result = $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
        $lastClose = null;
        $length = is_array($result) ? count($result) : 0;
        for ($i = 0; $i < count($result); $i++) {
            $j = $length - $i - 1;
            $entry = $result[$j];
            $open = $entry[1];
            if ($open === null) {
                $entry[1] = $lastClose;
                $entry[2] = $lastClose;
                $entry[3] = $lastClose;
                $entry[4] = $lastClose;
                $result[$j] = $entry;
            }
            $lastClose = $entry[4];
        }
        return $result;
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         __type => 'candle',
        //         $data => {
        //             time => '2020-06-05T20:46:00.000Z',
        //             open => 240.573975,
        //             close => 240.573975,
        //             high => 240.573975,
        //             low => 240.573975,
        //             volume => 0.01278413,
        //             quoteVolume => 3.075528,
        //             weightedAveragePrice => 240.573975,
        //             maxHeight => 2093895,
        //             txsCount => 5,
        //             timeClose => '2020-06-05T20:46:59.999Z'
        //         }
        //     }
        //
        $data = $this->safe_value($ohlcv, 'data', array());
        return array(
            $this->parse8601($this->safe_string($data, 'time')),
            $this->safe_float($data, 'open'),
            $this->safe_float($data, 'high'),
            $this->safe_float($data, 'low'),
            $this->safe_float($data, 'close'),
            $this->safe_float($data, 'volume', 0),
        );
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->get_access_token();
        $supportedCurrencies = yield $this->privateGetDepositCurrencies ();
        $currencies = array();
        $items = $this->safe_value($supportedCurrencies, 'items', array());
        for ($i = 0; $i < count($items); $i++) {
            $entry = $items[$i];
            $currencyCode = $this->safe_string($entry, 'id');
            $currencies[$currencyCode] = true;
        }
        if (!(is_array($currencies) && array_key_exists($code, $currencies))) {
            $codes = is_array($currencies) ? array_keys($currencies) : array();
            throw new ExchangeError($this->id . ' fetch ' . $code . ' deposit $address not supported. Currency $code must be one of ' . (string) $codes);
        }
        $request = array_merge(array(
            'code' => $code,
        ), $params);
        $response = yield $this->privateGetDepositAddressesCode ($request);
        // {
        //   "type" => "deposit_addresses",
        //   "currency" => {
        //     "type" => "deposit_currency",
        //     "id" => "ERGO",
        //     "waves_asset_id" => "5dJj4Hn9t2Ve3tRpNGirUHy4yBK6qdJRAJYV21yPPuGz",
        //     "decimals" => 9,
        //     "status" => "active",
        //     "allowed_amount" => array(
        //       "min" => 0.001,
        //       "max" => 100000
        //     ),
        //     "fees" => array(
        //       "flat" => 0,
        //       "rate" => 0
        //     }
        //   ),
        //   "deposit_addresses" => array(
        //     "9fRAAQjF8Yqg7qicQCL884zjimsRnuwsSavsM1rUdDaoG8mThku"
        //   )
        // }
        $addresses = $this->safe_value($response, 'deposit_addresses');
        $address = $this->safe_string($addresses, 0);
        return array(
            'address' => $address,
            'code' => $code,
            'tag' => null,
            'info' => $response,
        );
    }

    public function get_matcher_public_key() {
        // this method returns a single string
        $matcherPublicKey = $this->safe_string($this->options, 'matcherPublicKey');
        if ($matcherPublicKey) {
            return $matcherPublicKey;
        } else {
            $response = yield $this->matcherGetMatcher ();
            // remove trailing quotes from string $response
            $this->options['matcherPublicKey'] = mb_substr($response, 1, strlen($response) - 1 - 1);
            return $this->options['matcherPublicKey'];
        }
    }

    public function get_asset_bytes($currencyId) {
        if ($currencyId === 'WAVES') {
            return $this->number_to_be(0, 1);
        } else {
            return $this->binary_concat($this->number_to_be(1, 1), $this->base58_to_binary($currencyId));
        }
    }

    public function get_asset_id($currencyId) {
        if ($currencyId === 'WAVES') {
            return '';
        }
        return $currencyId;
    }

    public function price_to_precision($symbol, $price) {
        $market = $this->markets[$symbol];
        $wavesPrecision = $this->safe_integer($this->options, 'wavesPrecision', 8);
        $difference = $market['precision']['amount'] - $market['precision']['price'];
        return intval(floatval($this->to_wei($price, $wavesPrecision - $difference)));
    }

    public function amount_to_precision($symbol, $amount) {
        return intval(floatval($this->to_wei($amount, $this->markets[$symbol]['precision']['amount'])));
    }

    public function currency_to_precision($currency, $amount) {
        return intval(floatval($this->to_wei($amount, $this->currencies[$currency]['precision'])));
    }

    public function currency_from_precision($currency, $amount) {
        return $this->from_wei($amount, $this->currencies[$currency]['precision']);
    }

    public function price_from_precision($symbol, $price) {
        $market = $this->markets[$symbol];
        $wavesPrecision = $this->safe_integer($this->options, 'wavesPrecision', 8);
        $difference = $market['precision']['amount'] - $market['precision']['price'];
        return $this->from_wei($price, $wavesPrecision - $difference);
    }

    public function get_default_expiry() {
        $expiry = $this->safe_integer($this->options, 'createOrderDefaultExpiry');
        if ($expiry) {
            return $expiry;
        } else {
            $this->options['createOrderDefaultExpiry'] = 60 * 60 * 24 * 28 * 1000;
            return $this->options['createOrderDefaultExpiry'];
        }
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->check_required_dependencies();
        $this->check_required_keys();
        yield $this->load_markets();
        $market = $this->market($symbol);
        $matcherPublicKey = yield $this->get_matcher_public_key();
        $amountAsset = $this->get_asset_id($market['baseId']);
        $priceAsset = $this->get_asset_id($market['quoteId']);
        $amount = $this->amount_to_precision($symbol, $amount);
        $price = $this->price_to_precision($symbol, $price);
        $orderType = ($side === 'buy') ? 0 : 1;
        $timestamp = $this->milliseconds();
        $expiration = $this->sum($timestamp, $this->get_default_expiry());
        $settings = yield $this->matcherGetMatcherSettings ();
        // {
        //   "orderVersions" => array(
        //     1,
        //     2,
        //     3
        //   ),
        //   "success" => true,
        //   "$matcherPublicKey" => "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5",
        //   "$orderFee" => {
        //     "$dynamic" => {
        //       "baseFee" => 300000,
        //       "$rates" => array(
        //         "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ" => 1.0257813,
        //         "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH" => 0.01268146,
        //         "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk" => 0.05232404,
        //         "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" => 0.00023985,
        //         "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" => 19.5967716,
        //         "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu" => 0.00937073,
        //         "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p" => 2.19825,
        //         "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H" => 0.03180264,
        //         "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy" => 0.00996631,
        //         "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3" => 0.03254476,
        //         "WAVES" => 1,
        //         "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa" => 0.03703704
        //       }
        //     }
        //   ),
        //   "networkByte" => 87,
        //   "matcherVersion" => "2.1.4.8",
        //   "status" => "SimpleResponse",
        //   "$priceAssets" => array(
        //     "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck",
        //     "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
        //     "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ",
        //     "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU",
        //     "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN",
        //     "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS",
        //     "WAVES",
        //     "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu",
        //     "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy",
        //     "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH",
        //     "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk",
        //     "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H",
        //     "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3",
        //     "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa",
        //     "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8"
        //   )
        // }
        $orderFee = $this->safe_value($settings, 'orderFee');
        $dynamic = $this->safe_value($orderFee, 'dynamic');
        $baseMatcherFee = $this->safe_integer($dynamic, 'baseFee');
        $wavesMatcherFee = $this->currency_from_precision('WAVES', $baseMatcherFee);
        $rates = $this->safe_value($dynamic, 'rates');
        // choose sponsored assets from the list of $priceAssets above
        $priceAssets = is_array($rates) ? array_keys($rates) : array();
        $matcherFeeAssetId = null;
        $matcherFee = null;
        if (is_array($params) && array_key_exists('feeAssetId', $params)) {
            $matcherFeeAssetId = $params['feeAssetId'];
        } else if (is_array($this->options) && array_key_exists('feeAssetId', $this->options)) {
            $matcherFeeAssetId = $this->options['feeAssetId'];
        } else {
            $balances = yield $this->fetch_balance();
            if ($balances['WAVES']['free'] > $wavesMatcherFee) {
                $matcherFeeAssetId = 'WAVES';
                $matcherFee = $baseMatcherFee;
            } else {
                for ($i = 0; $i < count($priceAssets); $i++) {
                    $assetId = $priceAssets[$i];
                    $code = $this->safe_currency_code($assetId);
                    $balance = $this->safe_value($this->safe_value($balances, $code, array()), 'free');
                    $assetFee = $rates[$assetId] * $wavesMatcherFee;
                    if (($balance !== null) && ($balance > $assetFee)) {
                        $matcherFeeAssetId = $assetId;
                        break;
                    }
                }
            }
        }
        if ($matcherFeeAssetId === null) {
            throw InsufficientFunds ($this->id . ' not enough funds to cover the fee, specify feeAssetId in $params or options, or buy some WAVES');
        }
        if ($matcherFee === null) {
            $wavesPrecision = $this->safe_integer($this->options, 'wavesPrecision', 8);
            $rate = $this->safe_float($rates, $matcherFeeAssetId);
            $code = $this->safe_currency_code($matcherFeeAssetId);
            $currency = $this->currency($code);
            $newPrecison = pow(10, $wavesPrecision - $currency['precision']);
            $matcherFee = (int) ceil($rate * $baseMatcherFee / $newPrecison);
        }
        $byteArray = [
            $this->number_to_be(3, 1),
            $this->base58_to_binary($this->apiKey),
            $this->base58_to_binary($matcherPublicKey),
            $this->get_asset_bytes($market['baseId']),
            $this->get_asset_bytes($market['quoteId']),
            $this->number_to_be($orderType, 1),
            $this->number_to_be($price, 8),
            $this->number_to_be($amount, 8),
            $this->number_to_be($timestamp, 8),
            $this->number_to_be($expiration, 8),
            $this->number_to_be($matcherFee, 8),
            $this->get_asset_bytes($matcherFeeAssetId),
        ];
        $binary = $this->binary_concat_array($byteArray);
        $signature = $this->eddsa(bin2hex($binary), bin2hex($this->base58_to_binary($this->secret)), 'ed25519');
        $assetPair = array(
            'amountAsset' => $amountAsset,
            'priceAsset' => $priceAsset,
        );
        $body = array(
            'senderPublicKey' => $this->apiKey,
            'matcherPublicKey' => $matcherPublicKey,
            'assetPair' => $assetPair,
            'orderType' => $side,
            'price' => $price,
            'amount' => $amount,
            'timestamp' => $timestamp,
            'expiration' => $expiration,
            'matcherFee' => $matcherFee,
            'signature' => $signature,
            'version' => 3,
        );
        if ($matcherFeeAssetId !== 'WAVES') {
            $body['matcherFeeAssetId'] = $matcherFeeAssetId;
        }
        $response = yield $this->matcherPostMatcherOrderbook ($body);
        // { success => true,
        //   message:
        //    array( version => 3,
        //      id => 'Do7cDJMf2MJuFyorvxNNuzS42MXSGGEq1r1hGDn1PHiS',
        //      sender => '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH',
        //      senderPublicKey => 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF',
        //      $matcherPublicKey => '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
        //      $assetPair:
        //       array( $amountAsset => null,
        //         $priceAsset => '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' ),
        //      $orderType => 'sell',
        //      $amount => 1,
        //      $price => 100000000,
        //      $timestamp => 1591593117995,
        //      $expiration => 1594012317995,
        //      $matcherFee => 300000,
        //      $matcherFeeAssetId => null,
        //      $signature => '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq',
        //      proofs:
        //       array( '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq' ) ),
        //   status => 'OrderAccepted' }
        $value = $this->safe_value($response, 'message');
        return $this->parse_order($value, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->check_required_dependencies();
        $this->check_required_keys();
        yield $this->get_access_token();
        $wavesAddress = yield $this->get_waves_address();
        $response = yield $this->forwardPostMatcherOrdersWavesAddressCancel (array(
            'wavesAddress' => $wavesAddress,
            'orderId' => $id,
        ));
        //  {
        //    "success":true,
        //    "$message":[[array("orderId":"EBpJeGM36KKFz5gTJAUKDBm89V8wqxKipSFBdU35AN3c","success":true,"status":"OrderCanceled")]],
        //    "status":"BatchCancelCompleted"
        //  }
        $message = $this->safe_value($response, 'message');
        $firstMessage = $this->safe_value($message, 0);
        $firstOrder = $this->safe_value($firstMessage, 0);
        $returnedId = $this->safe_string($firstOrder, 'orderId');
        return array(
            'info' => $response,
            'id' => $returnedId,
            'clientOrderId' => null,
            'timestamp' => null,
            'datetime' => null,
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => null,
            'side' => null,
            'price' => null,
            'amount' => null,
            'cost' => null,
            'average' => null,
            'filled' => null,
            'remaining' => null,
            'status' => null,
            'fee' => null,
            'trades' => null,
        );
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->check_required_dependencies();
        $this->check_required_keys();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $timestamp = $this->milliseconds();
        $byteArray = array(
            $this->base58_to_binary($this->apiKey),
            $this->number_to_be($timestamp, 8),
        );
        $binary = $this->binary_concat_array($byteArray);
        $hexSecret = bin2hex($this->base58_to_binary($this->secret));
        $signature = $this->eddsa(bin2hex($binary), $hexSecret, 'ed25519');
        $request = array(
            'Accept' => 'application/json',
            'Timestamp' => (string) $timestamp,
            'Signature' => $signature,
            'publicKey' => $this->apiKey,
            'baseId' => $market['baseId'],
            'quoteId' => $market['quoteId'],
        );
        $response = yield $this->matcherGetMatcherOrderbookBaseIdQuoteIdPublicKeyPublicKey (array_merge($request, $params));
        // array( array( id => '3KicDeWayY2mdrRoYdCkP3gUAoUZUNT1AA6GAtWuPLfa',
        //     type => 'sell',
        //     orderType => 'limit',
        //     amount => 1,
        //     fee => 300000,
        //     price => 100000000,
        //     $timestamp => 1591651254076,
        //     filled => 0,
        //     filledFee => 0,
        //     feeAsset => 'WAVES',
        //     status => 'Accepted',
        //     assetPair:
        //      array( amountAsset => null,
        //        priceAsset => '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' ),
        //     avgWeighedPrice => 0 ), ... )
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->get_access_token();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $address = yield $this->get_waves_address();
        $request = array(
            'address' => $address,
            'activeOnly' => true,
        );
        $response = yield $this->forwardGetMatcherOrdersAddress ($request);
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->get_access_token();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $address = yield $this->get_waves_address();
        $request = array(
            'address' => $address,
            'closedOnly' => true,
        );
        $response = yield $this->forwardGetMatcherOrdersAddress ($request);
        // array(
        //   array(
        //     "id" => "9aXcxvXai73jbAm7tQNnqaQ2PwUjdmWuyjvRTKAHsw4f",
        //     "type" => "buy",
        //     "orderType" => "$limit",
        //     "amount" => 23738330,
        //     "fee" => 300000,
        //     "price" => 3828348334,
        //     "timestamp" => 1591926905636,
        //     "filled" => 23738330,
        //     "filledFee" => 300000,
        //     "feeAsset" => "WAVES",
        //     "status" => "Filled",
        //     "assetPair" => array(
        //       "amountAsset" => "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk",
        //       "priceAsset" => null
        //     ),
        //     "avgWeighedPrice" => 3828348334
        //   ), ...
        // )
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'Cancelled' => 'canceled',
            'Accepted' => 'open',
            'Filled' => 'closed',
            'PartiallyFilled' => 'open',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function get_symbol_from_asset_pair($assetPair) {
        // a blank string or null can indicate WAVES
        $baseId = $this->safe_string($assetPair, 'amountAsset', 'WAVES');
        $quoteId = $this->safe_string($assetPair, 'priceAsset', 'WAVES');
        return $this->safe_currency_code($baseId) . '/' . $this->safe_currency_code($quoteId);
    }

    public function parse_order($order, $market = null) {
        //
        //     createOrder
        //
        //     {
        //         version => 3,
        //         $id => 'BshyeHXDfJmTnjTdBYt371jD4yWaT3JTP6KpjpsiZepS',
        //         sender => '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH',
        //         senderPublicKey => 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF',
        //         matcherPublicKey => '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
        //         $assetPair => array(
        //             amountAsset => '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
        //             priceAsset => 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p'
        //         ),
        //         orderType => 'buy',
        //         $amount => 10000,
        //         $price => 400000000,
        //         $timestamp => 1599848586891,
        //         expiration => 1602267786891,
        //         matcherFee => 3008,
        //         matcherFeeAssetId => '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
        //         signature => '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy',
        //         proofs => array(
        //             '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy'
        //         )
        //     }
        //
        //     fetchClosedOrders
        //
        //     {
        //         $id => '81D9uKk2NfmZzfG7uaJsDtxqWFbJXZmjYvrL88h15fk8',
        //         $type => 'buy',
        //         orderType => 'limit',
        //         $amount => 30000000000,
        //         $filled => 0,
        //         $price => 1000000,
        //         $fee => 300000,
        //         filledFee => 0,
        //         feeAsset => 'WAVES',
        //         $timestamp => 1594303779322,
        //         $status => 'Cancelled',
        //         $assetPair => array(
        //             amountAsset => '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
        //             priceAsset => 'WAVES'
        //         ),
        //         avgWeighedPrice => 0,
        //         version => 3
        //     }
        //
        $timestamp = $this->safe_integer($order, 'timestamp');
        $side = $this->safe_string_2($order, 'type', 'orderType');
        $type = 'limit';
        if (is_array($order) && array_key_exists('type', $order)) {
            // fetchOrders
            $type = $this->safe_string($order, 'orderType', $type);
        }
        $id = $this->safe_string($order, 'id');
        $filled = $this->safe_string($order, 'filled');
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'amount');
        $assetPair = $this->safe_value($order, 'assetPair');
        $symbol = null;
        if ($assetPair !== null) {
            $symbol = $this->get_symbol_from_asset_pair($assetPair);
        } else if ($market !== null) {
            $symbol = $market['symbol'];
        }
        $amountCurrency = $this->safe_currency_code($this->safe_string($assetPair, 'amountAsset', 'WAVES'));
        $price = $this->price_from_precision($symbol, $price);
        $amount = $this->currency_from_precision($amountCurrency, $amount);
        $cost = null;
        if (($price !== null) && ($amount !== null)) {
            $cost = $price * $amount;
        }
        $filled = $this->currency_from_precision($amountCurrency, $filled);
        $remaining = null;
        if (($filled !== null) && ($amount !== null)) {
            $remaining = $amount - $filled;
        }
        $average = $this->price_from_precision($symbol, $this->safe_string($order, 'avgWeighedPrice'));
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $fee = null;
        if (is_array($order) && array_key_exists('type', $order)) {
            $currency = $this->safe_currency_code($this->safe_string($order, 'feeAsset'));
            $fee = array(
                'currency' => $currency,
                'fee' => $this->currency_from_precision($currency, $this->safe_integer($order, 'filledFee')),
            );
        } else {
            $currency = $this->safe_currency_code($this->safe_string($order, 'matcherFeeAssetId', 'WAVES'));
            $fee = array(
                'currency' => $currency,
                'fee' => $this->currency_from_precision($currency, $this->safe_integer($order, 'matcherFee')),
            );
        }
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        );
    }

    public function get_waves_address() {
        $cachedAddreess = $this->safe_string($this->options, 'wavesAddress');
        if ($cachedAddreess === null) {
            $request = array(
                'publicKey' => $this->apiKey,
            );
            $response = yield $this->nodeGetAddressesPublicKeyPublicKey ($request);
            $this->options['wavesAddress'] = $this->safe_string($response, 'address');
            return $this->options['wavesAddress'];
        } else {
            return $cachedAddreess;
        }
    }

    public function fetch_balance($params = array ()) {
        // makes a lot of different requests to get all the data
        // in particular:
        // fetchMarkets, getWavesAddress,
        // getTotalBalance (doesn't include waves), getReservedBalance (doesn't include waves)
        // getReservedBalance (includes WAVES)
        // I couldn't find another way to get all the data
        $this->check_required_dependencies();
        $this->check_required_keys();
        yield $this->load_markets();
        $wavesAddress = yield $this->get_waves_address();
        $request = array(
            'address' => $wavesAddress,
        );
        $totalBalance = yield $this->nodeGetAssetsBalanceAddress ($request);
        // {
        //   "address" => "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH",
        //   "$balances" => array(
        //     {
        //       "assetId" => "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
        //       "$balance" => 1177200,
        //       "reissuable" => false,
        //       "minSponsoredAssetFee" => 7420,
        //       "sponsorBalance" => 47492147189709,
        //       "quantity" => 999999999775381400,
        //       "$issueTransaction" => {
        //         "senderPublicKey" => "BRnVwSVctnV8pge5vRpsJdWnkjWEJspFb6QvrmZvu3Ht",
        //         "quantity" => 1000000000000000000,
        //         "fee" => 100400000,
        //         "description" => "Neutrino USD",
        //         "type" => 3,
        //         "version" => 2,
        //         "reissuable" => false,
        //         "script" => null,
        //         "sender" => "3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo",
        //         "feeAssetId" => null,
        //         "chainId" => 87,
        //         "proofs" => array(
        //           "3HNpbVkgP69NWSeb9hGYauiQDaXrRXh3tXFzNsGwsAAXnFrA29SYGbLtziW9JLpXEq7qW1uytv5Fnm5XTUMB2BxU"
        //         ),
        //         "assetId" => "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
        //         "$decimals" => 6,
        //         "name" => "USD-N",
        //         "id" => "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
        //         "$timestamp" => 1574429393962
        //       }
        //     }
        //   )
        // }
        $balances = $this->safe_value($totalBalance, 'balances');
        $result = array();
        for ($i = 0; $i < count($balances); $i++) {
            $entry = $balances[$i];
            $issueTransaction = $this->safe_value($entry, 'issueTransaction');
            $decimals = $this->safe_integer($issueTransaction, 'decimals');
            $currencyId = $this->safe_string($entry, 'assetId');
            $balance = $this->safe_float($entry, 'balance');
            $code = null;
            if (is_array($this->currencies_by_id) && array_key_exists($currencyId, $this->currencies_by_id)) {
                $code = $this->safe_currency_code($currencyId);
                $result[$code] = $this->account();
                $result[$code]['total'] = $this->from_wei($balance, $decimals);
            }
        }
        $timestamp = $this->milliseconds();
        $byteArray = array(
            $this->base58_to_binary($this->apiKey),
            $this->number_to_be($timestamp, 8),
        );
        $binary = $this->binary_concat_array($byteArray);
        $hexSecret = bin2hex($this->base58_to_binary($this->secret));
        $signature = $this->eddsa(bin2hex($binary), $hexSecret, 'ed25519');
        $matcherRequest = array(
            'publicKey' => $this->apiKey,
            'signature' => $signature,
            'timestamp' => (string) $timestamp,
        );
        $reservedBalance = yield $this->matcherGetMatcherBalanceReservedPublicKey ($matcherRequest);
        // array( WAVES => 200300000 )
        $reservedKeys = is_array($reservedBalance) ? array_keys($reservedBalance) : array();
        for ($i = 0; $i < count($reservedKeys); $i++) {
            $currencyId = $reservedKeys[$i];
            $code = $this->safe_currency_code($currencyId);
            if (!(is_array($result) && array_key_exists($code, $result))) {
                $result[$code] = $this->account();
            }
            $amount = $this->safe_float($reservedBalance, $currencyId);
            $result[$code]['used'] = $this->currency_from_precision($code, $amount);
        }
        $wavesRequest = array(
            'address' => $wavesAddress,
        );
        $wavesTotal = yield $this->nodeGetAddressesBalanceAddress ($wavesRequest);
        // {
        //   "address" => "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH",
        //   "confirmations" => 0,
        //   "$balance" => 909085978
        // }
        $result['WAVES'] = $this->safe_value($result, 'WAVES', array());
        $result['WAVES']['total'] = $this->currency_from_precision('WAVES', $this->safe_float($wavesTotal, 'balance'));
        $codes = is_array($result) ? array_keys($result) : array();
        for ($i = 0; $i < count($codes); $i++) {
            $code = $codes[$i];
            if ($this->safe_value($result[$code], 'used') === null) {
                $result[$code]['used'] = 0.0;
            }
        }
        return $this->parse_balance($result);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $address = yield $this->get_waves_address();
        $request = array(
            'sender' => $address,
            'amountAsset' => $market['baseId'],
            'priceAsset' => $market['quoteId'],
        );
        $response = yield $this->publicGetTransactionsExchange ($request);
        $data = $this->safe_value($response, 'data');
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'amountAsset' => $market['baseId'],
            'priceAsset' => $market['quoteId'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        if ($since !== null) {
            $request['timeStart'] = $since;
        }
        $response = yield $this->publicGetTransactionsExchange ($request);
        $data = $this->safe_value($response, 'data');
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function parse_trade($trade, $market = null) {
        // { __type => 'transaction',
        //   $data:
        //    { $id => 'HSdruioHqvYHeyn9hhyoHdRWPB2bFA8ujeCPZMK6992c',
        //      $timestamp => '2020-06-09T19:34:51.897Z',
        //      height => 2099684,
        //      type => 7,
        //      version => 2,
        //      proofs:
        //       array( '26teDHERQgwjjHqEn4REcDotNG8M21xjou3X42XuDuCvrRkQo6aPyrswByH3UrkWG8v27ZAaVNzoxDg4teNcLtde' ),
        //      $fee => 0.003,
        //      sender => '3PEjHv3JGjcWNpYEEkif2w8NXV4kbhnoGgu',
        //      senderPublicKey => '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
        //      buyMatcherFee => 0.00299999,
        //      sellMatcherFee => 0.00299999,
        //      $price => 0.00012003,
        //      $amount => 60.80421562,
        //      $order1:
        //       array( $id => 'CBRwP3ar4oMvvpUiGyfxc1syh41488SDi2GkrjuBDegv',
        //         senderPublicKey => 'DBXSHBz96NFsMu7xh4fi2eT9ZnyxefAHXsMxUayzgC6a',
        //         matcherPublicKey => '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
        //         $assetPair => [Object],
        //         orderType => 'buy',
        //         $price => 0.00012003,
        //         sender => '3PJfFRgVuJ47UY4ckb74EGzEBzkHXtmG1LA',
        //         $amount => 60.80424773,
        //         $timestamp => '2020-06-09T19:34:51.885Z',
        //         expiration => '2020-06-10T12:31:31.885Z',
        //         matcherFee => 0.003,
        //         signature => '4cA3ZAb3XAEEXaFG7caqpto5TRbpR5PkhZpxoNQZ9ZReNvjuJQs5a3THnumv7rcqmVUiVtuHAgk2f67ANcqtKyJ8',
        //         matcherFeeAssetId => null ),
        //      $order2:
        //       { $id => 'CHJSLQ6dfSPs6gu2mAegrMUcRiDEDqaj2GKfvptMjS3M',
        //         senderPublicKey => '3RUC4NGFZm9H8VJhSSjJyFLdiE42qNiUagDcZPwjgDf8',
        //         matcherPublicKey => '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
        //         $assetPair => [Object],
        //         orderType => 'sell',
        //         $price => 0.00012003,
        //         sender => '3P9vKoQpMZtaSkHKpNh977YY9ZPzTuntLAq',
        //         $amount => 60.80424773,
        //         $timestamp => '2020-06-09T19:34:51.887Z',
        //         expiration => '2020-06-10T12:31:31.887Z',
        //         matcherFee => 0.003,
        //         signature => '3SFyrcqzou2ddZyNisnLYaGhLt5qRjKxH8Nw3s4T5U7CEKGX9DDo8dS27RgThPVGbYF1rYET1FwrWoQ2UFZ6SMTR',
        //         matcherFeeAssetId => null } } }
        $data = $this->safe_value($trade, 'data');
        $datetime = $this->safe_string($data, 'timestamp');
        $timestamp = $this->parse8601($datetime);
        $id = $this->safe_string($data, 'id');
        $price = $this->safe_float($data, 'price');
        $amount = $this->safe_float($data, 'amount');
        $order1 = $this->safe_value($data, 'order1');
        $order2 = $this->safe_value($data, 'order2');
        $order = null;
        // $order2 arrived after $order1
        if ($this->safe_string($order1, 'senderPublicKey') === $this->apiKey) {
            $order = $order1;
        } else {
            $order = $order2;
        }
        $symbol = null;
        $assetPair = $this->safe_value($order, 'assetPair');
        if ($assetPair !== null) {
            $symbol = $this->get_symbol_from_asset_pair($assetPair);
        } else if ($market !== null) {
            $symbol = $market['symbol'];
        }
        $side = $this->safe_string($order, 'orderType');
        $orderId = $this->safe_string($order, 'id');
        $cost = null;
        if (($price !== null) && ($amount !== null)) {
            $cost = $price * $amount;
        }
        $fee = array(
            'cost' => $this->safe_float($data, 'fee'),
            'currency' => $this->safe_currency_code($this->safe_string($order, 'matcherFeeAssetId', 'WAVES')),
        );
        return array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $datetime,
            'symbol' => $symbol,
            'id' => $id,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        $errorCode = $this->safe_string($response, 'error');
        $success = $this->safe_value($response, 'success', true);
        $Exception = $this->safe_value($this->exceptions, $errorCode);
        if ($Exception !== null) {
            $message = $this->safe_string($response, 'message');
            throw new $Exception($this->id . ' ' . $message);
        }
        $message = $this->safe_string($response, 'message');
        if ($message === 'Validation Error') {
            throw new BadRequest($this->id . ' ' . $body);
        }
        if (!$success) {
            throw new ExchangeError($this->id . ' ' . $body);
        }
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        // currently only works for BTC and WAVES
        if ($code !== 'WAVES') {
            $supportedCurrencies = yield $this->privateGetWithdrawCurrencies ();
            $currencies = array();
            $items = $this->safe_value($supportedCurrencies, 'items', array());
            for ($i = 0; $i < count($items); $i++) {
                $entry = $items[$i];
                $currencyCode = $this->safe_string($entry, 'id');
                $currencies[$currencyCode] = true;
            }
            if (!(is_array($currencies) && array_key_exists($code, $currencies))) {
                $codes = is_array($currencies) ? array_keys($currencies) : array();
                throw new ExchangeError($this->id . ' fetch ' . $code . ' withdrawals are not supported. Currency $code must be one of ' . (string) $codes);
            }
        }
        yield $this->load_markets();
        $hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
        $set = array();
        for ($i = 0; $i < count($hexChars); $i++) {
            $key = $hexChars[$i];
            $set[$key] = true;
        }
        $isErc20 = true;
        $noPrefix = $this->remove0x_prefix($address);
        $lower = strtolower($noPrefix);
        for ($i = 0; $i < count($lower); $i++) {
            $character = $lower[$i];
            if (!(is_array($set) && array_key_exists($character, $set))) {
                $isErc20 = false;
                break;
            }
        }
        yield $this->get_access_token();
        $proxyAddress = null;
        if ($code === 'WAVES' && !$isErc20) {
            $proxyAddress = $address;
        } else {
            $withdrawAddressRequest = array(
                'address' => $address,
                'currency' => $code,
            );
            $withdrawAddress = yield $this->privateGetWithdrawAddressesCurrencyAddress ($withdrawAddressRequest);
            $currency = $this->safe_value($withdrawAddress, 'currency');
            $allowedAmount = $this->safe_value($currency, 'allowed_amount');
            $minimum = $this->safe_float($allowedAmount, 'min');
            if ($amount <= $minimum) {
                throw new BadRequest($this->id . ' ' . $code . ' withdraw failed, $amount ' . (string) $amount . ' must be greater than the $minimum allowed $amount of ' . (string) $minimum);
            }
            // {
            //   "$type" => "withdrawal_addresses",
            //   "$currency" => {
            //     "$type" => "withdrawal_currency",
            //     "id" => "BTC",
            //     "waves_asset_id" => "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS",
            //     "decimals" => 8,
            //     "status" => "active",
            //     "allowed_amount" => array(
            //       "min" => 0.001,
            //       "max" => 20
            //     ),
            //     "fees" => array(
            //       "flat" => 0.001,
            //       "rate" => 0
            //     }
            //   ),
            //   "proxy_addresses" => array(
            //     "3P3qqmkiLwNHB7x1FeoE8bvkRtULwGpo9ga"
            //   )
            // }
            $proxyAddresses = $this->safe_value($withdrawAddress, 'proxy_addresses', array());
            $proxyAddress = $this->safe_string($proxyAddresses, 0);
        }
        $fee = $this->safe_integer($this->options, 'withdrawFeeWAVES', 100000);  // 0.001 WAVES
        $feeAssetId = 'WAVES';
        $type = 4;  // transfer
        $version = 2;
        $amountInteger = $this->currency_to_precision($code, $amount);
        $currency = $this->currency($code);
        $timestamp = $this->milliseconds();
        $byteArray = [
            $this->number_to_be(4, 1),
            $this->number_to_be(2, 1),
            $this->base58_to_binary($this->apiKey),
            $this->get_asset_bytes($currency['id']),
            $this->get_asset_bytes($feeAssetId),
            $this->number_to_be($timestamp, 8),
            $this->number_to_be($amountInteger, 8),
            $this->number_to_be($fee, 8),
            $this->base58_to_binary($proxyAddress),
            $this->number_to_be(0, 2),
        ];
        $binary = $this->binary_concat_array($byteArray);
        $hexSecret = bin2hex($this->base58_to_binary($this->secret));
        $signature = $this->eddsa(bin2hex($binary), $hexSecret, 'ed25519');
        $request = array(
            'senderPublicKey' => $this->apiKey,
            'amount' => $amountInteger,
            'fee' => $fee,
            'type' => $type,
            'version' => $version,
            'attachment' => '',
            'feeAssetId' => $this->get_asset_id($feeAssetId),
            'proofs' => array(
                $signature,
            ),
            'assetId' => $this->get_asset_id($currency['id']),
            'recipient' => $proxyAddress,
            'timestamp' => $timestamp,
            'signature' => $signature,
        );
        return yield $this->nodePostTransactionsBroadcast ($request);
    }
}
