ccxt
Version:
1,032 lines (1,029 loc) • 157 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var lighter$1 = require('./abstract/lighter.js');
var errors = require('./base/errors.js');
var number = require('./base/functions/number.js');
var Precise = require('./base/Precise.js');
var crypto = require('./base/functions/crypto.js');
var sha3 = require('./static_dependencies/noble-hashes/sha3.js');
var secp256k1 = require('./static_dependencies/noble-curves/secp256k1.js');
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* @class lighter
* @augments Exchange
*/
class lighter extends lighter$1["default"] {
describe() {
return this.deepExtend(super.describe(), {
'id': 'lighter',
'name': 'Lighter',
'countries': [],
'version': 'v1',
'rateLimit': 1000,
'certified': false,
'pro': true,
'dex': true,
'quoteJsonNumbers': false,
'has': {
'CORS': undefined,
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'addMargin': true,
'borrowCrossMargin': false,
'borrowIsolatedMargin': false,
'borrowMargin': false,
'cancelAllOrders': true,
'cancelAllOrdersAfter': true,
'cancelOrder': true,
'cancelOrders': false,
'cancelOrdersForSymbols': false,
'closeAllPositions': false,
'closePosition': false,
'createMarketBuyOrderWithCost': false,
'createMarketOrderWithCost': false,
'createMarketSellOrderWithCost': false,
'createOrder': true,
'createOrders': false,
'createPostOnlyOrder': false,
'createReduceOnlyOrder': false,
'createStopOrder': false,
'createTriggerOrder': false,
'editOrder': true,
'fetchAccounts': true,
'fetchAllGreeks': false,
'fetchBalance': true,
'fetchBorrowInterest': false,
'fetchBorrowRate': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchBorrowRates': false,
'fetchBorrowRatesPerSymbol': false,
'fetchCanceledAndClosedOrders': false,
'fetchCanceledOrders': false,
'fetchClosedOrders': true,
'fetchCrossBorrowRate': false,
'fetchCrossBorrowRates': false,
'fetchCurrencies': true,
'fetchDepositAddress': false,
'fetchDepositAddresses': false,
'fetchDeposits': true,
'fetchDepositWithdrawFee': false,
'fetchDepositWithdrawFees': false,
'fetchFundingHistory': false,
'fetchFundingRate': false,
'fetchFundingRateHistory': false,
'fetchFundingRates': true,
'fetchGreeks': false,
'fetchIndexOHLCV': false,
'fetchIsolatedBorrowRate': false,
'fetchIsolatedBorrowRates': false,
'fetchLedger': false,
'fetchLeverage': false,
'fetchLeverageTiers': false,
'fetchLiquidations': false,
'fetchMarginMode': false,
'fetchMarketLeverageTiers': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyLiquidations': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterest': false,
'fetchOpenInterestHistory': false,
'fetchOpenInterests': false,
'fetchOpenOrders': true,
'fetchOption': false,
'fetchOptionChain': false,
'fetchOrder': false,
'fetchOrderBook': true,
'fetchOrders': false,
'fetchOrderTrades': false,
'fetchPosition': true,
'fetchPositionMode': false,
'fetchPositions': true,
'fetchPositionsRisk': false,
'fetchPremiumIndexOHLCV': false,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': false,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchTransfer': false,
'fetchTransfers': true,
'fetchVolatilityHistory': false,
'fetchWithdrawal': false,
'fetchWithdrawals': true,
'reduceMargin': true,
'repayCrossMargin': false,
'repayIsolatedMargin': false,
'sandbox': true,
'setLeverage': true,
'setMargin': true,
'setMarginMode': true,
'setPositionMode': false,
'transfer': true,
'withdraw': true,
},
'timeframes': {
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'4h': '4h',
'12h': '12h',
'1d': '1d',
'1w': '1w',
},
'hostname': 'zklighter.elliot.ai',
'urls': {
'logo': 'https://github.com/user-attachments/assets/ff1aaf96-bffb-4545-a750-5eba716e75d0',
'api': {
'root': 'https://mainnet.{hostname}',
'public': 'https://mainnet.{hostname}',
'private': 'https://mainnet.{hostname}',
},
'test': {
'root': 'https://testnet.{hostname}',
'public': 'https://testnet.{hostname}',
'private': 'https://testnet.{hostname}',
},
'www': 'https://lighter.xyz/',
'doc': 'https://apidocs.lighter.xyz/',
'fees': 'https://docs.lighter.xyz/perpetual-futures/fees',
'referral': {
'url': 'app.lighter.xyz/?referral=715955W9',
'discount': 0.1, // user gets 10% of the points
},
},
'api': {
'root': {
'get': {
// root
'': 1,
'info': 1,
},
},
'public': {
'get': {
// account
'account': 1,
'accountsByL1Address': 1,
'apikeys': 1,
// order
'exchangeStats': 1,
'assetDetails': 1,
'orderBookDetails': 1,
'orderBookOrders': 1,
'orderBooks': 1,
'recentTrades': 1,
// transaction
'blockTxs': 1,
'nextNonce': 1,
'tx': 1,
'txFromL1TxHash': 1,
'txs': 1,
// announcement
'announcement': 1,
// block
'block': 1,
'blocks': 1,
'currentHeight': 1,
// candlestick
'candles': 1,
'fundings': 1,
// bridge
'fastbridge/info': 1,
// funding
'funding-rates': 1,
// info
'withdrawalDelay': 1,
},
'post': {
// transaction
'sendTx': 1,
'sendTxBatch': 1,
},
},
'private': {
'get': {
// account
'accountLimits': 1,
'accountMetadata': 1,
'pnl': 1,
'l1Metadata': 1,
'liquidations': 1,
'positionFunding': 1,
'publicPoolsMetadata': 1,
// order
'accountActiveOrders': 1,
'accountInactiveOrders': 1,
'export': 1,
'trades': 1,
// transaction
'accountTxs': 1,
'deposit/history': 1,
'transfer/history': 1,
'withdraw/history': 1,
// referral
'referral/points': 1,
// info
'transferFeeInfo': 1,
},
'post': {
// account
'changeAccountTier': 1,
// notification
'notification/ack': 1,
},
},
},
'httpExceptions': {},
'exceptions': {
'exact': {
'21146': errors.ExchangeError,
'21500': errors.ExchangeError,
'21501': errors.ExchangeError,
'21502': errors.ExchangeError,
'21503': errors.ExchangeError,
'21504': errors.ExchangeError,
'21505': errors.ExchangeError,
'21506': errors.ExchangeError,
'21507': errors.ExchangeError,
'21508': errors.ExchangeError,
'21511': errors.ExchangeError,
'21512': errors.ExchangeError,
'21600': errors.InvalidOrder,
'21601': errors.InvalidOrder,
'21602': errors.InvalidOrder,
'21603': errors.InvalidOrder,
'21604': errors.InvalidOrder,
'21605': errors.InvalidOrder,
'21606': errors.InvalidOrder,
'21607': errors.InvalidOrder,
'21608': errors.InvalidOrder,
'21611': errors.InvalidOrder,
'21612': errors.InvalidOrder,
'21613': errors.InvalidOrder,
'21614': errors.InvalidOrder,
'21700': errors.InvalidOrder,
'21701': errors.InvalidOrder,
'21702': errors.InvalidOrder,
'21703': errors.InvalidOrder,
'21704': errors.InvalidOrder,
'21705': errors.InvalidOrder,
'21706': errors.InvalidOrder,
'21707': errors.InvalidOrder,
'21708': errors.InvalidOrder,
'21709': errors.InvalidOrder,
'21710': errors.InvalidOrder,
'21711': errors.InvalidOrder,
'21712': errors.InvalidOrder,
'21713': errors.InvalidOrder,
'21714': errors.InvalidOrder,
'21715': errors.InvalidOrder,
'21716': errors.InvalidOrder,
'21717': errors.InvalidOrder,
'21718': errors.InvalidOrder,
'21719': errors.InvalidOrder,
'21720': errors.InvalidOrder,
'21721': errors.InvalidOrder,
'21722': errors.InvalidOrder,
'21723': errors.InvalidOrder,
'21724': errors.InvalidOrder,
'21725': errors.InvalidOrder,
'21726': errors.InvalidOrder,
'21727': errors.InvalidOrder,
'21728': errors.InvalidOrder,
'21729': errors.InvalidOrder,
'21730': errors.InvalidOrder,
'21731': errors.InvalidOrder,
'21732': errors.InvalidOrder,
'21733': errors.InvalidOrder,
'21734': errors.InvalidOrder,
'21735': errors.InvalidOrder,
'21736': errors.InvalidOrder,
'21737': errors.InvalidOrder,
'21738': errors.InvalidOrder,
'21739': errors.InvalidOrder,
'21740': errors.InvalidOrder,
'21901': errors.InvalidOrder,
'21902': errors.InvalidOrder,
'21903': errors.InvalidOrder,
'21904': errors.InvalidOrder,
'21905': errors.InvalidOrder,
'21906': errors.InvalidOrder,
'23000': errors.RateLimitExceeded,
'23001': errors.RateLimitExceeded,
'23002': errors.RateLimitExceeded,
'23003': errors.RateLimitExceeded, // Too Many Connections!
},
'broad': {},
},
'fees': {
'taker': 0,
'maker': 0,
},
'requiredCredentials': {
'apiKey': false,
'secret': false,
'walletAddress': false,
'privateKey': true,
'password': false,
},
'precisionMode': number.TICK_SIZE,
'commonCurrencies': {},
'options': {
'defaultType': 'swap',
'builderFee': true,
'chainId': 304,
'accountIndex': undefined,
'apiKeyIndex': undefined,
'lighterPrivateKey': undefined,
'wasmExecPath': undefined,
'libraryPath': undefined,
'integratorAccountIndex': 718718,
'integratorMakerFee': 1000,
'integratorTakerFee': 1000,
'authDeadlineExpiry': 28800,
'authDeadlineMinimumRemaining': 60,
},
'features': {
'default': {
'sandbox': true,
'createOrder': {
'timeInForce': {
'IOC': true,
'FOK': true,
'PO': true,
'GTD': false,
},
'leverage': false,
'marketBuyRequiresPrice': false,
'marketBuyByCost': false,
'selfTradePrevention': false,
'trailing': false,
'iceberg': false,
},
},
},
});
}
async loadAccount(chainId, privateKey, apiKeyIndex, accountIndex, params = {}) {
this.initAuthObject(accountIndex, apiKeyIndex);
const cachedAuths = this.safeDict(this.options['auths'][accountIndex], apiKeyIndex);
let signer = this.safeValue(cachedAuths, 'signer');
if (signer !== undefined) {
return signer;
}
let libraryPath = undefined;
[libraryPath, params] = this.handleOptionAndParams(params, 'loadAccount', 'libraryPath');
const lighterPrivateKeyIsSet = (privateKey !== undefined) && (privateKey !== '');
if (lighterPrivateKeyIsSet && (libraryPath !== undefined) && (apiKeyIndex !== undefined) && (accountIndex !== undefined)) {
// load lighter library, and create lighter client
signer = await this.loadLighterLibrary(libraryPath, chainId, privateKey, this.parseToInt(apiKeyIndex), this.parseToInt(accountIndex), true);
this.options['auths'][accountIndex][apiKeyIndex]['signer'] = signer;
return signer;
}
const privateKeyIsSet = (this.privateKey !== undefined) && (this.privateKey !== '');
if (privateKeyIsSet && (apiKeyIndex !== undefined) && (accountIndex !== undefined)) {
if (this.privateKey.length > 66) {
throw new errors.NotSupported(this.id + ' after the latest update (v4.5.50), CCXT now expects the l1 private key to be provided in the credentials. Please check for more details: https://github.com/ccxt/ccxt/wiki/FAQ#how-to-use-the-lighter-exchange-in-ccxt');
}
// load lighter library without creating lighter client
signer = await this.loadLighterLibrary(libraryPath, chainId, '', this.parseToInt(apiKeyIndex), this.parseToInt(accountIndex), false);
this.options['auths'][accountIndex][apiKeyIndex]['signer'] = signer;
const res = await this.changeApiKey();
await this.handleBuilderFeeApproval(this.parseToInt(accountIndex), this.parseToInt(apiKeyIndex));
return res;
}
return signer;
}
initAuthObject(strAccountIndex, strApiKeyIndex) {
if (!('auths' in this.options)) {
this.options['auths'] = {};
}
if (!(strAccountIndex in this.options['auths'])) {
this.options['auths'][strAccountIndex] = {};
}
if (!(strApiKeyIndex in this.options['auths'][strAccountIndex])) {
this.options['auths'][strAccountIndex][strApiKeyIndex] = {
'signer': undefined,
'lighterPrivateKey': undefined,
'deadline': undefined,
'token': undefined,
};
}
}
getLighterPrivateKey(strAccountIndex, strApiKeyIndex) {
if (!('auths' in this.options)) {
return undefined;
}
if (!(strAccountIndex in this.options['auths'])) {
return undefined;
}
if (!(strApiKeyIndex in this.options['auths'][strAccountIndex])) {
return undefined;
}
if (!('lighterPrivateKey' in this.options['auths'][strAccountIndex][strApiKeyIndex])) {
return undefined;
}
return this.options['auths'][strAccountIndex][strApiKeyIndex]['lighterPrivateKey'];
}
/**
* @method
* @name lighter#preLoadLighterLibrary
* @description if the required credentials are available in options, it will pre-load the lighter Signer to avoid delaying sensitive calls like createOrder the first time they're executed
* @param params
* @returns {boolean} true if the signer was loaded, false otherwise
*/
async preLoadLighterLibrary(params = {}) {
let apiKeyIndex = undefined;
[apiKeyIndex, params] = this.handleApiKeyIndex(params, 'loadAccount', 'apiKeyIndex', 'api_key_index');
let accountIndex = undefined;
[accountIndex, params] = await this.handleAccountIndex(params, 'loadAccount', 'accountIndex', 'account_index');
if (accountIndex === undefined) {
throw new errors.ArgumentsRequired(this.id + ' requires accountIndex or account_index');
}
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
this.initAuthObject(strAccountIndex, strApiKeyIndex);
let signer = this.safeDict(this.options['auths'][strAccountIndex][strApiKeyIndex], 'signer');
if (signer !== undefined) {
return true;
}
signer = await this.loadAccount(this.options['chainId'], this.getLighterPrivateKey(strAccountIndex, strApiKeyIndex), strApiKeyIndex, strAccountIndex);
await this.handleBuilderFeeApproval(accountIndex, apiKeyIndex);
return (signer !== undefined);
}
handleApiKeyIndex(params, methodName1, optionName1, optionName2, defaultValue = undefined) {
let apiKeyIndex = undefined;
[apiKeyIndex, params] = this.handleOptionAndParams2(params, methodName1, optionName1, optionName2, defaultValue);
if ((apiKeyIndex === undefined) || (apiKeyIndex < 4) || (apiKeyIndex > 254)) {
// apiKeyIndex = this.randNumber (2);
apiKeyIndex = 254;
this.options['apiKeyIndex'] = apiKeyIndex; // default to a value to avoid overriding other keys
}
return [this.parseToInt(apiKeyIndex), params];
}
async handleAccountIndex(params, methodName1, optionName1, optionName2, defaultValue = undefined) {
let accountIndex = undefined;
[accountIndex, params] = this.handleOptionAndParams2(params, methodName1, optionName1, optionName2, defaultValue);
if (accountIndex === undefined) {
let walletAddress = this.walletAddress;
if (this.privateKey !== undefined) {
if (this.privateKey.length > 66) {
throw new errors.NotSupported(this.id + ' after the latest update (v4.5.50), CCXT now expects the l1 private key to be provided in the credentials. Please check for more details: https://github.com/ccxt/ccxt/wiki/FAQ#how-to-use-the-lighter-exchange-in-ccxt');
}
walletAddress = this.ethGetAddressFromPrivateKey(this.privateKey);
}
if (walletAddress === undefined || walletAddress === '') {
throw new errors.ArgumentsRequired(this.id + ' ' + methodName1 + '() requires an ' + optionName1 + '/' + optionName2 + ' parameter or walletAddress to fetch accountIndex. Alternatively set privateKey in credentials to enable automatic walletAddress detection.');
}
const res = await this.publicGetAccountsByL1Address({ 'l1_address': walletAddress });
//
// {
// "code": 200,
// "l1_address": "0xaaaabbbb....ccccdddd",
// "sub_accounts": [
// {
// "code": 0,
// "account_type": 0,
// "index": 666666,
// "l1_address": "0xaaaabbbb....ccccdddd",
// "cancel_all_time": 0,
// "total_order_count": 0,
// "total_isolated_order_count": 0,
// "pending_order_count": 0,
// "available_balance": "",
// "status": 0,
// "collateral": "40",
// "transaction_time": 0,
// "account_trading_mode": 0
// }
// ]
// }
//
const subAccounts = this.safeList(res, 'sub_accounts');
if (Array.isArray(subAccounts)) {
const account = this.safeDict(subAccounts, 0);
if (account === undefined) {
throw new errors.ArgumentsRequired(this.id + ' ' + methodName1 + '() requires an ' + optionName1 + ' or ' + optionName2 + ' parameter');
}
accountIndex = account['index'];
this.options['accountIndex'] = accountIndex;
}
}
return [this.parseToInt(accountIndex), params];
}
async createSubAccount(name, params = {}) {
let apiKeyIndex = undefined;
[apiKeyIndex, params] = this.handleApiKeyIndex(params, 'createSubAccount', 'apiKeyIndex', 'api_key_index');
let accountIndex = undefined;
[accountIndex, params] = await this.handleAccountIndex(params, 'createSubAccount', 'accountIndex', 'account_index');
const nonce = await this.fetchNonce(accountIndex, apiKeyIndex, params);
const signRaw = {
'nonce': nonce,
'api_key_index': apiKeyIndex,
'account_index': accountIndex,
};
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
const signer = await this.loadAccount(this.options['chainId'], this.getLighterPrivateKey(strAccountIndex, strApiKeyIndex), strApiKeyIndex, strAccountIndex, params);
const [txType, txInfo] = this.lighterSignCreateSubAccount(signer, this.extend(signRaw, params));
const request = {
'tx_type': txType,
'tx_info': txInfo,
};
return await this.publicPostSendTx(request);
}
createAuth(params = {}) {
// don't omit [accountIndex, apiKeyIndex], request may need them
let apiKeyIndex = this.safeString2(params, 'apiKeyIndex', 'api_key_index');
if (apiKeyIndex === undefined) {
const res = this.handleOptionAndParams2({}, 'createAuth', 'apiKeyIndex', 'api_key_index');
apiKeyIndex = this.safeString(res, 0);
}
let accountIndex = this.safeString2(params, 'accountIndex', 'account_index');
if (accountIndex === undefined) {
const res = this.handleOptionAndParams2({}, 'createAuth', 'accountIndex', 'account_index');
accountIndex = this.safeString(res, 0);
}
const auths = this.safeDict(this.options, 'auths');
const accountAuths = this.safeDict(auths, accountIndex);
const cachedAuth = this.safeDict(accountAuths, apiKeyIndex);
const cachedDeadline = this.safeInteger(cachedAuth, 'deadline');
if (cachedDeadline !== undefined) {
const minimumDeadline = this.seconds() + this.safeInteger(this.options, 'authDeadlineMinimumRemaining');
if (cachedDeadline >= minimumDeadline) {
return this.safeString(cachedAuth, 'token');
}
}
const deadline = this.seconds() + this.safeInteger(this.options, 'authDeadlineExpiry');
const request = {
'deadline': deadline,
'api_key_index': this.parseToInt(apiKeyIndex),
'account_index': this.parseToInt(accountIndex),
};
const token = this.lighterCreateAuthToken(this.options['auths'][accountIndex][apiKeyIndex]['signer'], request);
this.options['auths'][accountIndex][apiKeyIndex]['deadline'] = deadline;
this.options['auths'][accountIndex][apiKeyIndex]['token'] = token;
return token;
}
pow(n, m) {
let r = Precise["default"].stringMul(n, '1');
const c = this.parseToInt(m);
if (c < 0) {
throw new errors.BadRequest(this.id + ' pow() requires m > 0.');
}
if (c === 0) {
return '1';
}
if (c > 100) {
throw new errors.BadRequest(this.id + ' pow() requires m < 100.');
}
for (let i = 1; i < c; i++) {
r = Precise["default"].stringMul(r, n);
}
return r;
}
hashMessage(message) {
const binaryMessage = this.encode(message);
const binaryMessageLength = this.binaryLength(binaryMessage);
const x19 = this.base16ToBinary('19');
const newline = this.base16ToBinary('0a');
const prefix = this.binaryConcat(x19, this.encode('Ethereum Signed Message:'), newline, this.encode(this.numberToString(binaryMessageLength)));
return '0x' + this.hash(this.binaryConcat(prefix, binaryMessage), sha3.keccak_256, 'hex');
}
signHash(hash, privateKey) {
this.checkRequiredCredentials();
const signature = crypto.ecdsa(hash.slice(-64), privateKey.slice(-64), secp256k1.secp256k1, undefined);
const r = signature['r'];
const s = signature['s'];
const v = this.intToBase16(this.sum(27, signature['v']));
return '0x' + r.padStart(64, '0') + s.padStart(64, '0') + v;
}
signL1AndPrepareTxInfo(txInfo, message, privateKey) {
const hashMessage = this.hashMessage(message);
const signature = this.signHash(hashMessage, privateKey);
const decTxInfo = this.parseJson(txInfo);
decTxInfo['L1Sig'] = signature;
return this.json(decTxInfo);
}
async handleBuilderFeeApproval(accountIndex, apiKeyIndex) {
const buildFee = this.safeBool(this.options, 'builderFee', true);
if (!buildFee) {
return false;
}
const approvedBuilderFee = this.safeBool(this.options, 'approvedBuilderFee', false);
if (approvedBuilderFee) {
return true;
}
try {
const builder = this.safeInteger(this.options, 'integratorAccountIndex', 718718);
const takerFeeRate = this.safeInteger(this.options, 'integratorTakerFee', 1000);
const makerFeeRate = this.safeInteger(this.options, 'integratorMakerFee', 1000);
await this.approveBuilderFee(builder, takerFeeRate, makerFeeRate, accountIndex, apiKeyIndex);
this.options['approvedBuilderFee'] = true;
}
catch (e) {
this.options['builderFee'] = false;
}
return true;
}
async approveBuilderFee(builder, takerFeeRate, makerFeeRate, accountIndex, apiKeyIndex, params = {}) {
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
const signer = await this.loadAccount(this.options['chainId'], this.getLighterPrivateKey(strAccountIndex, strApiKeyIndex), strApiKeyIndex, strAccountIndex, params);
const nonce = await this.fetchNonce(accountIndex, apiKeyIndex, this.extend(params, { 'skipNonce': false }));
const expiry = this.milliseconds() + 365 * 864000;
const signRaw = {
'integrator_account_index': builder,
'integrator_taker_fee': takerFeeRate,
'integrator_maker_fee': makerFeeRate,
'approval_expiry': expiry,
'nonce': nonce,
'api_key_index': apiKeyIndex,
'account_index': accountIndex,
};
const [txType, txInfo, messageToSign] = this.lighterSignApproveIntegrator(signer, this.extend(signRaw, params));
const newTxInfo = this.signL1AndPrepareTxInfo(txInfo, messageToSign, this.privateKey);
const request = {
'tx_type': txType,
'tx_info': newTxInfo,
};
const response = await this.publicPostSendTx(request);
return response;
}
async changeApiKey(params = {}) {
let apiKeyIndex = undefined;
[apiKeyIndex, params] = this.handleApiKeyIndex(params, 'changeApiKey', 'apiKeyIndex', 'api_key_index');
let accountIndex = undefined;
[accountIndex, params] = await this.handleAccountIndex(params, 'changeApiKey', 'accountIndex', 'account_index');
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
const signerNotLoad = this.options['auths'][strAccountIndex][strApiKeyIndex]['signer'];
const [privateKey, publicKey] = this.lighterGenerateApiKey(signerNotLoad);
const nonce = await this.fetchNonce(accountIndex, apiKeyIndex, this.extend(params, { 'skipNonce': false }));
const signRaw = {
'pubkey': this.encode(publicKey),
'nonce': nonce,
'api_key_index': apiKeyIndex,
'account_index': accountIndex,
};
// create lighter client
const signer = this.lighterCreateClient(signerNotLoad, this.options['chainId'], privateKey, apiKeyIndex, accountIndex);
const [txType, txInfo, messageToSign] = this.lighterSignChangePubkey(signer, this.extend(signRaw, params));
const newTxInfo = this.signL1AndPrepareTxInfo(txInfo, messageToSign, this.privateKey);
const request = {
'tx_type': txType,
'tx_info': newTxInfo,
};
await this.publicPostSendTx(request);
this.options['auths'][strAccountIndex][strApiKeyIndex]['lighterPrivateKey'] = privateKey;
this.options['auths'][strAccountIndex][strApiKeyIndex]['signer'] = signer; // reassign signer in go
await this.handleBuilderFeeApproval(accountIndex, apiKeyIndex);
return signer;
}
setSandboxMode(enable) {
super.setSandboxMode(enable);
this.options['sandboxMode'] = enable;
this.options['chainId'] = enable ? 300 : 304;
}
createOrderRequest(symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @ignore
* @name lighter#createOrderRequest
* @description helper function to build the request
* @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
* @param {float} [price] the price that 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 {int} [params.nonce] nonce for the account
* @param {int} [params.apiKeyIndex] apiKeyIndex
* @param {int} [params.accountIndex] accountIndex
* @param {int} [params.orderExpiry] orderExpiry
* @returns {any[]} request to be sent to the exchange
*/
if (price === undefined) {
throw new errors.ArgumentsRequired(this.id + ' createOrder() requires a price argument');
}
const reduceOnly = this.safeBool2(params, 'reduceOnly', 'reduce_only', false); // default false
const orderType = type.toUpperCase();
const market = this.market(symbol);
const orderSide = side.toUpperCase();
const request = {
'market_index': this.parseToInt(market['id']),
};
let nonce = undefined;
let apiKeyIndex = undefined;
let accountIndex = undefined;
let orderExpiry = undefined;
[apiKeyIndex, params] = this.handleApiKeyIndex(params, 'createOrder', 'apiKeyIndex', 'api_key_index');
[accountIndex, params] = this.handleOptionAndParams2(params, 'createOrder', 'accountIndex', 'account_index');
[nonce, params] = this.handleOptionAndParams(params, 'createOrder', 'nonce');
[orderExpiry, params] = this.handleOptionAndParams(params, 'createOrder', 'orderExpiry', 0);
if (nonce !== undefined) {
request['nonce'] = nonce;
}
request['api_key_index'] = apiKeyIndex;
request['account_index'] = this.parseToInt(accountIndex);
const triggerPrice = this.safeString2(params, 'triggerPrice', 'stopPrice');
const stopLossPrice = this.safeValue(params, 'stopLossPrice', triggerPrice);
const takeProfitPrice = this.safeValue(params, 'takeProfitPrice');
const stopLoss = this.safeValue(params, 'stopLoss');
const takeProfit = this.safeValue(params, 'takeProfit');
const hasStopLoss = (stopLoss !== undefined);
const hasTakeProfit = (takeProfit !== undefined);
const isConditional = (stopLossPrice || takeProfitPrice);
const isMarketOrder = (orderType === 'MARKET');
const timeInForce = this.safeStringLower(params, 'timeInForce', 'gtt');
const postOnly = this.isPostOnly(isMarketOrder, undefined, params);
params = this.omit(params, ['stopLoss', 'takeProfit', 'timeInForce']);
let orderTypeNum = undefined;
let timeInForceNum = undefined;
if (isMarketOrder) {
orderTypeNum = 1;
timeInForceNum = 0;
}
else {
orderTypeNum = 0;
}
if (orderSide === 'BUY') {
request['is_ask'] = 0;
}
else {
request['is_ask'] = 1;
}
if (postOnly) {
timeInForceNum = 2;
}
else {
if (!isMarketOrder) {
if (timeInForce === 'ioc') {
timeInForceNum = 0;
orderExpiry = 0;
}
else if (timeInForce === 'gtt') {
timeInForceNum = 1;
orderExpiry = -1;
}
}
}
const marketInfo = this.safeDict(market, 'info');
let amountStr = undefined;
const priceStr = this.priceToPrecision(symbol, price);
const amountScale = this.pow('10', marketInfo['size_decimals']);
const priceScale = this.pow('10', marketInfo['price_decimals']);
let triggerPriceStr = '0'; // default is 0
const defaultClientOrderId = this.randNumber(9); // c# only support int32 2147483647.
const clientOrderId = this.safeInteger2(params, 'client_order_index', 'clientOrderId', defaultClientOrderId);
params = this.omit(params, ['reduceOnly', 'reduce_only', 'timeInForce', 'postOnly', 'nonce', 'apiKeyIndex', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'client_order_index', 'clientOrderId']);
if (isConditional) {
amountStr = this.numberToString(amount);
if (stopLossPrice !== undefined) {
if (isMarketOrder) {
orderTypeNum = 2;
}
else {
orderTypeNum = 3;
}
triggerPriceStr = this.priceToPrecision(symbol, stopLossPrice);
}
else if (takeProfitPrice !== undefined) {
if (isMarketOrder) {
orderTypeNum = 4;
}
else {
orderTypeNum = 5;
}
triggerPriceStr = this.priceToPrecision(symbol, takeProfitPrice);
}
}
else {
amountStr = this.amountToPrecision(symbol, amount);
}
request['order_expiry'] = orderExpiry;
request['order_type'] = orderTypeNum;
request['time_in_force'] = timeInForceNum;
request['reduce_only'] = (reduceOnly) ? 1 : 0;
request['client_order_index'] = clientOrderId;
request['base_amount'] = this.parseToInt(Precise["default"].stringMul(amountStr, amountScale));
request['avg_execution_price'] = this.parseToInt(Precise["default"].stringMul(priceStr, priceScale));
request['trigger_price'] = this.parseToInt(Precise["default"].stringMul(triggerPriceStr, priceScale));
if (this.safeBool(this.options, 'builderFee', true)) {
request['integrator_account_index'] = this.options['integratorAccountIndex'];
request['integrator_taker_fee'] = this.options['integratorTakerFee'];
request['integrator_maker_fee'] = this.options['integratorMakerFee'];
}
const orders = [];
orders.push(this.extend(request, params));
if (hasStopLoss || hasTakeProfit) {
// group order
orders[0]['client_order_index'] = 0; // client order index should be 0
let triggerOrderSide = '';
if (side === 'BUY') {
triggerOrderSide = 'sell';
}
else {
triggerOrderSide = 'buy';
}
const stopLossOrderTriggerPrice = this.safeNumberN(stopLoss, ['triggerPrice', 'stopPrice']);
const stopLossOrderType = this.safeString(stopLoss, 'type', 'limit');
const stopLossOrderLimitPrice = this.safeNumberN(stopLoss, ['price', 'stopLossPrice'], stopLossOrderTriggerPrice);
const takeProfitOrderTriggerPrice = this.safeNumberN(takeProfit, ['triggerPrice', 'stopPrice']);
const takeProfitOrderType = this.safeString(takeProfit, 'type', 'limit');
const takeProfitOrderLimitPrice = this.safeNumberN(takeProfit, ['price', 'takeProfitPrice'], takeProfitOrderTriggerPrice);
// amount should be 0 for child orders
if (stopLoss !== undefined) {
const orderObj = this.createOrderRequest(symbol, stopLossOrderType, triggerOrderSide, 0, stopLossOrderLimitPrice, this.extend(params, {
'stopLossPrice': stopLossOrderTriggerPrice,
'reduceOnly': true,
}))[0];
orderObj['client_order_index'] = 0;
orders.push(orderObj);
}
if (takeProfit !== undefined) {
const orderObj = this.createOrderRequest(symbol, takeProfitOrderType, triggerOrderSide, 0, takeProfitOrderLimitPrice, this.extend(params, {
'takeProfitPrice': takeProfitOrderTriggerPrice,
'reduceOnly': true,
}))[0];
orderObj['client_order_index'] = 0;
orders.push(orderObj);
}
}
return orders;
}
async fetchNonce(accountIndex, apiKeyIndex, params = {}) {
if ((accountIndex === undefined) || (apiKeyIndex === undefined)) {
throw new errors.ArgumentsRequired(this.id + ' fetchNonce() requires accountIndex and apiKeyIndex.');
}
if ('nonce' in params) {
return this.safeInteger(params, 'nonce');
}
const nonceInOptions = this.safeInteger(this.options, 'nonce');
if (nonceInOptions !== undefined) {
return nonceInOptions;
}
// avoid skipNonce for l1 operations
let skipNonce = true;
[skipNonce, params] = this.handleOptionAndParams(params, 'fetchNonce', 'skipNonce', true);
if (skipNonce) {
return this.milliseconds();
}
const response = await this.publicGetNextNonce({ 'account_index': accountIndex, 'api_key_index': apiKeyIndex });
return this.safeInteger(response, 'nonce');
}
/**
* @method
* @name lighter#createOrder
* @description create a trade order
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of currency you want to trade in units of base currency
* @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.timeInForce] 'GTT' or 'IOC', default is 'GTT'
* @param {int} [params.clientOrderId] client order id, should be unique for each order, default is a random number
* @param {string} [params.triggerPrice] trigger price for stop loss or take profit orders, in units of the quote currency
* @param {boolean} [params.reduceOnly] whether the order is reduce only, default false
* @param {int} [params.nonce] nonce for the account
* @param {int} [params.apiKeyIndex] apiKeyIndex
* @param {int} [params.accountIndex] accountIndex
* @param {int} [params.orderExpiry] orderExpiry
* @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();
let accountIndex = undefined;
[accountIndex, params] = await this.handleAccountIndex(params, 'createOrder', 'accountIndex', 'account_index');
params['accountIndex'] = accountIndex;
const market = this.market(symbol);
let groupingType = undefined;
[groupingType, params] = this.handleOptionAndParams(params, 'createOrder', 'groupingType', 3); // default GROUPING_TYPE_ONE_TRIGGERS_A_ONE_CANCELS_THE_OTHER
const orderRequests = this.createOrderRequest(symbol, type, side, amount, price, params);
// for php
const totalOrderRequests = orderRequests.length;
let apiKeyIndex = undefined;
let order = undefined;
if (totalOrderRequests > 0) {
order = orderRequests[0];
apiKeyIndex = order['api_key_index'];
}
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
const signer = await this.loadAccount(this.options['chainId'], this.getLighterPrivateKey(strAccountIndex, strApiKeyIndex), strApiKeyIndex, strAccountIndex, params);
// the nonce could be updated
if (this.safeInteger(order, 'nonce') === undefined) {
order['nonce'] = await this.fetchNonce(accountIndex, apiKeyIndex);
}
let txType = undefined;
let txInfo = undefined;
if (totalOrderRequests < 2) {
[txType, txInfo] = this.lighterSignCreateOrder(signer, order);
}
else {
const signingPayload = {
'grouping_type': groupingType,
'orders': orderRequests,
'nonce': order['nonce'],
'api_key_index': apiKeyIndex,
'account_index': accountIndex,
};
if (this.safeBool(this.options, 'builderFee', true)) {
signingPayload['integrator_account_index'] = order['integrator_account_index'];
signingPayload['integrator_taker_fee'] = order['integrator_taker_fee'];
signingPayload['integrator_maker_fee'] = order['integrator_maker_fee'];
}
[txType, txInfo] = this.lighterSignCreateGroupedOrders(signer, signingPayload);
}
const request = {
'tx_type': txType,
'tx_info': txInfo,
};
const response = await this.publicPostSendTx(request);
//
// {
// "code": 200,
// "message": "{\"ratelimit\": \"didn't use volume quota\"}",
// "tx_hash": "txhash",
// "predicted_execution_time_ms": 1766088500120
// }
//
return this.parseOrder(this.deepExtend(response, order), market);
}
/**
* @method
* @name lighter#editOrder
* @description cancels an order and places a new order
* @param {string} id order id
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of the currency you want to trade in units of the base currency
* @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.accountIndex] account index
* @param {string} [params.apiKeyIndex] api key index
* @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();
let apiKeyIndex = undefined;
[apiKeyIndex, params] = this.handleApiKeyIndex(params, 'editOrder', 'apiKeyIndex', 'api_key_index');
let accountIndex = undefined;
[accountIndex, params] = await this.handleAccountIndex(params, 'editOrder', 'accountIndex', 'account_index');
const strAccountIndex = this.numberToString(accountIndex);
const strApiKeyIndex = this.numberToString(apiKeyIndex);
const signer = await this.loadAccount(this.options['chainId'], this.getLighterPrivateKey(strAccountIndex, strApiKeyIndex), strApiKeyIndex, strAccountIndex, params);
const market = this.market(symbol);
const marketInfo = this.safeDict(market, 'info');
const amountScale = this.pow('10', marketInfo['size_decimals']);
const priceScale = this.pow('10', marketInfo['price_decimals']);
const triggerPrice = this.safeStringN(params, ['stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice']);
params = this.omit(params, ['stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice']);
let amountStr = undefined;
const priceStr = this.priceToPrecision(symbol, price);
let triggerPriceStr = '0'; // default is 0
if (triggerPrice !== undefined) {
amountStr = this.numberToString(amount);
triggerPriceStr = this.priceToPrecision(symbol, triggerPrice);
}
else {
amountStr = this.amountToPrecision(symbol, amount);
}
const nonce = await this.fetchNonce(accountIndex, apiKeyIndex, params);
const signRaw = {
'market_index': this.parseToInt(market['id']),
'index': this.parseToInt(id),
'base_amount': this.parseToInt(Precise["default"].stringMul(amountStr, amountScale)),
'price': this.parseToInt(Precise["default"].stringMul(priceStr, priceScale)),
'trigger_price': this.parseToInt(Precise["default"].stringMul(triggerPriceStr, priceScale)),
'nonce': nonce,
'api_key_index': apiKeyIndex,
'account_index': accountIndex,
'integrator_account_index': this.options['integratorAccountIndex'],
'integrator_taker_fee': this.options['integratorTakerFee'],
'integrator_maker_fee': this.options['integratorMakerFee'],
};
const [txType, txInfo] = this.lighterSignModifyOrder(signer, this.extend(signRaw, params));
const request = {
'tx_type': txType,
'tx_info': txInfo,
};
const response = await this.publicPostSendTx(request);
return this.parseOrder(response, market);
}
/**
* @method
* @n