ccxt
Version:
1,019 lines (1,017 loc) • 159 kB
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import { keccak_256 as keccak } from '@noble/hashes/sha3.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import Exchange from './abstract/grvt.js';
import { ExchangeError, ArgumentsRequired, InsufficientFunds, InvalidOrder, InvalidNonce, AuthenticationError, RateLimitExceeded, PermissionDenied, BadRequest, BadSymbol, OperationFailed, OperationRejected } from './base/errors.js';
import { Precise } from './base/Precise.js';
import { ecdsa } from './base/functions/crypto.js';
import { TICK_SIZE } from './base/functions/number.js';
// ---------------------------------------------------------------------------
/**
* @class grvt
* @augments Exchange
*/
export default class grvt extends Exchange {
describe() {
const rlOthers = 40;
const rlOrders = 20;
return this.deepExtend(super.describe(), {
'id': 'grvt',
'name': 'GRVT',
'countries': ['SG'], // Singapore
'rateLimit': 10,
'certified': false,
'version': 'v1',
'dex': true,
'pro': true,
'has': {
'CORS': undefined,
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'cancelAllOrders': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchCurrencies': true,
'fetchDeposits': true,
'fetchFundingHistory': true,
'fetchFundingRateHistory': true,
'fetchLeverages': true,
'fetchMarginModes': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchPositions': true,
'fetchTicker': true,
'fetchTrades': true,
'fetchTransfers': true,
'fetchWithdrawals': true,
'setLeverage': true,
'signIn': true,
'transfer': true,
'withdraw': true,
},
'timeframes': {
'1m': 'CI_1_M',
'3m': 'CI_3_M',
'5m': 'CI_5_M',
'15m': 'CI_15_M',
'30m': 'CI_30_M',
'1h': 'CI_1_H',
'2h': 'CI_2_H',
'4h': 'CI_4_H',
'6h': 'CI_6_H',
'8h': 'CI_8_H',
'12h': 'CI_12_H',
'1d': 'CI_1_D',
'3d': 'CI_3_D',
'5d': 'CI_5_D',
'1w': 'CI_1_W',
'2w': 'CI_2_W',
'3w': 'CI_3_W',
'4w': 'CI_4_W',
},
'urls': {
'logo': 'https://github.com/user-attachments/assets/cff0d37c-e594-40cb-88b3-90650ddadc18',
'api': {
'privateEdge': 'https://edge.grvt.io/',
'privateTrading': 'https://trades.grvt.io/',
'publicMarket': 'https://market-data.grvt.io/',
},
'test': {
'privateEdge': 'https://edge.testnet.grvt.io/',
'privateTrading': 'https://trades.testnet.grvt.io/',
'publicMarket': 'https://market-data.testnet.grvt.io/',
},
'www': 'https://grvt.io',
'referral': 'https://grvt.io/?ref=WBLS9D1',
'doc': [
'https://api-docs.grvt.io/',
],
'fees': 'https://help.grvt.io/en/articles/9614699-how-does-grvt-s-fee-model-work',
},
'api': {
// RL : https://help.grvt.io/en/articles/9636566-what-are-the-rate-limitations-on-grvt
'privateEdge': {
'post': {
'auth/api_key/login': 100,
'auth/wallet/login': 100,
},
},
'publicMarket': {
'post': {
'full/v1/instrument': 4,
'full/v1/all_instruments': 4,
'full/v1/instruments': 4,
'full/v1/currency': 12,
'full/v1/margin_rules': 12,
'full/v1/mini': 4,
'full/v1/ticker': 4,
'full/v1/book': 12,
'full/v1/trade': 12,
'full/v1/trade_history': 12,
'full/v1/kline': 12,
'full/v1/funding': 12,
},
},
'privateTrading': {
'post': {
'full/v1/create_order': 5,
'full/v1/cancel_order': 5,
'full/v1/cancel_on_disconnect': 100,
'full/v1/cancel_all_orders': 50,
'full/v1/order': rlOrders,
'full/v1/order_history': rlOrders,
'full/v1/open_orders': rlOrders,
'full/v1/fill_history': rlOrders,
'full/v1/positions': rlOrders,
'full/v1/funding_payment_history': rlOthers,
'full/v1/get_sub_accounts': rlOthers,
'full/v1/account_summary': rlOthers,
'full/v1/account_history': rlOthers,
'full/v1/aggregated_account_summary': rlOthers,
'full/v1/funding_account_summary': rlOthers,
'full/v1/transfer': 100,
'full/v1/deposit_history': 100,
'full/v1/transfer_history': 100,
'full/v1/withdrawal': 100,
'full/v1/withdrawal_history': 100,
'full/v1/add_position_margin': rlOthers, // addMargin
'full/v1/get_position_margin_limits': rlOthers,
'full/v1/set_position_config': rlOthers, // setPositionMode/setMarginMode
'full/v1/set_initial_leverage': rlOthers,
'full/v1/get_all_initial_leverage': rlOthers,
'full/v1/set_derisk_mm_ratio': rlOthers,
'full/v1/vault_burn_tokens': rlOthers,
'full/v1/vault_invest': rlOthers,
'full/v1/vault_investor_summary': rlOthers,
'full/v1/vault_redeem': rlOthers,
'full/v1/vault_redeem_cancel': rlOthers,
'full/v1/vault_view_redemption_queue': rlOthers,
'full/v1/vault_manager_investor_history': rlOthers,
'full/v1/authorize_builder': rlOthers, // https://pastebin(dot)com/0Mb8cFhN
'full/v1/get_authorized_builders': rlOthers,
'full/v1/builder_fill_history': rlOthers,
},
},
},
// exchange-specific options
'options': {
'accountId': undefined, // needs to be set manually by user
// https://api.rhino.fi/bridge/configs
'networks': {
'ARBONE': '42161',
'AVAXC': '43114',
'BASE': '8453',
'BSC': '56',
'ETH': '1',
'ERC20': '1',
'OP': '10',
'SOL': '900',
'TRX': '728126428',
'ZKSYNCERA': '324',
'KAIA': '8217',
},
'networksById': {
'1': 'ERC20',
},
'builderFee': true,
'builder': '0x21d2a053495994b1132a38cd1171acec40c6741e',
'builderRate': 0.01,
},
'precisionMode': TICK_SIZE,
'features': {
'default': {
'sandbox': true,
'createOrder': {
'marginMode': false,
'triggerPrice': true,
'triggerPriceType': {
'last': true,
'mark': true,
'index': true,
'median': true, // mid
},
'triggerDirection': true,
'stopLossPrice': true,
'takeProfitPrice': true,
'attachedStopLossTakeProfit': undefined,
'timeInForce': {
'IOC': true,
'FOK': true,
'PO': true,
'GTD': false,
},
'hedged': false,
'leverage': false,
'marketBuyRequiresPrice': false,
'marketBuyByCost': false,
'selfTradePrevention': false,
'trailing': false,
'iceberg': false,
},
'createOrders': undefined,
'fetchMyTrades': {
'marginMode': false,
'limit': 1000,
'daysBack': 1000,
'untilDays': 1000,
'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': 1000,
'daysBack': undefined,
'untilDays': undefined,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchClosedOrders': undefined,
'fetchOHLCV': {
'limit': 1000,
},
},
'spot': undefined,
'swap': {
'linear': {
'extends': 'default',
},
'inverse': undefined,
},
'future': {
'linear': undefined,
'inverse': undefined,
},
},
'requiredCredentials': {
'privateKey': true,
'apiKey': false,
'secret': false,
},
'quoteJsonNumbers': false, // needed for some endpoints (todo: specify in implementations)
'exceptions': {
'exact': {
'1000': AuthenticationError, // "You need to authenticate prior to using this functionality"
'1001': PermissionDenied, // "You are not authorized to access this functionality"
'1002': OperationFailed, // "Internal Server Error"
'1003': BadRequest, // "Request could not be processed due to malformed syntax"
'1004': OperationRejected, // "Data Not Found"
'1005': OperationFailed, // "Unknown Error"
'1006': RateLimitExceeded, // "You have surpassed the allocated rate limit for your tier"
'1008': PermissionDenied, // "Your IP has not been whitelisted for access"
'1009': OperationRejected, // "We are temporarily deactivating this API endpoint, please try again later"
'1012': BadRequest, // "Invalid signature chain ID"
'1400': PermissionDenied, // "Signer does not have trade permission"
'2000': PermissionDenied, // "Signature is from an unauthorized signer"
'2001': InvalidNonce, // "Signature has expired"
'2002': BadRequest, // "Signature does not match payload"
'2003': PermissionDenied, // "Order sub account does not match logged in user"
'2004': InvalidNonce, // "Signature is from an expired session key"
'2005': BadRequest, // "Signature V must be 27/28"
'2006': BadRequest, // "Signature R/S must have exactly 64 characters long without 0x prefix"
'2007': BadRequest, // "Signature S must be in the lower half of the curve"
'2008': BadRequest, // "Signature exceeds maximum allowed duration."
'2010': InvalidOrder, // "Order ID should be empty when creating an order"
'2011': InvalidOrder, // "Client Order ID should be supplied when creating an order"
'2012': InvalidOrder, // "Client Order ID overlaps with existing active order"
'2020': InvalidOrder, // "Market Order must always be supplied without a limit price"
'2021': InvalidOrder, // "Limit Order must always be supplied with a limit price"
'2030': InvalidOrder, // "Orderbook Orders must have a TimeInForce of GTT/IOC/FOK"
'2031': InvalidOrder, // "RFQ Orders must have a TimeInForce of GTT/AON/IOC/FOK"
'2032': InvalidOrder, // "Post Only can only be set to true for GTT/AON orders"
'2040': InvalidOrder, // "Order must contain at least one leg"
'2041': InvalidOrder, // "Order Legs must be sorted by Derivative.Instrument/Underlying/BaseCurrency/Expiration/StrikePrice"
'2042': InvalidOrder, // "Orderbook Orders must contain only one leg"
'2050': InvalidOrder, // "Order state must be empty upon creation"
'2051': InvalidOrder, // "Order execution metadata must be empty upon creation"
'2060': BadSymbol, // "Order Legs contain one or more inactive derivative"
'2061': BadSymbol, // "Unsupported Instrument Requested"
'2062': InvalidOrder, // "Order size smaller than min size"
'2063': InvalidOrder, // "Order size smaller than min block size in block trade venue"
'2064': InvalidOrder, // "Invalid limit price tick"
'2065': InvalidOrder, // "Order size too granular"
'2070': InvalidOrder, // "Liquidation Order is not supported"
'2080': InsufficientFunds, // "Insufficient margin to create order"
'2081': OperationRejected, // "Order Fill would result in exceeding maximum position size"
'2082': InvalidOrder, // "Pre-order check failed"
'2083': OperationRejected, // "Order Fill would result in exceeding maximum position size under current configurable leverage tier"
'2090': RateLimitExceeded, // "Max open orders exceeded"
'2100': BadRequest, // "Invalid initial leverage"
'2101': BadRequest, // "Vaults cannot configure leverage"
'2102': OperationRejected, // "Margin type change failed, has open position for this instrument"
'2103': OperationRejected, // "Margin type change failed, has open orders for this instrument"
'2104': BadRequest, // "Margin type not supported"
'2105': BadRequest, // "Margin type change failed"
'2107': BadRequest, // "Attempted to set leverage below minimum"
'2108': BadRequest, // "Attempted to set leverage above maximum"
'2110': InvalidOrder, // "Invalid trigger by"
'2111': InvalidOrder, // "Unsupported trigger by"
'2112': InvalidOrder, // "Invalid trigger order"
'2113': InvalidOrder, // "Trigger price must be non-zero"
'2114': InvalidOrder, // "Invalid position linked TPSL orders, position linked TPSL must be a reduce-only order"
'2115': InvalidOrder, // "Invalid position linked TPSL orders, position linked TPSL must not have smaller size than the position"
'2116': InvalidOrder, // "Position linked TPSL order for this asset already exists"
'2117': InvalidOrder, // "Position linked TPSL orders must be created from web or mobile clients"
'2300': OperationRejected, // "Order cancel time-to-live settings currently disabled."
'2301': OperationRejected, // "Order cancel time-to-live exceeds maximum allowed value."
'2400': OperationRejected, // "Reduce only order with no position"
'2401': OperationRejected, // "Reduce only order must not increase position size"
'2402': OperationRejected, // "Reduce only order size exceeds maximum allowed value"
'3000': BadSymbol, // "Instrument is invalid"
'3004': OperationRejected, // "Instrument does not have a valid maintenance margin configuration"
'3005': OperationRejected, // "Instrument's underlying currency does not have a valid balance decimal configuration"
'3006': OperationRejected, // "Instrument's quote currency does not have a valid balance decimal configuration"
'3021': BadRequest, // "Either order ID or client order ID must be supplied"
'3031': BadRequest, // "Depth is invalid"
'4000': InsufficientFunds, // "Insufficient balance to complete transfer"
'4002': OperationFailed, // "Transfer failed with an unrefined failure reason, please report to GRVT"
'4010': OperationRejected, // "This wallet is not supported. Please try another wallet."
'5000': OperationRejected, // "Transfer Metadata does not match the expected structure."
'5001': OperationRejected, // "Transfer Provider does not match the expected provider."
'5002': OperationRejected, // "Direction of the transfer does not match the expected direction."
'5003': OperationRejected, // "Endpoint account ID is invalid."
'5004': OperationRejected, // "Funding account does not exist in our system."
'5005': OperationRejected, // "Invalid ChainID for the transfer request."
'6000': OperationRejected, // "Countdown time is bigger than 300s supported"
'6100': OperationRejected, // "Derisk MM Ratio is out of range"
'7000': OperationRejected, // "Vault ID provided is invalid and does not belong to any vault"
'7001': InsufficientFunds, // "Vault does not have sufficient LP token balance"
'7002': OperationFailed, // "User has an ongoing redemption"
'7003': OperationRejected, // "This vault has been delisted/closed."
'7004': OperationRejected, // "This investment would cause the vault to exceed its valuation cap."
'7005': InsufficientFunds, // "You are attempting to burn more vault tokens than you own."
'7006': OperationFailed, // "You are attempting to burn vault tokens whilst having an active redemption request."
'7007': PermissionDenied, // "The investor is not an LP for this vault."
'7100': OperationFailed, // "Unknown transaction type"
'7101': OperationRejected, // "Transfer account not found"
'7102': OperationRejected, // "Transfer sub-account not found"
'7103': OperationRejected, // "Charged trading fee below the config minimum"
'7201': OperationRejected, // "Attempted to create a limit order at a price outside of asset's price protection band."
'7450': OperationRejected, // "Add margin failed"
'7451': OperationRejected, // "Add margin to empty position"
'7452': OperationRejected, // "Add margin to non isolated position"
'7453': OperationRejected, // "Max addable amount exceeded"
'7454': OperationRejected, // "Max removable amount exceeded"
'7455': OperationRejected, // "Not isolated margin position"
'7500': OperationRejected, // "Builder Fee exceeds the allowed program limit."
'7501': BadRequest, // "Builder Fee can't be negative."
'7502': OperationRejected, // "Builder Account does not exist."
'7503': OperationRejected, // "Builder is already authorized for this account with the given fee."
'7504': OperationRejected, // "Builder is not authorized for the specified user.","status":400
},
'broad': {},
},
});
}
eipDefinitions() {
return {
'EIP712_ORDER_TYPE': {
'Order': [
{ 'name': 'subAccountID', 'type': 'uint64' },
{ 'name': 'isMarket', 'type': 'bool' },
{ 'name': 'timeInForce', 'type': 'uint8' },
{ 'name': 'postOnly', 'type': 'bool' },
{ 'name': 'reduceOnly', 'type': 'bool' },
{ 'name': 'legs', 'type': 'OrderLeg[]' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
'OrderLeg': [
{ 'name': 'assetID', 'type': 'uint256' },
{ 'name': 'contractSize', 'type': 'uint64' },
{ 'name': 'limitPrice', 'type': 'uint64' },
{ 'name': 'isBuyingContract', 'type': 'bool' },
],
},
'EIP712_ORDER_WITH_BUILDER_TYPE': {
'OrderWithBuilderFee': [
{ 'name': 'subAccountID', 'type': 'uint64' },
{ 'name': 'isMarket', 'type': 'bool' },
{ 'name': 'timeInForce', 'type': 'uint8' },
{ 'name': 'postOnly', 'type': 'bool' },
{ 'name': 'reduceOnly', 'type': 'bool' },
{ 'name': 'legs', 'type': 'OrderLeg[]' },
{ 'name': 'builder', 'type': 'address' },
{ 'name': 'builderFee', 'type': 'uint32' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
'OrderLeg': [
{ 'name': 'assetID', 'type': 'uint256' },
{ 'name': 'contractSize', 'type': 'uint64' },
{ 'name': 'limitPrice', 'type': 'uint64' },
{ 'name': 'isBuyingContract', 'type': 'bool' },
],
},
'EIP712_TRANSFER_TYPE': {
'Transfer': [
{ 'name': 'fromAccount', 'type': 'address' },
{ 'name': 'fromSubAccount', 'type': 'uint64' },
{ 'name': 'toAccount', 'type': 'address' },
{ 'name': 'toSubAccount', 'type': 'uint64' },
{ 'name': 'tokenCurrency', 'type': 'uint8' },
{ 'name': 'numTokens', 'type': 'uint64' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
},
'EIP712_WITHDRAWAL_TYPE': {
'Withdrawal': [
{ 'name': 'fromAccount', 'type': 'address' },
{ 'name': 'toEthAddress', 'type': 'address' },
{ 'name': 'tokenCurrency', 'type': 'uint8' },
{ 'name': 'numTokens', 'type': 'uint64' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
},
'EIP712_BUILDER_APPROVAL_TYPE': {
'AuthorizeBuilder': [
{ 'name': 'mainAccountID', 'type': 'address' },
{ 'name': 'builderAccountID', 'type': 'address' },
{ 'name': 'maxFutureFeeRate', 'type': 'uint32' },
{ 'name': 'maxSpotFeeRate', 'type': 'uint32' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
},
'EIP712_WALLETLOGIN_TYPE': {
'WalletLogin': [
{ 'name': 'signer', 'type': 'address' },
{ 'name': 'nonce', 'type': 'uint32' },
{ 'name': 'expiration', 'type': 'int64' },
],
},
};
}
usesPrivateKey() {
const privateKeyDefined = this.privateKey !== undefined && this.privateKey !== '';
const apiKeyDefined = this.apiKey !== undefined && this.apiKey !== '';
if (privateKeyDefined && apiKeyDefined) {
throw new ExchangeError('You should provide either "privateKey" or "apikey & secret"');
}
return privateKeyDefined;
}
/**
* @method
* @name grvt#signIn
* @description sign in, must be called prior to using other authenticated methods
* @see https://api-docs.grvt.io/#authentication
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns response from exchange
*/
async signIn(params = {}) {
// if (this.usesPrivateKey ()) {
// await this.signInWithPrivateKey (params);
// await this.initializeClient (params);
// } else {
// await this.signInWithApiKey (params);
// }
if (this.privateKey === undefined || this.privateKey === '') {
throw new PermissionDenied('Private key is required for this operation. If you used joined GRVT through email registration instead of Web3 wallet, then read: https://github.com/ccxt/ccxt/wiki/FAQ#how-to-use-the-grvt-exchange-in-ccxt');
}
await this.signInWithPrivateKey(params);
await this.initializeClient(params);
await this.loadAccountInfos();
return true;
}
async signInWithApiKey(params = {}) {
const now = this.milliseconds();
// expires in 24 hours as CS suggested
const expires = this.safeInteger(this.options, 'signInExpiration', 0);
// if previous sign-in not expired (give 10 seconds margin)
if (expires !== undefined && expires > now + 10000) {
return {};
}
const request = {
'api_key': this.apiKey,
};
const response = await this.privateEdgePostAuthApiKeyLogin(this.extend(request, params));
//
// {
// "location": "",
// "status": "success"
// }
//
this.options['signInExpiration'] = now + 86400000; // 24 hours
return response;
}
async signInWithPrivateKey(params = {}) {
this.checkRequiredCredentials();
const now = this.milliseconds();
// expires in 24 hours as CS suggested
const expires = this.safeInteger(this.options, 'signInExpiration', 0);
// if previous sign-in not expired (give 10 seconds margin)
if (expires !== undefined && expires > now + 10000) {
return {};
}
const walletAddress = this.ethGetAddressFromPrivateKey(this.privateKey);
let request = {
'address': walletAddress,
'signature': this.defaultSignature(),
};
request = this.createSignedRequest(request, 'EIP712_WALLETLOGIN_TYPE');
const response = await this.privateEdgePostAuthWalletLogin(this.extend(request, params));
//
// {
// "location": "",
// "status": "success"
// }
//
this.options['signInExpiration'] = now + 86400000; // 24 hours
return response;
}
async initializeClient(params = {}) {
const builderFee = this.safeBool(params, 'builderFee', this.safeBool(this.options, 'builderFee', true)); // we shouldn't omit here
if (!builderFee) {
return false; // skip if builder fee is not enabled
}
const approvedBuilderFee = this.safeBool(this.options, 'approvedBuilderFee', false);
if (approvedBuilderFee) {
return true; // skip if builder fee is already approved
}
const results = await Promise.all([this.privateTradingPostFullV1GetAuthorizedBuilders(), this.loadAccountInfos()]);
//
// {
// "results": [{
// "builder_account_id": "GRVT_MAIN_ACCOUNT_ID_HERE",
// "max_futures_fee_rate": 0.001,
// "max_spot_fee_rate": 0.0001
// }]
// }
//
const currentBuilders = results[0];
const approvedBuilder = this.safeList(currentBuilders, 'results', []);
const length = approvedBuilder.length;
let found = false;
for (let i = 0; i < length; i++) {
const builderInfo = this.safeDict(approvedBuilder, i, {});
const builderAccountId = this.safeString(builderInfo, 'builder_account_id');
if (builderAccountId === this.safeString(this.options, 'builder')) {
found = true;
break;
}
}
if (found) {
this.options['approvedBuilderFee'] = true;
}
else {
try {
const defaultFromAccountId = this.safeString(this.options, 'userMainAccountId'); // this.ethGetAddressFromPrivateKey (this.secret); // this.safeString (this.options, 'userMainAccountId');
let request = {
'main_account_id': defaultFromAccountId,
'builder_account_id': this.safeString(this.options, 'builder'),
'max_futures_fee_rate': this.safeString(this.options, 'builderRate'),
'max_spot_fee_rate': this.safeString(this.options, 'builderRate'),
'signature': this.defaultSignature(),
};
request = this.createSignedRequest(request, 'EIP712_BUILDER_APPROVAL_TYPE');
const authResponse = await this.privateTradingPostFullV1AuthorizeBuilder(this.extend(request, params));
//
// {
// "result": {
// "ack": "true",
// "tx_id":"0"
// }
// }
//
const authResult = this.safeDict(authResponse, 'result');
const ack = this.safeBool(authResult, 'ack');
if (!ack) {
throw new ExchangeError('Builder authorization failed, ' + this.json(authResponse));
}
this.options['approvedBuilderFee'] = true;
}
catch (e) {
this.options['builderFee'] = false; // disable builder fee if an error occurs
}
}
return undefined; // just c#
}
/**
* @method
* @name grvt#fetchMarkets
* @description retrieves data on all markets
* @see https://api-docs.grvt.io/market_data_api/#get-instrument-prod
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchMarkets(params = {}) {
const marketsPromise = this.publicMarketPostFullV1AllInstruments(params);
//
// {
// "result": [
// {
// "instrument": "AAVE_USDT_Perp",
// "instrument_hash": "0x032201",
// "base": "AAVE",
// "quote": "USDT",
// "kind": "PERPETUAL",
// "venues": [
// "ORDERBOOK",
// "RFQ"
// ],
// "settlement_period": "PERPETUAL",
// "base_decimals": "9",
// "quote_decimals": "6",
// "tick_size": "0.01",
// "min_size": "0.1",
// "create_time": "1764303867576216941",
// "max_position_size": "3000.0",
// "funding_interval_hours": "8",
// "adjusted_funding_rate_cap": "0.75",
// "adjusted_funding_rate_floor": "-0.75"
// },
// ...
//
const promises = [marketsPromise];
if (!this.isEmptyString(this.apiKey) || !this.isEmptyString(this.privateKey)) {
promises.push(this.signIn());
}
const results = await Promise.all(promises);
const response = results[0];
const result = this.safeList(response, 'result', []);
return this.parseMarkets(result);
}
parseMarket(market) {
//
// {
// "instrument": "BTC_USDT_Perp",
// "instrument_hash": "0x030501",
// "base": "BTC",
// "quote": "USDT",
// "kind": "PERPETUAL",
// "venues": [
// "ORDERBOOK",
// "RFQ"
// ],
// "settlement_period": "PERPETUAL",
// "base_decimals": 9,
// "quote_decimals": 6,
// "tick_size": "0.1",
// "min_size": "0.001",
// "create_time": "1768040726362828205",
// "max_position_size": "1000.0",
// "funding_interval_hours": 8,
// "adjusted_funding_rate_cap": "0.3",
// "adjusted_funding_rate_floor": "-0.3",
// "min_notional": "100.0"
// }
//
const marketId = this.safeString(market, 'instrument');
const baseId = this.safeString(market, 'base');
const quoteId = this.safeString(market, 'quote');
const settleId = quoteId;
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const settle = this.safeCurrencyCode(settleId);
const symbol = base + '/' + quote + ':' + settle;
let type = undefined;
const typeRaw = this.safeString(market, 'kind');
if (typeRaw === 'PERPETUAL') {
type = 'swap';
}
const isSpot = (type === 'spot');
const isSwap = (type === 'swap');
const isFuture = (type === 'future');
const isContract = isSwap || isFuture;
return {
'id': marketId,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': type,
'spot': isSpot,
'margin': false,
'swap': isSwap,
'future': isFuture,
'option': false,
'active': undefined, // todo: ask support to add
'contract': isContract,
'linear': isSwap ? true : undefined,
'inverse': isSwap ? false : undefined,
'contractSize': this.parseNumber('1'), // tbd, vague response from support
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.safeNumber(market, 'min_size'), // confirmed, not 'base_decimals'
'price': this.safeNumber(market, 'tick_size'),
'base': this.parseNumber(this.parsePrecision(this.safeString(market, 'base_decimals'))),
'quote': this.parseNumber(this.parsePrecision(this.safeString(market, 'quote_decimals'))),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber(market, 'min_size'),
'max': this.safeNumber(market, 'max_position_size'),
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.safeNumber(market, 'min_notional'),
'max': undefined,
},
},
'created': this.safeIntegerProduct(market, 'create_time', 0.000001),
'info': market,
};
}
/**
* @method
* @name grvt#fetchCurrencies
* @description fetches all available currencies on an exchange
* @see https://api-docs.grvt.io/market_data_api/#get-currency-response
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an associative dictionary of currencies
*/
async fetchCurrencies(params = {}) {
const request = { '': '' }; // workaround for php [] empty arr
const response = await this.publicMarketPostFullV1Currency(request);
//
// {
// "result": [
// {
// "id": "4",
// "symbol": "ETH",
// "balance_decimals": "9",
// "quantity_multiplier": "1000000000"
// },
// ..
//
const responseResult = this.safeList(response, 'result', []);
return this.parseCurrencies(responseResult);
}
parseCurrency(rawCurrency) {
//
// {
// "id": "4",
// "symbol": "ETH",
// "balance_decimals": "9",
// "quantity_multiplier": "1000000000"
// },
//
const id = this.safeString(rawCurrency, 'symbol');
const code = this.safeCurrencyCode(id);
return this.safeCurrencyStructure({
'info': rawCurrency,
'id': id,
'code': code,
'name': undefined,
'active': undefined,
'deposit': undefined,
'withdraw': undefined,
'fee': undefined,
'precision': this.parseNumber(this.parsePrecision(this.safeString(rawCurrency, 'balance_decimals'))),
'limits': {
'amount': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
'deposit': {
'min': undefined,
'max': undefined,
},
},
'type': 'crypto', // only crypto for now
'networks': undefined,
'numericId': this.safeInteger(rawCurrency, 'id'),
});
}
/**
* @method
* @name grvt#fetchTicker
* @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://api-docs.grvt.io/market_data_api/#ticker_1
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
*/
async fetchTicker(symbol, params = {}) {
if (this.markets === undefined) {
await this.loadMarkets();
}
const market = this.market(symbol);
const request = {
'instrument': this.marketId(symbol),
};
const response = await this.publicMarketPostFullV1Ticker(this.extend(request, params));
//
// {
// "result": {
// "event_time": "1764774730025055205",
// "instrument": "BTC_USDT_Perp",
// "mark_price": "92697.300078773",
// "index_price": "92727.818122278",
// "last_price": "92683.0",
// "last_size": "0.001",
// "mid_price": "92682.95",
// "best_bid_price": "92682.9",
// "best_bid_size": "5.332",
// "best_ask_price": "92683.0",
// "best_ask_size": "0.009",
// "funding_rate_8h_curr": "0.0037",
// "funding_rate_8h_avg": "0.0037",
// "interest_rate": "0.0",
// "forward_price": "0.0",
// "buy_volume_24h_b": "2893.898",
// "sell_volume_24h_b": "2907.847",
// "buy_volume_24h_q": "266955739.1606",
// "sell_volume_24h_q": "268170211.7109",
// "high_price": "93908.3",
// "low_price": "89900.1",
// "open_price": "90129.2",
// "open_interest": "1523.218935908",
// "long_short_ratio": "1.472543",
// "funding_rate": "0.0037",
// "next_funding_time": "1764777600000000000"
// }
// }
//
const result = this.safeDict(response, 'result', {});
return this.parseTicker(result, market);
}
parseTicker(ticker, market = undefined) {
//
// {
// "event_time": "1764774730025055205",
// "instrument": "BTC_USDT_Perp",
// "mark_price": "92697.300078773",
// "index_price": "92727.818122278",
// "last_price": "92683.0",
// "last_size": "0.001",
// "mid_price": "92682.95",
// "best_bid_price": "92682.9",
// "best_bid_size": "5.332",
// "best_ask_price": "92683.0",
// "best_ask_size": "0.009",
// "funding_rate_8h_curr": "0.0037",
// "funding_rate_8h_avg": "0.0037",
// "interest_rate": "0.0",
// "forward_price": "0.0",
// "buy_volume_24h_b": "2893.898",
// "sell_volume_24h_b": "2907.847",
// "buy_volume_24h_q": "266955739.1606",
// "sell_volume_24h_q": "268170211.7109",
// "high_price": "93908.3",
// "low_price": "89900.1",
// "open_price": "90129.2",
// "open_interest": "1523.218935908",
// "long_short_ratio": "1.472543",
// "funding_rate": "0.0037",
// "next_funding_time": "1764777600000000000"
// }
//
const marketId = this.safeString(ticker, 'instrument');
const timestamp = this.safeIntegerProduct(ticker, 'event_time', 0.000001);
return this.safeTicker({
'info': ticker,
'symbol': this.safeSymbol(marketId, market),
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'open': this.safeString(ticker, 'open_price'),
'high': this.safeString(ticker, 'high_price'),
'low': this.safeString(ticker, 'low_price'),
'last': this.safeString(ticker, 'last_price'),
'bid': this.safeString(ticker, 'best_bid_price'),
'bidVolume': this.safeString(ticker, 'best_bid_size'),
'ask': this.safeString(ticker, 'best_ask_price'),
'askVolume': this.safeString(ticker, 'best_ask_size'),
'change': undefined,
'percentage': undefined,
'baseVolume': this.safeString(ticker, 'buy_volume_24h_b'),
'quoteVolume': this.safeString(ticker, 'buy_volume_24h_q'),
'markPrice': this.safeString(ticker, 'mark_price'),
'indexPrice': this.safeString(ticker, 'index_price'),
'vwap': undefined,
'average': undefined,
'previousClose': undefined,
});
}
/**
* @method
* @name grvt#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://api-docs.grvt.io/market_data_api/#orderbook-levels
* @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 {string} [params.loc] crypto location, default: us
* @returns {object} an [order book structure]{@link https://docs.ccxt.com/?id=order-book-structure}
*/
async fetchOrderBook(symbol, limit = undefined, params = {}) {
if (this.markets === undefined) {
await this.loadMarkets();
}
const request = {
'instrument': this.marketId(symbol),
};
if (limit === undefined) {
limit = 100;
}
if (limit <= 500) {
request['depth'] = this.findNearestCeiling([10, 50, 100, 500], limit);
}
const response = await this.publicMarketPostFullV1Book(this.extend(request, params));
//
// {
// "result": {
// "event_time": "1764777396650000000",
// "instrument": "BTC_USDT_Perp",
// "bids": [
// { "price": "92336.0", "size": "0.005", "num_orders": "1" },
// ...
// ],
// "asks": [
// { "price": "92336.1", "size": "5.711", "num_orders": "37" },
// ...
// ]
// }
// }
//
const result = this.safeDict(response, 'result', {});
const timestamp = this.parse8601(this.safeString(result, 'event_time'));
const marketId = this.safeString(result, 'instrument');
return this.parseOrderBook(result, this.safeSymbol(marketId), timestamp, 'bids', 'asks', 'price', 'size');
}
/**
* @method
* @name grvt#fetchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://api-docs.grvt.io/market_data_api/#trade_1
* @param {string} symbol unified symbol of the market
* @param {int} [since] timestamp in ms of the earliest item to fetch
* @param {int} [limit] the maximum amount of items to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] timestamp in ms for the ending date filter, default is the current time
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades}
*/
async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
if (this.markets === undefined) {
await this.loadMarkets();
}
const market = this.market(symbol);
let request = {
'instrument': market['id'],
};
if (limit !== undefined) {
request['limit'] = Math.min(limit, 1000);
}
[request, params] = this.handleUntilOptionString('end_time', request, params, 1000000);
if (since !== undefined) {
request['start_time'] = this.numberToString(since * 1000000);
}
const response = await this.publicMarketPostFullV1TradeHistory(this.extend(request, params));
//
// {
// "next": "eyJ0cmFkZUlkIjo2NDc5MTAyMywidHJhZGVJbmRleCI6MX0",
// "result": [
// {
// "event_time": "1764779531332118705",
// "instrument": "ETH_USDT_Perp",
// "is_taker_buyer": false,
// "size": "23.73",
// "price": "3089.88",
// "mark_price": "3089.360002315",
// "index_price": "3090.443723246",
// "interest_rate": "0.0",
//