UNPKG

@drift-labs/common

Version:

Common functions for Drift

1,023 lines โ€ข 47 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseSwapMode = exports.parseDirection = exports.parseAmount = exports.parseArgs = exports.main = void 0; const anchor = __importStar(require("@coral-xyz/anchor")); const web3_js_1 = require("@solana/web3.js"); const sdk_1 = require("@drift-labs/sdk"); const tweetnacl_1 = require("tweetnacl"); const CentralServerDrift_1 = require("./Drift/clients/CentralServerDrift"); const utils_1 = require("../utils"); const apiUrls_1 = require("./constants/apiUrls"); const path = __importStar(require("path")); // Load environment variables from .env file const dotenv = require('dotenv'); dotenv.config({ path: path.resolve(__dirname, '.env') }); /** * CLI Tool for CentralServerDrift client * * This CLI tool allows you to execute various Drift protocol operations via command line * with human-readable parameters that are automatically converted to the proper precision. * * Prerequisites: * - Create a .env file in the same directory as this CLI file with: * ANCHOR_WALLET=[private key byte array] * ENDPOINT=https://your-rpc-endpoint.com * * Usage Examples: * ts-node cli.ts deposit --marketIndex=0 --amount=2 --userAccount=11111111111111111111111111111111 * ts-node cli.ts withdraw --marketIndex=0 --amount=1.5 --userAccount=11111111111111111111111111111111 * ts-node cli.ts settleFunding --userAccount=11111111111111111111111111111111 * ts-node cli.ts settlePnl --marketIndexes=0,1 --userAccount=11111111111111111111111111111111 * ts-node cli.ts openPerpMarketOrder --marketIndex=0 --direction=long --amount=0.1 --assetType=base --userAccount=11111111111111111111111111111111 * ts-node cli.ts openPerpMarketOrderSwift --marketIndex=0 --direction=short --amount=100 --assetType=quote --userAccount=11111111111111111111111111111111 * ts-node cli.ts openPerpNonMarketOrder --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=100 --userAccount=11111111111111111111111111111111 * ts-node cli.ts openPerpNonMarketOrderSwift --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=99.5 --userAccount=11111111111111111111111111111111 * ts-node cli.ts editOrder --userAccount=11111111111111111111111111111111 --orderId=123 --newLimitPrice=105.5 * ts-node cli.ts cancelOrder --userAccount=11111111111111111111111111111111 --orderIds=123,456,789 * ts-node cli.ts cancelAllOrders --userAccount=11111111111111111111111111111111 --marketType=perp * ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --fromAmount=1.5 --slippage=100 --swapMode=ExactIn * ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --toAmount=150 --slippage=100 --swapMode=ExactOut * ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary" */ // Shared configuration let centralServerDrift; let wallet; /** * Parse command line arguments into a key-value object */ function parseArgs(args) { const parsed = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const [key, value] = arg.substring(2).split('='); if (value !== undefined) { // Handle comma-separated arrays if (value.includes(',')) { parsed[key] = value.split(','); } else { parsed[key] = value; } } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) { // Handle space-separated values const nextValue = args[i + 1]; if (nextValue.includes(',')) { parsed[key] = nextValue.split(','); } else { parsed[key] = nextValue; } i++; // Skip the next argument as it's the value } else { parsed[key] = 'true'; // Boolean flag } } } return parsed; } exports.parseArgs = parseArgs; /** * Convert human-readable amount to BN with proper precision */ function parseAmount(amount, precision = sdk_1.QUOTE_PRECISION) { const floatAmount = parseFloat(amount); if (isNaN(floatAmount)) { throw new Error(`Invalid amount: ${amount}`); } // Convert to the proper precision const scaledAmount = Math.floor(floatAmount * precision.toNumber()); return new sdk_1.BN(scaledAmount); } exports.parseAmount = parseAmount; /** * Parse direction string to PositionDirection */ function parseDirection(direction) { const normalized = direction.toLowerCase(); if (normalized === 'long' || normalized === 'buy') { return sdk_1.PositionDirection.LONG; } else if (normalized === 'short' || normalized === 'sell') { return sdk_1.PositionDirection.SHORT; } else { throw new Error(`Invalid direction: ${direction}. Use 'long', 'short', 'buy', or 'sell'`); } } exports.parseDirection = parseDirection; /** * Parse order type string to OrderType */ function _parseOrderType(orderType) { const normalized = orderType.toLowerCase(); switch (normalized) { case 'limit': return sdk_1.OrderType.LIMIT; case 'market': return sdk_1.OrderType.MARKET; case 'oracle': return sdk_1.OrderType.ORACLE; case 'trigger_market': case 'stopmarket': return sdk_1.OrderType.TRIGGER_MARKET; case 'trigger_limit': case 'stoplimit': return sdk_1.OrderType.TRIGGER_LIMIT; default: throw new Error(`Invalid order type: ${orderType}. Use 'limit', 'market', 'oracle', 'trigger_market', or 'trigger_limit'`); } } /** * Parse post only string to PostOnlyParams */ function parsePostOnly(postOnly) { const normalized = postOnly.toLowerCase(); switch (normalized) { case 'none': case 'false': return sdk_1.PostOnlyParams.NONE; case 'must_post_only': case 'true': return sdk_1.PostOnlyParams.MUST_POST_ONLY; case 'try_post_only': return sdk_1.PostOnlyParams.TRY_POST_ONLY; case 'slide': return sdk_1.PostOnlyParams.SLIDE; default: throw new Error(`Invalid post only: ${postOnly}. Use 'none', 'must_post_only', 'try_post_only', or 'slide'`); } } /** * Parse swap mode string to SwapMode */ function parseSwapMode(swapMode) { const normalized = swapMode.toLowerCase(); switch (normalized) { case 'exactin': case 'exact_in': return 'ExactIn'; case 'exactout': case 'exact_out': return 'ExactOut'; default: throw new Error(`Invalid swap mode: ${swapMode}. Use 'ExactIn' or 'ExactOut'`); } } exports.parseSwapMode = parseSwapMode; /** * Get the precision for a spot market based on environment and market index */ function getMarketPrecision(marketIndex, isMainnet = true) { const markets = isMainnet ? sdk_1.MainnetSpotMarkets : sdk_1.DevnetSpotMarkets; const market = markets.find((m) => m.marketIndex === marketIndex); if (!market) { console.warn(`โš ๏ธ Market ${marketIndex} not found, using QUOTE_PRECISION as fallback`); return sdk_1.QUOTE_PRECISION; } return market.precision; } /** * Initialize CentralServerDrift instance */ async function initializeCentralServerDrift() { console.log('๐Ÿš€ Initializing CentralServerDrift...\n'); // Validate required environment variables if (!process.env.ANCHOR_WALLET) { throw new Error('ANCHOR_WALLET must be set in .env file'); } if (!process.env.ENDPOINT) { throw new Error('ENDPOINT environment variable must be set to your Solana RPC endpoint'); } // Set up the wallet wallet = new anchor.Wallet((0, sdk_1.loadKeypair)(process.env.ANCHOR_WALLET)); console.log(`โœ… Wallet Public Key: ${wallet.publicKey.toString()}`); console.log(`โœ… RPC Endpoint: ${process.env.ENDPOINT}\n`); // Initialize CentralServerDrift console.log('๐Ÿ—๏ธ Initializing CentralServerDrift...'); centralServerDrift = new CentralServerDrift_1.CentralServerDrift({ solanaRpcEndpoint: process.env.ENDPOINT, driftEnv: 'mainnet-beta', // Change to 'devnet' for devnet testing supportedPerpMarkets: [0, 1, 2], // SOL, BTC, ETH supportedSpotMarkets: [0, 1], // USDC, SOL additionalDriftClientConfig: { txVersion: 0, txParams: { computeUnits: 200000, computeUnitsPrice: 1000, }, }, }); console.log('โœ… CentralServerDrift instance created successfully\n'); // Subscribe to market data console.log('๐Ÿ“ก Subscribing to market data...'); await centralServerDrift.subscribe(); console.log('โœ… Successfully subscribed to market data\n'); } /** * Execute a regular transaction */ async function executeTransaction(txn, transactionType) { console.log(`โœ… ${transactionType} transaction created successfully`); console.log('\n๐Ÿ“ Signing Transaction...'); txn.sign([wallet.payer]); console.log('โœ… Transaction signed successfully'); console.log('\n๐Ÿš€ Sending transaction to the network...'); const { txSig } = await centralServerDrift.sendSignedTransaction(txn); console.log('โœ… Transaction sent successfully!'); console.log(`๐Ÿ“‹ Transaction Signature: ${txSig === null || txSig === void 0 ? void 0 : txSig.toString()}`); console.log(`๐Ÿ” View on Solscan: https://solscan.io/tx/${txSig === null || txSig === void 0 ? void 0 : txSig.toString()}`); } const createSwiftOrderCallbacks = (orderType) => { const terminalCall = () => { console.log('๐Ÿ Order monitoring completed'); }; return { onSigningExpiry: () => { console.log('Swift order signing expired'); }, onSigningSuccess: (signedMessage, orderUuid, orderParamsMessage) => { console.log('Swift order signed successfully. Signed message:', signedMessage, orderUuid, orderParamsMessage); }, onSent: () => { console.log(`โœ… ${orderType} Swift order submitted successfully`); }, onConfirmed: (event) => { console.log('โœ… Order confirmed!'); console.log(`๐Ÿ“‹ Order ID: ${event.orderId}`); console.log(`๐Ÿ“‹ Hash: ${event.hash}`); terminalCall(); }, onExpired: (event) => { console.error('โฐ Order expired:', event.message); terminalCall(); }, onErrored: (event) => { console.error('โŒ Order failed:', event.message); console.error(`๐Ÿ“‹ Status: ${event.status}`); terminalCall(); }, }; }; /** * CLI Command: deposit */ async function depositCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const amount = args.amount; if (!userAccount || isNaN(marketIndex) || !amount) { throw new Error('Required arguments: --userAccount, --marketIndex, --amount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const amountBN = parseAmount(amount, sdk_1.QUOTE_PRECISION); // Most deposits are USDC (quote precision) console.log('--- ๐Ÿ“ฅ Deposit Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); const depositTxn = await centralServerDrift.getDepositTxn(userAccountPubkey, amountBN, marketIndex); await executeTransaction(depositTxn, 'Deposit'); } /** * CLI Command: createUserAndDeposit */ async function createUserAndDepositCommand(args) { const marketIndexArg = args.marketIndex; const amountArg = args.amount; const referrerName = args.referrerName; const accountName = args.accountName; const poolIdArg = args.poolId; const fromSubAccountIdArg = args.fromSubAccountId; const customMaxMarginRatioArg = args.customMaxMarginRatio; if (!marketIndexArg || !amountArg) { throw new Error('Required arguments: --marketIndex, --amount'); } const marketIndex = parseInt(marketIndexArg, 10); if (isNaN(marketIndex)) { throw new Error(`Invalid marketIndex: ${marketIndexArg}`); } const precision = getMarketPrecision(marketIndex, true); const amountBN = parseAmount(amountArg, precision); const options = {}; if (referrerName) { options.referrerName = referrerName; } if (accountName) { options.accountName = accountName; } if (poolIdArg !== undefined) { const poolId = parseInt(poolIdArg, 10); if (isNaN(poolId)) { throw new Error(`Invalid poolId: ${poolIdArg}`); } options.poolId = poolId; } if (fromSubAccountIdArg !== undefined) { const fromSubAccountId = parseInt(fromSubAccountIdArg, 10); if (isNaN(fromSubAccountId)) { throw new Error(`Invalid fromSubAccountId: ${fromSubAccountIdArg}`); } options.fromSubAccountId = fromSubAccountId; } if (customMaxMarginRatioArg !== undefined) { const customMaxMarginRatio = parseFloat(customMaxMarginRatioArg); if (isNaN(customMaxMarginRatio)) { throw new Error(`Invalid customMaxMarginRatio: ${customMaxMarginRatioArg}`); } options.customMaxMarginRatio = customMaxMarginRatio; } console.log('--- ๐Ÿ†• Create User & Deposit Transaction ---'); console.log(`๐Ÿ”‘ Authority (wallet): ${wallet.publicKey.toString()}`); console.log(`๐Ÿช Spot Market Index: ${marketIndex}`); console.log(`๐Ÿ’ฐ Initial Deposit: ${amountArg} (${amountBN.toString()} raw units)`); if (options.accountName) { console.log(`๐Ÿ“ Account Name: ${options.accountName}`); } if (options.referrerName) { console.log(`๐Ÿ™Œ Referrer Name: ${options.referrerName}`); } if (options.poolId !== undefined) { console.log(`๐ŸŠ Pool ID: ${options.poolId}`); } if (options.fromSubAccountId !== undefined) { console.log(`๐Ÿ” Funding From Subaccount ID: ${options.fromSubAccountId}`); } if (options.customMaxMarginRatio !== undefined) { console.log(`๐Ÿ“ Custom Max Margin Ratio: ${options.customMaxMarginRatio}`); } const hasOptions = Object.keys(options).length > 0; const { transaction, userAccountPublicKey, subAccountId } = await centralServerDrift.getCreateAndDepositTxn(wallet.publicKey, amountBN, marketIndex, hasOptions ? options : undefined); console.log(`๐Ÿ†” New User Account: ${userAccountPublicKey.toString()}`); console.log(`๐Ÿงพ Subaccount ID: ${subAccountId}`); await executeTransaction(transaction, 'Create User & Deposit'); } /** * CLI Command: withdraw */ async function withdrawCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const amount = args.amount; const isBorrow = args.isBorrow === 'true'; const isMax = args.isMax === 'true'; if (!userAccount || isNaN(marketIndex) || !amount) { throw new Error('Required arguments: --userAccount, --marketIndex, --amount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const amountBN = parseAmount(amount, sdk_1.QUOTE_PRECISION); console.log('--- ๐Ÿ“ค Withdraw Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ณ Is Borrow: ${isBorrow}`); console.log(`๐Ÿ“Š Is Max: ${isMax}`); const withdrawTxn = await centralServerDrift.getWithdrawTxn(userAccountPubkey, amountBN, marketIndex, { isBorrow, isMax }); await executeTransaction(withdrawTxn, 'Withdraw'); } /** * CLI Command: settleFunding */ async function settleFundingCommand(args) { const userAccount = args.userAccount; if (!userAccount) { throw new Error('Required arguments: --userAccount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); console.log('--- ๐Ÿ’ฐ Settle Funding Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); const settleFundingTxn = await centralServerDrift.getSettleFundingTxn(userAccountPubkey); await executeTransaction(settleFundingTxn, 'Settle Funding'); } /** * CLI Command: settlePnl */ async function settlePnlCommand(args) { const userAccount = args.userAccount; const marketIndexesArg = args.marketIndexes; if (!userAccount || !marketIndexesArg) { throw new Error('Required arguments: --userAccount, --marketIndexes (comma-separated)'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const marketIndexes = Array.isArray(marketIndexesArg) ? marketIndexesArg.map((idx) => parseInt(idx)) : [parseInt(marketIndexesArg)]; console.log('--- ๐Ÿ“ˆ Settle PnL Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿ“Š Market Indexes: ${marketIndexes.join(', ')}`); const settlePnlTxn = await centralServerDrift.getSettlePnlTxn(userAccountPubkey, marketIndexes); await executeTransaction(settlePnlTxn, 'Settle PnL'); } /** * CLI Command: openPerpMarketOrder (regular transaction) */ async function openPerpMarketOrderCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const direction = args.direction; const amount = args.amount; const assetType = args.assetType || 'base'; const dlobServerHttpUrl = apiUrls_1.API_URLS.DLOB; if (!userAccount || isNaN(marketIndex) || !direction || !amount) { throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --amount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const directionEnum = parseDirection(direction); const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION; const amountBN = parseAmount(amount, precision); console.log('--- ๐ŸŽฏ Open Perp Order Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ“Š Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ฑ Asset Type: ${assetType}`); console.log(`๐ŸŒ DLOB Server: ${dlobServerHttpUrl}`); const orderTxn = await centralServerDrift.getOpenPerpMarketOrderTxn({ userAccountPublicKey: userAccountPubkey, assetType: assetType, marketIndex, direction: directionEnum, amount: amountBN, useSwift: false, }); await executeTransaction(orderTxn, 'Open Perp Order'); } /** * CLI Command: openPerpMarketOrderSwift (Swift signed message order) */ async function openPerpMarketOrderSwiftCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const direction = args.direction; const amount = args.amount; const assetType = args.assetType || 'base'; const dlobServerHttpUrl = apiUrls_1.API_URLS.DLOB; const swiftServerUrl = args.swiftServerUrl || apiUrls_1.API_URLS.SWIFT; if (!userAccount || isNaN(marketIndex) || !direction || !amount) { throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --amount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const directionEnum = parseDirection(direction); const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION; const amountBN = parseAmount(amount, precision); console.log('--- โšก Open Perp Order Swift ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ“Š Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ฑ Asset Type: ${assetType}`); console.log(`๐ŸŒ DLOB Server: ${dlobServerHttpUrl}`); console.log(`โšก Swift Server: ${swiftServerUrl}`); console.log(`๐Ÿ”‘ Wallet Public Key: ${wallet.publicKey.toString()}`); console.log('\n๐Ÿ‘๏ธ Monitoring order status...'); try { await centralServerDrift.getOpenPerpMarketOrderTxn({ userAccountPublicKey: userAccountPubkey, assetType: assetType, marketIndex, direction: directionEnum, amount: amountBN, useSwift: true, swiftOptions: { wallet: { signMessage: async (message) => { const signature = tweetnacl_1.sign.detached(message, wallet.payer.secretKey); return new Uint8Array(signature); }, takerAuthority: wallet.publicKey, signingAuthority: wallet.publicKey, }, callbacks: createSwiftOrderCallbacks('Open Perp Order'), }, }); console.log('โœ… [CLI] Swift order finished'); } catch (error) { console.error('โŒ [CLI] Error creating Swift order:', error); throw error; } } /** * CLI Command: openPerpNonMarketOrder (regular limit/oracle order) */ async function openPerpNonMarketOrderCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const direction = args.direction; const amount = args.amount; const assetType = args.assetType || 'base'; const baseAssetAmount = args.baseAssetAmount; const orderType = args.orderType; const limitPrice = args.limitPrice; const triggerPrice = args.triggerPrice; const reduceOnly = args.reduceOnly === 'true'; const postOnly = args.postOnly || 'none'; const oraclePriceOffset = args.oraclePriceOffset; if (!userAccount || isNaN(marketIndex) || !direction || !orderType) { throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --orderType'); } if (!amount && !baseAssetAmount) { throw new Error('Either --amount or --baseAssetAmount must be provided'); } // Validate price requirements based on order type if (orderType === 'limit') { if (!limitPrice) { throw new Error(`Order type '${orderType}' requires --limitPrice`); } } else if (orderType === 'takeProfit' || orderType === 'stopLoss') { if (!triggerPrice) { throw new Error(`Order type '${orderType}' requires --triggerPrice`); } } else if (orderType === 'oracleLimit') { if (!oraclePriceOffset) { throw new Error(`Order type '${orderType}' requires --oraclePriceOffset`); } } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const directionEnum = parseDirection(direction); const postOnlyEnum = parsePostOnly(postOnly); const limitPriceBN = limitPrice ? parseAmount(limitPrice, sdk_1.PRICE_PRECISION) : undefined; const triggerPriceBN = triggerPrice ? parseAmount(triggerPrice, sdk_1.PRICE_PRECISION) : undefined; const oraclePriceOffsetBN = oraclePriceOffset ? parseAmount(oraclePriceOffset, sdk_1.PRICE_PRECISION) : undefined; const orderConfig = orderType === 'limit' ? { orderType, limitPrice: limitPriceBN, } : orderType === 'takeProfit' || orderType === 'stopLoss' ? { orderType, triggerPrice: triggerPriceBN, } : { orderType, oraclePriceOffset: oraclePriceOffsetBN, }; console.log('--- ๐Ÿ“‹ Open Perp Non-Market Order ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ“Š Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`); let amountBN; if (amount) { const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION; amountBN = parseAmount(amount, precision); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ฑ Asset Type: ${assetType}`); } else { amountBN = parseAmount(baseAssetAmount, sdk_1.BASE_PRECISION); console.log(`๐Ÿ’ฐ Base Asset Amount: ${baseAssetAmount} (${amountBN.toString()} raw units)`); } if (limitPriceBN) { console.log(`๐Ÿ’ต Limit Price: ${limitPrice} (${limitPriceBN.toString()} raw units)`); } if (triggerPriceBN) { console.log(`๐ŸŽฏ Trigger Price: ${triggerPrice} (${triggerPriceBN.toString()} raw units)`); } console.log(`๐Ÿ“ Order Type: ${orderType}`); console.log(`๐Ÿ”„ Reduce Only: ${reduceOnly}`); console.log(`๐Ÿ“Œ Post Only: ${postOnly} (${utils_1.ENUM_UTILS.toStr(postOnlyEnum)})`); // Just call the main method - it will handle both approaches internally const orderTxn = await centralServerDrift.getOpenPerpNonMarketOrderTxn({ userAccountPublicKey: userAccountPubkey, marketIndex, direction: directionEnum, orderConfig, useSwift: false, reduceOnly, postOnly: postOnlyEnum, assetType: assetType, amount: amountBN, }); await executeTransaction(orderTxn, 'Open Perp Non-Market Order'); } /** * CLI Command: openPerpNonMarketOrderSwift (Swift signed message limit order) */ async function openPerpNonMarketOrderSwiftCommand(args) { const userAccount = args.userAccount; const marketIndex = parseInt(args.marketIndex); const direction = args.direction; const amount = args.amount; const assetType = args.assetType || 'base'; const baseAssetAmount = args.baseAssetAmount; const orderType = args.orderType; const limitPrice = args.limitPrice; const reduceOnly = args.reduceOnly === 'true'; const postOnly = args.postOnly || 'none'; const swiftServerUrl = apiUrls_1.API_URLS.SWIFT; if (!userAccount || isNaN(marketIndex) || !direction || !orderType) { throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --orderType'); } if (!amount && !baseAssetAmount) { throw new Error('Either --amount or --baseAssetAmount must be provided'); } // Swift orders only support LIMIT order type if (orderType.toLowerCase() !== 'limit') { throw new Error('Swift orders only support LIMIT order type'); } // LIMIT orders require limitPrice if (!limitPrice) { throw new Error('LIMIT orders require --limitPrice'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const directionEnum = parseDirection(direction); const postOnlyEnum = parsePostOnly(postOnly); const limitPriceBN = parseAmount(limitPrice, sdk_1.PRICE_PRECISION); const orderConfig = { orderType: 'limit', limitPrice: limitPriceBN, }; console.log('--- โšก Open Perp Non-Market Order Swift ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Index: ${marketIndex}`); console.log(`๐Ÿ“Š Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`); let amountBN; if (amount) { const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION; amountBN = parseAmount(amount, precision); console.log(`๐Ÿ’ฐ Amount: ${amount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ฑ Asset Type: ${assetType}`); } else { amountBN = parseAmount(baseAssetAmount, sdk_1.BASE_PRECISION); console.log(`๐Ÿ’ฐ Base Asset Amount: ${baseAssetAmount} (${amountBN.toString()} raw units)`); } console.log(`๐Ÿ’ต Limit Price: ${limitPrice} (${limitPriceBN.toString()} raw units)`); console.log(`๐Ÿ“ Order Type: ${orderType})`); console.log(`๐Ÿ”„ Reduce Only: ${reduceOnly}`); console.log(`๐Ÿ“Œ Post Only: ${postOnly} (${utils_1.ENUM_UTILS.toStr(postOnlyEnum)})`); console.log(`โšก Swift Server: ${swiftServerUrl}`); console.log(`๐Ÿ”‘ Wallet Public Key: ${wallet.publicKey.toString()}`); try { const swiftOptions = { wallet: { signMessage: async (message) => { const signature = tweetnacl_1.sign.detached(message, wallet.payer.secretKey); return new Uint8Array(signature); }, takerAuthority: wallet.publicKey, signingAuthority: wallet.publicKey, }, callbacks: createSwiftOrderCallbacks('Open Perp Non-Market Order'), }; console.log('\n๐Ÿ‘๏ธ Monitoring order status...'); // Use the main method - it handles both approaches internally await centralServerDrift.getOpenPerpNonMarketOrderTxn({ userAccountPublicKey: userAccountPubkey, marketIndex, direction: directionEnum, orderConfig, reduceOnly, postOnly: postOnlyEnum, assetType: assetType, amount: amountBN, useSwift: true, swiftOptions, }); console.log('โœ… [CLI] Swift order finished'); } catch (error) { console.error('โŒ [CLI] Error creating Swift non-market order:', error); throw error; } } /** * Show CLI usage information */ function showUsage() { console.log('๐Ÿ“‹ Drift CLI Usage'); console.log(''); console.log('Available Commands:'); console.log(''); console.log('๐Ÿ’ฐ deposit'); console.log(' ts-node cli.ts deposit --userAccount=<pubkey> --marketIndex=<num> --amount=<num>'); console.log(' Example: ts-node cli.ts deposit --userAccount=11111111111111111111111111111111 --marketIndex=0 --amount=100'); console.log(''); console.log('๐Ÿ†• createUserAndDeposit'); console.log(' ts-node cli.ts createUserAndDeposit --marketIndex=<num> --amount=<num> [--accountName=<string>] [--referrerName=<string>] [--poolId=<num>] [--fromSubAccountId=<num>] [--customMaxMarginRatio=<num>]'); console.log(' Example: ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary"'); console.log(''); console.log('๐Ÿ’ธ withdraw'); console.log(' ts-node cli.ts withdraw --userAccount=<pubkey> --marketIndex=<num> --amount=<num> [--isBorrow=<bool>] [--isMax=<bool>]'); console.log(' Example: ts-node cli.ts withdraw --userAccount=11111111111111111111111111111111 --marketIndex=0 --amount=50'); console.log(''); console.log('๐Ÿฆ settleFunding'); console.log(' ts-node cli.ts settleFunding --userAccount=<pubkey>'); console.log(' Example: ts-node cli.ts settleFunding --userAccount=11111111111111111111111111111111'); console.log(''); console.log('๐Ÿ“Š settlePnl'); console.log(' ts-node cli.ts settlePnl --userAccount=<pubkey> --marketIndexes=<comma-separated>'); console.log(' Example: ts-node cli.ts settlePnl --userAccount=11111111111111111111111111111111 --marketIndexes=0,1'); console.log(''); console.log('๐ŸŽฏ openPerpMarketOrder'); console.log(' ts-node cli.ts openPerpMarketOrder --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>]'); console.log(' Example: ts-node cli.ts openPerpMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base'); console.log(''); console.log('โšก openPerpMarketOrderSwift'); console.log(' ts-node cli.ts openPerpMarketOrderSwift --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>]'); console.log(' Example: ts-node cli.ts openPerpMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=short --amount=100 --assetType=quote'); console.log(''); console.log('๐Ÿ“‹ openPerpNonMarketOrder'); console.log(' ts-node cli.ts openPerpNonMarketOrder --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>] --orderType=<limit|trigger_limit|trigger_market|oracle> [--limitPrice=<num>] [--triggerPrice=<num>] [--reduceOnly=<true|false>] [--postOnly=<none|must_post_only|try_post_only|slide>]'); console.log(' New LIMIT: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base --orderType=limit --limitPrice=100'); console.log(' New QUOTE: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=100 --assetType=quote --orderType=limit --limitPrice=100'); console.log(' Legacy: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=100'); console.log(''); console.log('โšก openPerpNonMarketOrderSwift'); console.log(' ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>] --orderType=limit --limitPrice=<num> [--reduceOnly=<true|false>] [--postOnly=<none|must_post_only|try_post_only|slide>]'); console.log(' New: ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base --orderType=limit --limitPrice=99.5'); console.log(' Legacy: ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=99.5'); console.log(''); console.log('โœ๏ธ editOrder'); console.log(' ts-node cli.ts editOrder --userAccount=<pubkey> --orderId=<num> [--newDirection=<long|short>] [--newBaseAmount=<num>] [--newLimitPrice=<num>] [--newTriggerPrice=<num>] [--reduceOnly=<true|false>] [--postOnly=<true|false>]'); console.log(' Example: ts-node cli.ts editOrder --userAccount=11111111111111111111111111111111 --orderId=123 --newLimitPrice=105.5'); console.log(''); console.log('โŒ cancelOrder'); console.log(' ts-node cli.ts cancelOrder --userAccount=<pubkey> --orderIds=<comma-separated-list>'); console.log(' Example: ts-node cli.ts cancelOrder --userAccount=11111111111111111111111111111111 --orderIds=123,456,789'); console.log(''); console.log('๐Ÿงน cancelAllOrders'); console.log(' ts-node cli.ts cancelAllOrders --userAccount=<pubkey> [--marketType=<perp|spot>] [--marketIndex=<num>] [--direction=<long|short>]'); console.log(' Example: ts-node cli.ts cancelAllOrders --userAccount=11111111111111111111111111111111 --marketType=perp'); console.log(''); console.log('๐Ÿ”„ swap'); console.log(' ts-node cli.ts swap --userAccount=<pubkey> --fromMarketIndex=<num> --toMarketIndex=<num> [--fromAmount=<num>] [--toAmount=<num>] [--slippage=<bps>] [--swapMode=<ExactIn|ExactOut>]'); console.log(' ExactIn: ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --fromAmount=1.5 --swapMode=ExactIn'); console.log(' ExactOut: ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --toAmount=150 --swapMode=ExactOut'); console.log(''); console.log('Options:'); console.log(' --help, -h Show this help message'); console.log(''); console.log('Notes:'); console.log(' - Amounts are in human-readable format (e.g., 1.5 USDC, 0.1 SOL)'); console.log(' - Direction can be: long, short, buy, sell'); console.log(' - Asset type: base (for native tokens like SOL) or quote (for USDC amounts)'); console.log(' - Swap modes:'); console.log(' * ExactIn: Specify --fromAmount (how much input token to swap)'); console.log(' * ExactOut: Specify --toAmount (how much output token to receive)'); console.log(' - Ensure your .env file contains ANCHOR_WALLET and ENDPOINT'); } /** * CLI Command: editOrder */ async function editOrderCommand(args) { const userAccount = args.userAccount; const orderId = parseInt(args.orderId); const newDirection = args.newDirection; const newBaseAmount = args.newBaseAmount; const newLimitPrice = args.newLimitPrice; const newTriggerPrice = args.newTriggerPrice; const reduceOnly = args.reduceOnly === 'true'; const postOnly = args.postOnly === 'true'; if (!userAccount || isNaN(orderId)) { throw new Error('Required arguments: --userAccount, --orderId'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const editParams = {}; if (newDirection) { editParams.newDirection = parseDirection(newDirection); } if (newBaseAmount) { editParams.newBaseAmount = parseAmount(newBaseAmount, sdk_1.BASE_PRECISION); } if (newLimitPrice) { editParams.newLimitPrice = parseAmount(newLimitPrice, sdk_1.PRICE_PRECISION); } if (newTriggerPrice) { editParams.newTriggerPrice = parseAmount(newTriggerPrice, sdk_1.PRICE_PRECISION); } if (reduceOnly) { editParams.reduceOnly = true; } if (postOnly) { editParams.postOnly = true; } console.log('--- โœ๏ธ Edit Order ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿ†” Order ID: ${orderId}`); console.log(`๐Ÿ“ Edit Parameters:`, editParams); const editOrderTxn = await centralServerDrift.getEditOrderTxn(userAccountPubkey, orderId, editParams); await executeTransaction(editOrderTxn, 'Edit Order'); } /** * CLI Command: cancelOrder */ async function cancelOrderCommand(args) { const userAccount = args.userAccount; const orderIds = args.orderIds; if (!userAccount || !orderIds) { throw new Error('Required arguments: --userAccount, --orderIds'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); const orderIdArray = orderIds.split(',').map((id) => parseInt(id.trim())); console.log('--- โŒ Cancel Orders ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿ†” Order IDs: ${orderIdArray.join(', ')}`); const cancelOrderTxn = await centralServerDrift.getCancelOrdersTxn(userAccountPubkey, orderIdArray); await executeTransaction(cancelOrderTxn, 'Cancel Orders'); } /** * CLI Command: cancelAllOrders */ async function cancelAllOrdersCommand(args) { const userAccount = args.userAccount; const marketType = args.marketType; const marketIndex = args.marketIndex ? parseInt(args.marketIndex) : undefined; const direction = args.direction; if (!userAccount) { throw new Error('Required arguments: --userAccount'); } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); let marketTypeEnum = undefined; let directionEnum = undefined; if (marketType) { marketTypeEnum = marketType.toLowerCase() === 'perp' ? { perp: {} } : { spot: {} }; } if (direction) { directionEnum = parseDirection(direction); } console.log('--- ๐Ÿงน Cancel All Orders ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿช Market Type Filter: ${marketType || 'none'}`); console.log(`๐Ÿ“Š Market Index Filter: ${marketIndex !== undefined ? marketIndex : 'none'}`); console.log(`๐Ÿ“ˆ Direction Filter: ${direction || 'none'}`); const cancelAllOrdersTxn = await centralServerDrift.getCancelAllOrdersTxn(userAccountPubkey, marketTypeEnum, marketIndex, directionEnum); await executeTransaction(cancelAllOrdersTxn, 'Cancel All Orders'); } /** * CLI Command: swap */ async function swapCommand(args) { const userAccount = args.userAccount; const fromMarketIndex = parseInt(args.fromMarketIndex); const toMarketIndex = parseInt(args.toMarketIndex); const fromAmount = args.fromAmount; const toAmount = args.toAmount; const slippage = args.slippage ? parseInt(args.slippage) : 10; // Default 0.1% const swapMode = args.swapMode || 'ExactIn'; if (!userAccount || isNaN(fromMarketIndex) || isNaN(toMarketIndex)) { throw new Error('Required arguments: --userAccount, --fromMarketIndex, --toMarketIndex'); } const swapModeEnum = parseSwapMode(swapMode); // Validate amount parameters based on swap mode if (swapModeEnum === 'ExactIn') { if (!fromAmount) { throw new Error('ExactIn swap mode requires --fromAmount'); } if (toAmount) { console.warn('โš ๏ธ Warning: --toAmount ignored in ExactIn mode, using --fromAmount'); } } else if (swapModeEnum === 'ExactOut') { if (!toAmount) { throw new Error('ExactOut swap mode requires --toAmount'); } if (fromAmount) { console.warn('โš ๏ธ Warning: --fromAmount ignored in ExactOut mode, using --toAmount'); } } const userAccountPubkey = new web3_js_1.PublicKey(userAccount); // Use the appropriate amount based on swap mode const amount = swapModeEnum === 'ExactIn' ? fromAmount : toAmount; // Determine which market to use for precision (input market for ExactIn, output market for ExactOut) const precisionMarketIndex = swapModeEnum === 'ExactIn' ? fromMarketIndex : toMarketIndex; // Get the appropriate precision for the amount based on the market const isMainnet = true; // Default to mainnet, could be made configurable const precision = getMarketPrecision(precisionMarketIndex, isMainnet); const amountBN = parseAmount(amount, precision); console.log('--- ๐Ÿ”„ Swap Transaction ---'); console.log(`๐Ÿ‘ค User Account: ${userAccount}`); console.log(`๐Ÿ“ค From Market Index: ${fromMarketIndex}`); console.log(`๐Ÿ“ฅ To Market Index: ${toMarketIndex}`); console.log(`๐Ÿ”„ Swap Mode: ${swapMode}`); console.log(`๐ŸŽฏ Using precision from market ${precisionMarketIndex}: ${precision.toString()}`); if (swapModeEnum === 'ExactIn') { console.log(`๐Ÿ’ฐ From Amount: ${fromAmount} (${amountBN.toString()} raw units)`); console.log(`๐Ÿ’ฐ To Amount: Will be calculated based on quote`); } else { console.log(`๐Ÿ’ฐ From Amount: Will be calculated based on quote`); console.log(`๐Ÿ’ฐ To Amount: ${toAmount} (${amountBN.toString()} raw units)`); } console.log(`๐Ÿ“Š Slippage: ${slippage} BPS (${slippage / 100}%)`); const swapTxn = await centralServerDrift.getSwapTxn(userAccountPubkey, fromMarketIndex, toMarketIndex, amountBN, { slippageBps: slippage, swapMode: swapModeEnum, }); await executeTransaction(swapTxn, 'Swap'); } /** * Main CLI entry point */ async function main() { const args = process.argv.slice(2); if (args.length === 0 || args[0] === '--help' || args[0] === '-h' || args[0] === 'help') { showUsage(); return; } const command = args[0]; const parsedArgs = parseArgs(args.slice(1)); // Initialize the client first await initializeCentralServerDrift(); // Route to appropriate command switch (command) { case 'createUserAndDeposit': await createUserAndDepositCommand(parsedArgs); break; case 'deposit': await depositCommand(parsedArgs); break; case 'withdraw': await withdrawCommand(parsedArgs); break; case 'settleFunding': await settleFundingCommand(parsedArgs); break; case 'settlePnl': await settlePnlCommand(parsedArgs); break; case 'openPerpMarketOrder': await openPerpMarketOrderCommand(parsedArgs); break; case 'openPerpMarketOrderSwift': await openPerpMarketOrderSwiftCommand(parsedArgs); break; case 'openPerpNonMarketOrder': await openPerpNonMarketOrderCommand(parsedArgs); break; case 'openPerpNonMarketOrderSwift': await openPerpNonMarketOrderSwiftCommand(parsedArgs); break; case 'editOrder': await editOrderCommand(parsedArgs); break; case 'cancelOrder': await cancelOrderCommand(parsedArgs); break; case 'cancelAllOrders': await cancelAllOrdersCommand(parsedArgs); break; case 'swap': await swapCommand(parsedArgs); break; default: console.error(`โŒ Unknown command: ${command}`); console.error(''); showUsage(); process.exit(1); } } exports.main = main; // Run CLI if this file is executed directly if (require.main === module) { main() .then(() => { console.log('\nโœจ Command executed successfully!'); // Don't exit immediately for Swift orders (they need time to process) setTimeout(() => process.exit(0), 2000); }) .catch((error) => { console.error('\n๐Ÿ’ฅ Command failed:', error.message || error); process.exit(1); }); } //# sourceMappingURL=cli.js.map