UNPKG

ccxt

Version:

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

1,042 lines • 156 kB
// --------------------------------------------------------------------------- import Exchange from './abstract/lighter.js'; import { ArgumentsRequired, BadRequest, ExchangeError, InvalidOrder, NotSupported, RateLimitExceeded } from './base/errors.js'; import { TICK_SIZE } from './base/functions/number.js'; import Precise from './base/Precise.js'; import { ecdsa } from './base/functions/crypto.js'; import { keccak_256 as keccak } from './static_dependencies/noble-hashes/sha3.js'; import { secp256k1 } from './static_dependencies/noble-curves/secp256k1.js'; // --------------------------------------------------------------------------- /** * @class lighter * @augments Exchange */ export default class lighter extends Exchange { 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': ExchangeError, '21500': ExchangeError, '21501': ExchangeError, '21502': ExchangeError, '21503': ExchangeError, '21504': ExchangeError, '21505': ExchangeError, '21506': ExchangeError, '21507': ExchangeError, '21508': ExchangeError, '21511': ExchangeError, '21512': ExchangeError, '21600': InvalidOrder, '21601': InvalidOrder, '21602': InvalidOrder, '21603': InvalidOrder, '21604': InvalidOrder, '21605': InvalidOrder, '21606': InvalidOrder, '21607': InvalidOrder, '21608': InvalidOrder, '21611': InvalidOrder, '21612': InvalidOrder, '21613': InvalidOrder, '21614': InvalidOrder, '21700': InvalidOrder, '21701': InvalidOrder, '21702': InvalidOrder, '21703': InvalidOrder, '21704': InvalidOrder, '21705': InvalidOrder, '21706': InvalidOrder, '21707': InvalidOrder, '21708': InvalidOrder, '21709': InvalidOrder, '21710': InvalidOrder, '21711': InvalidOrder, '21712': InvalidOrder, '21713': InvalidOrder, '21714': InvalidOrder, '21715': InvalidOrder, '21716': InvalidOrder, '21717': InvalidOrder, '21718': InvalidOrder, '21719': InvalidOrder, '21720': InvalidOrder, '21721': InvalidOrder, '21722': InvalidOrder, '21723': InvalidOrder, '21724': InvalidOrder, '21725': InvalidOrder, '21726': InvalidOrder, '21727': InvalidOrder, '21728': InvalidOrder, '21729': InvalidOrder, '21730': InvalidOrder, '21731': InvalidOrder, '21732': InvalidOrder, '21733': InvalidOrder, '21734': InvalidOrder, '21735': InvalidOrder, '21736': InvalidOrder, '21737': InvalidOrder, '21738': InvalidOrder, '21739': InvalidOrder, '21740': InvalidOrder, '21901': InvalidOrder, '21902': InvalidOrder, '21903': InvalidOrder, '21904': InvalidOrder, '21905': InvalidOrder, '21906': InvalidOrder, '23000': RateLimitExceeded, '23001': RateLimitExceeded, '23002': RateLimitExceeded, '23003': RateLimitExceeded, // Too Many Connections! }, 'broad': {}, }, 'fees': { 'taker': 0, 'maker': 0, }, 'requiredCredentials': { 'apiKey': false, 'secret': false, 'walletAddress': false, 'privateKey': true, 'password': false, }, 'precisionMode': 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 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 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 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 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 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.stringMul(n, '1'); const c = this.parseToInt(m); if (c < 0) { throw new BadRequest(this.id + ' pow() requires m > 0.'); } if (c === 0) { return '1'; } if (c > 100) { throw new BadRequest(this.id + ' pow() requires m < 100.'); } for (let i = 1; i < c; i++) { r = Precise.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), keccak, 'hex'); } signHash(hash, privateKey) { this.checkRequiredCredentials(); const signature = ecdsa(hash.slice(-64), privateKey.slice(-64), 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 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.stringMul(amountStr, amountScale)); request['avg_execution_price'] = this.parseToInt(Precise.stringMul(priceStr, priceScale)); request['trigger_price'] = this.parseToInt(Precise.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 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.stringMul(amountStr, amountScale)), 'price': this.parseToInt(Precise.stringMul(priceStr, priceScale)), 'trigger_price': this.parseToInt(Precise.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 * @name lighter#fetchStatus * @description the latest known information on the availability of the exchange API * @see https://apidocs.lighter.xyz/reference/status * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [status structure]{@link https://docs.ccxt.com/?id=exchange-status-structure} */ async fetchStatus(params = {}) { const response = await this.rootGet(params); // // { // "status": "1", // "network_id": "1", // "timestamp": "1717777777" // } // const status = this.safeString(response, 'status');