fix(strategy): add missing order_type/sub_order_type, condition_orders, and group_tag

- Add --order-type and --sub-order-type as required flags to order strategy create
  (server requires both; omitting them caused every create call to 400)
- Remove --side from strategy create (field no longer exists in the API)
- Make --open-price optional in strategy create (matches API spec)
- Fix --gas-price in strategy create to convert gwei→wei (consistent with swap)
- Add --condition-orders and --sell-ratio-type to swap command for
  take-profit/stop-loss conditions attached after a successful swap
- Add --group-tag (LimitOrder/STMix) to order strategy list
- Add --order-type to order strategy cancel
- Add StrategyConditionOrder interface; extend SwapParams with condition_orders
  and sell_ratio_type; add order_type/sub_order_type to StrategyCreateParams;
  remove side; add order_type to StrategyCancelParams
- Update docs/cli-usage.md: fix strategy create table, add chain fee requirement
  notes (SOL requires priority_fee+tip_fee; BSC requires gas_price), add
  condition_orders example and strategy_order_id response field, add
  TRADING_BOT_UNAVAILABLE error code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
David Lau
2026-04-02 21:50:41 +08:00
committed by GMGN.AI
parent dd1cb9d2e5
commit 759ab74709
3 changed files with 71 additions and 20 deletions
+33 -10
View File
@@ -400,6 +400,8 @@ npx gmgn-cli swap \
[--gas-price <gwei>] \
[--max-fee-per-gas <amount>] \
[--max-priority-fee-per-gas <amount>] \
[--condition-orders <json>] \
[--sell-ratio-type <buy_amount|hold_amount>] \
[--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 <wallet_address> \
--base-token <base_token_address> \
--quote-token <quote_token_address> \
--side <buy|sell> \
--open-price <price> \
--order-type <limit_order> \
--sub-order-type <buy_low|buy_high|stop_loss|take_profit> \
--check-price <price> \
[--open-price <price>] \
[--amount-in <amount> | --amount-in-percent <pct>] \
[--slippage <n> | --auto-slippage] \
[--limit-price-mode <exact|slippage>] \
@@ -477,7 +491,7 @@ gmgn-cli order strategy create \
[--sell-ratio-type <buy_amount|hold_amount>] \
[--priority-fee <sol>] \
[--tip-fee <amount>] \
[--gas-price <amount>] \
[--gas-price <gwei>] \
[--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 <chain> [--type <open|history>] [--from <address>] [--base-token <address>] [--page-token <token>] [--limit <n>] [--raw]
gmgn-cli order strategy list --chain <chain> [--type <open|history>] [--from <address>] [--group-tag <tag>] [--base-token <address>] [--page-token <token>] [--limit <n>] [--raw]
```
| Option | Required | Description |
@@ -525,6 +545,7 @@ gmgn-cli order strategy list --chain <chain> [--type <open|history>] [--from <ad
| `--chain` | Yes | `sol` / `bsc` / `base` |
| `--type` | No | `open` (default) / `history` |
| `--from` | No | Filter by wallet address |
| `--group-tag` | No | Filter by group: `LimitOrder` / `STMix` |
| `--base-token` | No | Filter by token address |
| `--page-token` | No | Pagination cursor from previous response |
| `--limit` | No | Results per page |
@@ -544,7 +565,7 @@ gmgn-cli order strategy list --chain <chain> [--type <open|history>] [--from <ad
Cancel a strategy order. **Requires `GMGN_PRIVATE_KEY` configured in `.env`.**
```bash
gmgn-cli order strategy cancel --chain <chain> --from <wallet_address> --order-id <id> [--close-sell-model <model>] [--raw]
gmgn-cli order strategy cancel --chain <chain> --from <wallet_address> --order-id <id> [--order-type <type>] [--close-sell-model <model>] [--raw]
```
| Option | Required | Description |
@@ -552,6 +573,7 @@ gmgn-cli order strategy cancel --chain <chain> --from <wallet_address> --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 |
+13 -2
View File
@@ -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;
}
+25 -8
View File
@@ -24,6 +24,8 @@ export function registerSwapCommands(program: Command): void {
.option("--gas-price <gwei>", "Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01)")
.option("--max-fee-per-gas <amount>", "EIP-1559 max fee per gas (Base)")
.option("--max-priority-fee-per-gas <amount>", "EIP-1559 max priority fee per gas (Base)")
.option("--condition-orders <json>", '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 <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 <address>", "Wallet address (must match API Key binding)")
.requiredOption("--base-token <address>", "Base token contract address")
.requiredOption("--quote-token <address>", "Quote token contract address")
.requiredOption("--side <side>", "Direction: buy / sell")
.requiredOption("--open-price <price>", "Open price")
.requiredOption("--order-type <type>", "Order type: limit_order")
.requiredOption("--sub-order-type <type>", "Sub-order type: buy_low / buy_high / stop_loss / take_profit")
.requiredOption("--check-price <price>", "Trigger check price")
.option("--open-price <price>", "Open/entry price")
.option("--amount-in <amount>", "Input amount (smallest unit)")
.option("--amount-in-percent <pct>", "Input amount as a percentage (e.g. 50 = 50%)")
.option("--limit-price-mode <mode>", "Price mode: exact / slippage (default: slippage)")
@@ -117,9 +129,9 @@ export function registerSwapCommands(program: Command): void {
.option("--sell-ratio-type <type>", "Sell ratio basis: buy_amount (default) / hold_amount")
.option("--slippage <n>", "Slippage tolerance (e.g. 0.01 = 1%)", parseFloat)
.option("--auto-slippage", "Enable automatic slippage")
.option("--priority-fee <sol>", "Priority fee in SOL (SOL only)")
.option("--tip-fee <amount>", "Tip fee")
.option("--gas-price <amount>", "Gas price in wei (EVM chains)")
.option("--priority-fee <sol>", "Priority fee in SOL (required for SOL chain)")
.option("--tip-fee <amount>", "Tip fee (required for SOL chain)")
.option("--gas-price <gwei>", "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>", "Chain: sol / bsc / base")
.option("--type <type>", "open (default) / history")
.option("--from <address>", "Filter by wallet address")
.option("--group-tag <tag>", "Filter by group: LimitOrder / STMix")
.option("--base-token <address>", "Filter by token address")
.option("--page-token <token>", "Pagination cursor from previous response")
.option("--limit <n>", "Results per page", parseInt)
@@ -172,6 +186,7 @@ export function registerSwapCommands(program: Command): void {
const extra: Record<string, string | number> = {};
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>", "Chain: sol / bsc / base")
.requiredOption("--from <address>", "Wallet address (must match API Key binding)")
.requiredOption("--order-id <id>", "Order ID to cancel")
.option("--order-type <type>", "Order type: limit_order / smart_trade")
.option("--close-sell-model <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);