mirror of
https://github.com/GMGNAI/gmgn-skills.git
synced 2026-08-15 00:58:05 +00:00
Merge pull request #108 from GumpOnchain/fix/follow-wallet-critical-auth
fix(track): switch follow-wallet to critical auth (fix HTTP 401 AUTH_INVALID)
This commit is contained in:
@@ -93,8 +93,8 @@ EOF
|
||||
|
||||
| Mode | Commands | Requirements |
|
||||
|------|----------|--------------|
|
||||
| Normal | token / market / portfolio | `GMGN_API_KEY` only, no signature |
|
||||
| Critical | swap / order | `GMGN_API_KEY` + `GMGN_PRIVATE_KEY` — CLI handles signing automatically |
|
||||
| Normal | token / market / portfolio / track kol / track smartmoney | `GMGN_API_KEY` only, no signature |
|
||||
| Critical | swap / order / track follow-wallet | `GMGN_API_KEY` + `GMGN_PRIVATE_KEY` — CLI handles signing automatically |
|
||||
|
||||
## SKILL.md Authoring Rules
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ 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` — required for all sub-commands; no private key needed
|
||||
- `GMGN_API_KEY` configured in `~/.config/gmgn/.env` — required for all sub-commands
|
||||
- `GMGN_PRIVATE_KEY` — required for `track follow-wallet` only (critical auth); not needed for `kol` or `smartmoney`
|
||||
|
||||
## Rate Limit Handling
|
||||
|
||||
@@ -88,11 +89,13 @@ When a request returns `429`:
|
||||
```
|
||||
Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form, then send me the API Key value shown on the page."*
|
||||
|
||||
2. Wait for the user's API key, then configure:
|
||||
2. Wait for the user's API key, then configure (saves both API key and private key — private key is required for `track follow-wallet`):
|
||||
```bash
|
||||
mkdir -p ~/.config/gmgn
|
||||
echo 'GMGN_API_KEY=<key_from_user>' > ~/.config/gmgn/.env
|
||||
echo "GMGN_API_KEY=<key_from_user>" > ~/.config/gmgn/.env
|
||||
echo "GMGN_PRIVATE_KEY=$(awk '{printf "%s\\n", $0}' /tmp/gmgn_private.pem)" >> ~/.config/gmgn/.env
|
||||
chmod 600 ~/.config/gmgn/.env
|
||||
rm /tmp/gmgn_private.pem
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
@@ -308,7 +311,7 @@ To research any token surfaced by smart money activity, follow [`docs/workflow-t
|
||||
|
||||
## Notes
|
||||
|
||||
- All sub-commands use normal auth (API Key only, no signature required)
|
||||
- `track follow-wallet` uses critical auth (API Key + private key signature); `track kol` and `track smartmoney` use normal auth (API Key only)
|
||||
- `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
|
||||
|
||||
@@ -307,7 +307,7 @@ export class OpenApiClient {
|
||||
}
|
||||
|
||||
async getFollowWallet(chain: string, extra: Record<string, string | number | string[]> = {}): Promise<unknown> {
|
||||
return this.normalRequest("GET", "/v1/trade/follow_wallet", { chain, ...extra });
|
||||
return this.criticalRequest("GET", "/v1/trade/follow_wallet", { chain, ...extra }, null);
|
||||
}
|
||||
|
||||
async getKol(chain?: string, limit?: number): Promise<unknown> {
|
||||
@@ -409,16 +409,16 @@ export class OpenApiClient {
|
||||
private async criticalRequest(
|
||||
method: string,
|
||||
subPath: string,
|
||||
queryExtra: Record<string, string | number>,
|
||||
queryExtra: Record<string, string | number | string[]>,
|
||||
body: unknown
|
||||
): Promise<unknown> {
|
||||
if (!this.privateKeyPem) {
|
||||
throw new Error("GMGN_PRIVATE_KEY is required for critical-auth commands (swap and all order commands)");
|
||||
throw new Error("GMGN_PRIVATE_KEY is required for critical-auth commands (swap, order, and follow-wallet commands)");
|
||||
}
|
||||
|
||||
return this.executePreparedRequest(() => {
|
||||
const { timestamp, client_id } = buildAuthQuery();
|
||||
const query: Record<string, string | number> = { ...queryExtra, timestamp, client_id };
|
||||
const query: Record<string, string | number | string[]> = { ...queryExtra, timestamp, client_id };
|
||||
const bodyStr = body !== null ? JSON.stringify(body) : "";
|
||||
const message = buildMessage(subPath, query, bodyStr, timestamp);
|
||||
const signature = sign(message, this.privateKeyPem!, detectAlgorithm(this.privateKeyPem!));
|
||||
|
||||
+10
-3
@@ -32,17 +32,24 @@ export function buildAuthQuery(): { timestamp: number; client_id: string } {
|
||||
/**
|
||||
* Build the signature message (critical auth)
|
||||
* Format: {sub_path}:{sorted_query_string}:{request_body}:{timestamp}
|
||||
* sorted_query_string: all query params (including timestamp, client_id) sorted alphabetically by key
|
||||
* sorted_query_string: all query params (including timestamp, client_id) sorted alphabetically by key.
|
||||
* Array values are serialized as repeated k=v pairs (same as buildUrl / URLSearchParams), sorted by value.
|
||||
*/
|
||||
export function buildMessage(
|
||||
subPath: string,
|
||||
queryParams: Record<string, string | number>,
|
||||
queryParams: Record<string, string | number | string[]>,
|
||||
body: string,
|
||||
timestamp: number
|
||||
): string {
|
||||
const sortedQs = Object.keys(queryParams)
|
||||
.sort()
|
||||
.map((k) => `${k}=${queryParams[k]}`)
|
||||
.flatMap((k) => {
|
||||
const v = queryParams[k];
|
||||
if (Array.isArray(v)) {
|
||||
return [...v].sort().map((item) => `${k}=${item}`);
|
||||
}
|
||||
return [`${k}=${v}`];
|
||||
})
|
||||
.join("&");
|
||||
return `${subPath}:${sortedQs}:${body}:${timestamp}`;
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ export interface Config {
|
||||
|
||||
let _config: Config | null = null;
|
||||
const PRIVATE_KEY_REQUIRED_MSG =
|
||||
"GMGN_PRIVATE_KEY is required for critical-auth commands (swap and all order commands)";
|
||||
"GMGN_PRIVATE_KEY is required for critical-auth commands (swap, order, and follow-wallet commands)";
|
||||
|
||||
export function getConfig(requirePrivateKey = false): Config {
|
||||
if (_config) {
|
||||
|
||||
Reference in New Issue
Block a user