← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

gdex-sdk-debugging

解决GDEX SDK错误——错误代码、加密调试、特定链的特殊问题、HL注意事项以及复制交易的陷阱

person作者: jakexiaohubgithub

GDEX: SDK Debugging & Troubleshooting

Comprehensive troubleshooting guide for GDEX SDK errors, covering API error codes, encryption debugging, chain-specific quirks, HyperLiquid gotchas, and copy trade pitfalls. All issues documented from live E2E testing.

When to Use

  • Getting unexpected API errors (103, 102, TIMEOUT)
  • Trades failing silently or returning wrong data
  • Debugging encryption / computedData payload issues
  • Chain-specific problems (wrong chainId, wrong managed wallet)
  • HyperLiquid perp issues
  • Copy trade configuration problems

API Error Codes

| Code | Error | Common Cause | Fix | |------|-------|-------------|-----| | 103 | Unauthorized | Wrong walletAddress (using managed instead of control), wrong ABI type, signing with wrong key | Use control wallet address as walletAddress / userId. Verify session key matches sign-in. | | 102 | Invalid chainId | Using wrong chainId for HL operations | HL operations require chainId: 42161 (Arbitrum). Copy trades need specific chainIds (see below). | | 102 | Invalid params | Reused nonce, unsupported token, or malformed ABI payload | Check nonce is fresh (Date.now()). Verify token is supported on chain. | | 400 | Bad Request | Missing required fields or wrong field types | Check all required params are present and correctly typed. | | TIMEOUT | Request timeout | Backend processing delay (common with hlCloseAll) | Retry once, or use alternative approach (reduce-only order to close). | | JSON parse error | Invalid response | Backend returned non-JSON (HTML error page, Cloudflare block) | Check User-Agent header is set. Verify API key is valid. |

Critical: Control Wallet vs Managed Wallet

This is the #1 source of errors:

Control wallet:  0x53D029a671bd1CF61a2fB1F4F6e4bD830BFBb2eD  ← used for API calls (userId, walletAddress)
Managed wallet:  0x9967179de55bd67e6b90fcc4f908556d93938c0f  ← server-side, executes trades on-chain

Rule: All API calls use the control address — the one you signed in with. The managed address is only used when querying on-chain state directly (e.g., user_stats on HL needs the managed address).

The /v1/user endpoint returns a different managed wallet per chainId:

  • chainId=1 → EVM managed: 0x9967...
  • chainId=622112261 → Solana managed: CFSi4Y... (base58)

Chain ID Gotchas

| Issue | Wrong | Correct | Notes | |-------|-------|---------|-------| | Solana chain ID | 900 | 622112261 (ChainId.SOLANA) | 900 returns EVM managed address with balance: null | | Copy trade sign-in | chainId: 1 | chainId: 622112261 | Solana copy trades need Solana chainId for sign-in | | Perp copy trade sign-in | chainId: 622112261 | chainId: 1 | HL perp copy trades need EVM chainId for sign-in | | HL operations | Any other | 42161 (Arbitrum) | HL deposit, create order, close — all use Arbitrum chainId |

HyperLiquid-Specific Issues

hlCloseAll is Unreliable

The /v1/hl/close_all_positions endpoint frequently returns TIMEOUT or JSON parse errors.

Workaround: Place a reduce-only order for the exact position size:

// Instead of skill.hlCloseAll() — use this:
const positions = await skill.hlGetPositions();
for (const pos of positions) {
  if (Number(pos.szi) !== 0) {
    await skill.closePerpPosition({
      coin: pos.coin,
      closePercent: 100,  // close full position
    });
  }
}

Leverage Cannot Be Set via API

The hlUpdateLeverage / /v1/hl/update_leverage endpoint returns 404 — it's not implemented. The backend sets leverage automatically before each trade.

Implication: You control effective leverage through position sizing relative to your account balance.

HL Signature Format

message = "{action}-{userId.toLowerCase()}-{dataHex}"
digest  = keccak256(utf8Bytes(message))
output  = r(64hex) + s(64hex) + v(2hex)  // 130 chars total, v=00/01, no 0x prefix

Common signature errors:

  • Wrong userId (must be control wallet, lowercase)
  • Wrong action string (e.g., "hl_deposit" not "deposit")
  • Leading 0x on signature output (must be stripped)
  • Wrong v value (must be 00 or 01, not 27/28)

Encryption Debugging

All managed-custody trades use AES-256-CBC encrypted computedData:

1. ABI-encode trade parameters → hex string (no 0x prefix)
2. Sign: keccak256(action + userId + dataHex) → r+s+v (130 chars)
3. Encrypt: AES-256-CBC(dataHex + signature, derivedKey, iv)
4. POST to backend with { computedData, userId, chainId }

Common Encryption Errors

| Symptom | Cause | Fix | |---------|-------|-----| | 103 Unauthorized | ABI using uint256 when backend expects uint64, or vice versa | Check the exact ABI types for each endpoint — HL deposit uses uint64, not uint256 | | Invalid signature | Signing with wrong key or wrong message format | Ensure session key (not control wallet key) signs the message. Check action string exactly matches. | | Empty response | computedData encrypted with wrong key | Verify AES key derivation: SHA256(SHA256(SHA256(apiKey))) | | Reused nonce | Same nonce value in consecutive trades | Always use fresh Date.now() or incrementing nonce |

AES Key Derivation

const key = SHA256(SHA256(SHA256(apiKey)));  // 3× SHA256 hash chain
const iv = randomBytes(16);                  // fresh IV per request
const encrypted = AES_CBC_encrypt(payload, key, iv);
// computedData = iv.hex + encrypted.hex (concatenated)

Solana-Specific Issues

Meteora Swaps Are Broken

Tokens routed through Meteora DLMM (e.g., BONK) fail with Program error: 1 because the backend doesn't wrap SOL into WSOL.

Workaround: Use Raydium-routed tokens. Check token.dexId:

  • "raydium" / "raydium_v2" → works
  • "meteora" → will fail
  • "orca" → works

ATA Creation Costs

First trade on a new Solana token incurs ~0.007 SOL overhead:

  • ATA creation: ~0.002 SOL
  • Priority fee: ~0.0005 SOL (default)
  • Keep at least 0.01 SOL in the managed wallet for safety

Solana Managed Wallet

After sign-in with chainId: 622112261, the Solana managed wallet is a base58 address (not hex):

CFSi4YimeCbfSNqH2WmHwJKwj1YYG1cWBtQyVPB4sCe1

Copy Trade Pitfalls

isDelete and isChangeStatus Both Delete

On both Solana and HyperLiquid, setting either isDelete='1' or isChangeStatus='1' permanently deletes the copy trade. There is no "pause" or "toggle" — it's irreversible.

Boolean encoding: Use '' (empty string) for false and '1' for true. The string '0' is truthy and will trigger deletion.

ABI Offset Bugs in Responses

The backend returns ABI byte-offsets instead of actual values for some fields:

  • copyMode → Returns 416 or 480 (byte offset), not the JSON value
  • oppositeCopy → Always returns true regardless of actual setting

Workaround: Store your own copy trade configuration locally. Don't rely on response values for copyMode or oppositeCopy.

Max 3 HL Copy Trades

The HyperLiquid perp copy trading system allows a maximum of 3 active copy trades per user. Creating a 4th will fail.

Network Issues

Cloudflare Blocking

The GDEX backend sits behind Cloudflare. Requests without a browser-like User-Agent may be blocked.

// The SDK handles this automatically, but for manual requests:
headers: {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${apiKey}`,
}

Bridge Endpoint Paths

Bridge endpoints use non-obvious paths:

  • GET /v1/bridge/estimate_bridge (not /bridge/estimate)
  • POST /v1/bridge/request_bridge (not /bridge/execute)
  • GET /v1/bridge/bridge_orders (not /bridge/orders)

Debugging Checklist

When a trade fails, check in order:

  1. Auth: Is loginWithApiKey() called? Is the API key valid?
  2. Wallet: Are you using the control wallet address (not managed)?
  3. Chain ID: Is chain ID correct for the operation? (See table above)
  4. Nonce: Is the nonce fresh (Date.now())?
  5. ABI types: Do the ABI types exactly match what the backend expects?
  6. Signature: Is the session key signing (not control key)? Is the message format correct?
  7. Balance: Does the managed wallet have enough funds + gas?

Portfolio / Balances / Trade History Quirks (Live-Tested)

These are critical for autonomous agents — the high-level SDK methods have parameter mismatches:

| Endpoint | SDK Method Issue | Correct Raw Client Call | |----------|-----------------|------------------------| | Portfolio | getPortfolio() sends walletAddress+chain | GET /v1/portfolio?userId=X&chainId=N&data=Y | | Balances | getBalances() sends walletAddress+chain | GET /v1/portfolio?userId=X&chainId=N&data=Y (balances embedded under portfolio.balances[]) | | Trade History | getTradeHistory() sends userId | GET /v1/user_history?user=X&chainId=900&data=Y |

Key differences:

  • Portfolio/balances: Backend expects userId + chainId + data (encrypted session key), NOT walletAddress + chain. On v1.1.0 there is no separate /v1/balances endpoint — balances are embedded under portfolio.balances[].
  • Trade history: Backend expects user (NOT userId) and chainId: 900 for Solana (NOT 622112261)
  • All three require the encrypted session key from buildGdexUserSessionData()
import { buildGdexUserSessionData } from '@gemachdao/gdex-skill';
const data = buildGdexUserSessionData(sessionKey, apiKey);

// Correct portfolio call (balances are inside portfolio.balances[])
const portfolio = await skill.client.get('/v1/portfolio', {
  params: { userId: controlAddress, chainId: 622112261, data }
});

// Correct trade history call
const history = await skill.client.get('/v1/user_history', {
  params: { user: controlAddress, chainId: 900, data, page: 1, limit: 20 }
});

Limit Order ABI Bug (Fixed in SDK)

The limit_buy and update_order ABI schemas encode profitPercent and lossPercent as uint256, NOT string. Using string type causes the backend to receive ABI byte-offsets (e.g. 192) instead of the actual percentage values, which always fails the 0-100 range validation.

Symptom: "lossPercent must be between 0 and 100" even with valid values like lossPercent: '25' Cause: ABI string type → encoded offset (192) → fails range check Fix: SDK encodes as uint256 (correct). Only matters if encoding manually. 8. Token support: Is the token supported on this chain? Is the DEX working? (Meteora is broken)

Backend-Limited Behavior (Not SDK Bugs)

These are server-side behaviors, verified live. They are not fixable in the SDK — don't waste a debugging loop on them. Treat the listed responses as expected.

Deposit returns HTTP 400 even when the transfer succeeds

perpDeposit (USDC → HyperLiquid) frequently returns HTTP 400 while the on-chain transfer still completes. The error is a backend response-handling bug, not a failed deposit.

Symptom: perpDeposit throws a 400, but the USDC arrives in the HL account moments later. Do: Don't retry blindly on the 400 (you'll double-deposit). Poll the HL perp balance / getHlAccount to confirm before retrying. Verify state, not the HTTP status.

Outcome-market listings are controlled by the backend, not the SDK

The SDK can place/cancel/close orders on any outcome market (coin format #<outcomeId><sideIndex>, e.g. #1010 = outcome 101, Yes side), but which markets exist — and that they may currently be limited (e.g. only a BTC over/under) — is decided by the backend / HIP-3 deployer, not the SDK.

Symptom: listOutcomeMarkets returns few/limited markets, or a #… coin you expect is absent. Do: Only trade markets returned by listOutcomeMarkets / the outcome account query. An empty or short list is a market-config state on the backend, not a discovery bug. HIP-3/HIP-4 markets may also quote in non-USDC collateral (USDH/USDE/USDT0) — use swap_collateral first.

Related Skills

  • gdex-authentication — Full auth flow and encryption details
  • gdex-spot-trading — Spot trade parameters and chain support
  • gdex-perp-trading — HL perp specifics
  • gdex-copy-trading — Solana copy trade ABI format
  • gdex-perp-copy-trading — HL perp copy trade ABI format