import { Command } from "commander"; import { OpenApiClient, SwapParams, MultiSwapParams, StrategyCreateParams, StrategyCancelParams } from "../client/OpenApiClient.js"; import { getConfig } from "../config.js"; import { exitOnError, printResult } from "../output.js"; import { validateAddress, validateChain, validatePercent, validatePositiveInt } from "../validate.js"; export function registerSwapCommands(program: Command): void { program .command("swap") .description("Submit a token swap") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .requiredOption("--from
", "Wallet address (must match API Key binding)") .requiredOption("--input-token
", "Input token contract address") .requiredOption("--output-token
", "Output token contract address") .option("--amount ", "Input raw amount (smallest unit)") .option("--percent ", "Input amount as a percentage, e.g. 50 = 50%, 1 = 1%; only valid when input_token is NOT a currency", parseFloat) .option("--slippage ", "Slippage tolerance (e.g. 30 = 30%)", parseFloat) .option("--auto-slippage", "Enable automatic slippage") .option("--min-output ", "Minimum output amount") .option("--anti-mev", "Enable anti-MEV protection, default true") .option("--priority-fee ", "Priority fee in SOL (≥ 0.00001, SOL only)") .option("--tip-fee ", "Tip fee (SOL ≥ 0.00001 SOL / BSC ≥ 0.000001 BNB)") .option("--gas-price ", "Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01); mutually exclusive with --gas-level") .option("--gas-level ", "Gas price tier (eth only): low / average / high; mutually exclusive with --gas-price") .option("--auto-fee", "Auto fee mode (eth only); delegates fee selection to trading bot for condition_orders strategy") .option("--max-fee-per-gas ", "EIP-1559 max fee per gas (BSC / BASE / ETH)") .option("--max-priority-fee-per-gas ", "EIP-1559 max priority fee per gas (BSC / BASE / ETH)") .option("--condition-orders ", 'JSON array of take-profit/stop-loss conditions, e.g. \'[{"order_type":"profit_stop","side":"sell","price_scale":"150","sell_ratio":"100"}]\'; trace types: \'[{"order_type":"profit_stop_trace","side":"sell","price_scale":"150","sell_ratio":"100","drawdown_rate":"50"}]\'') .option("--sell-ratio-type ", "Sell ratio base: buy_amount (default) / hold_amount; only used with --condition-orders") .option("--raw", "Output raw JSON") .action(async (opts) => { if (opts.percent == null && !opts.amount) { console.error("[gmgn-cli] Either --amount or --percent must be provided"); process.exit(1); } validateChain(opts.chain); validateAddress(opts.from, opts.chain, "--from"); validateAddress(opts.inputToken, opts.chain, "--input-token"); validateAddress(opts.outputToken, opts.chain, "--output-token"); if (opts.amount) validatePositiveInt(opts.amount, "--amount"); if (opts.percent != null) validatePercent(opts.percent); const params: SwapParams = { chain: opts.chain, from_address: opts.chain === "sol" ? opts.from : opts.from.toLowerCase(), input_token: opts.inputToken, output_token: opts.outputToken, input_amount: opts.percent != null ? (opts.amount ?? "0") : opts.amount, }; if (opts.percent != null) params.input_amount_bps = String(Math.round(opts.percent * 100)); if (opts.slippage != null) params.slippage = opts.slippage; if (opts.autoSlippage) params.auto_slippage = true; if (opts.minOutput) params.min_output_amount = opts.minOutput; if (opts.antiMev) params.is_anti_mev = true; if (opts.priorityFee) params.priority_fee = opts.priorityFee; if (opts.tipFee) params.tip_fee = opts.tipFee; if (opts.autoFee) params.auto_fee = true; if (opts.gasPrice) params.gas_price = String(Math.round(parseFloat(opts.gasPrice) * 1e9)); if (opts.gasLevel) params.gas_level = opts.gasLevel; if (opts.maxFeePerGas) params.max_fee_per_gas = opts.maxFeePerGas; if (opts.maxPriorityFeePerGas) params.max_priority_fee_per_gas = opts.maxPriorityFeePerGas; if (opts.conditionOrders) { try { params.condition_orders = JSON.parse(opts.conditionOrders); } catch { console.error("[gmgn-cli] --condition-orders must be valid JSON"); process.exit(1); } } if (opts.sellRatioType) params.sell_ratio_type = opts.sellRatioType; const client = new OpenApiClient(getConfig(true)); const data = await client.swap(params).catch(exitOnError); printResult(data, opts.raw); }); program .command("multi-swap") .description("Submit token swaps across multiple wallets concurrently (up to 100 wallets)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .requiredOption("--accounts ", "Comma-separated wallet addresses (all must be bound to the API Key)") .requiredOption("--input-token
", "Input token contract address") .requiredOption("--output-token
", "Output token contract address") .option("--input-amount ", 'JSON map of wallet→amount (smallest unit), e.g. \'{"addr1":"1000000","addr2":"2000000"}\'') .option("--input-amount-bps ", 'JSON map of wallet→percent in bps (1–10000, e.g. 5000=50%), e.g. \'{"addr1":"5000"}\'') .option("--output-amount ", "JSON map of wallet→target output amount") .option("--slippage ", "Slippage tolerance (e.g. 30 = 30%)", parseFloat) .option("--auto-slippage", "Enable automatic slippage") .option("--anti-mev", "Enable anti-MEV protection") .option("--priority-fee ", "Priority fee in SOL (SOL only, ≥ 0.00001)") .option("--tip-fee ", "Tip fee (SOL ≥ 0.00001 / BSC ≥ 0.000001 BNB)") .option("--gas-price ", "Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01); mutually exclusive with --gas-level") .option("--gas-level ", "Gas price tier (eth only): low / average / high; mutually exclusive with --gas-price") .option("--auto-fee", "Auto fee mode (eth only); delegates fee selection to trading bot for condition_orders strategy") .option("--max-fee-per-gas ", "EIP-1559 max fee per gas (BSC / BASE / ETH)") .option("--max-priority-fee-per-gas ", "EIP-1559 max priority fee per gas (BSC / BASE / ETH)") .option("--condition-orders ", "JSON array of take-profit/stop-loss conditions attached to each successful wallet's swap") .option("--sell-ratio-type ", "Sell ratio base: buy_amount (default) / hold_amount; only used with --condition-orders") .option("--raw", "Output raw JSON") .action(async (opts) => { if (!opts.inputAmount && !opts.inputAmountBps && !opts.outputAmount) { console.error("[gmgn-cli] At least one of --input-amount, --input-amount-bps, or --output-amount must be provided"); process.exit(1); } validateChain(opts.chain); const accounts = (opts.accounts as string).split(",").map((a: string) => a.trim()).filter(Boolean); if (accounts.length === 0 || accounts.length > 100) { console.error("[gmgn-cli] --accounts must be 1–100 comma-separated wallet addresses"); process.exit(1); } const params: MultiSwapParams = { chain: opts.chain, accounts: opts.chain === "sol" ? accounts : accounts.map((a: string) => a.toLowerCase()), input_token: opts.inputToken, output_token: opts.outputToken, }; if (opts.inputAmount) { try { params.input_amount = JSON.parse(opts.inputAmount); } catch { console.error("[gmgn-cli] --input-amount must be valid JSON"); process.exit(1); } } if (opts.inputAmountBps) { try { params.input_amount_bps = JSON.parse(opts.inputAmountBps); } catch { console.error("[gmgn-cli] --input-amount-bps must be valid JSON"); process.exit(1); } } if (opts.outputAmount) { try { params.output_amount = JSON.parse(opts.outputAmount); } catch { console.error("[gmgn-cli] --output-amount must be valid JSON"); process.exit(1); } } if (opts.slippage != null) params.slippage = opts.slippage; if (opts.autoSlippage) params.auto_slippage = true; if (opts.antiMev) params.is_anti_mev = true; if (opts.priorityFee) params.priority_fee = opts.priorityFee; if (opts.tipFee) params.tip_fee = opts.tipFee; if (opts.autoFee) params.auto_fee = true; if (opts.gasPrice) params.gas_price = String(Math.round(parseFloat(opts.gasPrice) * 1e9)); if (opts.gasLevel) params.gas_level = opts.gasLevel; if (opts.maxFeePerGas) params.max_fee_per_gas = opts.maxFeePerGas; if (opts.maxPriorityFeePerGas) params.max_priority_fee_per_gas = opts.maxPriorityFeePerGas; if (opts.conditionOrders) { try { params.condition_orders = JSON.parse(opts.conditionOrders); } catch { console.error("[gmgn-cli] --condition-orders must be valid JSON"); process.exit(1); } } if (opts.sellRatioType) params.sell_ratio_type = opts.sellRatioType; const client = new OpenApiClient(getConfig(true)); const data = await client.multiSwap(params).catch(exitOnError); printResult(data, opts.raw); }); const order = program.command("order").description("Order management commands"); order .command("quote") .description("Get a swap quote without submitting a transaction (exist auth — GMGN_API_KEY only, no private key needed)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .requiredOption("--from
", "Wallet address") .requiredOption("--input-token
", "Input token contract address") .requiredOption("--output-token
", "Output token contract address") .requiredOption("--amount ", "Input amount (smallest unit)") .requiredOption("--slippage ", "Slippage tolerance (e.g. 30 = 30%)", parseFloat) .option("--raw", "Output raw JSON") .action(async (opts) => { validateChain(opts.chain); validateAddress(opts.from, opts.chain, "--from"); validateAddress(opts.inputToken, opts.chain, "--input-token"); validateAddress(opts.outputToken, opts.chain, "--output-token"); validatePositiveInt(opts.amount, "--amount"); const client = new OpenApiClient(getConfig(true)); const data = await client .quoteOrder(opts.chain, opts.from, opts.inputToken, opts.outputToken, opts.amount, opts.slippage) .catch(exitOnError); printResult(data, opts.raw); }); order .command("get") .description("Query order status (requires private key)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth / monad") .requiredOption("--order-id ", "Order ID") .option("--raw", "Output raw JSON") .action(async (opts) => { validateChain(opts.chain); const client = new OpenApiClient(getConfig(true)); const data = await client.queryOrder(opts.orderId, opts.chain).catch(exitOnError); printResult(data, opts.raw); }); program .command("gas-price") .description("Query recommended gas price tiers for any chain (exist auth — API Key only; eth / bsc / base / sol)") .requiredOption("--chain ", "Chain: eth / bsc / base / sol") .option("--raw", "Output raw JSON") .action(async (opts) => { const client = new OpenApiClient(getConfig(false)); const data = await client.getGasPrice(opts.chain).catch(exitOnError); printResult(data, opts.raw); }); const strategy = order.command("strategy").description("Limit/strategy order management"); strategy .command("create") .description("Create a limit/strategy order (requires private key)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .requiredOption("--from
", "Wallet address (must match API Key binding)") .requiredOption("--base-token
", "Base token contract address") .requiredOption("--quote-token
", "Quote token contract address") .requiredOption("--order-type ", "Order type: limit_order / smart_trade") .requiredOption("--sub-order-type ", "Sub-order type: buy_low / buy_high / stop_loss / take_profit (limit_order); mix_trade (smart_trade with condition_orders)") .option("--check-price ", "Trigger check price (required for limit_order; omit for smart_trade)") .option("--open-price ", "Open price of the position") .option("--amount-in ", "Input amount (smallest unit)") .option("--amount-in-percent ", "Input amount as a percentage (e.g. 50 = 50%)") .option("--limit-price-mode ", "Price mode: exact / slippage (default: slippage)") .option("--expire-in ", "Order expiry in seconds", parseInt) .option("--sell-ratio-type ", "Sell ratio basis: buy_amount (default) / hold_amount") .option("--quote-investment ", "Quote token investment amount (smart_trade)") .option("--slippage ", "Slippage tolerance (e.g. 30 = 30%)", parseFloat) .option("--auto-slippage", "Enable automatic slippage") .option("--priority-fee ", "Priority fee in SOL (required for SOL chain)") .option("--tip-fee ", "Tip fee (required for SOL chain)") .option("--auto-fee", "Auto fee mode (eth only); delegates fee selection to trading bot") .option("--gas-price ", "Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01 gwei); mutually exclusive with --gas-level") .option("--gas-level ", "Gas price tier (eth only): low / average / high; mutually exclusive with --gas-price") .option("--max-fee-per-gas ", "EIP-1559 max fee per gas (BSC / BASE / ETH)") .option("--max-priority-fee-per-gas ", "EIP-1559 max priority fee per gas (BSC / BASE / ETH)") .option("--anti-mev", "Enable anti-MEV protection") .option("--condition-orders ", "JSON array of condition sub-orders for smart_trade (must include a buy_low entry + TP/SL entries)") .option("--sell-param ", "JSON object of sell-side trade params used when a TP/SL condition fires (required for smart_trade)") .option("--buy-param ", "JSON object of buy-side trade params override for smart_trade") .option("--raw", "Output raw JSON") .action(async (opts) => { if (!opts.amountIn && !opts.amountInPercent) { console.error("[gmgn-cli] Either --amount-in or --amount-in-percent must be provided"); process.exit(1); } if (!opts.slippage && !opts.autoSlippage) { console.error("[gmgn-cli] Either --slippage or --auto-slippage must be provided"); process.exit(1); } validateChain(opts.chain); const params: StrategyCreateParams = { chain: opts.chain, from_address: opts.from, base_token: opts.baseToken, quote_token: opts.quoteToken, order_type: opts.orderType, sub_order_type: opts.subOrderType, }; if (opts.checkPrice) params.check_price = opts.checkPrice; if (opts.openPrice) params.open_price = opts.openPrice; if (opts.amountIn) params.amount_in = opts.amountIn; if (opts.amountInPercent) params.amount_in_percent = opts.amountInPercent; if (opts.limitPriceMode) params.limit_price_mode = opts.limitPriceMode; if (opts.expireIn != null) params.expire_in = opts.expireIn; if (opts.sellRatioType) params.sell_ratio_type = opts.sellRatioType; if (opts.quoteInvestment) params.quote_investment = opts.quoteInvestment; if (opts.slippage != null) params.slippage = opts.slippage; if (opts.autoSlippage) params.auto_slippage = true; if (opts.priorityFee) params.priority_fee = opts.priorityFee; if (opts.tipFee) params.tip_fee = opts.tipFee; if (opts.autoFee) params.auto_fee = true; if (opts.gasPrice) params.gas_price = String(Math.round(parseFloat(opts.gasPrice) * 1e9)); if (opts.gasLevel) params.gas_level = opts.gasLevel; if (opts.maxFeePerGas) params.max_fee_per_gas = opts.maxFeePerGas; if (opts.maxPriorityFeePerGas) params.max_priority_fee_per_gas = opts.maxPriorityFeePerGas; if (opts.antiMev) params.is_anti_mev = true; if (opts.conditionOrders) { try { params.condition_orders = JSON.parse(opts.conditionOrders); } catch { console.error("[gmgn-cli] --condition-orders must be valid JSON"); process.exit(1); } } if (opts.sellParam) { try { params.sell_param = JSON.parse(opts.sellParam); } catch { console.error("[gmgn-cli] --sell-param must be valid JSON"); process.exit(1); } } if (opts.buyParam) { try { params.buy_param = JSON.parse(opts.buyParam); } catch { console.error("[gmgn-cli] --buy-param must be valid JSON"); process.exit(1); } } const client = new OpenApiClient(getConfig(true)); const data = await client.createStrategyOrder(params).catch(exitOnError); printResult(data, opts.raw); }); strategy .command("list") .description("List strategy orders (requires private key)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .option("--type ", "open (default) / history") .option("--from
", "Filter by wallet address") .option("--group-tag ", "Filter by group: LimitOrder / STMix") .option("--base-token
", "Filter by token address") .option("--page-token ", "Pagination cursor from previous response") .option("--limit ", "Results per page", parseInt) .option("--raw", "Output raw JSON") .action(async (opts) => { validateChain(opts.chain); const extra: Record = {}; if (opts.type) extra["type"] = opts.type; if (opts.from) extra["from_address"] = opts.from; if (opts.groupTag) extra["group_tag"] = opts.groupTag; if (opts.baseToken) extra["base_token"] = opts.baseToken; if (opts.pageToken) extra["page_token"] = opts.pageToken; if (opts.limit != null) extra["limit"] = opts.limit; const client = new OpenApiClient(getConfig(true)); const data = await client.getStrategyOrders(opts.chain, extra).catch(exitOnError); printResult(data, opts.raw); }); strategy .command("cancel") .description("Cancel a strategy order (requires private key)") .requiredOption("--chain ", "Chain: sol / bsc / base / eth") .requiredOption("--from
", "Wallet address (must match API Key binding)") .requiredOption("--order-id ", "Order ID to cancel") .option("--order-type ", "Order type: limit_order / smart_trade") .option("--close-sell-model ", "Sell model when closing") .option("--raw", "Output raw JSON") .action(async (opts) => { validateChain(opts.chain); const params: StrategyCancelParams = { chain: opts.chain, from_address: opts.from, order_id: opts.orderId, }; if (opts.orderType) params.order_type = opts.orderType; if (opts.closeSellModel) params.close_sell_model = opts.closeSellModel; const client = new OpenApiClient(getConfig(true)); const data = await client.cancelStrategyOrder(params).catch(exitOnError); printResult(data, opts.raw); }); }