diff --git a/Readme.md b/Readme.md index 1fa6a4e..32274e7 100644 --- a/Readme.md +++ b/Readme.md @@ -495,9 +495,9 @@ gmgn-cli portfolio token-balance --chain sol --wallet --token --side buy +gmgn-cli track follow-wallet --chain sol --limit 20 --min-amount-usd 1000 # KOL trade records gmgn-cli track kol --limit 100 --raw @@ -558,6 +558,15 @@ gmgn-cli order quote \ # Query order gmgn-cli order get --chain sol --order-id + +# Multi-wallet concurrent swap +gmgn-cli multi-swap \ + --chain sol \ + --accounts , \ + --input-token \ + --output-token \ + --input-amount '{"":"1000000","":"2000000"}' \ + --slippage 0.01 ``` > `order quote` uses critical auth on `sol` / `bsc` / `base` and requires `GMGN_PRIVATE_KEY`. diff --git a/Readme.zh.md b/Readme.zh.md index 5080e38..447ea38 100644 --- a/Readme.zh.md +++ b/Readme.zh.md @@ -526,9 +526,9 @@ gmgn-cli portfolio token-balance --chain sol --wallet --token --side buy +gmgn-cli track follow-wallet --chain sol --limit 20 --min-amount-usd 1000 # KOL 交易动态 gmgn-cli track kol --limit 100 --raw @@ -589,6 +589,15 @@ gmgn-cli order quote \ # 查询订单状态 gmgn-cli order get --chain sol --order-id + +# 多钱包并发 Swap +gmgn-cli multi-swap \ + --chain sol \ + --accounts , \ + --input-token \ + --output-token \ + --input-amount '{"":"1000000","":"2000000"}' \ + --slippage 0.01 ``` > `order quote` 在 `sol` / `bsc` / `base` 上都走关键鉴权,必须配置 `GMGN_PRIVATE_KEY`。 diff --git a/docs/cli-usage.md b/docs/cli-usage.md index fac586f..510d7ef 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -276,69 +276,61 @@ npx gmgn-cli market trenches --chain [--type ] [--launchpad-pla ## portfolio follow-wallet -Query follow-wallet trade records. +Query follow-wallet trade records. Returns trades from wallets you personally follow on the GMGN platform. The follow list is resolved automatically from the GMGN user account bound to the API Key — `--wallet` is optional. Normal auth (API Key only, no private key needed). ```bash -npx gmgn-cli portfolio follow-wallet \ +gmgn-cli track follow-wallet \ --chain \ [--wallet ] \ - [--base-token ] \ - [--page-token ] \ [--limit ] \ [--side ] \ - [--cost ] \ [--filter ] \ - [--with-balance] \ - [--with-security] \ [--min-amount-usd ] \ [--max-amount-usd ] \ - [--is-gray] \ [--raw] ``` | Option | Required | Description | |--------|----------|-------------| -| `--chain` | Yes | `sol` / `bsc` / `base` / `eth` | -| `--wallet` | No | Filter by wallet address | -| `--base-token` | No | Filter by base token address | -| `--page-token` | No | Pagination cursor | -| `--limit` | No | Page size (1–200, default 100) | -| `--side` | No | Trade direction filter | -| `--cost` | No | Cost filter | +| `--chain` | Yes | `sol` / `bsc` / `base` | +| `--wallet` | No | Wallet address (optional; follow list resolved from API Key's bound user account) | +| `--limit` | No | Page size (1–100, default 10) | +| `--side` | No | Trade direction: `buy` / `sell` | | `--filter` | No | Filter conditions (repeatable) | -| `--with-balance` | No | Include balance in response | -| `--with-security` | No | Include security info in response | | `--min-amount-usd` | No | Minimum trade amount (USD) | | `--max-amount-usd` | No | Maximum trade amount (USD) | -| `--is-gray` | No | Gray mode filter | --- ## portfolio kol -Query KOL trade records (SOL chain). +Query KOL trade records. ```bash -npx gmgn-cli portfolio kol [--limit ] [--raw] +gmgn-cli track kol [--chain ] [--limit ] [--side ] [--raw] ``` | Option | Required | Description | |--------|----------|-------------| +| `--chain` | No | `sol` / `bsc` / `base` (default `sol`) | | `--limit` | No | Page size (1–200, default 100) | +| `--side` | No | Filter by trade direction: `buy` / `sell` (client-side filter) | --- ## portfolio smartmoney -Query Smart Money trade records (SOL chain). +Query Smart Money trade records. ```bash -npx gmgn-cli portfolio smartmoney [--limit ] [--raw] +gmgn-cli track smartmoney [--chain ] [--limit ] [--side ] [--raw] ``` | Option | Required | Description | |--------|----------|-------------| +| `--chain` | No | `sol` / `bsc` / `base` (default `sol`) | | `--limit` | No | Page size (1–200, default 100) | +| `--side` | No | Filter by trade direction: `buy` / `sell` (client-side filter) | --- @@ -455,6 +447,69 @@ npx gmgn-cli swap \ --- +## multi-swap + +Submit token swaps across multiple wallets concurrently. Each wallet executes independently. Up to 100 wallets per request, all must be bound to the API Key. **Requires `GMGN_PRIVATE_KEY` configured in `.env`.** + +```bash +gmgn-cli multi-swap \ + --chain \ + --accounts , \ + --input-token \ + --output-token \ + [--input-amount ] \ + [--input-amount-bps ] \ + [--output-amount ] \ + [--slippage ] \ + [--auto-slippage] \ + [--anti-mev] \ + [--priority-fee ] \ + [--tip-fee ] \ + [--auto-tip-fee] \ + [--max-auto-fee ] \ + [--gas-price ] \ + [--max-fee-per-gas ] \ + [--max-priority-fee-per-gas ] \ + [--condition-orders ] \ + [--sell-ratio-type ] \ + [--raw] +``` + +| Option | Required | Description | +|--------|----------|-------------| +| `--chain` | Yes | `sol` / `bsc` / `base` | +| `--accounts` | Yes | Comma-separated wallet addresses (1–100, all bound to API Key) | +| `--input-token` | Yes | Input token contract address | +| `--output-token` | Yes | Output token contract address | +| `--input-amount` | No* | JSON map `{"addr":"amount"}` in smallest unit; one of the three amount fields is required | +| `--input-amount-bps` | No* | JSON map `{"addr":"bps"}` where 5000 = 50%; only valid when input token is not a currency | +| `--output-amount` | No* | JSON map `{"addr":"amount"}` target output in smallest unit | +| `--slippage` | No | Slippage tolerance, e.g. `0.01` = 1% | +| `--auto-slippage` | No | Enable automatic slippage | +| `--anti-mev` | No | Enable anti-MEV protection | +| `--priority-fee` | No | Priority fee in SOL (≥ 0.00001, SOL only) | +| `--tip-fee` | No | Tip fee (SOL ≥ 0.00001 / BSC ≥ 0.000001 BNB) | +| `--auto-tip-fee` | No | Enable automatic tip fee | +| `--max-auto-fee` | No | Max automatic fee cap | +| `--gas-price` | No | Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01) | +| `--max-fee-per-gas` | No | EIP-1559 max fee per gas (Base only) | +| `--max-priority-fee-per-gas` | No | EIP-1559 max priority fee per gas (Base only) | +| `--condition-orders` | No | JSON array of take-profit/stop-loss conditions, attached to each successful wallet's swap (best-effort) | +| `--sell-ratio-type` | No | Sell ratio base: `buy_amount` (default) / `hold_amount` | + +**Response fields (data):** Array of per-wallet results: + +| Field | Type | Description | +|-------|------|-------------| +| `account` | string | Wallet address | +| `success` | bool | Whether this wallet's swap succeeded | +| `error` | string | Error message on failure | +| `error_code` | string | Error code on failure | +| `result` | object | OrderResponse on success (same fields as `swap` response) | +| `result.strategy_order_id` | string | Strategy order ID; only present when `--condition-orders` passed and strategy creation succeeded | + +--- + ## order get Query order status. **Requires `GMGN_PRIVATE_KEY` configured in `.env`.** diff --git a/skills/gmgn-swap/SKILL.md b/skills/gmgn-swap/SKILL.md index ee4b7b4..f61e4fe 100644 --- a/skills/gmgn-swap/SKILL.md +++ b/skills/gmgn-swap/SKILL.md @@ -46,6 +46,7 @@ Use the `gmgn-cli` tool to submit a token swap or query an existing order. `GMGN | Sub-command | Description | |-------------|-------------| | `swap` | Submit a token swap | +| `multi-swap` | Submit token swaps across multiple wallets concurrently (up to 100) | | `order quote` | Get a swap quote (no transaction submitted; requires critical auth) | | `order get` | Query order status | | `order strategy create` | Create a limit/strategy order (requires private key) | @@ -81,6 +82,7 @@ All swap-related routes used by this skill go through GMGN's leaky-bucket limite | Command | Route | Weight | |---------|-------|--------| | `swap` | `POST /v1/trade/swap` | 5 | +| `multi-swap` | `POST /v1/trade/multi_swap` | 5 | | `order quote` | `GET /v1/trade/quote` | 2 | | `order get` | `GET /v1/trade/query_order` | 1 | @@ -163,6 +165,79 @@ gmgn-cli swap \ --percent 50 ``` +## `multi-swap` Usage + +Submit a token swap across multiple wallets concurrently. Each wallet executes independently — one wallet's failure does not affect others. Up to 100 wallets per request. All wallets must be bound to the API Key. Requires `GMGN_PRIVATE_KEY`. + +```bash +# Basic multi-wallet swap +gmgn-cli multi-swap \ + --chain sol \ + --accounts , \ + --input-token \ + --output-token \ + --input-amount '{"":"1000000","":"2000000"}' \ + --slippage 0.01 + +# Sell a percentage of each wallet's balance (use --input-amount-bps) +gmgn-cli multi-swap \ + --chain sol \ + --accounts , \ + --input-token \ + --output-token \ + --input-amount-bps '{"":"5000","":"10000"}' \ + --slippage 0.01 + +# With per-wallet take-profit / stop-loss (condition_orders) +gmgn-cli multi-swap \ + --chain sol \ + --accounts , \ + --input-token So11111111111111111111111111111111111111112 \ + --output-token \ + --input-amount '{"":"1000000","":"2000000"}' \ + --slippage 0.3 \ + --priority-fee 0.00001 \ + --tip-fee 0.00001 \ + --condition-orders '[{"order_type":"profit_stop","side":"sell","price_scale":"100","sell_ratio":"100"},{"order_type":"loss_stop","side":"sell","price_scale":"50","sell_ratio":"100"}]' +``` + +## `multi-swap` Parameters + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `--chain` | Yes | `sol` / `bsc` / `base` | +| `--accounts` | Yes | Comma-separated wallet addresses (1–100, all must be bound to the API Key) | +| `--input-token` | Yes | Input token contract address | +| `--output-token` | Yes | Output token contract address | +| `--input-amount` | No* | JSON map of `wallet_address → input amount` (smallest unit). One of `--input-amount`, `--input-amount-bps`, or `--output-amount` is required. | +| `--input-amount-bps` | No* | JSON map of `wallet_address → percent in bps` (1–10000; 5000 = 50%). Only valid when `input_token` is NOT a currency. | +| `--output-amount` | No* | JSON map of `wallet_address → target output amount` (smallest unit). | +| `--slippage ` | No | Slippage tolerance, e.g. `0.01` = 1%. Mutually exclusive with `--auto-slippage`. | +| `--auto-slippage` | No | Enable automatic slippage. | +| `--anti-mev` | No | Enable anti-MEV protection. | +| `--priority-fee ` | No | Priority fee in SOL (≥ 0.00001, SOL only). Required when using `--condition-orders` on SOL. | +| `--tip-fee ` | No | Tip fee (SOL ≥ 0.00001 / BSC ≥ 0.000001 BNB). Required when using `--condition-orders` on SOL. | +| `--auto-tip-fee` | No | Enable automatic tip fee. | +| `--max-auto-fee ` | No | Max automatic fee cap. | +| `--gas-price ` | No | Gas price in gwei (BSC ≥ 0.05 / BASE/ETH ≥ 0.01). Required when using `--condition-orders` on BSC. | +| `--max-fee-per-gas ` | No | EIP-1559 max fee per gas (Base only). | +| `--max-priority-fee-per-gas ` | No | EIP-1559 max priority fee per gas (Base only). | +| `--condition-orders ` | No | JSON array of condition sub-orders (take-profit / stop-loss) attached to each successful wallet's swap. Same structure as `swap --condition-orders`. Strategy creation is best-effort per wallet. | +| `--sell-ratio-type ` | No | Sell ratio base for `--condition-orders`: `buy_amount` (default) / `hold_amount`. | + +## `multi-swap` Response Fields + +The response `data` is an array — one element per wallet: + +| Field | Type | Description | +|-------|------|-------------| +| `account` | string | Wallet address | +| `success` | bool | Whether this wallet's swap succeeded | +| `error` | string | Error message on failure; absent on success | +| `error_code` | string | Error code on failure; absent on success | +| `result` | object | On success: OrderResponse (same fields as `swap` response). On failure: absent. | +| `result.strategy_order_id` | string | Strategy order ID; only present when `--condition-orders` was passed and strategy creation succeeded (best-effort) | + ## `order quote` Usage Get an estimated output amount before submitting a swap. All supported quote chains use critical auth and require `GMGN_PRIVATE_KEY`. diff --git a/skills/gmgn-track/SKILL.md b/skills/gmgn-track/SKILL.md index d0f47c2..c87380d 100644 --- a/skills/gmgn-track/SKILL.md +++ b/skills/gmgn-track/SKILL.md @@ -16,7 +16,7 @@ Use the `gmgn-cli` tool to query on-chain tracking data based on the user's requ ## Core Concepts -- **`follow-wallet` vs `kol` vs `smartmoney`** — Three distinct data sources. `follow-wallet` returns trades from wallets the user has personally followed on the GMGN platform (user-specific, requires private key). `kol` and `smartmoney` return trades from platform-tagged public wallet lists (not user-specific, API Key only). Never substitute one for another. +- **`follow-wallet` vs `kol` vs `smartmoney`** — Three distinct data sources. `follow-wallet` returns trades from wallets the user has personally followed on the GMGN platform (user-specific; the follow list is resolved from the GMGN user account bound to the API Key). `kol` and `smartmoney` return trades from platform-tagged public wallet lists (not user-specific). Never substitute one for another. - **KOL (Key Opinion Leader)** — Wallets publicly identified as influencers or well-known traders on GMGN. Tagged as `renowned` in the platform's wallet label system. Their trades carry social/marketing signal, not necessarily alpha. @@ -60,8 +60,7 @@ Use the `gmgn-cli` tool to query on-chain tracking data based on the user's requ ## Prerequisites - `gmgn-cli` installed globally — if missing, run: `npm install -g gmgn-cli` -- `GMGN_API_KEY` configured in `~/.config/gmgn/.env` -- `GMGN_PRIVATE_KEY` required only for `track follow-wallet`; not needed for `track kol` / `track smartmoney` +- `GMGN_API_KEY` configured in `~/.config/gmgn/.env` — required for all sub-commands; no private key needed ## Rate Limit Handling @@ -95,10 +94,6 @@ When a request returns `429`: echo 'GMGN_API_KEY=' > ~/.config/gmgn/.env chmod 600 ~/.config/gmgn/.env ``` - If the user also needs `track follow-wallet`, append the private key: - ```bash - echo 'GMGN_PRIVATE_KEY=""' >> ~/.config/gmgn/.env - ``` ## Usage Examples @@ -308,13 +303,12 @@ To research any token surfaced by smart money activity, follow [`docs/workflow-t ## Safety Constraints -- **`track follow-wallet` requires `GMGN_PRIVATE_KEY`** — this signing key is linked to your GMGN account. It is used for authentication only (no on-chain access), but must be protected like any credential. Never expose it in logs or command output. - **`follow-wallet` reveals your following list** — results expose which wallets you have followed on GMGN. Do not share raw output in public channels. - **`track kol` / `track smartmoney` expose no personal data** — these use API Key auth only and return platform-tagged public wallet activity. Safe to share raw output. ## Notes -- `track kol` / `track smartmoney` use normal auth (API Key only, no signature required) -- `track follow-wallet` requires `GMGN_PRIVATE_KEY` for signature auth — and wallets must be followed via the GMGN platform first +- All sub-commands use normal auth (API Key only, no signature required) +- `track follow-wallet` returns trades from wallets followed on the GMGN platform; the follow list is resolved automatically from the GMGN user account bound to the API Key — `--wallet` is optional - Use `--raw` to get single-line JSON for further processing - `track kol` / `track smartmoney` `--side` is a **client-side filter** — the CLI fetches all results then filters locally; it is NOT sent to the API diff --git a/src/client/OpenApiClient.ts b/src/client/OpenApiClient.ts index f932e59..7fe356d 100644 --- a/src/client/OpenApiClient.ts +++ b/src/client/OpenApiClient.ts @@ -97,6 +97,29 @@ export interface StrategyConditionOrder { drawdown_rate?: string; } +export interface MultiSwapParams { + chain: string; + accounts: string[]; + input_token: string; + output_token: string; + input_amount?: Record; + input_amount_bps?: Record; + output_amount?: Record; + swap_mode?: string; + slippage?: number; + auto_slippage?: boolean; + is_anti_mev?: boolean; + priority_fee?: string; + tip_fee?: string; + auto_tip_fee?: boolean; + max_auto_fee?: string; + gas_price?: string; + max_fee_per_gas?: string; + max_priority_fee_per_gas?: string; + condition_orders?: StrategyConditionOrder[]; + sell_ratio_type?: string; +} + export interface StrategyCreateParams { chain: string; from_address: string; @@ -268,7 +291,7 @@ export class OpenApiClient { } async getFollowWallet(chain: string, extra: Record = {}): Promise { - return this.criticalRequest("GET", "/v1/trade/follow_wallet", { chain, ...extra }, null); + return this.normalRequest("GET", "/v1/trade/follow_wallet", { chain, ...extra }); } async getKol(chain?: string, limit?: number): Promise { @@ -303,6 +326,10 @@ export class OpenApiClient { return this.criticalRequest("POST", "/v1/trade/swap", {}, params); } + async multiSwap(params: MultiSwapParams): Promise { + return this.criticalRequest("POST", "/v1/trade/multi_swap", {}, params); + } + async queryOrder(orderId: string, chain: string): Promise { return this.criticalRequest("GET", "/v1/trade/query_order", { order_id: orderId, chain }, null); } diff --git a/src/commands/swap.ts b/src/commands/swap.ts index 57e4a48..49bf2e8 100644 --- a/src/commands/swap.ts +++ b/src/commands/swap.ts @@ -1,5 +1,5 @@ import { Command } from "commander"; -import { OpenApiClient, SwapParams, StrategyCreateParams, StrategyCancelParams } from "../client/OpenApiClient.js"; +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"; @@ -109,6 +109,78 @@ export function registerSwapCommands(program: Command): void { 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") + .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. 0.01 = 1%)", 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("--auto-tip-fee", "Enable automatic tip fee") + .option("--max-auto-fee ", "Max auto fee cap") + .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 only)") + .option("--max-priority-fee-per-gas ", "EIP-1559 max priority fee per gas (Base only)") + .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, + 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.autoTipFee) params.auto_tip_fee = true; + if (opts.maxAutoFee) params.max_auto_fee = opts.maxAutoFee; + 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.multiSwap(params).catch(exitOnError); + printResult(data, opts.raw); + }); + const strategy = order.command("strategy").description("Limit/strategy order management"); strategy diff --git a/src/commands/track.ts b/src/commands/track.ts index b9146cc..8c88b33 100644 --- a/src/commands/track.ts +++ b/src/commands/track.ts @@ -27,7 +27,7 @@ export function registerTrackCommands(program: Command): void { if (opts.filter?.length) extra["filters"] = opts.filter; if (opts.minAmountUsd != null) extra["min_amount_usd"] = opts.minAmountUsd; if (opts.maxAmountUsd != null) extra["max_amount_usd"] = opts.maxAmountUsd; - const client = new OpenApiClient(getConfig(true)); + const client = new OpenApiClient(getConfig()); const data = await client.getFollowWallet(opts.chain, extra).catch(exitOnError); printResult(data, opts.raw); });