astro-perp-ccxt-dev
Version:
5,170 lines • 253 kB
JavaScript
'use strict';
var coinbase$1 = require('./abstract/coinbase.js');
var errors = require('./base/errors.js');
var Precise = require('./base/Precise.js');
var number = require('./base/functions/number.js');
var sha256 = require('./static_dependencies/noble-hashes/sha256.js');
var rsa = require('./base/functions/rsa.js');
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
/**
* @class coinbase
* @augments Exchange
*/
class coinbase extends coinbase$1 {
describe() {
return this.deepExtend(super.describe(), {
'id': 'coinbase',
'name': 'Coinbase Advanced',
'countries': ['US'],
'pro': true,
'certified': false,
// rate-limits:
// ADVANCED API: https://docs.cloud.coinbase.com/advanced-trade/docs/rest-api-rate-limits
// - max 30 req/second for private data, 10 req/s for public data
// DATA API : https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/rate-limiting
// - max 10000 req/hour (to prevent userland mistakes we apply ~3 req/second RL per call
'rateLimit': 34,
'version': 'v2',
'userAgent': this.userAgents['chrome'],
'headers': {
'CB-VERSION': '2018-05-30',
},
'has': {
'CORS': true,
'spot': true,
'margin': false,
'swap': false,
'future': false,
'option': false,
'addMargin': false,
'cancelOrder': true,
'cancelOrders': true,
'closeAllPositions': false,
'closePosition': true,
'createConvertTrade': true,
'createDepositAddress': true,
'createLimitBuyOrder': true,
'createLimitSellOrder': true,
'createMarketBuyOrder': true,
'createMarketBuyOrderWithCost': true,
'createMarketOrderWithCost': false,
'createMarketSellOrder': true,
'createMarketSellOrderWithCost': false,
'createOrder': true,
'createPostOnlyOrder': true,
'createReduceOnlyOrder': false,
'createStopLimitOrder': true,
'createStopMarketOrder': false,
'createStopOrder': true,
'deposit': true,
'editOrder': true,
'fetchAccounts': true,
'fetchBalance': true,
'fetchBidsAsks': true,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchCanceledOrders': true,
'fetchClosedOrders': true,
'fetchConvertQuote': true,
'fetchConvertTrade': true,
'fetchConvertTradeHistory': false,
'fetchCrossBorrowRate': false,
'fetchCrossBorrowRates': false,
'fetchCurrencies': true,
'fetchDeposit': true,
'fetchDepositAddress': 'emulated',
'fetchDepositAddresses': false,
'fetchDepositAddressesByNetwork': true,
'fetchDepositMethodId': true,
'fetchDepositMethodIds': true,
'fetchDeposits': true,
'fetchDepositsWithdrawals': true,
'fetchFundingHistory': false,
'fetchFundingRate': false,
'fetchFundingRateHistory': false,
'fetchFundingRates': false,
'fetchIndexOHLCV': false,
'fetchIsolatedBorrowRate': false,
'fetchIsolatedBorrowRates': false,
'fetchL2OrderBook': false,
'fetchLedger': true,
'fetchLeverage': false,
'fetchLeverageTiers': false,
'fetchMarginMode': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyBuys': true,
'fetchMySells': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterestHistory': false,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchPosition': true,
'fetchPositionMode': false,
'fetchPositions': true,
'fetchPositionsRisk': false,
'fetchPremiumIndexOHLCV': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTradingFee': 'emulated',
'fetchTradingFees': true,
'fetchWithdrawals': true,
'reduceMargin': false,
'setLeverage': false,
'setMarginMode': false,
'setPositionMode': false,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg',
'api': {
'rest': 'https://api.coinbase.com',
},
'www': 'https://www.coinbase.com',
'doc': [
'https://developers.coinbase.com/api/v2',
'https://docs.cloud.coinbase.com/advanced-trade/docs/welcome',
],
'fees': [
'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees',
'https://www.coinbase.com/advanced-fees',
],
'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
},
'api': {
'v2': {
'public': {
'get': {
'currencies': 10.6,
'currencies/crypto': 10.6,
'time': 10.6,
'exchange-rates': 10.6,
'users/{user_id}': 10.6,
'prices/{symbol}/buy': 10.6,
'prices/{symbol}/sell': 10.6,
'prices/{symbol}/spot': 10.6,
},
},
'private': {
'get': {
'accounts': 10.6,
'accounts/{account_id}': 10.6,
'accounts/{account_id}/addresses': 10.6,
'accounts/{account_id}/addresses/{address_id}': 10.6,
'accounts/{account_id}/addresses/{address_id}/transactions': 10.6,
'accounts/{account_id}/transactions': 10.6,
'accounts/{account_id}/transactions/{transaction_id}': 10.6,
'accounts/{account_id}/buys': 10.6,
'accounts/{account_id}/buys/{buy_id}': 10.6,
'accounts/{account_id}/sells': 10.6,
'accounts/{account_id}/sells/{sell_id}': 10.6,
'accounts/{account_id}/deposits': 10.6,
'accounts/{account_id}/deposits/{deposit_id}': 10.6,
'accounts/{account_id}/withdrawals': 10.6,
'accounts/{account_id}/withdrawals/{withdrawal_id}': 10.6,
'payment-methods': 10.6,
'payment-methods/{payment_method_id}': 10.6,
'user': 10.6,
'user/auth': 10.6,
},
'post': {
'accounts': 10.6,
'accounts/{account_id}/primary': 10.6,
'accounts/{account_id}/addresses': 10.6,
'accounts/{account_id}/transactions': 10.6,
'accounts/{account_id}/transactions/{transaction_id}/complete': 10.6,
'accounts/{account_id}/transactions/{transaction_id}/resend': 10.6,
'accounts/{account_id}/buys': 10.6,
'accounts/{account_id}/buys/{buy_id}/commit': 10.6,
'accounts/{account_id}/sells': 10.6,
'accounts/{account_id}/sells/{sell_id}/commit': 10.6,
'accounts/{account_id}/deposits': 10.6,
'accounts/{account_id}/deposits/{deposit_id}/commit': 10.6,
'accounts/{account_id}/withdrawals': 10.6,
'accounts/{account_id}/withdrawals/{withdrawal_id}/commit': 10.6,
},
'put': {
'accounts/{account_id}': 10.6,
'user': 10.6,
},
'delete': {
'accounts/{id}': 10.6,
'accounts/{account_id}/transactions/{transaction_id}': 10.6,
},
},
},
'v3': {
'public': {
'get': {
'brokerage/time': 3,
'brokerage/market/product_book': 3,
'brokerage/market/products': 3,
'brokerage/market/products/{product_id}': 3,
'brokerage/market/products/{product_id}/candles': 3,
'brokerage/market/products/{product_id}/ticker': 3,
},
},
'private': {
'get': {
'brokerage/accounts': 1,
'brokerage/accounts/{account_uuid}': 1,
'brokerage/orders/historical/batch': 1,
'brokerage/orders/historical/fills': 1,
'brokerage/orders/historical/{order_id}': 1,
'brokerage/products': 3,
'brokerage/products/{product_id}': 3,
'brokerage/products/{product_id}/candles': 3,
'brokerage/products/{product_id}/ticker': 3,
'brokerage/best_bid_ask': 3,
'brokerage/product_book': 3,
'brokerage/transaction_summary': 3,
'brokerage/portfolios': 1,
'brokerage/portfolios/{portfolio_uuid}': 1,
'brokerage/convert/trade/{trade_id}': 1,
'brokerage/cfm/balance_summary': 1,
'brokerage/cfm/positions': 1,
'brokerage/cfm/positions/{product_id}': 1,
'brokerage/cfm/sweeps': 1,
'brokerage/intx/portfolio/{portfolio_uuid}': 1,
'brokerage/intx/positions/{portfolio_uuid}': 1,
'brokerage/intx/positions/{portfolio_uuid}/{symbol}': 1,
'brokerage/payment_methods': 1,
'brokerage/payment_methods/{payment_method_id}': 1,
},
'post': {
'brokerage/orders': 1,
'brokerage/orders/batch_cancel': 1,
'brokerage/orders/edit': 1,
'brokerage/orders/edit_preview': 1,
'brokerage/orders/preview': 1,
'brokerage/portfolios': 1,
'brokerage/portfolios/move_funds': 1,
'brokerage/convert/quote': 1,
'brokerage/convert/trade/{trade_id}': 1,
'brokerage/cfm/sweeps/schedule': 1,
'brokerage/intx/allocate': 1,
// futures
'brokerage/orders/close_position': 1,
},
'put': {
'brokerage/portfolios/{portfolio_uuid}': 1,
},
'delete': {
'brokerage/portfolios/{portfolio_uuid}': 1,
'brokerage/cfm/sweeps': 1,
},
},
},
},
'fees': {
'trading': {
'taker': this.parseNumber('0.012'),
'maker': this.parseNumber('0.006'),
'tierBased': true,
'percentage': true,
'tiers': {
'taker': [
[this.parseNumber('0'), this.parseNumber('0.006')],
[this.parseNumber('10000'), this.parseNumber('0.004')],
[this.parseNumber('50000'), this.parseNumber('0.0025')],
[this.parseNumber('100000'), this.parseNumber('0.002')],
[this.parseNumber('1000000'), this.parseNumber('0.0018')],
[this.parseNumber('15000000'), this.parseNumber('0.0016')],
[this.parseNumber('75000000'), this.parseNumber('0.0012')],
[this.parseNumber('250000000'), this.parseNumber('0.0008')],
[this.parseNumber('400000000'), this.parseNumber('0.0005')],
],
'maker': [
[this.parseNumber('0'), this.parseNumber('0.004')],
[this.parseNumber('10000'), this.parseNumber('0.0025')],
[this.parseNumber('50000'), this.parseNumber('0.0015')],
[this.parseNumber('100000'), this.parseNumber('0.001')],
[this.parseNumber('1000000'), this.parseNumber('0.0008')],
[this.parseNumber('15000000'), this.parseNumber('0.0006')],
[this.parseNumber('75000000'), this.parseNumber('0.0003')],
[this.parseNumber('250000000'), this.parseNumber('0.0')],
[this.parseNumber('400000000'), this.parseNumber('0.0')],
],
},
},
},
'precisionMode': number.TICK_SIZE,
'exceptions': {
'exact': {
'two_factor_required': errors.AuthenticationError,
'param_required': errors.ExchangeError,
'validation_error': errors.ExchangeError,
'invalid_request': errors.ExchangeError,
'personal_details_required': errors.AuthenticationError,
'identity_verification_required': errors.AuthenticationError,
'jumio_verification_required': errors.AuthenticationError,
'jumio_face_match_verification_required': errors.AuthenticationError,
'unverified_email': errors.AuthenticationError,
'authentication_error': errors.AuthenticationError,
'invalid_authentication_method': errors.AuthenticationError,
'invalid_token': errors.AuthenticationError,
'revoked_token': errors.AuthenticationError,
'expired_token': errors.AuthenticationError,
'invalid_scope': errors.AuthenticationError,
'not_found': errors.ExchangeError,
'rate_limit_exceeded': errors.RateLimitExceeded,
'internal_server_error': errors.ExchangeError,
'UNSUPPORTED_ORDER_CONFIGURATION': errors.BadRequest,
'INSUFFICIENT_FUND': errors.BadRequest,
'PERMISSION_DENIED': errors.PermissionDenied,
'INVALID_ARGUMENT': errors.BadRequest,
'PREVIEW_STOP_PRICE_ABOVE_LAST_TRADE_PRICE': errors.InvalidOrder,
},
'broad': {
'request timestamp expired': errors.InvalidNonce,
'order with this orderID was not found': errors.OrderNotFound, // {"error":"unknown","error_details":"order with this orderID was not found","message":"order with this orderID was not found"}
},
},
'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',
},
'commonCurrencies': {
'CGLD': 'CELO',
},
'options': {
'usePrivate': false,
'brokerId': 'ccxt',
'stablePairs': ['BUSD-USD', 'CBETH-ETH', 'DAI-USD', 'GUSD-USD', 'GYEN-USD', 'PAX-USD', 'PAX-USDT', 'USDC-EUR', 'USDC-GBP', 'USDT-EUR', 'USDT-GBP', 'USDT-USD', 'USDT-USDC', 'WBTC-BTC'],
'fetchCurrencies': {
'expires': 5000,
},
'accounts': [
'wallet',
'fiat',
// 'vault',
],
'v3Accounts': [
'ACCOUNT_TYPE_CRYPTO',
'ACCOUNT_TYPE_FIAT',
],
'networks': {
'ERC20': 'ethereum',
'XLM': 'stellar',
},
'createMarketBuyOrderRequiresPrice': true,
'advanced': true,
'fetchMarkets': 'fetchMarketsV3',
'timeDifference': 0,
'adjustForTimeDifference': false,
'fetchTicker': 'fetchTickerV3',
'fetchTickers': 'fetchTickersV3',
'fetchAccounts': 'fetchAccountsV3',
'fetchBalance': 'v2PrivateGetAccounts',
'fetchTime': 'v2PublicGetTime',
'user_native_currency': 'USD', // needed to get fees for v3
},
'features': {
'default': {
'sandbox': false,
'createOrder': {
'marginMode': true,
'triggerPrice': true,
'triggerPriceType': undefined,
'triggerDirection': true,
'stopLossPrice': true,
'takeProfitPrice': true,
'attachedStopLossTakeProfit': undefined,
'timeInForce': {
'IOC': true,
'FOK': true,
'PO': true,
'GTD': true,
},
'hedged': false,
'trailing': false,
'leverage': true,
'marketBuyByCost': true,
'marketBuyRequiresPrice': true,
'selfTradePrevention': false,
'iceberg': false,
},
'createOrders': undefined,
'fetchMyTrades': {
'marginMode': false,
'limit': 3000,
'daysBack': undefined,
'untilDays': 10000,
'symbolRequired': false,
},
'fetchOrder': {
'marginMode': false,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchOpenOrders': {
'marginMode': false,
'limit': undefined,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchOrders': {
'marginMode': false,
'limit': undefined,
'daysBack': undefined,
'untilDays': 10000,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchClosedOrders': {
'marginMode': false,
'limit': undefined,
'daysBack': undefined,
'daysBackCanceled': undefined,
'untilDays': 10000,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchOHLCV': {
'limit': 300,
},
},
'spot': {
'extends': 'default',
},
'swap': {
'linear': {
'extends': 'default',
},
'inverse': undefined,
},
'future': {
'linear': {
'extends': 'default',
},
'inverse': undefined,
},
},
});
}
/**
* @method
* @name coinbase#fetchTime
* @description fetches the current integer timestamp in milliseconds from the exchange server
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-time#http-request
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.method] 'v2PublicGetTime' or 'v3PublicGetBrokerageTime' default is 'v2PublicGetTime'
* @returns {int} the current integer timestamp in milliseconds from the exchange server
*/
async fetchTime(params = {}) {
const defaultMethod = this.safeString(this.options, 'fetchTime', 'v2PublicGetTime');
const method = this.safeString(params, 'method', defaultMethod);
params = this.omit(params, 'method');
let response = undefined;
if (method === 'v2PublicGetTime') {
response = await this.v2PublicGetTime(params);
//
// {
// "data": {
// "epoch": 1589295679,
// "iso": "2020-05-12T15:01:19Z"
// }
// }
//
response = this.safeDict(response, 'data', {});
}
else {
response = await this.v3PublicGetBrokerageTime(params);
//
// {
// "iso": "2024-02-27T03:37:14Z",
// "epochSeconds": "1709005034",
// "epochMillis": "1709005034333"
// }
//
}
return this.safeTimestamp2(response, 'epoch', 'epochSeconds');
}
/**
* @method
* @name coinbase#fetchAccounts
* @description fetch all the accounts associated with a profile
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object} a dictionary of [account structures]{@link https://docs.ccxt.com/#/?id=account-structure} indexed by the account type
*/
async fetchAccounts(params = {}) {
const method = this.safeString(this.options, 'fetchAccounts', 'fetchAccountsV3');
if (method === 'fetchAccountsV3') {
return await this.fetchAccountsV3(params);
}
return await this.fetchAccountsV2(params);
}
async fetchAccountsV2(params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchAccounts', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchAccounts', undefined, undefined, undefined, params, 'next_starting_after', 'starting_after', undefined, 100);
}
const request = {
'limit': 100,
};
const response = await this.v2PrivateGetAccounts(this.extend(request, params));
//
// {
// "pagination": {
// "ending_before": null,
// "starting_after": null,
// "previous_ending_before": null,
// "next_starting_after": null,
// "limit": 244,
// "order": "desc",
// "previous_uri": null,
// "next_uri": null
// },
// "data": [
// {
// "id": "XLM",
// "name": "XLM Wallet",
// "primary": false,
// "type": "wallet",
// "currency": {
// "code": "XLM",
// "name": "Stellar Lumens",
// "color": "#000000",
// "sort_index": 127,
// "exponent": 7,
// "type": "crypto",
// "address_regex": "^G[A-Z2-7]{55}$",
// "asset_id": "13b83335-5ede-595b-821e-5bcdfa80560f",
// "destination_tag_name": "XLM Memo ID",
// "destination_tag_regex": "^[ -~]{1,28}$"
// },
// "balance": {
// "amount": "0.0000000",
// "currency": "XLM"
// },
// "created_at": null,
// "updated_at": null,
// "resource": "account",
// "resource_path": "/v2/accounts/XLM",
// "allow_deposits": true,
// "allow_withdrawals": true
// },
// ]
// }
//
const data = this.safeList(response, 'data', []);
const pagination = this.safeDict(response, 'pagination', {});
const cursor = this.safeString(pagination, 'next_starting_after');
const accounts = this.safeList(response, 'data', []);
const length = accounts.length;
const lastIndex = length - 1;
const last = this.safeDict(accounts, lastIndex);
if ((cursor !== undefined) && (cursor !== '')) {
last['next_starting_after'] = cursor;
accounts[lastIndex] = last;
}
return this.parseAccounts(data, params);
}
async fetchAccountsV3(params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchAccounts', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchAccounts', undefined, undefined, undefined, params, 'cursor', 'cursor', undefined, 250);
}
const request = {
'limit': 250,
};
const response = await this.v3PrivateGetBrokerageAccounts(this.extend(request, params));
//
// {
// "accounts": [
// {
// "uuid": "11111111-1111-1111-1111-111111111111",
// "name": "USDC Wallet",
// "currency": "USDC",
// "available_balance": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// },
// "default": true,
// "active": true,
// "created_at": "2023-01-04T06:20:06.456Z",
// "updated_at": "2023-01-04T06:20:07.181Z",
// "deleted_at": null,
// "type": "ACCOUNT_TYPE_CRYPTO",
// "ready": false,
// "hold": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// }
// },
// ...
// ],
// "has_next": false,
// "cursor": "",
// "size": 9
// }
//
const accounts = this.safeList(response, 'accounts', []);
const length = accounts.length;
const lastIndex = length - 1;
const last = this.safeDict(accounts, lastIndex);
const cursor = this.safeString(response, 'cursor');
if ((cursor !== undefined) && (cursor !== '')) {
last['cursor'] = cursor;
accounts[lastIndex] = last;
}
return this.parseAccounts(accounts, params);
}
/**
* @method
* @name coinbase#fetchPortfolios
* @description fetch all the portfolios
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getportfolios
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a dictionary of [account structures]{@link https://docs.ccxt.com/#/?id=account-structure} indexed by the account type
*/
async fetchPortfolios(params = {}) {
const response = await this.v3PrivateGetBrokeragePortfolios(params);
const portfolios = this.safeList(response, 'portfolios', []);
const result = [];
for (let i = 0; i < portfolios.length; i++) {
const portfolio = portfolios[i];
result.push({
'id': this.safeString(portfolio, 'uuid'),
'type': this.safeString(portfolio, 'type'),
'code': undefined,
'info': portfolio,
});
}
return result;
}
parseAccount(account) {
//
// fetchAccountsV2
//
// {
// "id": "XLM",
// "name": "XLM Wallet",
// "primary": false,
// "type": "wallet",
// "currency": {
// "code": "XLM",
// "name": "Stellar Lumens",
// "color": "#000000",
// "sort_index": 127,
// "exponent": 7,
// "type": "crypto",
// "address_regex": "^G[A-Z2-7]{55}$",
// "asset_id": "13b83335-5ede-595b-821e-5bcdfa80560f",
// "destination_tag_name": "XLM Memo ID",
// "destination_tag_regex": "^[ -~]{1,28}$"
// },
// "balance": {
// "amount": "0.0000000",
// "currency": "XLM"
// },
// "created_at": null,
// "updated_at": null,
// "resource": "account",
// "resource_path": "/v2/accounts/XLM",
// "allow_deposits": true,
// "allow_withdrawals": true
// }
//
// fetchAccountsV3
//
// {
// "uuid": "11111111-1111-1111-1111-111111111111",
// "name": "USDC Wallet",
// "currency": "USDC",
// "available_balance": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// },
// "default": true,
// "active": true,
// "created_at": "2023-01-04T06:20:06.456Z",
// "updated_at": "2023-01-04T06:20:07.181Z",
// "deleted_at": null,
// "type": "ACCOUNT_TYPE_CRYPTO",
// "ready": false,
// "hold": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// }
// }
//
const active = this.safeBool(account, 'active');
const currencyIdV3 = this.safeString(account, 'currency');
const currency = this.safeDict(account, 'currency', {});
const currencyId = this.safeString(currency, 'code', currencyIdV3);
const typeV3 = this.safeString(account, 'name');
const typeV2 = this.safeString(account, 'type');
const parts = typeV3.split(' ');
return {
'id': this.safeString2(account, 'id', 'uuid'),
'type': (active !== undefined) ? this.safeStringLower(parts, 1) : typeV2,
'code': this.safeCurrencyCode(currencyId),
'info': account,
};
}
/**
* @method
* @name coinbase#createDepositAddress
* @description create a currency deposit address
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-addresses#create-address
* @param {string} code unified currency code of the currency for the deposit address
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure}
*/
async createDepositAddress(code, params = {}) {
let accountId = this.safeString(params, 'account_id');
params = this.omit(params, 'account_id');
if (accountId === undefined) {
await this.loadAccounts();
for (let i = 0; i < this.accounts.length; i++) {
const account = this.accounts[i];
if (account['code'] === code && account['type'] === 'wallet') {
accountId = account['id'];
break;
}
}
}
if (accountId === undefined) {
throw new errors.ExchangeError(this.id + ' createDepositAddress() could not find the account with matching currency code ' + code + ', specify an `account_id` extra param to target specific wallet');
}
const request = {
'account_id': accountId,
};
const response = await this.v2PrivatePostAccountsAccountIdAddresses(this.extend(request, params));
//
// {
// "data": {
// "id": "05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
// "address": "coinbasebase",
// "address_info": {
// "address": "coinbasebase",
// "destination_tag": "287594668"
// },
// "name": null,
// "created_at": "2019-07-01T14:39:29Z",
// "updated_at": "2019-07-01T14:39:29Z",
// "network": "eosio",
// "uri_scheme": "eosio",
// "resource": "address",
// "resource_path": "/v2/accounts/14cfc769-e852-52f3-b831-711c104d194c/addresses/05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
// "warnings": [
// {
// "title": "Only send EOS (EOS) to this address",
// "details": "Sending any other cryptocurrency will result in permanent loss.",
// "image_url": "https://dynamic-assets.coinbase.com/deaca3d47b10ed4a91a872e9618706eec34081127762d88f2476ac8e99ada4b48525a9565cf2206d18c04053f278f693434af4d4629ca084a9d01b7a286a7e26/asset_icons/1f8489bb280fb0a0fd643c1161312ba49655040e9aaaced5f9ad3eeaf868eadc.png"
// },
// {
// "title": "Both an address and EOS memo are required to receive EOS",
// "details": "If you send funds without an EOS memo or with an incorrect EOS memo, your funds cannot be credited to your account.",
// "image_url": "https://www.coinbase.com/assets/receive-warning-2f3269d83547a7748fb39d6e0c1c393aee26669bfea6b9f12718094a1abff155.png"
// }
// ],
// "warning_title": "Only send EOS (EOS) to this address",
// "warning_details": "Sending any other cryptocurrency will result in permanent loss.",
// "destination_tag": "287594668",
// "deposit_uri": "eosio:coinbasebase?dt=287594668",
// "callback_url": null
// }
// }
//
const data = this.safeDict(response, 'data', {});
const tag = this.safeString(data, 'destination_tag');
const address = this.safeString(data, 'address');
return {
'currency': code,
'tag': tag,
'address': address,
'network': undefined,
'info': response,
};
}
/**
* @method
* @name coinbase#fetchMySells
* @ignore
* @description fetch sells
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-sells#list-sells
* @param {string} symbol not used by coinbase fetchMySells ()
* @param {int} [since] timestamp in ms of the earliest sell, default is undefined
* @param {int} [limit] max number of sells to return, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [list of order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchMySells(symbol = undefined, since = undefined, limit = undefined, params = {}) {
// v2 did't have an endpoint for all historical trades
const request = this.prepareAccountRequest(limit, params);
await this.loadMarkets();
const query = this.omit(params, ['account_id', 'accountId']);
const sells = await this.v2PrivateGetAccountsAccountIdSells(this.extend(request, query));
return this.parseTrades(sells['data'], undefined, since, limit);
}
/**
* @method
* @name coinbase#fetchMyBuys
* @ignore
* @description fetch buys
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-buys#list-buys
* @param {string} symbol not used by coinbase fetchMyBuys ()
* @param {int} [since] timestamp in ms of the earliest buy, default is undefined
* @param {int} [limit] max number of buys to return, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchMyBuys(symbol = undefined, since = undefined, limit = undefined, params = {}) {
// v2 did't have an endpoint for all historical trades
const request = this.prepareAccountRequest(limit, params);
await this.loadMarkets();
const query = this.omit(params, ['account_id', 'accountId']);
const buys = await this.v2PrivateGetAccountsAccountIdBuys(this.extend(request, query));
return this.parseTrades(buys['data'], undefined, since, limit);
}
async fetchTransactionsWithMethod(method, code = undefined, since = undefined, limit = undefined, params = {}) {
let request = undefined;
[request, params] = await this.prepareAccountRequestWithCurrencyCode(code, limit, params);
await this.loadMarkets();
const response = await this[method](this.extend(request, params));
return this.parseTransactions(response['data'], undefined, since, limit);
}
/**
* @method
* @name coinbase#fetchWithdrawals
* @description Fetch all withdrawals made from an account. Won't return crypto withdrawals. Use fetchLedger for those.
* @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-withdrawals#list-withdrawals
* @param {string} code unified currency code
* @param {int} [since] the earliest time in ms to fetch withdrawals for
* @param {int} [limit] the maximum number of withdrawals structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.currencyType] "fiat" or "crypto"
* @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchWithdrawals(code = undefined, since = undefined, limit = undefined, params = {}) {
let currencyType = undefined;
[currencyType, params] = this.handleOptionAndParams(params, 'fetchWithdrawals', 'currencyType');
if (currencyType === 'crypto') {
const results = await this.fetchTransactionsWithMethod('v2PrivateGetAccountsAccountIdTransactions', code, since, limit, params);
return this.filterByArray(results, 'type', 'withdrawal', false);
}
return await this.fetchTransactionsWithMethod('v2PrivateGetAccountsAccountIdWithdrawals', code, since, limit, params);
}
/**
* @method
* @name coinbase#fetchDeposits
* @description Fetch all fiat deposits made to an account. Won't return crypto deposits or staking rewards. Use fetchLedger for those.
* @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-deposits#list-deposits
* @param {string} code unified currency code
* @param {int} [since] the earliest time in ms to fetch deposits for
* @param {int} [limit] the maximum number of deposits structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.currencyType] "fiat" or "crypto"
* @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchDeposits(code = undefined, since = undefined, limit = undefined, params = {}) {
let currencyType = undefined;
[currencyType, params] = this.handleOptionAndParams(params, 'fetchWithdrawals', 'currencyType');
if (currencyType === 'crypto') {
const results = await this.fetchTransactionsWithMethod('v2PrivateGetAccountsAccountIdTransactions', code, since, limit, params);
return this.filterByArray(results, 'type', 'deposit', false);
}
return await this.fetchTransactionsWithMethod('v2PrivateGetAccountsAccountIdDeposits', code, since, limit, params);
}
/**
* @method
* @name coinbase#fetchDepositsWithdrawals
* @description fetch history of deposits and withdrawals
* @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-transactions
* @param {string} [code] unified currency code for the currency of the deposit/withdrawals, default is undefined
* @param {int} [since] timestamp in ms of the earliest deposit/withdrawal, default is undefined
* @param {int} [limit] max number of deposit/withdrawals to return, default = 50, Min: 1, Max: 100
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a list of [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchDepositsWithdrawals(code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const results = await this.fetchTransactionsWithMethod('v2PrivateGetAccountsAccountIdTransactions', code, since, limit, params);
return this.filterByArray(results, 'type', ['deposit', 'withdrawal'], false);
}
parseTransactionStatus(status) {
const statuses = {
'created': 'pending',
'completed': 'ok',
'canceled': 'canceled',
};
return this.safeString(statuses, status, status);
}
parseTransaction(transaction, currency = undefined) {
//
// fiat deposit
//
// {
// "id": "f34c19f3-b730-5e3d-9f72",
// "status": "completed",
// "payment_method": {
// "id": "a022b31d-f9c7-5043-98f2",
// "resource": "payment_method",
// "resource_path": "/v2/payment-methods/a022b31d-f9c7-5043-98f2"
// },
// "transaction": {
// "id": "04ed4113-3732-5b0c-af86-b1d2146977d0",
// "resource": "transaction",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86"
// },
// "user_reference": "2VTYTH",
// "created_at": "2017-02-09T07:01:18Z",
// "updated_at": "2017-02-09T07:01:26Z",
// "resource": "deposit",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72",
// "committed": true,
// "payout_at": "2017-02-12T07:01:17Z",
// "instant": false,
// "fee": { "amount": "0.00", "currency": "EUR" },
// "amount": { "amount": "114.02", "currency": "EUR" },
// "subtotal": { "amount": "114.02", "currency": "EUR" },
// "hold_until": null,
// "hold_days": 0,
// "hold_business_days": 0,
// "next_step": null
// }
//
// fiat_withdrawal
//
// {
// "id": "cfcc3b4a-eeb6-5e8c-8058",
// "status": "completed",
// "payment_method": {
// "id": "8b94cfa4-f7fd-5a12-a76a",
// "resource": "payment_method",
// "resource_path": "/v2/payment-methods/8b94cfa4-f7fd-5a12-a76a"
// },
// "transaction": {
// "id": "fcc2550b-5104-5f83-a444",
// "resource": "transaction",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/fcc2550b-5104-5f83-a444"
// },
// "user_reference": "MEUGK",
// "created_at": "2018-07-26T08:55:12Z",
// "updated_at": "2018-07-26T08:58:18Z",
// "resource": "withdrawal",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/cfcc3b4a-eeb6-5e8c-8058",
// "committed": true,
// "payout_at": "2018-07-31T08:55:12Z",
// "instant": false,
// "fee": { "amount": "0.15", "currency": "EUR" },
// "amount": { "amount": "13130.69", "currency": "EUR" },
// "subtotal": { "amount": "13130.84", "currency": "EUR" },
// "idem": "e549dee5-63ed-4e79-8a96",
// "next_step": null
// }
//
// withdraw
//
// {
// "id": "a1794ecf-5693-55fa-70cf-ef731748ed82",
// "type": "send",
// "status": "pending",
// "amount": {
// "amount": "-14.008308",
// "currency": "USDC"
// },
// "native_amount": {
// "amount": "-18.74",
// "currency": "CAD"
// },
// "description": null,
// "created_at": "2024-01-12T01:27:31Z",
// "updated_at": "2024-01-12T01:27:31Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/a34bgfad-ed67-538b-bffc-730c98c10da0/transactions/a1794ecf-5693-55fa-70cf-ef731748ed82",
// "instant_exchange": false,
// "network": {
// "status": "pending",
// "status_description": "Pending (est. less than 10 minutes)",
// "transaction_fee": {
// "amount": "4.008308",
// "currency": "USDC"
// },
// "transaction_amount": {
// "amount": "10.000000",
// "currency": "USDC"
// },
// "confirmations": 0
// },
// "to": {
// "resource": "ethereum_address",
// "address": "0x9...",
// "currency": "USDC",
// "address_info": {
// "address": "0x9..."
// }
// },
// "idem": "748d8591-dg9a-7831-a45b-crd61dg78762",
// "details": {
// "title": "Sent USDC",
// "subtitle": "To USDC address on Ethereum network",
// "header": "Sent 14.008308 USDC ($18.74)",
// "health": "warning"
// },
// "hide_native_amount": false
// }
//
//
// crypto deposit & withdrawal (using `/transactions` endpoint)
// {
// "amount": {
// "amount": "0.00014200", (negative for withdrawal)
// "currency": "BTC"
// },
// "created_at": "2024-03-29T15:48:30Z",
// "id": "0031a605-241d-514d-a97b-d4b99f3225d3",
// "idem": "092a979b-017e-4403-940a-2ca57811f442", // field present only in case of withdrawal
// "native_amount": {
// "amount": "9.85", (negative for withdrawal)
// "currency": "USD"
// },
// "network": {
// "status": "pending", // if status is `off_blockchain` then no more other fields are present in this object
// "hash": "5jYuvrNsvX2DZoMnzGYzVpYxJLfYu4GSK3xetG1H5LHrSovsuFCFYdFMwNRoiht3s6fBk92MM8QLLnz65xuEFTrE",
// "network_name": "solana",
// "transaction_fee": {
// "amount": "0.000100000",
// "currency": "SOL"
// }
// },
// "resource": "transaction",
// "resource_path": "/v2/accounts/dc504b1c-248e-5b68-a3b0-b991f7fa84e6/transactions/0031a605-241d-514d-a97b-d4b99f3225d3",
// "status": "completed",
// "type": "send",
// "from": { // in some cases, field might be present for deposit
// "id": "7fd10cd7-b091-5cee-ba41-c29e49a7cccf",
// "name": "Coinbase",
// "resource": "user"
// },
// "to": { // field only present for withdrawal
// "address": "5HA12BNthAvBwNYARYf9y5MqqCpB4qhCNFCs1Qw48ACE",
// "resource": "address"
// },
// "description": "C3 - One Time BTC Credit . Reference Case # 123.", // in some cases, field might be present for deposit
// }
//
const transactionType = this.safeString(transaction, 'type');
let amountAndCurrencyObject = undefined;
let feeObject = undefined;
const network = this.safeDict(transaction, 'network', {});
if (transactionType === 'send') {
amountAndCurrencyObject = this.safeDict(network, 'transaction_amount');
feeObject = this.safeDict(network, 'transaction_fee', {});
}
else {
amountAndCurrencyObject = this.safeDict(transaction, 'subtotal');
feeObject = this.safeDict(transaction, 'fee', {});
}
if (amountAndCurrencyObject === undefined) {
amountAndCurrencyObject = this.safeDict(transaction, 'amount');
}
const amountString = this.safeString(amountAndCurrencyObject, 'amount');
const amountStringAbs = Precise["default"].stringAbs(amountString);
let status = this.parseTransactionStatus(this.safeString(transaction, 'status'));
if (status === undefined) {
const committed = this.safeBool(transaction, 'committed');
status = committed ? 'ok' : 'pending';
}
const id = this.safeString(transaction, 'id');
const currencyId = this.safeString(amountAndCurrencyObject, 'currency');
const feeCurrencyId = this.safeString(feeObject, 'currency');
const datetime = this.safeString(transaction, 'created_at');
const resource = this.safeString(transaction, 'resource');
let type = resource;
if (!this.inArray(type, ['deposit', 'withdrawal'])) {
if (Precise["default"].stringGt(amountString, '0')) {
type = 'deposit';
}
else if (Precise["default"].stringLt(amountString, '0')) {
type = 'withdrawal';
}
}
const toObject = this.safeDict(transaction, 'to');
const addressTo = this.safeString(toObject, 'address');
const networkId = this.safeString(network, 'network_name');
return {
'info': transaction,
'id': id,
'txid': this.safeString(network, 'hash', id),
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'network': this.networkIdToCode(networkId),
'address': addressTo,
'addressTo': addressTo,
'addressFrom': undefined,
'tag': undefined,
'tagTo': undefined,
'tagFrom': undefined,
'type': type,
'amount': this.parseNumber(amountStringAbs),
'currency': this.safeCurrencyCode(currencyId, currency),
'status': status,
'updated': this.parse8601(this.safeString(transaction, 'updated_at')),
'fee': {
'cost': this.safeNumber(feeObject, 'amount'),
'currency': this.safeCurrencyCode(feeCurrencyId),
},
};
}
parseTrade(trade, market = undefined) {
//
// fetchMyBuys, fetchMySells
//
// {
// "id": "67e0eaec-07d7-54c4-a72c-2e92826897df",
// "status": "completed",
// "payment_method": {
// "id": "83562370-3e5c-51db-87da-752af5ab9559",
// "resource": "payment_method",
// "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
// },
// "transaction": {
// "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
// "resource": "transaction",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
// },
// "amount": { "amount": "1.00000000", "currency": "BTC" },
// "total": { "amount": "10.25", "currency": "USD" },
// "subtotal": { "amount": "10.10", "currency": "USD" },
// "created_at": "2015-01-31T20:49:02Z",
// "updated_at": "2015-02-11T16:54:02-08:00",
// "resource": "buy",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/buys/67e0eaec-07d7-54c4-a72c-2e92826897df",
// "committed": true,
// "instant": false,
// "fee": { "amount": "0.15", "currency": "USD" },
// "payout_at": "2015-02-18T16:54:00-08:00"
// }
//
// fetchTrades
//
// {
// "trade_id": "10092327",
// "product_id": "BTC-USDT",
// "price": "17488.12",
// "size": "0.0000623",
// "time": "2023-01-11T00:52:37.557001Z",
// "side": "BUY",
// "bid": "",
// "ask": ""
// }
//
// fetchMyTrades
//
// {
// "entry_id": "b88b82cc89e326a2778874795102cbafd08dd979a2a7a3c69603fc4c23c2e010",
// "trade_id": "cdc39e45-bbd3-44ec-bf02-61742dfb16a1",
// "order_id": "813a53c5-3e39-47bb-863d-2faf685d22d8",
// "trade_time": "2023-01-18T01:37:38.091377090Z",
// "trade_type": "FILL",
// "price": "21220.64",
// "size": "0.0046830664333996",
// "commission": "0.0000280983986004",
// "product_id": "BTC-USDT",
// "sequence_timestamp": "2023-01-18T01:37:38.092520Z",
// "liquidity_indicator": "UNKNOWN_LIQUIDITY_INDICATOR",
// "size_in_quote": true,
// "user_id": "1111111-1111-1111-1111-111111111111",
// "side": "BUY"
// }
//
let symbol = undefined;
const totalObject = this.safeDict(trade, 'total', {});
const amountObject = this.safeDict(trade, 'amount', {});
const subtotalObject = this.safeDict(trade, 'subtotal', {});
const feeObject = this.safeDict(trade, 'fee', {});
const marketId = this.safeString(trade, 'product_id');
market = this.safeMarket(marketId, market, '-');
if (market !== undefined) {
symbol = market['symbol'];
}
else {
const baseId = this.safeString(amountObject, 'currency');
const quoteId = this.safeString(totalObject, 'currency');
if ((baseId !== undefined) && (quoteId !== undefined)) {
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
symbol = base + '/' + quote;
}
}
const sizeInQuote = this.safeBool(trade, 'size_in_quote');
const v3Price = this.safeString(trade, 'price');
let v3Cost = undefined;
let v3Amount = this.safeString(trade, 'size');
if (sizeInQuote) {
// calculate base size
v3Cost = v3Amount;
v3Amount = Precise["default"].stringDiv(v3Amount, v3Price);
}
const v3FeeCost = this.safeString(trade, 'commission');
const amountString = this.safeString(amountObject, 'amount', v3Amount);
const costString = this.safeString(subtotalObject, 'amount', v3Cost);
let priceString = undefined;
let cost = undefined;
if ((costString !== undefined) && (amountString !== undefined)) {
priceString = Precise["default"].stringDiv(costString, amountString);
}
else {
priceString = v3Price;
}
if ((priceString !== undefined) && (amountString !== undefined)) {
cost = Precise["default"].stringMul(priceString, amountString);
}
else {
cost = costString;
}
let feeCurrencyId = this.safeString(feeObject, 'currency');
const feeCost = this.safeNumber(feeObject, 'amount', this.parseNumber(v3FeeCost));
if ((feeCurrencyId === undefined) && (market !== undefined) && (feeCost !== undefined)) {
feeCurrencyId = market['quote'];
}
const datetime = this.safeStringN(trade, ['created_at', 'trade_time', 'time']);
const side = this.safeStringLower2(trade, 'resource', 'side');
const takerOrMaker = this.safeStringLower(trade, 'liquidity_indicator');
return this.safeTrade({
'info': trade,
'id': this.safeString2(trade, 'id', 'trade_id'),
'order': this.safeString(trade, 'order_id'),
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'symbol': symbol,
'type': undefined,
'side': (side === 'unknown_order_side') ? undefined : side,
'takerOrMaker': (takerOrMaker === 'unknown_liquidity_indicator') ? undefined : takerOrMaker,
'price': priceString,
'amount': amountString,
'cost': cost,
'fee': {
'cost': feeCost,
'currency': this.safeCurrencyCode(feeCurrencyId),
},
});
}
/**
* @method
* @name coinbase#fetchMarkets
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicproducts
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-currencies#get-fiat-currencies
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
* @description retrieves data on all markets for coinbase
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.usePrivate] use private endpoint for fetching markets
* @returns {object[]} an array of objects representing market data
*/
async fetchMarkets(params = {}) {
if (this.options['adjustForTimeDifference']) {
await this.loadTimeDifference();
}
const method = this.safeString(this.options, 'fetchMarkets', 'fetchMarketsV3');
if (method === 'fetchMarketsV3') {
return await this.fetchMarketsV3(params);
}
return await this.fetchMarketsV2(params);
}
async fetchMarketsV2(params = {}) {
const response = await this.fetchCurrenciesFromCache(params);
const currencies = this.safeDict(response, 'currencies', {});
const exchangeRates = this.safeDict(response, 'exchangeRates', {});
const data = this.safeList(currencies, 'data', []);
const dataById = this.indexBy(data, 'id');
const rates = this.safeDict(this.safeDict(exchangeRates, 'data', {}), 'rates', {});
const baseIds = Object.keys(rates);
const result = [];
for (let i = 0; i < baseIds.length; i++) {
const baseId = baseIds[i];
const base = this.safeCurrencyCode(baseId);
const type = (baseId in dataById) ? 'fiat' : 'crypto';
// https://github.com/ccxt/ccxt/issues/6066
if (type === 'crypto') {
for (let j = 0; j < data.length; j++) {
const quoteCurrency = data[j];
const quoteId = this.safeString(quoteCurrency, 'id');
const quote = this.safeCurrencyCode(quoteId);
result.push(this.safeMarketStructure({
'id': baseId + '-' + quoteId,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': undefined,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': 'spot',
'spot': true,
'margin': false,
'swap': false,
'future': false,
'option': false,
'active': undefined,
'contract': false,
'linear': undefined,
'inverse': undefined,
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': undefined,
'price': undefined,
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.safeNumber(quoteCurrency, 'min_size'),
'max': undefined,
},
},
'info': quoteCurrency,
}));
}
}
}
return result;
}
async fetchMarketsV3(params = {}) {
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchMarkets', 'usePrivate', false);
const spotUnresolvedPromises = [];
if (usePrivate) {
spotUnresolvedPromises.push(this.v3PrivateGetBrokerageProducts(params));
}
else {
spotUnresolvedPromises.push(this.v3PublicGetBrokerageMarketProducts(params));
}
//
// {
// products: [
// {
// product_id: 'BTC-USD',
// price: '67060',
// price_percentage_change_24h: '3.30054960636883',
// volume_24h: '10967.87426597',
// volume_percentage_change_24h: '141.73048325503036',
// base_increment: '0.00000001',
// quote_increment: '0.01',
// quote_min_size: '1',
// quote_max_size: '150000000',
// base_min_size: '0.00000001',
// base_max_size: '3400',
// base_name: 'Bitcoin',
// quote_name: 'US Dollar',
// watched: false,
// is_disabled: false,
// new: false,
// status: 'online',
// cancel_only: false,
// limit_only: false,
// post_only: false,
// trading_disabled: false,
// auction_mode: false,
// product_type: 'SPOT',
// quote_currency_id: 'USD',
// base_currency_id: 'BTC',
// fcm_trading_session_details: null,
// mid_market_price: '',
// alias: '',
// alias_to: [ 'BTC-USDC' ],
// base_display_symbol: 'BTC',
// quote_display_symbol: 'USD',
// view_only: false,
// price_increment: '0.01',
// display_name: 'BTC-USD',
// product_venue: 'CBE'
// },
// ...
// ],
// num_products: '646'
// }
//
if (this.checkRequiredCredentials(false)) {
spotUnresolvedPromises.push(this.v3PrivateGetBrokerageTransactionSummary(params));
}
//
// {
// total_volume: '9.995989116664404',
// total_fees: '0.07996791093331522',
// fee_tier: {
// pricing_tier: 'Advanced 1',
// usd_from: '0',
// usd_to: '1000',
// taker_fee_rate: '0.008',
// maker_fee_rate: '0.006',
// aop_from: '',
// aop_to: ''
// },
// margin_rate: null,
// goods_and_services_tax: null,
// advanced_trade_only_volume: '9.995989116664404',
// advanced_trade_only_fees: '0.07996791093331522',
// coinbase_pro_volume: '0',
// coinbase_pro_fees: '0',
// total_balance: '',
// has_promo_fee: false
// }
//
let unresolvedContractPromises = [];
try {
unresolvedContractPromises = [
this.v3PublicGetBrokerageMarketProducts(this.extend(params, { 'product_type': 'FUTURE' })),
this.v3PublicGetBrokerageMarketProducts(this.extend(params, { 'product_type': 'FUTURE', 'contract_expiry_type': 'PERPETUAL' })),
];
}
catch (e) {
unresolvedContractPromises = []; // the sync version of ccxt won't have the promise.all line so the request is made here. Some users can't access perpetual products
}
const promises = await Promise.all(spotUnresolvedPromises);
let contractPromises = undefined;
try {
contractPromises = await Promise.all(unresolvedContractPromises); // some users don't have access to contracts
}
catch (e) {
contractPromises = [];
}
const spot = this.safeDict(promises, 0, {});
const fees = this.safeDict(promises, 1, {});
const expiringFutures = this.safeDict(contractPromises, 0, {});
const perpetualFutures = this.safeDict(contractPromises, 1, {});
const expiringFees = this.safeDict(contractPromises, 0, {});
const perpetualFees = this.safeDict(contractPromises, 1, {});
//
// {
// "total_volume": 0,
// "total_fees": 0,
// "fee_tier": {
// "pricing_tier": "",
// "usd_from": "0",
// "usd_to": "10000",
// "taker_fee_rate": "0.006",
// "maker_fee_rate": "0.004"
// },
// "margin_rate": null,
// "goods_and_services_tax": null,
// "advanced_trade_only_volume": 0,
// "advanced_trade_only_fees": 0,
// "coinbase_pro_volume": 0,
// "coinbase_pro_fees": 0
// }
//
const feeTier = this.safeDict(fees, 'fee_tier', {});
const expiringFeeTier = this.safeDict(expiringFees, 'fee_tier', {}); // fee tier null?
const perpetualFeeTier = this.safeDict(perpetualFees, 'fee_tier', {}); // fee tier null?
const data = this.safeList(spot, 'products', []);
const result = [];
for (let i = 0; i < data.length; i++) {
result.push(this.parseSpotMarket(data[i], feeTier));
}
const futureData = this.safeList(expiringFutures, 'products', []);
for (let i = 0; i < futureData.length; i++) {
result.push(this.parseContractMarket(futureData[i], expiringFeeTier));
}
const perpetualData = this.safeList(perpetualFutures, 'products', []);
for (let i = 0; i < perpetualData.length; i++) {
result.push(this.parseContractMarket(perpetualData[i], perpetualFeeTier));
}
const newMarkets = [];
for (let i = 0; i < result.length; i++) {
const market = result[i];
const info = this.safeValue(market, 'info', {});
const realMarketIds = this.safeList(info, 'alias_to', []);
const length = realMarketIds.length;
if (length > 0) {
market['alias'] = realMarketIds[0];
}
else {
market['alias'] = undefined;
}
newMarkets.push(market);
}
return newMarkets;
}
parseSpotMarket(market, feeTier) {
//
// {
// "product_id": "TONE-USD",
// "price": "0.01523",
// "price_percentage_change_24h": "1.94109772423025",
// "volume_24h": "19773129",
// "volume_percentage_change_24h": "437.0170530929949",
// "base_increment": "1",
// "quote_increment": "0.00001",
// "quote_min_size": "1",
// "quote_max_size": "10000000",
// "base_min_size": "26.7187147229469674",
// "base_max_size": "267187147.2294696735908216",
// "base_name": "TE-FOOD",
// "quote_name": "US Dollar",
// "watched": false,
// "is_disabled": false,
// "new": false,
// "status": "online",
// "cancel_only": false,
// "limit_only": false,
// "post_only": false,
// "trading_disabled": false,
// "auction_mode": false,
// "product_type": "SPOT",
// "quote_currency_id": "USD",
// "base_currency_id": "TONE",
// "fcm_trading_session_details": null,
// "mid_market_price": ""
// }
//
const id = this.safeString(market, 'product_id');
const baseId = this.safeString(market, 'base_currency_id');
const quoteId = this.safeString(market, 'quote_currency_id');
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const marketType = this.safeStringLower(market, 'product_type');
const tradingDisabled = this.safeBool(market, 'trading_disabled');
const stablePairs = this.safeList(this.options, 'stablePairs', []);
const defaultTakerFee = this.safeNumber(this.fees['trading'], 'taker');
const defaultMakerFee = this.safeNumber(this.fees['trading'], 'maker');
const takerFee = this.inArray(id, stablePairs) ? 0.00001 : this.safeNumber(feeTier, 'taker_fee_rate', defaultTakerFee);
const makerFee = this.inArray(id, stablePairs) ? 0.0 : this.safeNumber(feeTier, 'maker_fee_rate', defaultMakerFee);
return this.safeMarketStructure({
'id': id,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': undefined,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': marketType,
'spot': (marketType === 'spot'),
'margin': undefined,
'swap': false,
'future': false,
'option': false,
'active': !tradingDisabled,
'contract': false,
'linear': undefined,
'inverse': undefined,
'taker': takerFee,
'maker': makerFee,
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.safeNumber(market, 'base_increment'),
'price': this.safeNumber2(market, 'price_increment', 'quote_increment'),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber(market, 'base_min_size'),
'max': this.safeNumber(market, 'base_max_size'),
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.safeNumber(market, 'quote_min_size'),
'max': this.safeNumber(market, 'quote_max_size'),
},
},
'created': undefined,
'info': market,
});
}
parseContractMarket(market, feeTier) {
// expiring
//
// {
// "product_id":"BIT-26APR24-CDE",
// "price":"71145",
// "price_percentage_change_24h":"-2.36722931247427",
// "volume_24h":"108549",
// "volume_percentage_change_24h":"155.78255337197794",
// "base_increment":"1",
// "quote_increment":"0.01",
// "quote_min_size":"0",
// "quote_max_size":"100000000",
// "base_min_size":"1",
// "base_max_size":"100000000",
// "base_name":"",
// "quote_name":"US Dollar",
// "watched":false,
// "is_disabled":false,
// "new":false,
// "status":"",
// "cancel_only":false,
// "limit_only":false,
// "post_only":false,
// "trading_disabled":false,
// "auction_mode":false,
// "product_type":"FUTURE",
// "quote_currency_id":"USD",
// "base_currency_id":"",
// "fcm_trading_session_details":{
// "is_session_open":true,
// "open_time":"2024-04-08T22:00:00Z",
// "close_time":"2024-04-09T21:00:00Z"
// },
// "mid_market_price":"71105",
// "alias":"",
// "alias_to":[
// ],
// "base_display_symbol":"",
// "quote_display_symbol":"USD",
// "view_only":false,
// "price_increment":"5",
// "display_name":"BTC 26 APR 24",
// "product_venue":"FCM",
// "future_product_details":{
// "venue":"cde",
// "contract_code":"BIT",
// "contract_expiry":"2024-04-26T15:00:00Z",
// "contract_size":"0.01",
// "contract_root_unit":"BTC",
// "group_description":"Nano Bitcoin Futures",
// "contract_expiry_timezone":"Europe/London",
// "group_short_description":"Nano BTC",
// "risk_managed_by":"MANAGED_BY_FCM",
// "contract_expiry_type":"EXPIRING",
// "contract_display_name":"BTC 26 APR 24"
// }
// }
//
// perpetual
//
// {
// "product_id":"ETH-PERP-INTX",
// "price":"3630.98",
// "price_percentage_change_24h":"0.65142426292038",
// "volume_24h":"114020.1501",
// "volume_percentage_change_24h":"63.33650787154869",
// "base_increment":"0.0001",
// "quote_increment":"0.01",
// "quote_min_size":"10",
// "quote_max_size":"50000000",
// "base_min_size":"0.0001",
// "base_max_size":"50000",
// "base_name":"",
// "quote_name":"USDC",
// "watched":false,
// "is_disabled":false,
// "new":false,
// "status":"",
// "cancel_only":false,
// "limit_only":false,
// "post_only":false,
// "trading_disabled":false,
// "auction_mode":false,
// "product_type":"FUTURE",
// "quote_currency_id":"USDC",
// "base_currency_id":"",
// "fcm_trading_session_details":null,
// "mid_market_price":"3630.975",
// "alias":"",
// "alias_to":[],
// "base_display_symbol":"",
// "quote_display_symbol":"USDC",
// "view_only":false,
// "price_increment":"0.01",
// "display_name":"ETH PERP",
// "product_venue":"INTX",
// "future_product_details":{
// "venue":"",
// "contract_code":"ETH",
// "contract_expiry":null,
// "contract_size":"1",
// "contract_root_unit":"ETH",
// "group_description":"",
// "contract_expiry_timezone":"",
// "group_short_description":"",
// "risk_managed_by":"MANAGED_BY_VENUE",
// "contract_expiry_type":"PERPETUAL",
// "perpetual_details":{
// "open_interest":"0",
// "funding_rate":"0.000016",
// "funding_time":"2024-04-09T09:00:00.000008Z",
// "max_leverage":"10"
// },
// "contract_display_name":"ETH PERPETUAL"
// }
// }
//
const id = this.safeString(market, 'product_id');
const futureProductDetails = this.safeDict(market, 'future_product_details', {});
const contractExpiryType = this.safeString(futureProductDetails, 'contract_expiry_type');
const contractSize = this.safeNumber(futureProductDetails, 'contract_size');
const contractExpire = this.safeString(futureProductDetails, 'contract_expiry');
const expireTimestamp = this.parse8601(contractExpire);
const expireDateTime = this.iso8601(expireTimestamp);
const isSwap = (contractExpiryType === 'PERPETUAL');
const baseId = this.safeString(futureProductDetails, 'contract_root_unit');
const quoteId = this.safeString(market, 'quote_currency_id');
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const tradingDisabled = this.safeBool(market, 'is_disabled');
let symbol = base + '/' + quote;
let type = undefined;
if (isSwap) {
type = 'swap';
symbol = symbol + ':' + quote;
}
else {
type = 'future';
symbol = symbol + ':' + quote + '-' + this.yymmdd(expireTimestamp);
}
const takerFeeRate = this.safeNumber(feeTier, 'taker_fee_rate');
const makerFeeRate = this.safeNumber(feeTier, 'maker_fee_rate');
const taker = takerFeeRate ? takerFeeRate : this.parseNumber('0.06');
const maker = makerFeeRate ? makerFeeRate : this.parseNumber('0.04');
return this.safeMarketStructure({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': quote,
'baseId': baseId,
'quoteId': quoteId,
'settleId': quoteId,
'type': type,
'spot': false,
'margin': false,
'swap': isSwap,
'future': !isSwap,
'option': false,
'active': !tradingDisabled,
'contract': true,
'linear': true,
'inverse': false,
'taker': taker,
'maker': maker,
'contractSize': contractSize,
'expiry': expireTimestamp,
'expiryDatetime': expireDateTime,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.safeNumber(market, 'base_increment'),
'price': this.safeNumber2(market, 'price_increment', 'quote_increment'),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber(market, 'base_min_size'),
'max': this.safeNumber(market, 'base_max_size'),
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.safeNumber(market, 'quote_min_size'),
'max': this.safeNumber(market, 'quote_max_size'),
},
},
'created': undefined,
'info': market,
});
}
async fetchCurrenciesFromCache(params = {}) {
const options = this.safeDict(this.options, 'fetchCurrencies', {});
const timestamp = this.safeInteger(options, 'timestamp');
const expires = this.safeInteger(options, 'expires', 1000);
const now = this.milliseconds();
if ((timestamp === undefined) || ((now - timestamp) > expires)) {
const promises = [
this.v2PublicGetCurrencies(params),
this.v2PublicGetCurrenciesCrypto(params),
];
const promisesResult = await Promise.all(promises);
const fiatResponse = this.safeDict(promisesResult, 0, {});
//
// [
// "data": {
// id: 'IMP',
// name: 'Isle of Man Pound',
// min_size: '0.01'
// },
// ...
// ]
//
const cryptoResponse = this.safeDict(promisesResult, 1, {});
//
// {
// asset_id: '9476e3be-b731-47fa-82be-347fabc573d9',
// code: 'AERO',
// name: 'Aerodrome Finance',
// color: '#0433FF',
// sort_index: '340',
// exponent: '8',
// type: 'crypto',
// address_regex: '^(?:0x)?[0-9a-fA-F]{40}$'
// }
//
const fiatData = this.safeList(fiatResponse, 'data', []);
const cryptoData = this.safeList(cryptoResponse, 'data', []);
const exchangeRates = await this.v2PublicGetExchangeRates(params);
this.options['fetchCurrencies'] = this.extend(options, {
'currencies': this.arrayConcat(fiatData, cryptoData),
'exchangeRates': exchangeRates,
'timestamp': now,
});
}
return this.safeDict(this.options, 'fetchCurrencies', {});
}
/**
* @method
* @name coinbase#fetchCurrencies
* @description fetches all available currencies on an exchange
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-currencies#get-fiat-currencies
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an associative dictionary of currencies
*/
async fetchCurrencies(params = {}) {
const promises = [
this.v2PublicGetCurrencies(params),
this.v2PublicGetCurrenciesCrypto(params),
this.v2PublicGetExchangeRates(params),
];
const promisesResult = await Promise.all(promises);
const fiatResponse = this.safeDict(promisesResult, 0, {});
//
// [
// "data": [
// {
// id: 'IMP',
// name: 'Isle of Man Pound',
// min_size: '0.01'
// },
// ...
//
const cryptoResponse = this.safeDict(promisesResult, 1, {});
//
// [
// "data": [
// {
// asset_id: '9476e3be-b731-47fa-82be-347fabc573d9',
// code: 'AERO',
// name: 'Aerodrome Finance',
// color: '#0433FF',
// sort_index: '340',
// exponent: '8',
// type: 'crypto',
// address_regex: '^(?:0x)?[0-9a-fA-F]{40}$'
// },
// ...
//
const ratesResponse = this.safeDict(promisesResult, 2, {});
const fiatData = this.safeList(fiatResponse, 'data', []);
const cryptoData = this.safeList(cryptoResponse, 'data', []);
const ratesData = this.safeDict(ratesResponse, 'data', {});
const rates = this.safeDict(ratesData, 'rates', {});
const ratesIds = Object.keys(rates);
const currencies = this.arrayConcat(fiatData, cryptoData);
const result = {};
const networks = {};
const networksById = {};
for (let i = 0; i < currencies.length; i++) {
const currency = currencies[i];
const assetId = this.safeString(currency, 'asset_id');
const id = this.safeString2(currency, 'id', 'code');
const code = this.safeCurrencyCode(id);
const name = this.safeString(currency, 'name');
this.options['networks'][code] = name.toLowerCase();
this.options['networksById'][code] = name.toLowerCase();
const type = (assetId !== undefined) ? 'crypto' : 'fiat';
result[code] = this.safeCurrencyStructure({
'info': currency,
'id': id,
'code': code,
'type': type,
'name': name,
'active': true,
'deposit': undefined,
'withdraw': undefined,
'fee': undefined,
'precision': undefined,
'networks': {},
'limits': {
'amount': {
'min': this.safeNumber(currency, 'min_size'),
'max': undefined,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
},
});
if (assetId !== undefined) {
const lowerCaseName = name.toLowerCase();
networks[code] = lowerCaseName;
networksById[lowerCaseName] = code;
}
}
// we have to add other currencies here ( https://discord.com/channels/1220414409550336183/1220464770239430761/1372215891940479098 )
for (let i = 0; i < ratesIds.length; i++) {
const currencyId = ratesIds[i];
const code = this.safeCurrencyCode(currencyId);
if (!(code in result)) {
result[code] = this.safeCurrencyStructure({
'info': {},
'id': currencyId,
'code': code,
'type': 'crypto',
'networks': {}, // todo
});
}
}
this.options['networks'] = this.extend(networks, this.options['networks']);
this.options['networksById'] = this.extend(networksById, this.options['networksById']);
return result;
}
/**
* @method
* @name coinbase#fetchTickers
* @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getproducts
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
* @param {string[]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.usePrivate] use private endpoint for fetching tickers
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTickers(symbols = undefined, params = {}) {
const method = this.safeString(this.options, 'fetchTickers', 'fetchTickersV3');
if (method === 'fetchTickersV3') {
return await this.fetchTickersV3(symbols, params);
}
return await this.fetchTickersV2(symbols, params);
}
async fetchTickersV2(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const request = {
// 'currency': 'USD',
};
const response = await this.v2PublicGetExchangeRates(this.extend(request, params));
//
// {
// "data":{
// "currency":"USD",
// "rates":{
// "AED":"3.6731",
// "AFN":"103.163942",
// "ALL":"106.973038",
// }
// }
// }
//
const data = this.safeDict(response, 'data', {});
const rates = this.safeDict(data, 'rates', {});
const quoteId = this.safeString(data, 'currency');
const result = {};
const baseIds = Object.keys(rates);
const delimiter = '-';
for (let i = 0; i < baseIds.length; i++) {
const baseId = baseIds[i];
const marketId = baseId + delimiter + quoteId;
const market = this.safeMarket(marketId, undefined, delimiter);
const symbol = market['symbol'];
result[symbol] = this.parseTicker(rates[baseId], market);
}
return this.filterByArrayTickers(result, 'symbol', symbols);
}
async fetchTickersV3(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const request = {};
if (symbols !== undefined) {
request['product_ids'] = this.marketIds(symbols);
}
let marketType = undefined;
[marketType, params] = this.handleMarketTypeAndParams('fetchTickers', this.getMarketFromSymbols(symbols), params, 'default');
if (marketType !== undefined && marketType !== 'default') {
request['product_type'] = (marketType === 'swap') ? 'FUTURE' : 'SPOT';
}
let response = undefined;
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchTickers', 'usePrivate', false);
if (usePrivate) {
response = await this.v3PrivateGetBrokerageProducts(this.extend(request, params));
}
else {
response = await this.v3PublicGetBrokerageMarketProducts(this.extend(request, params));
}
//
// {
// "products": [
// {
// "product_id": "TONE-USD",
// "price": "0.01523",
// "price_percentage_change_24h": "1.94109772423025",
// "volume_24h": "19773129",
// "volume_percentage_change_24h": "437.0170530929949",
// "base_increment": "1",
// "quote_increment": "0.00001",
// "quote_min_size": "1",
// "quote_max_size": "10000000",
// "base_min_size": "26.7187147229469674",
// "base_max_size": "267187147.2294696735908216",
// "base_name": "TE-FOOD",
// "quote_name": "US Dollar",
// "watched": false,
// "is_disabled": false,
// "new": false,
// "status": "online",
// "cancel_only": false,
// "limit_only": false,
// "post_only": false,
// "trading_disabled": false,
// "auction_mode": false,
// "product_type": "SPOT",
// "quote_currency_id": "USD",
// "base_currency_id": "TONE",
// "fcm_trading_session_details": null,
// "mid_market_price": ""
// },
// ...
// ],
// "num_products": 549
// }
//
const data = this.safeList(response, 'products', []);
const result = {};
for (let i = 0; i < data.length; i++) {
const entry = data[i];
const marketId = this.safeString(entry, 'product_id');
const market = this.safeMarket(marketId, undefined, '-');
const symbol = market['symbol'];
result[symbol] = this.parseTicker(entry, market);
}
return this.filterByArrayTickers(result, 'symbol', symbols);
}
/**
* @method
* @name coinbase#fetchTicker
* @description 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/advanced-trade/reference/retailbrokerageapi_getmarkettrades
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-spot-price
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-buy-price
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-sell-price
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.usePrivate] whether to use the private endpoint for fetching the ticker
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTicker(symbol, params = {}) {
const method = this.safeString(this.options, 'fetchTicker', 'fetchTickerV3');
if (method === 'fetchTickerV3') {
return await this.fetchTickerV3(symbol, params);
}
return await this.fetchTickerV2(symbol, params);
}
async fetchTickerV2(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = this.extend({
'symbol': market['id'],
}, params);
const spot = await this.v2PublicGetPricesSymbolSpot(request);
//
// {"data":{"base":"BTC","currency":"USD","amount":"48691.23"}}
//
const ask = await this.v2PublicGetPricesSymbolBuy(request);
//
// {"data":{"base":"BTC","currency":"USD","amount":"48691.23"}}
//
const bid = await this.v2PublicGetPricesSymbolSell(request);
//
// {"data":{"base":"BTC","currency":"USD","amount":"48691.23"}}
//
const spotData = this.safeDict(spot, 'data', {});
const askData = this.safeDict(ask, 'data', {});
const bidData = this.safeDict(bid, 'data', {});
const bidAskLast = {
'bid': this.safeNumber(bidData, 'amount'),
'ask': this.safeNumber(askData, 'amount'),
'price': this.safeNumber(spotData, 'amount'),
};
return this.parseTicker(bidAskLast, market);
}
async fetchTickerV3(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'product_id': market['id'],
'limit': 1,
};
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchTicker', 'usePrivate', false);
let response = undefined;
if (usePrivate) {
response = await this.v3PrivateGetBrokerageProductsProductIdTicker(this.extend(request, params));
}
else {
response = await this.v3PublicGetBrokerageMarketProductsProductIdTicker(this.extend(request, params));
}
//
// {
// "trades": [
// {
// "trade_id": "518078013",
// "product_id": "BTC-USD",
// "price": "28208.1",
// "size": "0.00659179",
// "time": "2023-04-04T23:05:34.492746Z",
// "side": "BUY",
// "bid": "",
// "ask": ""
// }
// ],
// "best_bid": "28208.61",
// "best_ask": "28208.62"
// }
//
const data = this.safeList(response, 'trades', []);
const ticker = this.parseTicker(data[0], market);
ticker['bid'] = this.safeNumber(response, 'best_bid');
ticker['ask'] = this.safeNumber(response, 'best_ask');
return ticker;
}
parseTicker(ticker, market = undefined) {
//
// fetchTickerV2
//
// {
// "bid": 20713.37,
// "ask": 20924.65,
// "price": 20809.83
// }
//
// fetchTickerV3
//
// {
// "trade_id": "10209805",
// "product_id": "BTC-USDT",
// "price": "19381.27",
// "size": "0.1",
// "time": "2023-01-13T20:35:41.865970Z",
// "side": "BUY",
// "bid": "",
// "ask": ""
// }
//
// fetchTickersV2
//
// "48691.23"
//
// fetchTickersV3
//
// [
// {
// "product_id": "TONE-USD",
// "price": "0.01523",
// "price_percentage_change_24h": "1.94109772423025",
// "volume_24h": "19773129",
// "volume_percentage_change_24h": "437.0170530929949",
// "base_increment": "1",
// "quote_increment": "0.00001",
// "quote_min_size": "1",
// "quote_max_size": "10000000",
// "base_min_size": "26.7187147229469674",
// "base_max_size": "267187147.2294696735908216",
// "base_name": "TE-FOOD",
// "quote_name": "US Dollar",
// "watched": false,
// "is_disabled": false,
// "new": false,
// "status": "online",
// "cancel_only": false,
// "limit_only": false,
// "post_only": false,
// "trading_disabled": false,
// "auction_mode": false,
// "product_type": "SPOT",
// "quote_currency_id": "USD",
// "base_currency_id": "TONE",
// "fcm_trading_session_details": null,
// "mid_market_price": ""
// },
// ...
// ]
//
// fetchBidsAsks
//
// {
// "product_id": "TRAC-EUR",
// "bids": [
// {
// "price": "0.2384",
// "size": "386.1"
// }
// ],
// "asks": [
// {
// "price": "0.2406",
// "size": "672"
// }
// ],
// "time": "2023-06-30T07:15:24.656044Z"
// }
//
let bid = this.safeNumber(ticker, 'bid');
let ask = this.safeNumber(ticker, 'ask');
let bidVolume = undefined;
let askVolume = undefined;
if (('bids' in ticker)) {
const bids = this.safeList(ticker, 'bids', []);
const asks = this.safeList(ticker, 'asks', []);
bid = this.safeNumber(bids[0], 'price');
bidVolume = this.safeNumber(bids[0], 'size');
ask = this.safeNumber(asks[0], 'price');
askVolume = this.safeNumber(asks[0], 'size');
}
const marketId = this.safeString(ticker, 'product_id');
market = this.safeMarket(marketId, market);
const last = this.safeNumber(ticker, 'price');
const datetime = this.safeString(ticker, 'time');
return this.safeTicker({
'symbol': market['symbol'],
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'bid': bid,
'ask': ask,
'last': last,
'high': undefined,
'low': undefined,
'bidVolume': bidVolume,
'askVolume': askVolume,
'vwap': undefined,
'open': undefined,
'close': last,
'previousClose': undefined,
'change': undefined,
'percentage': this.safeNumber(ticker, 'price_percentage_change_24h'),
'average': undefined,
'baseVolume': undefined,
'quoteVolume': undefined,
'info': ticker,
}, market);
}
parseCustomBalance(response, params = {}) {
const balances = this.safeList2(response, 'data', 'accounts', []);
const accounts = this.safeList(params, 'type', this.options['accounts']);
const v3Accounts = this.safeList(params, 'type', this.options['v3Accounts']);
const result = { 'info': response };
for (let b = 0; b < balances.length; b++) {
const balance = balances[b];
const type = this.safeString(balance, 'type');
if (this.inArray(type, accounts)) {
const value = this.safeDict(balance, 'balance');
if (value !== undefined) {
const currencyId = this.safeString(value, 'currency');
const code = this.safeCurrencyCode(currencyId);
const total = this.safeString(value, 'amount');
const free = total;
let account = this.safeDict(result, code);
if (account === undefined) {
account = this.account();
account['free'] = free;
account['total'] = total;
}
else {
account['free'] = Precise["default"].stringAdd(account['free'], total);
account['total'] = Precise["default"].stringAdd(account['total'], total);
}
result[code] = account;
}
}
else if (this.inArray(type, v3Accounts)) {
const available = this.safeDict(balance, 'available_balance');
const hold = this.safeDict(balance, 'hold');
if (available !== undefined && hold !== undefined) {
const currencyId = this.safeString(available, 'currency');
const code = this.safeCurrencyCode(currencyId);
const used = this.safeString(hold, 'value');
const free = this.safeString(available, 'value');
const total = Precise["default"].stringAdd(used, free);
let account = this.safeDict(result, code);
if (account === undefined) {
account = this.account();
account['free'] = free;
account['used'] = used;
account['total'] = total;
}
else {
account['free'] = Precise["default"].stringAdd(account['free'], free);
account['used'] = Precise["default"].stringAdd(account['used'], used);
account['total'] = Precise["default"].stringAdd(account['total'], total);
}
result[code] = account;
}
}
}
return this.safeBalance(result);
}
/**
* @method
* @name coinbase#fetchBalance
* @description query for balance and get the amount of funds available for trading or funds locked in orders
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmbalancesummary
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.v3] default false, set true to use v3 api endpoint
* @param {string} [params.type] "spot" (default) or "swap" or "future"
* @param {int} [params.limit] default 250, maximum number of accounts to return
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
*/
async fetchBalance(params = {}) {
await this.loadMarkets();
const request = {};
let response = undefined;
const isV3 = this.safeBool(params, 'v3', false);
params = this.omit(params, ['v3']);
let marketType = undefined;
[marketType, params] = this.handleMarketTypeAndParams('fetchBalance', undefined, params);
const method = this.safeString(this.options, 'fetchBalance', 'v3PrivateGetBrokerageAccounts');
if (marketType === 'future') {
response = await this.v3PrivateGetBrokerageCfmBalanceSummary(this.extend(request, params));
}
else if ((isV3) || (method === 'v3PrivateGetBrokerageAccounts')) {
request['limit'] = 250;
response = await this.v3PrivateGetBrokerageAccounts(this.extend(request, params));
}
else {
request['limit'] = 250;
response = await this.v2PrivateGetAccounts(this.extend(request, params));
}
//
// v2PrivateGetAccounts
// {
// "pagination":{
// "ending_before":null,
// "starting_after":null,
// "previous_ending_before":null,
// "next_starting_after":"6b17acd6-2e68-5eb0-9f45-72d67cef578a",
// "limit":100,
// "order":"desc",
// "previous_uri":null,
// "next_uri":"/v2/accounts?limit=100\u0026starting_after=6b17acd6-2e68-5eb0-9f45-72d67cef578b"
// },
// "data":[
// {
// "id":"94ad58bc-0f15-5309-b35a-a4c86d7bad60",
// "name":"MINA Wallet",
// "primary":false,
// "type":"wallet",
// "currency":{
// "code":"MINA",
// "name":"Mina",
// "color":"#EA6B48",
// "sort_index":397,
// "exponent":9,
// "type":"crypto",
// "address_regex":"^(B62)[A-Za-z0-9]{52}$",
// "asset_id":"a4ffc575-942c-5e26-b70c-cb3befdd4229",
// "slug":"mina"
// },
// "balance":{"amount":"0.000000000","currency":"MINA"},
// "created_at":"2022-03-25T00:36:16Z",
// "updated_at":"2022-03-25T00:36:16Z",
// "resource":"account",
// "resource_path":"/v2/accounts/94ad58bc-0f15-5309-b35a-a4c86d7bad60",
// "allow_deposits":true,
// "allow_withdrawals":true
// },
// ]
// }
//
// v3PrivateGetBrokerageAccounts
// {
// "accounts": [
// {
// "uuid": "11111111-1111-1111-1111-111111111111",
// "name": "USDC Wallet",
// "currency": "USDC",
// "available_balance": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// },
// "default": true,
// "active": true,
// "created_at": "2023-01-04T06:20:06.456Z",
// "updated_at": "2023-01-04T06:20:07.181Z",
// "deleted_at": null,
// "type": "ACCOUNT_TYPE_CRYPTO",
// "ready": false,
// "hold": {
// "value": "0.0000000000000000",
// "currency": "USDC"
// }
// },
// ...
// ],
// "has_next": false,
// "cursor": "",
// "size": 9
// }
//
params['type'] = marketType;
return this.parseCustomBalance(response, params);
}
/**
* @method
* @name coinbase#fetchLedger
* @description Fetch the history of changes, i.e. actions done by the user or operations that altered the balance. Will return staking rewards, and crypto deposits or withdrawals.
* @see https://docs.cdp.coinbase.com/coinbase-app/docs/api-transactions#list-transactions
* @param {string} [code] unified currency code, default is undefined
* @param {int} [since] timestamp in ms of the earliest ledger entry, default is undefined
* @param {int} [limit] max number of ledger entries to return, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {object} a [ledger structure]{@link https://docs.ccxt.com/#/?id=ledger}
*/
async fetchLedger(code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchLedger', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchLedger', code, since, limit, params, 'next_starting_after', 'starting_after', undefined, 100);
}
let currency = undefined;
if (code !== undefined) {
currency = this.currency(code);
}
let request = undefined;
[request, params] = await this.prepareAccountRequestWithCurrencyCode(code, limit, params);
// for pagination use parameter 'starting_after'
// the value for the next page can be obtained from the result of the previous call in the 'pagination' field
// eg: instance.last_http_response -> pagination.next_starting_after
const response = await this.v2PrivateGetAccountsAccountIdTransactions(this.extend(request, params));
const ledger = this.parseLedger(response['data'], currency, since, limit);
const length = ledger.length;
if (length === 0) {
return ledger;
}
const lastIndex = length - 1;
const last = this.safeDict(ledger, lastIndex);
const pagination = this.safeDict(response, 'pagination', {});
const cursor = this.safeString(pagination, 'next_starting_after');
if ((cursor !== undefined) && (cursor !== '')) {
last['info']['next_starting_after'] = cursor;
ledger[lastIndex] = last;
}
return ledger;
}
parseLedgerEntryStatus(status) {
const types = {
'completed': 'ok',
};
return this.safeString(types, status, status);
}
parseLedgerEntryType(type) {
const types = {
'buy': 'trade',
'sell': 'trade',
'fiat_deposit': 'transaction',
'fiat_withdrawal': 'transaction',
'exchange_deposit': 'transaction',
'exchange_withdrawal': 'transaction',
'send': 'transaction',
'pro_deposit': 'transaction',
'pro_withdrawal': 'transaction', // crypto deposit (to coinbase from coinbasepro)
};
return this.safeString(types, type, type);
}
parseLedgerEntry(item, currency = undefined) {
//
// crypto deposit transaction
//
// {
// "id": "34e4816b-4c8c-5323-a01c-35a9fa26e490",
// "type": "send",
// "status": "completed",
// "amount": { amount: "28.31976528", currency: "BCH" },
// "native_amount": { amount: "2799.65", currency: "GBP" },
// "description": null,
// "created_at": "2019-02-28T12:35:20Z",
// "updated_at": "2019-02-28T12:43:24Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/34e4816b-4c8c-5323-a01c-35a9fa26e490",
// "instant_exchange": false,
// "network": {
// "status": "confirmed",
// "hash": "56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701",
// "transaction_url": "https://bch.btc.com/56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701"
// },
// "from": { resource: "bitcoin_cash_network", currency: "BCH" },
// "details": { title: 'Received Bitcoin Cash', subtitle: "From Bitcoin Cash address" }
// }
//
// crypto withdrawal transaction
//
// {
// "id": "459aad99-2c41-5698-ac71-b6b81a05196c",
// "type": "send",
// "status": "completed",
// "amount": { amount: "-0.36775642", currency: "BTC" },
// "native_amount": { amount: "-1111.65", currency: "GBP" },
// "description": null,
// "created_at": "2019-03-20T08:37:07Z",
// "updated_at": "2019-03-20T08:49:33Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/459aad99-2c41-5698-ac71-b6b81a05196c",
// "instant_exchange": false,
// "network": {
// "status": "confirmed",
// "hash": "2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b",
// "transaction_url": "https://blockchain.info/tx/2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b",
// "transaction_fee": { amount: "0.00000000", currency: "BTC" },
// "transaction_amount": { amount: "0.36775642", currency: "BTC" },
// "confirmations": 15682
// },
// "to": {
// "resource": "bitcoin_address",
// "address": "1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX",
// "currency": "BTC",
// "address_info": { address: "1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX" }
// },
// "idem": "da0a2f14-a2af-4c5a-a37e-d4484caf582bsend",
// "application": {
// "id": "5756ab6e-836b-553b-8950-5e389451225d",
// "resource": "application",
// "resource_path": "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
// },
// "details": { title: 'Sent Bitcoin', subtitle: "To Bitcoin address" }
// }
//
// withdrawal transaction from coinbase to coinbasepro
//
// {
// "id": "5b1b9fb8-5007-5393-b923-02903b973fdc",
// "type": "pro_deposit",
// "status": "completed",
// "amount": { amount: "-0.00001111", currency: "BCH" },
// "native_amount": { amount: "0.00", currency: "GBP" },
// "description": null,
// "created_at": "2019-02-28T13:31:58Z",
// "updated_at": "2019-02-28T13:31:58Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/5b1b9fb8-5007-5393-b923-02903b973fdc",
// "instant_exchange": false,
// "application": {
// "id": "5756ab6e-836b-553b-8950-5e389451225d",
// "resource": "application",
// "resource_path": "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
// },
// "details": { title: 'Transferred Bitcoin Cash', subtitle: "To Coinbase Pro" }
// }
//
// withdrawal transaction from coinbase to gdax
//
// {
// "id": "badb7313-a9d3-5c07-abd0-00f8b44199b1",
// "type": "exchange_deposit",
// "status": "completed",
// "amount": { amount: "-0.43704149", currency: "BCH" },
// "native_amount": { amount: "-51.90", currency: "GBP" },
// "description": null,
// "created_at": "2019-03-19T10:30:40Z",
// "updated_at": "2019-03-19T10:30:40Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/badb7313-a9d3-5c07-abd0-00f8b44199b1",
// "instant_exchange": false,
// "details": { title: 'Transferred Bitcoin Cash', subtitle: "To GDAX" }
// }
//
// deposit transaction from gdax to coinbase
//
// {
// "id": "9c4b642c-8688-58bf-8962-13cef64097de",
// "type": "exchange_withdrawal",
// "status": "completed",
// "amount": { amount: "0.57729420", currency: "BTC" },
// "native_amount": { amount: "4418.72", currency: "GBP" },
// "description": null,
// "created_at": "2018-02-17T11:33:33Z",
// "updated_at": "2018-02-17T11:33:33Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/9c4b642c-8688-58bf-8962-13cef64097de",
// "instant_exchange": false,
// "details": { title: 'Transferred Bitcoin', subtitle: "From GDAX" }
// }
//
// deposit transaction from coinbasepro to coinbase
//
// {
// "id": "8d6dd0b9-3416-568a-889d-8f112fae9e81",
// "type": "pro_withdrawal",
// "status": "completed",
// "amount": { amount: "0.40555386", currency: "BTC" },
// "native_amount": { amount: "1140.27", currency: "GBP" },
// "description": null,
// "created_at": "2019-03-04T19:41:58Z",
// "updated_at": "2019-03-04T19:41:58Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/8d6dd0b9-3416-568a-889d-8f112fae9e81",
// "instant_exchange": false,
// "application": {
// "id": "5756ab6e-836b-553b-8950-5e389451225d",
// "resource": "application",
// "resource_path": "/v2/applications/5756ab6e-836b-553b-8950-5e389451225d"
// },
// "details": { title: 'Transferred Bitcoin', subtitle: "From Coinbase Pro" }
// }
//
// sell trade
//
// {
// "id": "a9409207-df64-585b-97ab-a50780d2149e",
// "type": "sell",
// "status": "completed",
// "amount": { amount: "-9.09922880", currency: "BTC" },
// "native_amount": { amount: "-7285.73", currency: "GBP" },
// "description": null,
// "created_at": "2017-03-27T15:38:34Z",
// "updated_at": "2017-03-27T15:38:34Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/a9409207-df64-585b-97ab-a50780d2149e",
// "instant_exchange": false,
// "sell": {
// "id": "e3550b4d-8ae6-5de3-95fe-1fb01ba83051",
// "resource": "sell",
// "resource_path": "/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/sells/e3550b4d-8ae6-5de3-95fe-1fb01ba83051"
// },
// "details": {
// "title": "Sold Bitcoin",
// "subtitle": "Using EUR Wallet",
// "payment_method_name": "EUR Wallet"
// }
// }
//
// buy trade
//
// {
// "id": "63eeed67-9396-5912-86e9-73c4f10fe147",
// "type": "buy",
// "status": "completed",
// "amount": { amount: "2.39605772", currency: "ETH" },
// "native_amount": { amount: "98.31", currency: "GBP" },
// "description": null,
// "created_at": "2017-03-27T09:07:56Z",
// "updated_at": "2017-03-27T09:07:57Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/transactions/63eeed67-9396-5912-86e9-73c4f10fe147",
// "instant_exchange": false,
// "buy": {
// "id": "20b25b36-76c6-5353-aa57-b06a29a39d82",
// "resource": "buy",
// "resource_path": "/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/buys/20b25b36-76c6-5353-aa57-b06a29a39d82"
// },
// "details": {
// "title": "Bought Ethereum",
// "subtitle": "Using EUR Wallet",
// "payment_method_name": "EUR Wallet"
// }
// }
//
// fiat deposit transaction
//
// {
// "id": "04ed4113-3732-5b0c-af86-b1d2146977d0",
// "type": "fiat_deposit",
// "status": "completed",
// "amount": { amount: "114.02", currency: "EUR" },
// "native_amount": { amount: "97.23", currency: "GBP" },
// "description": null,
// "created_at": "2017-02-09T07:01:21Z",
// "updated_at": "2017-02-09T07:01:22Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86-b1d2146977d0",
// "instant_exchange": false,
// "fiat_deposit": {
// "id": "f34c19f3-b730-5e3d-9f72-96520448677a",
// "resource": "fiat_deposit",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72-96520448677a"
// },
// "details": {
// "title": "Deposited funds",
// "subtitle": "From SEPA Transfer (GB47 BARC 20..., reference CBADVI)",
// "payment_method_name": "SEPA Transfer (GB47 BARC 20..., reference CBADVI)"
// }
// }
//
// fiat withdrawal transaction
//
// {
// "id": "957d98e2-f80e-5e2f-a28e-02945aa93079",
// "type": "fiat_withdrawal",
// "status": "completed",
// "amount": { amount: "-11000.00", currency: "EUR" },
// "native_amount": { amount: "-9698.22", currency: "GBP" },
// "description": null,
// "created_at": "2017-12-06T13:19:19Z",
// "updated_at": "2017-12-06T13:19:19Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/957d98e2-f80e-5e2f-a28e-02945aa93079",
// "instant_exchange": false,
// "fiat_withdrawal": {
// "id": "f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7",
// "resource": "fiat_withdrawal",
// "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7"
// },
// "details": {
// "title": "Withdrew funds",
// "subtitle": "To HSBC BANK PLC (GB74 MIDL...)",
// "payment_method_name": "HSBC BANK PLC (GB74 MIDL...)"
// }
// }
//
const amountInfo = this.safeDict(item, 'amount', {});
let amount = this.safeString(amountInfo, 'amount');
let direction = undefined;
if (Precise["default"].stringLt(amount, '0')) {
direction = 'out';
amount = Precise["default"].stringNeg(amount);
}
else {
direction = 'in';
}
const currencyId = this.safeString(amountInfo, 'currency');
const code = this.safeCurrencyCode(currencyId, currency);
currency = this.safeCurrency(currencyId, currency);
//
// the address and txid do not belong to the unified ledger structure
//
// let address = undefined;
// if (item['to']) {
// address = this.safeString (item['to'], 'address');
// }
// let txid = undefined;
//
let fee = undefined;
const networkInfo = this.safeDict(item, 'network', {});
// txid = network['hash']; // txid does not belong to the unified ledger structure
const feeInfo = this.safeDict(networkInfo, 'transaction_fee');
if (feeInfo !== undefined) {
const feeCurrencyId = this.safeString(feeInfo, 'currency');
const feeCurrencyCode = this.safeCurrencyCode(feeCurrencyId, currency);
const feeAmount = this.safeNumber(feeInfo, 'amount');
fee = {
'cost': feeAmount,
'currency': feeCurrencyCode,
};
}
const timestamp = this.parse8601(this.safeString(item, 'created_at'));
const id = this.safeString(item, 'id');
const type = this.parseLedgerEntryType(this.safeString(item, 'type'));
const status = this.parseLedgerEntryStatus(this.safeString(item, 'status'));
const path = this.safeString(item, 'resource_path');
let accountId = undefined;
if (path !== undefined) {
const parts = path.split('/');
const numParts = parts.length;
if (numParts > 3) {
accountId = parts[3];
}
}
return this.safeLedgerEntry({
'info': item,
'id': id,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'direction': direction,
'account': accountId,
'referenceId': undefined,
'referenceAccount': undefined,
'type': type,
'currency': code,
'amount': this.parseNumber(amount),
'before': undefined,
'after': undefined,
'status': status,
'fee': fee,
}, currency);
}
async findAccountId(code, params = {}) {
await this.loadMarkets();
await this.loadAccounts(false, params);
for (let i = 0; i < this.accounts.length; i++) {
const account = this.accounts[i];
if (account['code'] === code) {
return account['id'];
}
}
return undefined;
}
prepareAccountRequest(limit = undefined, params = {}) {
const accountId = this.safeString2(params, 'account_id', 'accountId');
if (accountId === undefined) {
throw new errors.ArgumentsRequired(this.id + ' prepareAccountRequest() method requires an account_id (or accountId) parameter');
}
const request = {
'account_id': accountId,
};
if (limit !== undefined) {
request['limit'] = limit;
}
return request;
}
async prepareAccountRequestWithCurrencyCode(code = undefined, limit = undefined, params = {}) {
let accountId = this.safeString2(params, 'account_id', 'accountId');
params = this.omit(params, ['account_id', 'accountId']);
if (accountId === undefined) {
if (code === undefined) {
throw new errors.ArgumentsRequired(this.id + ' prepareAccountRequestWithCurrencyCode() method requires an account_id (or accountId) parameter OR a currency code argument');
}
accountId = await this.findAccountId(code, params);
if (accountId === undefined) {
throw new errors.ExchangeError(this.id + ' prepareAccountRequestWithCurrencyCode() could not find account id for ' + code);
}
}
const request = {
'account_id': accountId,
};
if (limit !== undefined) {
request['limit'] = limit;
}
return [request, params];
}
/**
* @method
* @name coinbase#createMarketBuyOrderWithCost
* @description create a market buy order by providing the symbol and cost
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_postorder
* @param {string} symbol unified symbol of the market to create an order in
* @param {float} cost how much you want to trade in units of the quote currency
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async createMarketBuyOrderWithCost(symbol, cost, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
if (!market['spot']) {
throw new errors.NotSupported(this.id + ' createMarketBuyOrderWithCost() supports spot orders only');
}
params['createMarketBuyOrderRequiresPrice'] = false;
return await this.createOrder(symbol, 'market', 'buy', cost, undefined, params);
}
/**
* @method
* @name coinbase#createOrder
* @description create a trade order
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_postorder
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much 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 {object} [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 {float} [params.takeProfitPrice] price to trigger take-profit orders
* @param {bool} [params.postOnly] true or false
* @param {string} [params.timeInForce] 'GTC', 'IOC', 'GTD' or 'PO', 'FOK'
* @param {string} [params.stop_direction] 'UNKNOWN_STOP_DIRECTION', 'STOP_DIRECTION_STOP_UP', 'STOP_DIRECTION_STOP_DOWN' the direction the stopPrice is triggered from
* @param {string} [params.end_time] '2023-05-25T17:01:05.092Z' for 'GTD' orders
* @param {float} [params.cost] *spot market buy only* the quote quantity that can be used as an alternative for the amount
* @param {boolean} [params.preview] default to false, wether to use the test/preview endpoint or not
* @param {float} [params.leverage] default to 1, the leverage to use for the order
* @param {string} [params.marginMode] 'cross' or 'isolated'
* @param {string} [params.retail_portfolio_id] portfolio uid
* @param {boolean} [params.is_max] Used in conjunction with tradable_balance to indicate the user wants to use their entire tradable balance
* @param {string} [params.tradable_balance] amount of tradable balance
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async createOrder(symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const id = this.safeString(this.options, 'brokerId', 'ccxt');
let request = {
'client_order_id': id + '-' + this.uuid(),
'product_id': market['id'],
'side': side.toUpperCase(),
};
const triggerPrice = this.safeNumberN(params, ['stopPrice', 'stop_price', 'triggerPrice']);
const stopLossPrice = this.safeNumber(params, 'stopLossPrice');
const takeProfitPrice = this.safeNumber(params, 'takeProfitPrice');
const isStop = triggerPrice !== undefined;
const isStopLoss = stopLossPrice !== undefined;
const isTakeProfit = takeProfitPrice !== undefined;
const timeInForce = this.safeString(params, 'timeInForce');
const postOnly = (timeInForce === 'PO') ? true : this.safeBool2(params, 'postOnly', 'post_only', false);
const endTime = this.safeString(params, 'end_time');
let stopDirection = this.safeString(params, 'stop_direction');
if (type === 'limit') {
if (isStop) {
if (stopDirection === undefined) {
stopDirection = (side === 'buy') ? 'STOP_DIRECTION_STOP_DOWN' : 'STOP_DIRECTION_STOP_UP';
}
if ((timeInForce === 'GTD') || (endTime !== undefined)) {
if (endTime === undefined) {
throw new errors.ExchangeError(this.id + ' createOrder() requires an end_time parameter for a GTD order');
}
request['order_configuration'] = {
'stop_limit_stop_limit_gtd': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
'stop_price': this.priceToPrecision(symbol, triggerPrice),
'stop_direction': stopDirection,
'end_time': endTime,
},
};
}
else {
request['order_configuration'] = {
'stop_limit_stop_limit_gtc': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
'stop_price': this.priceToPrecision(symbol, triggerPrice),
'stop_direction': stopDirection,
},
};
}
}
else if (isStopLoss || isTakeProfit) {
let tpslPrice = undefined;
if (isStopLoss) {
if (stopDirection === undefined) {
stopDirection = (side === 'buy') ? 'STOP_DIRECTION_STOP_UP' : 'STOP_DIRECTION_STOP_DOWN';
}
tpslPrice = this.priceToPrecision(symbol, stopLossPrice);
}
else {
if (stopDirection === undefined) {
stopDirection = (side === 'buy') ? 'STOP_DIRECTION_STOP_DOWN' : 'STOP_DIRECTION_STOP_UP';
}
tpslPrice = this.priceToPrecision(symbol, takeProfitPrice);
}
request['order_configuration'] = {
'stop_limit_stop_limit_gtc': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
'stop_price': tpslPrice,
'stop_direction': stopDirection,
},
};
}
else {
if ((timeInForce === 'GTD') || (endTime !== undefined)) {
if (endTime === undefined) {
throw new errors.ExchangeError(this.id + ' createOrder() requires an end_time parameter for a GTD order');
}
request['order_configuration'] = {
'limit_limit_gtd': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
'end_time': endTime,
'post_only': postOnly,
},
};
}
else if (timeInForce === 'IOC') {
request['order_configuration'] = {
'sor_limit_ioc': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
},
};
}
else if (timeInForce === 'FOK') {
request['order_configuration'] = {
'limit_limit_fok': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
},
};
}
else {
request['order_configuration'] = {
'limit_limit_gtc': {
'base_size': this.amountToPrecision(symbol, amount),
'limit_price': this.priceToPrecision(symbol, price),
'post_only': postOnly,
},
};
}
}
}
else {
if (isStop || isStopLoss || isTakeProfit) {
throw new errors.NotSupported(this.id + ' createOrder() only stop limit orders are supported');
}
if (market['spot'] && (side === 'buy')) {
let total = undefined;
let createMarketBuyOrderRequiresPrice = true;
[createMarketBuyOrderRequiresPrice, params] = this.handleOptionAndParams(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
const cost = this.safeNumber(params, 'cost');
params = this.omit(params, 'cost');
if (cost !== undefined) {
total = this.costToPrecision(symbol, cost);
}
else if (createMarketBuyOrderRequiresPrice) {
if (price === undefined) {
throw new errors.InvalidOrder(this.id + ' createOrder() requires a price argument for market buy orders on spot markets to calculate the total amount to spend (amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to false and pass the cost to spend in the amount argument');
}
else {
const amountString = this.numberToString(amount);
const priceString = this.numberToString(price);
const costRequest = Precise["default"].stringMul(amountString, priceString);
total = this.costToPrecision(symbol, costRequest);
}
}
else {
total = this.costToPrecision(symbol, amount);
}
request['order_configuration'] = {
'market_market_ioc': {
'quote_size': total,
},
};
}
else {
request['order_configuration'] = {
'market_market_ioc': {
'base_size': this.amountToPrecision(symbol, amount),
},
};
}
}
const marginMode = this.safeString(params, 'marginMode');
if (marginMode !== undefined) {
if (marginMode === 'isolated') {
request['margin_type'] = 'ISOLATED';
}
else if (marginMode === 'cross') {
request['margin_type'] = 'CROSS';
}
}
params = this.omit(params, ['timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time', 'marginMode']);
const preview = this.safeBool2(params, 'preview', 'test', false);
let response = undefined;
if (preview) {
params = this.omit(params, ['preview', 'test']);
request = this.omit(request, 'client_order_id');
response = await this.v3PrivatePostBrokerageOrdersPreview(this.extend(request, params));
}
else {
response = await this.v3PrivatePostBrokerageOrders(this.extend(request, params));
}
//
// successful order
//
// {
// "success": true,
// "failure_reason": "UNKNOWN_FAILURE_REASON",
// "order_id": "52cfe5e2-0b29-4c19-a245-a6a773de5030",
// "success_response": {
// "order_id": "52cfe5e2-0b29-4c19-a245-a6a773de5030",
// "product_id": "LTC-BTC",
// "side": "SELL",
// "client_order_id": "4d760580-6fca-4094-a70b-ebcca8626288"
// },
// "order_configuration": null
// }
//
// failed order
//
// {
// "success": false,
// "failure_reason": "UNKNOWN_FAILURE_REASON",
// "order_id": "",
// "error_response": {
// "error": "UNSUPPORTED_ORDER_CONFIGURATION",
// "message": "source is not enabled for trading",
// "error_details": "",
// "new_order_failure_reason": "UNSUPPORTED_ORDER_CONFIGURATION"
// },
// "order_configuration": {
// "limit_limit_gtc": {
// "base_size": "100",
// "limit_price": "40000",
// "post_only": false
// }
// }
// }
//
const success = this.safeBool(response, 'success');
if (success !== true) {
const errorResponse = this.safeDict(response, 'error_response');
const errorTitle = this.safeString(errorResponse, 'error');
const errorMessage = this.safeString(errorResponse, 'message');
if (errorResponse !== undefined) {
this.throwExactlyMatchedException(this.exceptions['exact'], errorTitle, errorMessage);
this.throwBroadlyMatchedException(this.exceptions['broad'], errorTitle, errorMessage);
throw new errors.ExchangeError(errorMessage);
}
}
const data = this.safeDict(response, 'success_response', {});
return this.parseOrder(data, market);
}
parseOrder(order, market = undefined) {
//
// createOrder
//
// {
// "order_id": "52cfe5e2-0b29-4c19-a245-a6a773de5030",
// "product_id": "LTC-BTC",
// "side": "SELL",
// "client_order_id": "4d760580-6fca-4094-a70b-ebcca8626288"
// }
//
// cancelOrder, cancelOrders
//
// {
// "success": true,
// "failure_reason": "UNKNOWN_CANCEL_FAILURE_REASON",
// "order_id": "bb8851a3-4fda-4a2c-aa06-9048db0e0f0d"
// }
//
// fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
//
// {
// "order_id": "9bc1eb3b-5b46-4b71-9628-ae2ed0cca75b",
// "product_id": "LTC-BTC",
// "user_id": "1111111-1111-1111-1111-111111111111",
// "order_configuration": {
// "limit_limit_gtc": {
// "base_size": "0.2",
// "limit_price": "0.006",
// "post_only": false
// },
// "stop_limit_stop_limit_gtc": {
// "base_size": "48.54",
// "limit_price": "6.998",
// "stop_price": "7.0687",
// "stop_direction": "STOP_DIRECTION_STOP_DOWN"
// }
// },
// "side": "SELL",
// "client_order_id": "e5fe8482-05bb-428f-ad4d-dbc8ce39239c",
// "status": "OPEN",
// "time_in_force": "GOOD_UNTIL_CANCELLED",
// "created_time": "2023-01-16T23:37:23.947030Z",
// "completion_percentage": "0",
// "filled_size": "0",
// "average_filled_price": "0",
// "fee": "",
// "number_of_fills": "0",
// "filled_value": "0",
// "pending_cancel": false,
// "size_in_quote": false,
// "total_fees": "0",
// "size_inclusive_of_fees": false,
// "total_value_after_fees": "0",
// "trigger_status": "INVALID_ORDER_TYPE",
// "order_type": "LIMIT",
// "reject_reason": "REJECT_REASON_UNSPECIFIED",
// "settled": false,
// "product_type": "SPOT",
// "reject_message": "",
// "cancel_message": ""
// }
//
const marketId = this.safeString(order, 'product_id');
const symbol = this.safeSymbol(marketId, market, '-');
if (symbol !== undefined) {
market = this.safeMarket(symbol, market);
}
const orderConfiguration = this.safeDict(order, 'order_configuration', {});
const limitGTC = this.safeDict(orderConfiguration, 'limit_limit_gtc');
const limitGTD = this.safeDict(orderConfiguration, 'limit_limit_gtd');
const limitIOC = this.safeDict(orderConfiguration, 'sor_limit_ioc');
const stopLimitGTC = this.safeDict(orderConfiguration, 'stop_limit_stop_limit_gtc');
const stopLimitGTD = this.safeDict(orderConfiguration, 'stop_limit_stop_limit_gtd');
const marketIOC = this.safeDict(orderConfiguration, 'market_market_ioc');
const isLimit = ((limitGTC !== undefined) || (limitGTD !== undefined) || (limitIOC !== undefined));
const isStop = ((stopLimitGTC !== undefined) || (stopLimitGTD !== undefined));
let price = undefined;
let amount = undefined;
let postOnly = undefined;
let triggerPrice = undefined;
if (isLimit) {
let target = undefined;
if (limitGTC !== undefined) {
target = limitGTC;
}
else if (limitGTD !== undefined) {
target = limitGTD;
}
else {
target = limitIOC;
}
price = this.safeString(target, 'limit_price');
amount = this.safeString(target, 'base_size');
postOnly = this.safeBool(target, 'post_only');
}
else if (isStop) {
const stopTarget = (stopLimitGTC !== undefined) ? stopLimitGTC : stopLimitGTD;
price = this.safeString(stopTarget, 'limit_price');
amount = this.safeString(stopTarget, 'base_size');
postOnly = this.safeBool(stopTarget, 'post_only');
triggerPrice = this.safeString(stopTarget, 'stop_price');
}
else {
amount = this.safeString(marketIOC, 'base_size');
}
const datetime = this.safeString(order, 'created_time');
const totalFees = this.safeString(order, 'total_fees');
let currencyFee = undefined;
if ((totalFees !== undefined) && (market !== undefined)) {
currencyFee = market['quote'];
}
return this.safeOrder({
'info': order,
'id': this.safeString(order, 'order_id'),
'clientOrderId': this.safeString(order, 'client_order_id'),
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': this.parseOrderType(this.safeString(order, 'order_type')),
'timeInForce': this.parseTimeInForce(this.safeString(order, 'time_in_force')),
'postOnly': postOnly,
'side': this.safeStringLower(order, 'side'),
'price': price,
'triggerPrice': triggerPrice,
'amount': amount,
'filled': this.safeString(order, 'filled_size'),
'remaining': undefined,
'cost': undefined,
'average': this.safeString(order, 'average_filled_price'),
'status': this.parseOrderStatus(this.safeString(order, 'status')),
'fee': {
'cost': this.safeString(order, 'total_fees'),
'currency': currencyFee,
},
'trades': undefined,
}, market);
}
parseOrderStatus(status) {
const statuses = {
'OPEN': 'open',
'FILLED': 'closed',
'CANCELLED': 'canceled',
'EXPIRED': 'canceled',
'FAILED': 'canceled',
'UNKNOWN_ORDER_STATUS': undefined,
};
return this.safeString(statuses, status, status);
}
parseOrderType(type) {
if (type === 'UNKNOWN_ORDER_TYPE') {
return undefined;
}
const types = {
'MARKET': 'market',
'LIMIT': 'limit',
'STOP': 'limit',
'STOP_LIMIT': 'limit',
};
return this.safeString(types, type, type);
}
parseTimeInForce(timeInForce) {
const timeInForces = {
'GOOD_UNTIL_CANCELLED': 'GTC',
'GOOD_UNTIL_DATE_TIME': 'GTD',
'IMMEDIATE_OR_CANCEL': 'IOC',
'FILL_OR_KILL': 'FOK',
'UNKNOWN_TIME_IN_FORCE': undefined,
};
return this.safeString(timeInForces, timeInForce, timeInForce);
}
/**
* @method
* @name coinbase#cancelOrder
* @description cancels an open order
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_cancelorders
* @param {string} id order id
* @param {string} symbol not used by coinbase cancelOrder()
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async cancelOrder(id, symbol = undefined, params = {}) {
await this.loadMarkets();
const orders = await this.cancelOrders([id], symbol, params);
return this.safeDict(orders, 0, {});
}
/**
* @method
* @name coinbase#cancelOrders
* @description cancel multiple orders
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_cancelorders
* @param {string[]} ids order ids
* @param {string} symbol not used by coinbase cancelOrders()
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async cancelOrders(ids, symbol = undefined, params = {}) {
await this.loadMarkets();
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {
'order_ids': ids,
};
const response = await this.v3PrivatePostBrokerageOrdersBatchCancel(this.extend(request, params));
//
// {
// "results": [
// {
// "success": true,
// "failure_reason": "UNKNOWN_CANCEL_FAILURE_REASON",
// "order_id": "bb8851a3-4fda-4a2c-aa06-9048db0e0f0d"
// }
// ]
// }
//
const orders = this.safeList(response, 'results', []);
for (let i = 0; i < orders.length; i++) {
const success = this.safeBool(orders[i], 'success');
if (success !== true) {
throw new errors.BadRequest(this.id + ' cancelOrders() has failed, check your arguments and parameters');
}
}
return this.parseOrders(orders, market);
}
/**
* @method
* @name coinbase#editOrder
* @description edit a trade order
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_editorder
* @param {string} id cancel order id
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of 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 {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.preview] default to false, wether to use the test/preview endpoint or not
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async editOrder(id, symbol, type, side, amount = undefined, price = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'order_id': id,
};
if (amount !== undefined) {
request['size'] = this.amountToPrecision(symbol, amount);
}
if (price !== undefined) {
request['price'] = this.priceToPrecision(symbol, price);
}
const preview = this.safeBool2(params, 'preview', 'test', false);
let response = undefined;
if (preview) {
params = this.omit(params, ['preview', 'test']);
response = await this.v3PrivatePostBrokerageOrdersEditPreview(this.extend(request, params));
}
else {
response = await this.v3PrivatePostBrokerageOrdersEdit(this.extend(request, params));
}
//
// {
// "success": true,
// "errors": {
// "edit_failure_reason": "UNKNOWN_EDIT_ORDER_FAILURE_REASON",
// "preview_failure_reason": "UNKNOWN_PREVIEW_FAILURE_REASON"
// }
// }
//
return this.parseOrder(response, market);
}
/**
* @method
* @name coinbase#fetchOrder
* @description fetches information on an order made by the user
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorder
* @param {string} id the order id
* @param {string} symbol unified market symbol that the order was made in
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchOrder(id, symbol = undefined, params = {}) {
await this.loadMarkets();
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {
'order_id': id,
};
const response = await this.v3PrivateGetBrokerageOrdersHistoricalOrderId(this.extend(request, params));
//
// {
// "order": {
// "order_id": "9bc1eb3b-5b46-4b71-9628-ae2ed0cca75b",
// "product_id": "LTC-BTC",
// "user_id": "1111111-1111-1111-1111-111111111111",
// "order_configuration": {
// "limit_limit_gtc": {
// "base_size": "0.2",
// "limit_price": "0.006",
// "post_only": false
// }
// },
// "side": "SELL",
// "client_order_id": "e5fe8482-05bb-428f-ad4d-dbc8ce39239c",
// "status": "OPEN",
// "time_in_force": "GOOD_UNTIL_CANCELLED",
// "created_time": "2023-01-16T23:37:23.947030Z",
// "completion_percentage": "0",
// "filled_size": "0",
// "average_filled_price": "0",
// "fee": "",
// "number_of_fills": "0",
// "filled_value": "0",
// "pending_cancel": false,
// "size_in_quote": false,
// "total_fees": "0",
// "size_inclusive_of_fees": false,
// "total_value_after_fees": "0",
// "trigger_status": "INVALID_ORDER_TYPE",
// "order_type": "LIMIT",
// "reject_reason": "REJECT_REASON_UNSPECIFIED",
// "settled": false,
// "product_type": "SPOT",
// "reject_message": "",
// "cancel_message": ""
// }
// }
//
const order = this.safeDict(response, 'order', {});
return this.parseOrder(order, market);
}
/**
* @method
* @name coinbase#fetchOrders
* @description fetches information on multiple orders made by the user
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
* @param {string} symbol unified market symbol that the orders were made in
* @param {int} [since] the earliest time in ms to fetch orders
* @param {int} [limit] the maximum number of order structures to retrieve
* @param {object} [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 this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchOrders(symbol = undefined, since = undefined, limit = 100, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchOrders', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchOrders', symbol, since, limit, params, 'cursor', 'cursor', undefined, 1000);
}
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {};
if (market !== undefined) {
request['product_id'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit;
}
if (since !== undefined) {
request['start_date'] = this.iso8601(since);
}
const until = this.safeIntegerN(params, ['until']);
if (until !== undefined) {
params = this.omit(params, ['until']);
request['end_date'] = this.iso8601(until);
}
const response = await this.v3PrivateGetBrokerageOrdersHistoricalBatch(this.extend(request, params));
//
// {
// "orders": [
// {
// "order_id": "813a53c5-3e39-47bb-863d-2faf685d22d8",
// "product_id": "BTC-USDT",
// "user_id": "1111111-1111-1111-1111-111111111111",
// "order_configuration": {
// "market_market_ioc": {
// "quote_size": "6.36"
// }
// },
// "side": "BUY",
// "client_order_id": "18eb9947-db49-4874-8e7b-39b8fe5f4317",
// "status": "FILLED",
// "time_in_force": "IMMEDIATE_OR_CANCEL",
// "created_time": "2023-01-18T01:37:37.975552Z",
// "completion_percentage": "100",
// "filled_size": "0.000297920684505",
// "average_filled_price": "21220.6399999973697697",
// "fee": "",
// "number_of_fills": "2",
// "filled_value": "6.3220675944333996",
// "pending_cancel": false,
// "size_in_quote": true,
// "total_fees": "0.0379324055666004",
// "size_inclusive_of_fees": true,
// "total_value_after_fees": "6.36",
// "trigger_status": "INVALID_ORDER_TYPE",
// "order_type": "MARKET",
// "reject_reason": "REJECT_REASON_UNSPECIFIED",
// "settled": true,
// "product_type": "SPOT",
// "reject_message": "",
// "cancel_message": "Internal error"
// },
// ],
// "sequence": "0",
// "has_next": false,
// "cursor": ""
// }
//
const orders = this.safeList(response, 'orders', []);
const first = this.safeDict(orders, 0);
const cursor = this.safeString(response, 'cursor');
if ((cursor !== undefined) && (cursor !== '')) {
first['cursor'] = cursor;
orders[0] = first;
}
return this.parseOrders(orders, market, since, limit);
}
async fetchOrdersByStatus(status, symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {
'order_status': status,
};
if (market !== undefined) {
request['product_id'] = market['id'];
}
if (limit === undefined) {
limit = 100;
}
request['limit'] = limit;
if (since !== undefined) {
request['start_date'] = this.iso8601(since);
}
const until = this.safeIntegerN(params, ['until']);
if (until !== undefined) {
params = this.omit(params, ['until']);
request['end_date'] = this.iso8601(until);
}
const response = await this.v3PrivateGetBrokerageOrdersHistoricalBatch(this.extend(request, params));
//
// {
// "orders": [
// {
// "order_id": "813a53c5-3e39-47bb-863d-2faf685d22d8",
// "product_id": "BTC-USDT",
// "user_id": "1111111-1111-1111-1111-111111111111",
// "order_configuration": {
// "market_market_ioc": {
// "quote_size": "6.36"
// }
// },
// "side": "BUY",
// "client_order_id": "18eb9947-db49-4874-8e7b-39b8fe5f4317",
// "status": "FILLED",
// "time_in_force": "IMMEDIATE_OR_CANCEL",
// "created_time": "2023-01-18T01:37:37.975552Z",
// "completion_percentage": "100",
// "filled_size": "0.000297920684505",
// "average_filled_price": "21220.6399999973697697",
// "fee": "",
// "number_of_fills": "2",
// "filled_value": "6.3220675944333996",
// "pending_cancel": false,
// "size_in_quote": true,
// "total_fees": "0.0379324055666004",
// "size_inclusive_of_fees": true,
// "total_value_after_fees": "6.36",
// "trigger_status": "INVALID_ORDER_TYPE",
// "order_type": "MARKET",
// "reject_reason": "REJECT_REASON_UNSPECIFIED",
// "settled": true,
// "product_type": "SPOT",
// "reject_message": "",
// "cancel_message": "Internal error"
// },
// ],
// "sequence": "0",
// "has_next": false,
// "cursor": ""
// }
//
const orders = this.safeList(response, 'orders', []);
const first = this.safeDict(orders, 0);
const cursor = this.safeString(response, 'cursor');
if ((cursor !== undefined) && (cursor !== '')) {
first['cursor'] = cursor;
orders[0] = first;
}
return this.parseOrders(orders, market, since, limit);
}
/**
* @method
* @name coinbase#fetchOpenOrders
* @description fetches information on all currently open orders
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
* @param {string} symbol unified market symbol of the orders
* @param {int} [since] timestamp in ms of the earliest order, default is undefined
* @param {int} [limit] the maximum number of open order structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @param {int} [params.until] the latest time in ms to fetch trades for
* @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchOpenOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchOpenOrders', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchOpenOrders', symbol, since, limit, params, 'cursor', 'cursor', undefined, 100);
}
return await this.fetchOrdersByStatus('OPEN', symbol, since, limit, params);
}
/**
* @method
* @name coinbase#fetchClosedOrders
* @description fetches information on multiple closed orders made by the user
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
* @param {string} symbol unified market symbol of the orders
* @param {int} [since] timestamp in ms of the earliest order, default is undefined
* @param {int} [limit] the maximum number of closed order structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @param {int} [params.until] the latest time in ms to fetch trades for
* @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchClosedOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchClosedOrders', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchClosedOrders', symbol, since, limit, params, 'cursor', 'cursor', undefined, 100);
}
return await this.fetchOrdersByStatus('FILLED', symbol, since, limit, params);
}
/**
* @method
* @name coinbase#fetchCanceledOrders
* @description fetches information on multiple canceled orders made by the user
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorders
* @param {string} symbol unified market symbol of the orders
* @param {int} [since] timestamp in ms of the earliest order, default is undefined
* @param {int} [limit] the maximum number of canceled order structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async fetchCanceledOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
return await this.fetchOrdersByStatus('CANCELLED', symbol, since, limit, params);
}
/**
* @method
* @name coinbase#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpubliccandles
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch, not used by coinbase
* @param {object} [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 this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @param {boolean} [params.usePrivate] default false, when true will use the private endpoint to fetch the candles
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const maxLimit = 300;
limit = (limit === undefined) ? maxLimit : Math.min(limit, maxLimit);
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchOHLCV', 'paginate', false);
if (paginate) {
return await this.fetchPaginatedCallDeterministic('fetchOHLCV', symbol, since, limit, timeframe, params, maxLimit - 1);
}
const market = this.market(symbol);
const request = {
'product_id': market['id'],
'granularity': this.safeString(this.timeframes, timeframe, timeframe),
};
const until = this.safeIntegerN(params, ['until', 'end']);
params = this.omit(params, ['until']);
const duration = this.parseTimeframe(timeframe);
const requestedDuration = limit * duration;
let sinceString = undefined;
if (since !== undefined) {
sinceString = this.numberToString(this.parseToInt(since / 1000));
}
else {
const now = this.seconds().toString();
sinceString = Precise["default"].stringSub(now, requestedDuration.toString());
}
request['start'] = sinceString;
if (until !== undefined) {
request['end'] = this.numberToString(this.parseToInt(until / 1000));
}
else {
// 300 candles max
request['end'] = Precise["default"].stringAdd(sinceString, requestedDuration.toString());
}
let response = undefined;
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchOHLCV', 'usePrivate', false);
if (usePrivate) {
response = await this.v3PrivateGetBrokerageProductsProductIdCandles(this.extend(request, params));
}
else {
response = await this.v3PublicGetBrokerageMarketProductsProductIdCandles(this.extend(request, params));
}
//
// {
// "candles": [
// {
// "start": "1673391780",
// "low": "17414.36",
// "high": "17417.99",
// "open": "17417.74",
// "close": "17417.38",
// "volume": "1.87780853"
// },
// ]
// }
//
const candles = this.safeList(response, 'candles', []);
return this.parseOHLCVs(candles, market, timeframe, since, limit);
}
parseOHLCV(ohlcv, market = undefined) {
//
// [
// {
// "start": "1673391780",
// "low": "17414.36",
// "high": "17417.99",
// "open": "17417.74",
// "close": "17417.38",
// "volume": "1.87780853"
// },
// ]
//
return [
this.safeTimestamp(ohlcv, 'start'),
this.safeNumber(ohlcv, 'open'),
this.safeNumber(ohlcv, 'high'),
this.safeNumber(ohlcv, 'low'),
this.safeNumber(ohlcv, 'close'),
this.safeNumber(ohlcv, 'volume'),
];
}
/**
* @method
* @name coinbase#fetchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicmarkettrades
* @param {string} symbol unified market symbol of the trades
* @param {int} [since] not used by coinbase fetchTrades
* @param {int} [limit] the maximum number of trade structures to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.usePrivate] default false, when true will use the private endpoint to fetch the trades
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'product_id': market['id'],
};
if (since !== undefined) {
request['start'] = this.numberToString(this.parseToInt(since / 1000));
}
if (limit !== undefined) {
request['limit'] = Math.min(limit, 1000);
}
let until = undefined;
[until, params] = this.handleOptionAndParams(params, 'fetchTrades', 'until');
if (until !== undefined) {
request['end'] = this.numberToString(this.parseToInt(until / 1000));
}
else if (since !== undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchTrades() requires a `until` parameter when you use `since` argument');
}
let response = undefined;
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchTrades', 'usePrivate', false);
if (usePrivate) {
response = await this.v3PrivateGetBrokerageProductsProductIdTicker(this.extend(request, params));
}
else {
response = await this.v3PublicGetBrokerageMarketProductsProductIdTicker(this.extend(request, params));
}
//
// {
// "trades": [
// {
// "trade_id": "10092327",
// "product_id": "BTC-USDT",
// "price": "17488.12",
// "size": "0.0000623",
// "time": "2023-01-11T00:52:37.557001Z",
// "side": "BUY",
// "bid": "",
// "ask": ""
// },
// ]
// }
//
const trades = this.safeList(response, 'trades', []);
return this.parseTrades(trades, market, since, limit);
}
/**
* @method
* @name coinbase#fetchMyTrades
* @description fetch all trades made by the user
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfills
* @param {string} symbol unified market symbol of the trades
* @param {int} [since] timestamp in ms of the earliest order, default is undefined
* @param {int} [limit] the maximum number of trade structures to fetch
* @param {object} [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 this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
*/
async fetchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchMyTrades', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchMyTrades', symbol, since, limit, params, 'cursor', 'cursor', undefined, 250);
}
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const request = {};
if (market !== undefined) {
request['product_id'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit;
}
if (since !== undefined) {
request['start_sequence_timestamp'] = this.iso8601(since);
}
const until = this.safeIntegerN(params, ['until']);
if (until !== undefined) {
params = this.omit(params, ['until']);
request['end_sequence_timestamp'] = this.iso8601(until);
}
const response = await this.v3PrivateGetBrokerageOrdersHistoricalFills(this.extend(request, params));
//
// {
// "fills": [
// {
// "entry_id": "b88b82cc89e326a2778874795102cbafd08dd979a2a7a3c69603fc4c23c2e010",
// "trade_id": "cdc39e45-bbd3-44ec-bf02-61742dfb16a1",
// "order_id": "813a53c5-3e39-47bb-863d-2faf685d22d8",
// "trade_time": "2023-01-18T01:37:38.091377090Z",
// "trade_type": "FILL",
// "price": "21220.64",
// "size": "0.0046830664333996",
// "commission": "0.0000280983986004",
// "product_id": "BTC-USDT",
// "sequence_timestamp": "2023-01-18T01:37:38.092520Z",
// "liquidity_indicator": "UNKNOWN_LIQUIDITY_INDICATOR",
// "size_in_quote": true,
// "user_id": "1111111-1111-1111-1111-111111111111",
// "side": "BUY"
// },
// ],
// "cursor": ""
// }
//
const trades = this.safeList(response, 'fills', []);
const first = this.safeDict(trades, 0);
const cursor = this.safeString(response, 'cursor');
if ((cursor !== undefined) && (cursor !== '')) {
first['cursor'] = cursor;
trades[0] = first;
}
return this.parseTrades(trades, market, since, limit);
}
/**
* @method
* @name coinbase#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpublicproductbook
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {boolean} [params.usePrivate] default false, when true will use the private endpoint to fetch the order book
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
async fetchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'product_id': market['id'],
};
if (limit !== undefined) {
request['limit'] = limit;
}
let response = undefined;
let usePrivate = false;
[usePrivate, params] = this.handleOptionAndParams(params, 'fetchOrderBook', 'usePrivate', false);
if (usePrivate) {
response = await this.v3PrivateGetBrokerageProductBook(this.extend(request, params));
}
else {
response = await this.v3PublicGetBrokerageMarketProductBook(this.extend(request, params));
}
//
// {
// "pricebook": {
// "product_id": "BTC-USDT",
// "bids": [
// {
// "price": "30757.85",
// "size": "0.115"
// },
// ],
// "asks": [
// {
// "price": "30759.07",
// "size": "0.04877659"
// },
// ],
// "time": "2023-06-30T04:02:40.533606Z"
// }
// }
//
const data = this.safeDict(response, 'pricebook', {});
const time = this.safeString(data, 'time');
const timestamp = this.parse8601(time);
return this.parseOrderBook(data, symbol, timestamp, 'bids', 'asks', 'price', 'size');
}
/**
* @method
* @name coinbase#fetchBidsAsks
* @description fetches the bid and ask price and volume for multiple markets
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getbestbidask
* @param {string[]} [symbols] unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchBidsAsks(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const request = {};
if (symbols !== undefined) {
request['product_ids'] = this.marketIds(symbols);
}
const response = await this.v3PrivateGetBrokerageBestBidAsk(this.extend(request, params));
//
// {
// "pricebooks": [
// {
// "product_id": "TRAC-EUR",
// "bids": [
// {
// "price": "0.2384",
// "size": "386.1"
// }
// ],
// "asks": [
// {
// "price": "0.2406",
// "size": "672"
// }
// ],
// "time": "2023-06-30T07:15:24.656044Z"
// },
// ]
// }
//
const tickers = this.safeList(response, 'pricebooks', []);
return this.parseTickers(tickers, symbols);
}
/**
* @method
* @name coinbase#withdraw
* @description make a withdrawal
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-transactions#send-money
* @param {string} code unified currency code
* @param {float} amount the amount to withdraw
* @param {string} address the address to withdraw to
* @param {string} [tag] an optional tag for the withdrawal
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async withdraw(code, amount, address, tag = undefined, params = {}) {
[tag, params] = this.handleWithdrawTagAndParams(tag, params);
this.checkAddress(address);
await this.loadMarkets();
const currency = this.currency(code);
let accountId = this.safeString2(params, 'account_id', 'accountId');
params = this.omit(params, ['account_id', 'accountId']);
if (accountId === undefined) {
if (code === undefined) {
throw new errors.ArgumentsRequired(this.id + ' withdraw() requires an account_id (or accountId) parameter OR a currency code argument');
}
accountId = await this.findAccountId(code, params);
if (accountId === undefined) {
throw new errors.ExchangeError(this.id + ' withdraw() could not find account id for ' + code);
}
}
const request = {
'account_id': accountId,
'type': 'send',
'to': address,
'amount': amount,
'currency': currency['id'],
};
if (tag !== undefined) {
request['destination_tag'] = tag;
}
const response = await this.v2PrivatePostAccountsAccountIdTransactions(this.extend(request, params));
//
// {
// "data": {
// "id": "a1794ecf-5693-55fa-70cf-ef731748ed82",
// "type": "send",
// "status": "pending",
// "amount": {
// "amount": "-14.008308",
// "currency": "USDC"
// },
// "native_amount": {
// "amount": "-18.74",
// "currency": "CAD"
// },
// "description": null,
// "created_at": "2024-01-12T01:27:31Z",
// "updated_at": "2024-01-12T01:27:31Z",
// "resource": "transaction",
// "resource_path": "/v2/accounts/a34bgfad-ed67-538b-bffc-730c98c10da0/transactions/a1794ecf-5693-55fa-70cf-ef731748ed82",
// "instant_exchange": false,
// "network": {
// "status": "pending",
// "status_description": "Pending (est. less than 10 minutes)",
// "transaction_fee": {
// "amount": "4.008308",
// "currency": "USDC"
// },
// "transaction_amount": {
// "amount": "10.000000",
// "currency": "USDC"
// },
// "confirmations": 0
// },
// "to": {
// "resource": "ethereum_address",
// "address": "0x9...",
// "currency": "USDC",
// "address_info": {
// "address": "0x9..."
// }
// },
// "idem": "748d8591-dg9a-7831-a45b-crd61dg78762",
// "details": {
// "title": "Sent USDC",
// "subtitle": "To USDC address on Ethereum network",
// "header": "Sent 14.008308 USDC ($18.74)",
// "health": "warning"
// },
// "hide_native_amount": false
// }
// }
//
const data = this.safeDict(response, 'data', {});
return this.parseTransaction(data, currency);
}
/**
* @method
* @name coinbase#fetchDepositAddress
* @description fetch the deposit address for a currency associated with this account
* @see https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_postcoinbaseaccountaddresses
* @param {string} code unified currency code
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure}
*/
async fetchDepositAddressesByNetwork(code, params = {}) {
await this.loadMarkets();
const currency = this.currency(code);
let request = undefined;
[request, params] = await this.prepareAccountRequestWithCurrencyCode(currency['code'], undefined, params);
const response = await this.v2PrivateGetAccountsAccountIdAddresses(this.extend(request, params));
//
// {
// pagination: {
// ending_before: null,
// starting_after: null,
// previous_ending_before: null,
// next_starting_after: null,
// limit: '25',
// order: 'desc',
// previous_uri: null,
// next_uri: null
// },
// data: [
// {
// id: '64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
// address: '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
// address_info: { address: '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk' },
// name: null,
// created_at: '2023-05-29T21:12:12Z',
// updated_at: '2023-05-29T21:12:12Z',
// network: 'solana',
// uri_scheme: 'solana',
// resource: 'address',
// resource_path: '/v2/accounts/a7b3d387-bfb8-5ce7-b8da-1f507e81cf25/addresses/64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
// warnings: [
// {
// type: 'correct_address_warning',
// title: 'This is an ERC20 USDC address.',
// details: 'Only send ERC20 USD Coin (USDC) to this address.',
// image_url: 'https://www.coinbase.com/assets/addresses/global-receive-warning-a3d91807e61c717e5a38d270965003dcc025ca8a3cea40ec3d7835b7c86087fa.png',
// options: [ { text: 'I understand', style: 'primary', id: 'dismiss' } ]
// }
// ],
// qr_code_image_url: 'https://static-assets.coinbase.com/p2p/l2/asset_network_combinations/v5/usdc-solana.png',
// address_label: 'USDC address (Solana)',
// default_receive: true,
// deposit_uri: 'solana:5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk?spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
// callback_url: null,
// share_address_copy: {
// line1: '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
// line2: 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.'
// },
// receive_subtitle: 'ERC-20',
// inline_warning: {
// text: 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.',
// tooltip: {
// title: 'USDC (Solana)',
// subtitle: 'This address can only receive USDC-SPL from Solana network.'
// }
// }
// },
// ...
// ]
// }
//
const data = this.safeList(response, 'data', []);
const addressStructures = this.parseDepositAddresses(data, undefined, false);
return this.indexBy(addressStructures, 'network');
}
parseDepositAddress(depositAddress, currency = undefined) {
//
// {
// id: '64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
// address: '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
// address_info: {
// address: 'GCF74576I7AQ56SLMKBQAP255EGUOWCRVII3S44KEXVNJEOIFVBDMXVL',
// destination_tag: '3722061866'
// },
// name: null,
// created_at: '2023-05-29T21:12:12Z',
// updated_at: '2023-05-29T21:12:12Z',
// network: 'solana',
// uri_scheme: 'solana',
// resource: 'address',
// resource_path: '/v2/accounts/a7b3d387-bfb8-5ce7-b8da-1f507e81cf25/addresses/64ceb5f1-5fa2-5310-a4ff-9fd46271003d',
// warnings: [
// {
// type: 'correct_address_warning',
// title: 'This is an ERC20 USDC address.',
// details: 'Only send ERC20 USD Coin (USDC) to this address.',
// image_url: 'https://www.coinbase.com/assets/addresses/global-receive-warning-a3d91807e61c717e5a38d270965003dcc025ca8a3cea40ec3d7835b7c86087fa.png',
// options: [ { text: 'I understand', style: 'primary', id: 'dismiss' } ]
// }
// ],
// qr_code_image_url: 'https://static-assets.coinbase.com/p2p/l2/asset_network_combinations/v5/usdc-solana.png',
// address_label: 'USDC address (Solana)',
// default_receive: true,
// deposit_uri: 'solana:5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk?spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
// callback_url: null,
// share_address_copy: {
// line1: '5xjPKeAXpnhA2kHyinvdVeui6RXVdEa3B2J3SCAwiKnk',
// line2: 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.'
// },
// receive_subtitle: 'ERC-20',
// inline_warning: {
// text: 'This address can only receive USDC-SPL from Solana network. Don’t send USDC from other networks, other SPL tokens or NFTs, or it may result in a loss of funds.',
// tooltip: {
// title: 'USDC (Solana)',
// subtitle: 'This address can only receive USDC-SPL from Solana network.'
// }
// }
// }
//
const address = this.safeString(depositAddress, 'address');
this.checkAddress(address);
const networkId = this.safeString(depositAddress, 'network');
const code = this.safeCurrencyCode(undefined, currency);
const addressLabel = this.safeString(depositAddress, 'address_label');
let currencyId = undefined;
if (addressLabel !== undefined) {
const splitAddressLabel = addressLabel.split(' ');
currencyId = this.safeString(splitAddressLabel, 0);
}
const addressInfo = this.safeDict(depositAddress, 'address_info');
return {
'info': depositAddress,
'currency': this.safeCurrencyCode(currencyId, currency),
'network': this.networkIdToCode(networkId, code),
'address': address,
'tag': this.safeString(addressInfo, 'destination_tag'),
};
}
/**
* @method
* @name coinbase#deposit
* @description make a deposit
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-deposits#deposit-funds
* @param {string} code unified currency code
* @param {float} amount the amount to deposit
* @param {string} id the payment method id to be used for the deposit, can be retrieved from v2PrivateGetPaymentMethods
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.accountId] the id of the account to deposit into
* @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async deposit(code, amount, id, params = {}) {
await this.loadMarkets();
let accountId = this.safeString2(params, 'account_id', 'accountId');
params = this.omit(params, ['account_id', 'accountId']);
if (accountId === undefined) {
if (code === undefined) {
throw new errors.ArgumentsRequired(this.id + ' deposit() requires an account_id (or accountId) parameter OR a currency code argument');
}
accountId = await this.findAccountId(code, params);
if (accountId === undefined) {
throw new errors.ExchangeError(this.id + ' deposit() could not find account id for ' + code);
}
}
const request = {
'account_id': accountId,
'amount': this.numberToString(amount),
'currency': code.toUpperCase(),
'payment_method': id,
'commit': true, // otheriwse the deposit does not go through
};
const response = await this.v2PrivatePostAccountsAccountIdDeposits(this.extend(request, params));
//
// {
// "data": {
// "id": "67e0eaec-07d7-54c4-a72c-2e92826897df",
// "status": "created",
// "payment_method": {
// "id": "83562370-3e5c-51db-87da-752af5ab9559",
// "resource": "payment_method",
// "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
// },
// "transaction": {
// "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
// "resource": "transaction",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
// },
// "amount": {
// "amount": "10.00",
// "currency": "USD"
// },
// "subtotal": {
// "amount": "10.00",
// "currency": "USD"
// },
// "created_at": "2015-01-31T20:49:02Z",
// "updated_at": "2015-02-11T16:54:02-08:00",
// "resource": "deposit",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/deposits/67e0eaec-07d7-54c4-a72c-2e92826897df",
// "committed": true,
// "fee": {
// "amount": "0.00",
// "currency": "USD"
// },
// "payout_at": "2015-02-18T16:54:00-08:00"
// }
// }
//
// https://github.com/ccxt/ccxt/issues/25484
const data = this.safeDict2(response, 'data', 'transfer', {});
return this.parseTransaction(data);
}
/**
* @method
* @name coinbase#fetchDeposit
* @description fetch information on a deposit, fiat only, for crypto transactions use fetchLedger
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-deposits#show-deposit
* @param {string} id deposit id
* @param {string} [code] unified currency code
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.accountId] the id of the account that the funds were deposited into
* @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}
*/
async fetchDeposit(id, code = undefined, params = {}) {
await this.loadMarkets();
let accountId = this.safeString2(params, 'account_id', 'accountId');
params = this.omit(params, ['account_id', 'accountId']);
if (accountId === undefined) {
if (code === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchDeposit() requires an account_id (or accountId) parameter OR a currency code argument');
}
accountId = await this.findAccountId(code, params);
if (accountId === undefined) {
throw new errors.ExchangeError(this.id + ' fetchDeposit() could not find account id for ' + code);
}
}
const request = {
'account_id': accountId,
'deposit_id': id,
};
const response = await this.v2PrivateGetAccountsAccountIdDepositsDepositId(this.extend(request, params));
//
// {
// "data": {
// "id": "67e0eaec-07d7-54c4-a72c-2e92826897df",
// "status": "completed",
// "payment_method": {
// "id": "83562370-3e5c-51db-87da-752af5ab9559",
// "resource": "payment_method",
// "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
// },
// "transaction": {
// "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
// "resource": "transaction",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
// },
// "amount": {
// "amount": "10.00",
// "currency": "USD"
// },
// "subtotal": {
// "amount": "10.00",
// "currency": "USD"
// },
// "created_at": "2015-01-31T20:49:02Z",
// "updated_at": "2015-02-11T16:54:02-08:00",
// "resource": "deposit",
// "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/deposits/67e0eaec-07d7-54c4-a72c-2e92826897df",
// "committed": true,
// "fee": {
// "amount": "0.00",
// "currency": "USD"
// },
// "payout_at": "2015-02-18T16:54:00-08:00"
// }
// }
//
// https://github.com/ccxt/ccxt/issues/25484
const data = this.safeDict2(response, 'data', 'transfer', {});
return this.parseTransaction(data);
}
/**
* @method
* @name coinbase#fetchDepositMethodIds
* @description fetch the deposit id for a fiat currency associated with this account
* @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpaymentmethods
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an array of [deposit id structures]{@link https://docs.ccxt.com/#/?id=deposit-id-structure}
*/
async fetchDepositMethodIds(params = {}) {
await this.loadMarkets();
const response = await this.v3PrivateGetBrokeragePaymentMethods(params);
//
// {
// "payment_methods": [
// {
// "id": "21b39a5d-f7b46876fb2e",
// "type": "COINBASE_FIAT_ACCOUNT",
// "name": "CAD Wallet",
// "currency": "CAD",
// "verified": true,
// "allow_buy": false,
// "allow_sell": true,
// "allow_deposit": false,
// "allow_withdraw": false,
// "created_at": "2023-06-29T19:58:46Z",
// "updated_at": "2023-10-30T20:25:01Z"
// }
// ]
// }
//
const result = this.safeList(response, 'payment_methods', []);
return this.parseDepositMethodIds(result);
}
/**
* @method
* @name coinbase#fetchDepositMethodId
* @description fetch the deposit id for a fiat currency associated with this account
* @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getpaymentmethod
* @param {string} id the deposit payment method id
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [deposit id structure]{@link https://docs.ccxt.com/#/?id=deposit-id-structure}
*/
async fetchDepositMethodId(id, params = {}) {
await this.loadMarkets();
const request = {
'payment_method_id': id,
};
const response = await this.v3PrivateGetBrokeragePaymentMethodsPaymentMethodId(this.extend(request, params));
//
// {
// "payment_method": {
// "id": "21b39a5d-f7b46876fb2e",
// "type": "COINBASE_FIAT_ACCOUNT",
// "name": "CAD Wallet",
// "currency": "CAD",
// "verified": true,
// "allow_buy": false,
// "allow_sell": true,
// "allow_deposit": false,
// "allow_withdraw": false,
// "created_at": "2023-06-29T19:58:46Z",
// "updated_at": "2023-10-30T20:25:01Z"
// }
// }
//
const result = this.safeDict(response, 'payment_method', {});
return this.parseDepositMethodId(result);
}
parseDepositMethodIds(ids, params = {}) {
const result = [];
for (let i = 0; i < ids.length; i++) {
const id = this.extend(this.parseDepositMethodId(ids[i]), params);
result.push(id);
}
return result;
}
parseDepositMethodId(depositId) {
return {
'info': depositId,
'id': this.safeString(depositId, 'id'),
'currency': this.safeString(depositId, 'currency'),
'verified': this.safeBool(depositId, 'verified'),
'tag': this.safeString(depositId, 'name'),
};
}
/**
* @method
* @name coinbase#fetchConvertQuote
* @description fetch a quote for converting from one currency to another
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_createconvertquote
* @param {string} fromCode the currency that you want to sell and convert from
* @param {string} toCode the currency that you want to buy and convert into
* @param {float} [amount] how much you want to trade in units of the from currency
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {object} [params.trade_incentive_metadata] an object to fill in user incentive data
* @param {string} [params.trade_incentive_metadata.user_incentive_id] the id of the incentive
* @param {string} [params.trade_incentive_metadata.code_val] the code value of the incentive
* @returns {object} a [conversion structure]{@link https://docs.ccxt.com/#/?id=conversion-structure}
*/
async fetchConvertQuote(fromCode, toCode, amount = undefined, params = {}) {
await this.loadMarkets();
const request = {
'from_account': fromCode,
'to_account': toCode,
'amount': this.numberToString(amount),
};
const response = await this.v3PrivatePostBrokerageConvertQuote(this.extend(request, params));
const data = this.safeDict(response, 'trade', {});
return this.parseConversion(data);
}
/**
* @method
* @name coinbase#createConvertTrade
* @description convert from one currency to another
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_commitconverttrade
* @param {string} id the id of the trade that you want to make
* @param {string} fromCode the currency that you want to sell and convert from
* @param {string} toCode the currency that you want to buy and convert into
* @param {float} [amount] how much you want to trade in units of the from currency
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [conversion structure]{@link https://docs.ccxt.com/#/?id=conversion-structure}
*/
async createConvertTrade(id, fromCode, toCode, amount = undefined, params = {}) {
await this.loadMarkets();
const request = {
'trade_id': id,
'from_account': fromCode,
'to_account': toCode,
};
const response = await this.v3PrivatePostBrokerageConvertTradeTradeId(this.extend(request, params));
const data = this.safeDict(response, 'trade', {});
return this.parseConversion(data);
}
/**
* @method
* @name coinbase#fetchConvertTrade
* @description fetch the data for a conversion trade
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getconverttrade
* @param {string} id the id of the trade that you want to commit
* @param {string} code the unified currency code that was converted from
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {strng} params.toCode the unified currency code that was converted into
* @returns {object} a [conversion structure]{@link https://docs.ccxt.com/#/?id=conversion-structure}
*/
async fetchConvertTrade(id, code = undefined, params = {}) {
await this.loadMarkets();
if (code === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchConvertTrade() requires a code argument');
}
const toCode = this.safeString(params, 'toCode');
if (toCode === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchConvertTrade() requires a toCode parameter');
}
params = this.omit(params, 'toCode');
const request = {
'trade_id': id,
'from_account': code,
'to_account': toCode,
};
const response = await this.v3PrivateGetBrokerageConvertTradeTradeId(this.extend(request, params));
const data = this.safeDict(response, 'trade', {});
return this.parseConversion(data);
}
parseConversion(conversion, fromCurrency = undefined, toCurrency = undefined) {
const fromCoin = this.safeString(conversion, 'source_currency');
const fromCode = this.safeCurrencyCode(fromCoin, fromCurrency);
const to = this.safeString(conversion, 'target_currency');
const toCode = this.safeCurrencyCode(to, toCurrency);
const fromAmountStructure = this.safeDict(conversion, 'user_entered_amount');
const feeStructure = this.safeDict(conversion, 'total_fee');
const feeAmountStructure = this.safeDict(feeStructure, 'amount');
return {
'info': conversion,
'timestamp': undefined,
'datetime': undefined,
'id': this.safeString(conversion, 'id'),
'fromCurrency': fromCode,
'fromAmount': this.safeNumber(fromAmountStructure, 'value'),
'toCurrency': toCode,
'toAmount': undefined,
'price': undefined,
'fee': this.safeNumber(feeAmountStructure, 'value'),
};
}
/**
* @method
* @name coinbase#closePosition
* @description *futures only* closes open positions for a market
* @see https://docs.cdp.coinbase.com/coinbase-app/trade/reference/retailbrokerageapi_closeposition
* @param {string} symbol Unified CCXT market symbol
* @param {string} [side] not used by coinbase
* @param {object} [params] extra parameters specific to the coinbase api endpoint
* @param {string} params.clientOrderId *mandatory* the client order id of the position to close
* @param {float} [params.size] the size of the position to close, optional
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async closePosition(symbol, side = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const clientOrderId = this.safeString2(params, 'client_order_id', 'clientOrderId');
params = this.omit(params, 'clientOrderId');
const request = {
'product_id': market['id'],
};
if (clientOrderId === undefined) {
throw new errors.ArgumentsRequired(this.id + ' closePosition() requires a clientOrderId parameter');
}
request['client_order_id'] = clientOrderId;
const response = await this.v3PrivatePostBrokerageOrdersClosePosition(this.extend(request, params));
const order = this.safeDict(response, 'success_response', {});
return this.parseOrder(order);
}
/**
* @method
* @name coinbase#fetchPositions
* @description fetch all open positions
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmpositions
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getintxpositions
* @param {string[]} [symbols] list of unified market symbols
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.portfolio] the portfolio UUID to fetch positions for
* @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
*/
async fetchPositions(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
let market = undefined;
if (symbols !== undefined) {
market = this.market(symbols[0]);
}
let type = undefined;
[type, params] = this.handleMarketTypeAndParams('fetchPositions', market, params);
let response = undefined;
if (type === 'future') {
response = await this.v3PrivateGetBrokerageCfmPositions(params);
}
else {
let portfolio = undefined;
[portfolio, params] = this.handleOptionAndParams(params, 'fetchPositions', 'portfolio');
if (portfolio === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchPositions() requires a "portfolio" value in params (eg: dbcb91e7-2bc9-515), or set as exchange.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
}
const request = {
'portfolio_uuid': portfolio,
};
response = await this.v3PrivateGetBrokerageIntxPositionsPortfolioUuid(this.extend(request, params));
}
const positions = this.safeList(response, 'positions', []);
return this.parsePositions(positions, symbols);
}
/**
* @method
* @name coinbase#fetchPosition
* @description fetch data on a single open contract trade position
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getintxposition
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfcmposition
* @param {string} symbol unified market symbol of the market the position is held in, default is undefined
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.product_id] *futures only* the product id of the position to fetch, required for futures markets only
* @param {string} [params.portfolio] *perpetual/swaps only* the portfolio UUID to fetch the position for, required for perpetual/swaps markets only
* @returns {object} a [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
*/
async fetchPosition(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
let response = undefined;
if (market['future']) {
const productId = this.safeString(market, 'product_id');
if (productId === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchPosition() requires a "product_id" in params');
}
const futureRequest = {
'product_id': productId,
};
response = await this.v3PrivateGetBrokerageCfmPositionsProductId(this.extend(futureRequest, params));
}
else {
let portfolio = undefined;
[portfolio, params] = this.handleOptionAndParams(params, 'fetchPositions', 'portfolio');
if (portfolio === undefined) {
throw new errors.ArgumentsRequired(this.id + ' fetchPosition() requires a "portfolio" value in params (eg: dbcb91e7-2bc9-515), or set as exchange.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
}
const request = {
'symbol': market['id'],
'portfolio_uuid': portfolio,
};
response = await this.v3PrivateGetBrokerageIntxPositionsPortfolioUuidSymbol(this.extend(request, params));
}
const position = this.safeDict(response, 'position', {});
return this.parsePosition(position, market);
}
parsePosition(position, market = undefined) {
//
// {
// "product_id": "1r4njf84-0-0",
// "product_uuid": "cd34c18b-3665-4ed8-9305-3db277c49fc5",
// "symbol": "ADA-PERP-INTX",
// "vwap": {
// "value": "0.6171",
// "currency": "USDC"
// },
// "position_side": "POSITION_SIDE_LONG",
// "net_size": "20",
// "buy_order_size": "0",
// "sell_order_size": "0",
// "im_contribution": "0.1",
// "unrealized_pnl": {
// "value": "0.074",
// "currency": "USDC"
// },
// "mark_price": {
// "value": "0.6208",
// "currency": "USDC"
// },
// "liquidation_price": {
// "value": "0",
// "currency": "USDC"
// },
// "leverage": "1",
// "im_notional": {
// "value": "12.342",
// "currency": "USDC"
// },
// "mm_notional": {
// "value": "0.814572",
// "currency": "USDC"
// },
// "position_notional": {
// "value": "12.342",
// "currency": "USDC"
// },
// "margin_type": "MARGIN_TYPE_CROSS",
// "liquidation_buffer": "19.677828",
// "liquidation_percentage": "4689.3506",
// "portfolio_summary": {
// "portfolio_uuid": "018ebd63-1f6d-7c8e-ada9-0761c5a2235f",
// "collateral": "20.4184",
// "position_notional": "12.342",
// "open_position_notional": "12.342",
// "pending_fees": "0",
// "borrow": "0",
// "accrued_interest": "0",
// "rolling_debt": "0",
// "portfolio_initial_margin": "0.1",
// "portfolio_im_notional": {
// "value": "12.342",
// "currency": "USDC"
// },
// "portfolio_maintenance_margin": "0.066",
// "portfolio_mm_notional": {
// "value": "0.814572",
// "currency": "USDC"
// },
// "liquidation_percentage": "4689.3506",
// "liquidation_buffer": "19.677828",
// "margin_type": "MARGIN_TYPE_CROSS",
// "margin_flags": "PORTFOLIO_MARGIN_FLAGS_UNSPECIFIED",
// "liquidation_status": "PORTFOLIO_LIQUIDATION_STATUS_NOT_LIQUIDATING",
// "unrealized_pnl": {
// "value": "0.074",
// "currency": "USDC"
// },
// "buying_power": {
// "value": "8.1504",
// "currency": "USDC"
// },
// "total_balance": {
// "value": "20.4924",
// "currency": "USDC"
// },
// "max_withdrawal": {
// "value": "8.0764",
// "currency": "USDC"
// }
// },
// "entry_vwap": {
// "value": "0.6091",
// "currency": "USDC"
// }
// }
//
const marketId = this.safeString(position, 'symbol', '');
market = this.safeMarket(marketId, market);
const rawMargin = this.safeString(position, 'margin_type');
let marginMode = undefined;
if (rawMargin !== undefined) {
marginMode = (rawMargin === 'MARGIN_TYPE_CROSS') ? 'cross' : 'isolated';
}
const notionalObject = this.safeDict(position, 'position_notional', {});
const positionSide = this.safeString(position, 'position_side');
const side = (positionSide === 'POSITION_SIDE_LONG') ? 'long' : 'short';
const unrealizedPNLObject = this.safeDict(position, 'unrealized_pnl', {});
const liquidationPriceObject = this.safeDict(position, 'liquidation_price', {});
const liquidationPrice = this.safeNumber(liquidationPriceObject, 'value');
const vwapObject = this.safeDict(position, 'vwap', {});
const summaryObject = this.safeDict(position, 'portfolio_summary', {});
return this.safePosition({
'info': position,
'id': this.safeString(position, 'product_id'),
'symbol': this.safeSymbol(marketId, market),
'notional': this.safeNumber(notionalObject, 'value'),
'marginMode': marginMode,
'liquidationPrice': liquidationPrice,
'entryPrice': this.safeNumber(vwapObject, 'value'),
'unrealizedPnl': this.safeNumber(unrealizedPNLObject, 'value'),
'realizedPnl': undefined,
'percentage': undefined,
'contracts': this.safeNumber(position, 'net_size'),
'contractSize': market['contractSize'],
'markPrice': undefined,
'lastPrice': undefined,
'side': side,
'hedged': undefined,
'timestamp': undefined,
'datetime': undefined,
'lastUpdateTimestamp': undefined,
'maintenanceMargin': undefined,
'maintenanceMarginPercentage': undefined,
'collateral': this.safeNumber(summaryObject, 'collateral'),
'initialMargin': undefined,
'initialMarginPercentage': undefined,
'leverage': this.safeNumber(position, 'leverage'),
'marginRatio': undefined,
'stopLossPrice': undefined,
'takeProfitPrice': undefined,
});
}
/**
* @method
* @name coinbase#fetchTradingFees
* @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_gettransactionsummary/
* @description fetch the trading fees for multiple markets
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.type] 'spot' or 'swap'
* @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols
*/
async fetchTradingFees(params = {}) {
await this.loadMarkets();
let type = undefined;
[type, params] = this.handleMarketTypeAndParams('fetchTradingFees', undefined, params);
const isSpot = (type === 'spot');
const productType = isSpot ? 'SPOT' : 'FUTURE';
const request = {
'product_type': productType,
};
const response = await this.v3PrivateGetBrokerageTransactionSummary(this.extend(request, params));
//
// {
// total_volume: '0',
// total_fees: '0',
// fee_tier: {
// pricing_tier: 'Advanced 1',
// usd_from: '0',
// usd_to: '1000',
// taker_fee_rate: '0.008',
// maker_fee_rate: '0.006',
// aop_from: '',
// aop_to: ''
// },
// margin_rate: null,
// goods_and_services_tax: null,
// advanced_trade_only_volume: '0',
// advanced_trade_only_fees: '0',
// coinbase_pro_volume: '0',
// coinbase_pro_fees: '0',
// total_balance: '',
// has_promo_fee: false
// }
//
const data = this.safeDict(response, 'fee_tier', {});
const taker_fee = this.safeNumber(data, 'taker_fee_rate');
const marker_fee = this.safeNumber(data, 'maker_fee_rate');
const result = {};
for (let i = 0; i < this.symbols.length; i++) {
const symbol = this.symbols[i];
const market = this.market(symbol);
if ((isSpot && market['spot']) || (!isSpot && !market['spot'])) {
result[symbol] = {
'info': response,
'symbol': symbol,
'maker': taker_fee,
'taker': marker_fee,
'percentage': true,
};
}
}
return result;
}
/**
* @method
* @name coinbase#fetchPortfolioDetails
* @description Fetch details for a specific portfolio by UUID
* @see https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getportfolios
* @param {string} portfolioUuid The unique identifier of the portfolio to fetch
* @param {Dict} [params] Extra parameters specific to the exchange API endpoint
* @returns {any[]} An account structure <https://docs.ccxt.com/#/?id=account-structure>
*/
async fetchPortfolioDetails(portfolioUuid, params = {}) {
await this.loadMarkets();
const request = {
'portfolio_uuid': portfolioUuid,
};
const response = await this.v3PrivateGetBrokeragePortfoliosPortfolioUuid(this.extend(request, params));
const result = this.parsePortfolioDetails(response);
return result;
}
parsePortfolioDetails(portfolioData) {
const breakdown = portfolioData['breakdown'];
const portfolioInfo = this.safeDict(breakdown, 'portfolio', {});
const portfolioName = this.safeString(portfolioInfo, 'name', 'Unknown');
const portfolioUuid = this.safeString(portfolioInfo, 'uuid', '');
const spotPositions = this.safeList(breakdown, 'spot_positions', []);
const parsedPositions = [];
for (let i = 0; i < spotPositions.length; i++) {
const position = spotPositions[i];
const currencyCode = this.safeString(position, 'asset', 'Unknown');
const availableBalanceStr = this.safeString(position, 'available_to_trade_fiat', '0');
const availableBalance = this.parseNumber(availableBalanceStr);
const totalBalanceFiatStr = this.safeString(position, 'total_balance_fiat', '0');
const totalBalanceFiat = this.parseNumber(totalBalanceFiatStr);
const holdAmount = totalBalanceFiat - availableBalance;
const costBasisDict = this.safeDict(position, 'cost_basis', {});
const costBasisStr = this.safeString(costBasisDict, 'value', '0');
const averageEntryPriceDict = this.safeDict(position, 'average_entry_price', {});
const averageEntryPriceStr = this.safeString(averageEntryPriceDict, 'value', '0');
const positionData = {
'currency': currencyCode,
'available_balance': availableBalance,
'hold_amount': holdAmount > 0 ? holdAmount : 0,
'wallet_name': portfolioName,
'account_id': portfolioUuid,
'account_uuid': this.safeString(position, 'account_uuid', ''),
'total_balance_fiat': totalBalanceFiat,
'total_balance_crypto': this.parseNumber(this.safeString(position, 'total_balance_crypto', '0')),
'available_to_trade_fiat': this.parseNumber(this.safeString(position, 'available_to_trade_fiat', '0')),
'available_to_trade_crypto': this.parseNumber(this.safeString(position, 'available_to_trade_crypto', '0')),
'available_to_transfer_fiat': this.parseNumber(this.safeString(position, 'available_to_transfer_fiat', '0')),
'available_to_transfer_crypto': this.parseNumber(this.safeString(position, 'available_to_trade_crypto', '0')),
'allocation': this.parseNumber(this.safeString(position, 'allocation', '0')),
'cost_basis': this.parseNumber(costBasisStr),
'cost_basis_currency': this.safeString(costBasisDict, 'currency', 'USD'),
'is_cash': this.safeBool(position, 'is_cash', false),
'average_entry_price': this.parseNumber(averageEntryPriceStr),
'average_entry_price_currency': this.safeString(averageEntryPriceDict, 'currency', 'USD'),
'asset_uuid': this.safeString(position, 'asset_uuid', ''),
'unrealized_pnl': this.parseNumber(this.safeString(position, 'unrealized_pnl', '0')),
'asset_color': this.safeString(position, 'asset_color', ''),
'account_type': this.safeString(position, 'account_type', ''),
};
parsedPositions.push(positionData);
}
return parsedPositions;
}
createAuthToken(seconds, method = undefined, url = undefined) {
// it may not work for v2
let uri = undefined;
if (url !== undefined) {
uri = method + ' ' + url.replace('https://', '');
const quesPos = uri.indexOf('?');
// Due to we use mb_strpos, quesPos could be false in php. In that case, the quesPos >= 0 is true
// Also it's not possible that the question mark is first character, only check > 0 here.
if (quesPos > 0) {
uri = uri.slice(0, quesPos);
}
}
const nonce = this.randomBytes(16);
const request = {
'aud': ['retail_rest_api_proxy'],
'iss': 'coinbase-cloud',
'nbf': seconds,
'exp': seconds + 120,
'sub': this.apiKey,
'iat': seconds,
};
if (uri !== undefined) {
request['uri'] = uri;
}
const token = rsa.jwt(request, this.encode(this.secret), sha256.sha256, false, { 'kid': this.apiKey, 'nonce': nonce, 'alg': 'ES256' });
return token;
}
nonce() {
return this.milliseconds() - this.options['timeDifference'];
}
sign(path, api = [], method = 'GET', params = {}, headers = undefined, body = undefined) {
const version = api[0];
const signed = api[1] === 'private';
const isV3 = version === 'v3';
const pathPart = (isV3) ? 'api/v3' : 'v2';
let fullPath = '/' + pathPart + '/' + this.implodeParams(path, params);
const query = this.omit(params, this.extractParams(path));
const savedPath = fullPath;
if (method === 'GET') {
if (Object.keys(query).length) {
fullPath += '?' + this.urlencodeWithArrayRepeat(query);
}
}
const url = this.urls['api']['rest'] + fullPath;
if (signed) {
const authorization = this.safeString(this.headers, 'Authorization');
let authorizationString = undefined;
if (authorization !== undefined) {
authorizationString = authorization;
}
else if (this.token && !this.checkRequiredCredentials(false)) {
authorizationString = 'Bearer ' + this.token;
}
else {
this.checkRequiredCredentials();
const seconds = this.seconds();
let payload = '';
if (method !== 'GET') {
if (Object.keys(query).length) {
body = this.json(query);
payload = body;
}
}
else {
if (!isV3) {
if (Object.keys(query).length) {
payload += '?' + this.urlencode(query);
}
}
}
// v3: 'GET' doesn't need payload in the signature. inside url is enough
// https://docs.cloud.coinbase.com/advanced-trade/docs/auth#example-request
// v2: 'GET' require payload in the signature
// https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-key-authentication
const isCloudAPiKey = (this.apiKey.indexOf('organizations/') >= 0) || (this.secret.startsWith('-----BEGIN'));
if (isCloudAPiKey) {
if (this.apiKey.startsWith('-----BEGIN')) {
throw new errors.ArgumentsRequired(this.id + ' apiKey should contain the name (eg: organizations/3b910e93....) and not the public key');
}
// // it may not work for v2
// let uri = method + ' ' + url.replace ('https://', '');
// const quesPos = uri.indexOf ('?');
// // Due to we use mb_strpos, quesPos could be false in php. In that case, the quesPos >= 0 is true
// // Also it's not possible that the question mark is first character, only check > 0 here.
// if (quesPos > 0) {
// uri = uri.slice (0, quesPos);
// }
// const nonce = this.randomBytes (16);
// const request: Dict = {
// 'aud': [ 'retail_rest_api_proxy' ],
// 'iss': 'coinbase-cloud',
// 'nbf': seconds,
// 'exp': seconds + 120,
// 'sub': this.apiKey,
// 'uri': uri,
// 'iat': seconds,
// };
const token = this.createAuthToken(seconds, method, url);
// const token = jwt (request, this.encode (this.secret), sha256, false, { 'kid': this.apiKey, 'nonce': nonce, 'alg': 'ES256' });
authorizationString = 'Bearer ' + token;
}
else {
const nonce = this.nonce();
const timestamp = this.parseToInt(nonce / 1000);
const timestampString = timestamp.toString();
const auth = timestampString + method + savedPath + payload;
const signature = this.hmac(this.encode(auth), this.encode(this.secret), sha256.sha256);
headers = {
'CB-ACCESS-KEY': this.apiKey,
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestampString,
'Content-Type': 'application/json',
};
}
}
if (authorizationString !== undefined) {
headers = {
'Authorization': authorizationString,
'Content-Type': 'application/json',
};
if (method !== 'GET') {
if (Object.keys(query).length) {
body = this.json(query);
}
}
}
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors(code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return undefined; // fallback to default error handler
}
const feedback = this.id + ' ' + body;
//
// {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
//
// or
//
// {
// "errors": [
// {
// "id": "not_found",
// "message": "Not found"
// }
// ]
// }
// or
// {
// "success": false,
// "error_response": {
// "error": "UNKNOWN_FAILURE_REASON",
// "message": "",
// "error_details": "",
// "preview_failure_reason": "PREVIEW_STOP_PRICE_ABOVE_LAST_TRADE_PRICE"
// },
// "order_configuration": {
// "stop_limit_stop_limit_gtc": {
// "base_size": "0.0001",
// "limit_price": "2000",
// "stop_price": "2005",
// "stop_direction": "STOP_DIRECTION_STOP_DOWN",
// "reduce_only": false
// }
// }
// }
//
let errorCode = this.safeString(response, 'error');
if (errorCode !== undefined) {
const errorMessage = this.safeString2(response, 'error_description', 'error');
this.throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback);
this.throwBroadlyMatchedException(this.exceptions['broad'], errorMessage, feedback);
throw new errors.ExchangeError(feedback);
}
const errorResponse = this.safeDict(response, 'error_response');
if (errorResponse !== undefined) {
const errorMessageInner = this.safeString2(errorResponse, 'preview_failure_reason', 'preview_failure_reason');
this.throwExactlyMatchedException(this.exceptions['exact'], errorMessageInner, feedback);
this.throwBroadlyMatchedException(this.exceptions['broad'], errorMessageInner, feedback);
throw new errors.ExchangeError(feedback);
}
const errors$1 = this.safeList(response, 'errors');
if (errors$1 !== undefined) {
if (Array.isArray(errors$1)) {
const numErrors = errors$1.length;
if (numErrors > 0) {
errorCode = this.safeString(errors$1[0], 'id');
const errorMessage = this.safeString(errors$1[0], 'message');
if (errorCode !== undefined) {
this.throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback);
this.throwBroadlyMatchedException(this.exceptions['broad'], errorMessage, feedback);
throw new errors.ExchangeError(feedback);
}
}
}
}
const advancedTrade = this.options['advanced'];
if (!('data' in response) && (!advancedTrade)) {
throw new errors.ExchangeError(this.id + ' failed due to a malformed response ' + this.json(response));
}
return undefined;
}
}
module.exports = coinbase;