diff --git a/docs/cli-usage.md b/docs/cli-usage.md index 6677dde..5fd41e0 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -400,6 +400,8 @@ npx gmgn-cli swap \ [--gas-price ] \ [--max-fee-per-gas ] \ [--max-priority-fee-per-gas ] \ + [--condition-orders ] \ + [--sell-ratio-type ] \ [--raw] ``` @@ -421,6 +423,16 @@ npx gmgn-cli swap \ | `--gas-price` | No | Gas price in gwei (BSC ≥ 0.05 gwei / BASE/ETH ≥ 0.01 gwei) | | `--max-fee-per-gas` | No | EIP-1559 max fee per gas (Base/ETH only) | | `--max-priority-fee-per-gas` | No | EIP-1559 max priority fee per gas (Base/ETH only) | +| `--condition-orders` | No | JSON array of take-profit/stop-loss conditions attached after a successful swap (see example below); only `profit_stop` and `loss_stop` are supported | +| `--sell-ratio-type` | No | Sell ratio base for `--condition-orders`: `buy_amount` (default) / `hold_amount` | + +**`--condition-orders` example** (100% sell at 2× price, 100% sell at 50% price): + +```json +[{"order_type":"profit_stop","side":"sell","price_scale":"100","sell_ratio":"100"},{"order_type":"loss_stop","side":"sell","price_scale":"50","sell_ratio":"100"}] +``` + +> Strategy creation is **best-effort**: if the swap succeeds but strategy creation fails, the swap result is still returned (with `strategy_order_id` absent). Only `order_type`, `side`, `price_scale`, and `sell_ratio` are accepted per condition — extra fields cause a 400 error. **Response fields (data):** @@ -439,6 +451,7 @@ npx gmgn-cli swap \ | `output_token` | string | Output token contract address | | `filled_input_amount` | string | Actual input consumed (smallest unit); empty if not filled | | `filled_output_amount` | string | Actual output received (smallest unit); empty if not filled | +| `strategy_order_id` | string | Strategy order ID; only present when `--condition-orders` was passed and strategy creation succeeded | --- @@ -467,9 +480,10 @@ gmgn-cli order strategy create \ --from \ --base-token \ --quote-token \ - --side \ - --open-price \ + --order-type \ + --sub-order-type \ --check-price \ + [--open-price ] \ [--amount-in | --amount-in-percent ] \ [--slippage | --auto-slippage] \ [--limit-price-mode ] \ @@ -477,7 +491,7 @@ gmgn-cli order strategy create \ [--sell-ratio-type ] \ [--priority-fee ] \ [--tip-fee ] \ - [--gas-price ] \ + [--gas-price ] \ [--anti-mev] \ [--raw] ``` @@ -488,9 +502,10 @@ gmgn-cli order strategy create \ | `--from` | Yes | Wallet address (must match API Key binding) | | `--base-token` | Yes | Base token contract address | | `--quote-token` | Yes | Quote token contract address | -| `--side` | Yes | `buy` / `sell` | -| `--open-price` | Yes | Open price | +| `--order-type` | Yes | Order type: `limit_order` | +| `--sub-order-type` | Yes | Sub-order type: `buy_low` / `buy_high` / `stop_loss` / `take_profit` | | `--check-price` | Yes | Trigger check price | +| `--open-price` | No | Open/entry price | | `--amount-in` | No* | Input amount (smallest unit); required unless `--amount-in-percent` is used | | `--amount-in-percent` | No* | Input as percentage (e.g. `50` = 50%); required unless `--amount-in` is used | | `--limit-price-mode` | No | `exact` / `slippage` (default: `slippage`) | @@ -498,11 +513,16 @@ gmgn-cli order strategy create \ | `--sell-ratio-type` | No | `buy_amount` (default) / `hold_amount` | | `--slippage` | No | Slippage tolerance, e.g. `0.01` = 1% | | `--auto-slippage` | No | Enable automatic slippage | -| `--priority-fee` | No | Priority fee in SOL (SOL only) | -| `--tip-fee` | No | Tip fee | -| `--gas-price` | No | Gas price in wei (EVM chains) | +| `--priority-fee` | No | Priority fee in SOL (**required for SOL chain**) | +| `--tip-fee` | No | Tip fee (**required for SOL chain**) | +| `--gas-price` | No | Gas price in gwei (**required for BSC**; ≥ 0.05 / BASE/ETH ≥ 0.01) | | `--anti-mev` | No | Enable anti-MEV protection | +> **Chain-specific fee requirements:** +> - **SOL:** `--priority-fee` and `--tip-fee` are both **required** (returns 400 if missing) +> - **BSC:** `--gas-price` is **required** (returns 400 if missing) +> - **ETH/BASE:** no required fee fields + **Response fields (data):** | Field | Type | Description | @@ -517,7 +537,7 @@ gmgn-cli order strategy create \ List strategy orders. Uses normal auth. ```bash -gmgn-cli order strategy list --chain [--type ] [--from
] [--base-token
] [--page-token ] [--limit ] [--raw] +gmgn-cli order strategy list --chain [--type ] [--from
] [--group-tag ] [--base-token
] [--page-token ] [--limit ] [--raw] ``` | Option | Required | Description | @@ -525,6 +545,7 @@ gmgn-cli order strategy list --chain [--type ] [--from [--type ] [--from --from --order-id [--close-sell-model ] [--raw] +gmgn-cli order strategy cancel --chain --from --order-id [--order-type ] [--close-sell-model ] [--raw] ``` | Option | Required | Description | @@ -552,6 +573,7 @@ gmgn-cli order strategy cancel --chain --from --order-i | `--chain` | Yes | `sol` / `bsc` / `base` | | `--from` | Yes | Wallet address (must match API Key binding) | | `--order-id` | Yes | Order ID to cancel | +| `--order-type` | No | Order type: `limit_order` / `smart_trade` | | `--close-sell-model` | No | Sell model when closing | --- @@ -662,4 +684,5 @@ Important notes: | `BAD_REQUEST` | 400 | Missing or invalid request parameters | | `INTERNAL_API_UNAVAILABLE` | 502 | Downstream market API unavailable | | `BROKER_UNAVAILABLE` | 502 | Downstream trade broker unavailable | +| `TRADING_BOT_UNAVAILABLE` | 502 | Trading bot service unreachable (strategy endpoints) | | `INTERNAL_ERROR` | 500 | Internal server error | diff --git a/src/client/OpenApiClient.ts b/src/client/OpenApiClient.ts index c9d2ce6..cb51e3c 100644 --- a/src/client/OpenApiClient.ts +++ b/src/client/OpenApiClient.ts @@ -85,6 +85,15 @@ export interface SwapParams { gas_price?: string; max_fee_per_gas?: string; max_priority_fee_per_gas?: string; + condition_orders?: StrategyConditionOrder[]; + sell_ratio_type?: string; +} + +export interface StrategyConditionOrder { + order_type: string; // "profit_stop" | "loss_stop" + side: string; // "sell" + price_scale: string; + sell_ratio: string; } export interface StrategyCreateParams { @@ -92,9 +101,10 @@ export interface StrategyCreateParams { from_address: string; base_token: string; quote_token: string; - side: string; - open_price: string; + order_type: string; + sub_order_type: string; check_price: string; + open_price?: string; amount_in?: string; amount_in_percent?: string; limit_price_mode?: string; @@ -118,6 +128,7 @@ export interface StrategyCancelParams { chain: string; from_address: string; order_id: string; + order_type?: string; close_sell_model?: string; } diff --git a/src/commands/swap.ts b/src/commands/swap.ts index d4d9f7d..7a3abff 100644 --- a/src/commands/swap.ts +++ b/src/commands/swap.ts @@ -24,6 +24,8 @@ export function registerSwapCommands(program: Command): void { .option("--gas-price ", "Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01)") .option("--max-fee-per-gas ", "EIP-1559 max fee per gas (Base)") .option("--max-priority-fee-per-gas ", "EIP-1559 max priority fee per gas (Base)") + .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"}]\'') + .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) { @@ -54,6 +56,15 @@ export function registerSwapCommands(program: Command): void { if (opts.gasPrice) params.gas_price = String(Math.round(parseFloat(opts.gasPrice) * 1e9)); 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); @@ -107,9 +118,10 @@ export function registerSwapCommands(program: Command): void { .requiredOption("--from
", "Wallet address (must match API Key binding)") .requiredOption("--base-token
", "Base token contract address") .requiredOption("--quote-token
", "Quote token contract address") - .requiredOption("--side ", "Direction: buy / sell") - .requiredOption("--open-price ", "Open price") + .requiredOption("--order-type ", "Order type: limit_order") + .requiredOption("--sub-order-type ", "Sub-order type: buy_low / buy_high / stop_loss / take_profit") .requiredOption("--check-price ", "Trigger check price") + .option("--open-price ", "Open/entry price") .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)") @@ -117,9 +129,9 @@ export function registerSwapCommands(program: Command): void { .option("--sell-ratio-type ", "Sell ratio basis: buy_amount (default) / hold_amount") .option("--slippage ", "Slippage tolerance (e.g. 0.01 = 1%)", parseFloat) .option("--auto-slippage", "Enable automatic slippage") - .option("--priority-fee ", "Priority fee in SOL (SOL only)") - .option("--tip-fee ", "Tip fee") - .option("--gas-price ", "Gas price in wei (EVM chains)") + .option("--priority-fee ", "Priority fee in SOL (required for SOL chain)") + .option("--tip-fee ", "Tip fee (required for SOL chain)") + .option("--gas-price ", "Gas price in gwei (required for BSC; ≥ 0.05 gwei / BASE/ETH ≥ 0.01 gwei)") .option("--anti-mev", "Enable anti-MEV protection") .option("--raw", "Output raw JSON") .action(async (opts) => { @@ -137,10 +149,11 @@ export function registerSwapCommands(program: Command): void { from_address: opts.from, base_token: opts.baseToken, quote_token: opts.quoteToken, - side: opts.side, - open_price: opts.openPrice, + order_type: opts.orderType, + sub_order_type: opts.subOrderType, 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; @@ -150,7 +163,7 @@ export function registerSwapCommands(program: Command): void { 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.gasPrice) params.gas_price = opts.gasPrice; + if (opts.gasPrice) params.gas_price = String(Math.round(parseFloat(opts.gasPrice) * 1e9)); if (opts.antiMev) params.is_anti_mev = true; const client = new OpenApiClient(getConfig(true)); const data = await client.createStrategyOrder(params).catch(exitOnError); @@ -163,6 +176,7 @@ export function registerSwapCommands(program: Command): void { .requiredOption("--chain ", "Chain: sol / bsc / base") .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) @@ -172,6 +186,7 @@ export function registerSwapCommands(program: Command): void { 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; @@ -186,6 +201,7 @@ export function registerSwapCommands(program: Command): void { .requiredOption("--chain ", "Chain: sol / bsc / base") .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) => { @@ -195,6 +211,7 @@ export function registerSwapCommands(program: Command): void { 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);