Skip to content
Back to skills

Gdex Sdk Debugging

ASecurity

Troubleshoot GDEX SDK errors — error codes, encryption debugging, chain-specific quirks, HL gotchas, and copy trade pitfalls

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 3, 2026
ai-agentstypescriptgotestingdebuggingapibackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 3, 2026

npx -y skills add GemachDAO/gdex-skill --skill gdex-sdk-debugging --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gdex Sdk Debugging?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gdex Sdk Debugging
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gemachdao-gdex-sdk-debugging/badge)](https://www.skillsdirectory.com/skills/gemachdao-gdex-sdk-debugging)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: gdex-sdk-debugging
description: Troubleshoot GDEX SDK errors — error codes, encryption debugging, chain-specific quirks, HL gotchas, and copy trade pitfalls
---

# 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:
```typescript
// 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

```typescript
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.

```typescript
// 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()`

```typescript
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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.