Why agents use GDEX
GDEX is the trading surface. This skill is how AI agents (Claude, Cursor, Codex, and 40+ others) install and trade on it — spot, perps, copy-trade, and bridge — without building their own exchange stack.
Fastest path for an agent:
npx skills add GemachDAO/gdex-skill --all --agent '*' -g
Then open gdex.pro for the human terminal, or run the MCP server so the agent can execute trades itself.
Table of Contents
🤖 Install as an Agent Skill
Install directly into Claude Code, Cursor, Codex, Windsurf, and 40+ other agents using the skills CLI:
npx skills add GemachDAO/gdex-skill --all --agent '*' -g
npx skills add GemachDAO/gdex-skill
npx skills add GemachDAO/gdex-skill --skill gdex-spot-trading
GDEX uses a multi-skill architecture — agents load only the skills they need, keeping context lean and focused.
Available Skills
gdex-onboarding | Platform overview, architecture, supported chains, quickstart |
gdex-retailer-onboarding | Retailer partner integrations — branded onboarding partners on the GDEX stack |
gdex-authentication | Managed-custody auth, encryption, session keys, API key login |
gdex-spot-trading | Buy/sell tokens on any chain with DEX routing |
gdex-perp-trading | HyperLiquid perpetual futures — positions, orders, leverage |
gdex-perp-funding | Deposit/withdraw USDC to/from HyperLiquid |
gdex-limit-orders | Create, cancel, and list limit orders |
gdex-portfolio | Cross-chain portfolio, balances, trade history |
gdex-token-discovery | Token details, trending tokens, OHLCV charts (no auth) |
gdex-token-import | Import custom tokens into details, balances and portfolio |
gdex-livestream-discovery | Solana livestream tokens, live status, big-buy alerts |
gdex-watchlist-social | Watchlists, token comments, sentiment voting |
gdex-trending-promotion | Book paid trending slots and check booking status |
gdex-xstocks | Tokenised equities (xStocks) listing |
gdex-content-coins | Zora content coins and creator coins on Base |
gdex-copy-trading | Copy trade create/delete, leaderboards, tx history, DEX list (Solana only for writes) |
gdex-perp-copy-trading | HL perp copy trading — top traders, create/manage configs, market data |
gdex-hl-outcomes | HyperLiquid outcome (event) markets — list, order, manage positions |
gdex-hl-referral | HyperLiquid referral info and reward claims |
gdex-bridge | Cross-chain bridging with quotes |
gdex-transfers | Native and ERC20/SPL transfers via managed custody |
gdex-wallet-setup | Generate EVM wallets, session keys, wallet info (no auth) |
gdex-ui-install-setup | React/Next.js project setup, SDK context providers, environment variables |
gdex-ui-trading-components | React component patterns for order forms, position tables, copy trade panels |
gdex-ui-portfolio-dashboard | Portfolio dashboard components — balances, trade history, chain selectors |
gdex-ui-wallet-connection | Wallet connection UI — connect buttons, auth state, chain switching |
gdex-ui-theming | CSS theming — dark/light mode, trading colors, responsive breakpoints, Tailwind |
gdex-ui-page-layouts | Full page compositions — trading, portfolio, copy trading, bridge pages |
gdex-sdk-debugging | Troubleshoot errors — error codes, encryption debugging, chain quirks, HL gotchas |
Each skill's description tells the agent when to load it. No API key setup required for trading skills (shared keys are built in).
Risk & Market Data Skills
Deterministic data feeds and events for risk and research. Each ships a standard-library Python script that
prints NDJSON: no API key, no install, and every number comes from the script, not a model.
gdex-hl-market-risk | ~234 HyperLiquid core perps: funding, open interest, oracle premium, leverage caps, delisting |
gdex-hl-anomaly | Scored, time-stamped HyperLiquid anomaly events (oracle divergence, funding extremity, liquidity shock) against per-market learned baselines, plus a coverage record per run. Backtest |
gdex-token-risk | GDEX token screen on 12 chains: price, liquidity, volume, honeypot, taxes, LP lock, holder concentration (missing security data is never "safe") |
gvault | GVault (GMACL, Enzyme on Ethereum): NAV, share price, holdings, cumulative and annualised return |
python3 skills/gdex-hl-market-risk/scripts/hl_market_risk.py > hl.ndjson
Skills Harness (bring your own key)
harness/ runs these skills with Claude on your own Anthropic API key.
export produces every feed with no model; ask answers questions with an agent that can only
cite figures that skill scripts printed. Every script run is logged with a sha256 of its output.
🔌 MCP Server
The GDEX MCP server exposes 117 tools — full trading execution + SDK documentation — as Model Context Protocol tools. Any MCP-compatible AI agent can trade autonomously.
Source code: the server is implemented in this repository, in mcp-server/. The entry point is mcp-server/src/index.ts and the tool handlers are in mcp-server/src/tools/. The npm package @gemachdao/gdex-mcp-server is built from that directory by the release workflow. See mcp-server/README.md to run it from source or with the repo's Dockerfile.
Quick Setup
npx @gemachdao/gdex-mcp-server init --client claude
npx @gemachdao/gdex-mcp-server init --client cursor
npx @gemachdao/gdex-mcp-server init --client vscode
npx @gemachdao/gdex-mcp-server init --client codex
npx @gemachdao/gdex-mcp-server init --client opencode
Claude Code plugin
/plugin marketplace add GemachDAO/gdex-skill
/plugin install gdex@gemachdao
Installs every GDEX skill plus the MCP server (npx -y @gemachdao/gdex-mcp-server). The optional GDEX API key is kept in your system credential store.
Gemini CLI extension
gemini extensions install https://github.com/GemachDAO/gdex-skill
Manual Config
Add to your client's MCP config:
{
"mcpServers": {
"gdex-mcp-server": {
"command": "npx",
"args": ["@gemachdao/gdex-mcp-server"],
"env": {
"GDEX_API_KEY": "your-api-key"
}
}
}
}
Environment Variables
GDEX_API_KEY | GDEX API key — auto-authenticates on startup | Optional |
GDEX_API_URL | Override API base URL (default: https://trade-api.gemach.io/v1) | Optional |
MCP Execution Tools (109 tools)
| Auth | auth_login, generate_session_keypair, managed_sign_in, build_sign_in_payload | API key login, session keys, managed custody sign-in |
| Spot Trading | buy_token, sell_token | Buy/sell on Solana, Sui and 10 EVM chains (Ethereum, Base, Arbitrum, BSC and more) |
| Perp Trading | open_perp_position, place_perp_order, close_perp_position, close_all_positions, cancel_perp_order, cancel_all_perp_orders, set_leverage, perp_deposit, perp_withdraw | Full HyperLiquid perpetual futures — long/short, TP/SL; leverage up to each market's cap (40x on core BTC) |
| Perp Data | get_account_state, get_perp_positions, get_mark_price, get_all_mid_prices, get_usdc_balance, get_hl_open_orders, get_hl_trade_history, get_hl_spot_state, get_trader_leverage | Real-time HyperLiquid account, positions, prices |
| Direct Execution | execute_cross_perp, execute_isolated_perp, execute_spot, direct_cancel_order | Private-key execution — cross/isolated margin, spot, cancel |
| Limit Orders | limit_buy, limit_sell, update_order, get_limit_orders | Limit buy/sell with TP/SL, order management |
| Copy Trading (Solana) | get_copy_trade_wallets, get_copy_trade_custom_wallets, get_copy_trade_gems, get_copy_trade_dexes, get_copy_trade_list, get_copy_trade_tx_list, create_copy_trade, update_copy_trade | Auto-mirror top Solana traders |
| Copy Trading (HL Perp) | get_hl_top_traders, get_hl_top_traders_by_pnl, get_hl_user_stats, get_hl_perp_dexes, get_hl_all_assets, get_hl_clearinghouse_state, get_hl_meta_and_asset_ctxs, get_hl_deposit_tokens, get_hl_copy_trade_list, get_hl_copy_trade_tx_list, create_hl_copy_trade, update_hl_copy_trade | Copy HyperLiquid perp traders |
| Portfolio & Data | get_portfolio, get_balances, get_trade_history, get_token_details, get_trending_tokens, get_ohlcv, get_top_traders, get_wallet_info, generate_evm_wallet | Cross-chain portfolio, market data, OHLCV candles |
| Bridge | estimate_bridge, execute_bridge, get_bridge_orders | Cross-chain native token bridging |
| Managed Custody | managed_purchase, managed_sell, managed_trade_status, build_trade_payload | Low-level encrypted trade submission |
| Transfers | transfer_native, transfer_token | Send native and ERC20/SPL tokens via managed custody |
| Social & Watchlist | add_comment, get_comments, vote_sentiment, get_watchlist, change_watchlist | Token comments, sentiment votes, watchlists |
| Token Import | import_token | Add a custom token so it appears in details, balances and portfolio |
| Market Discovery & Analytics | get_newest_tokens, get_top_tokens, get_token_trades, get_token_image, get_native_prices, get_xstocks, get_zora_tokens, get_wallet_performance, get_nof1_analytics, generate_pnl | New and top tokens, trades, prices, xStocks, Zora coins, wallet performance, NoF1 analytics, PnL generation |
| Livestream | get_currently_live, get_live_status, get_bigbuys | Solana livestream tokens and big-buy alerts |
| HL Outcome Markets | hl_outcomes, get_hl_outcome_volumes, hl_outcome_account, hl_create_outcome_order, hl_cancel_outcome_order, hl_close_outcome_order | HyperLiquid outcome (event) markets |
| HL Account & Referral | hl_enable_trading, hl_swap_collateral, hl_tx_list, hl_list_user_copy_pnl, hl_ref_info, hl_ref_claim, hl_builder_referral | One-time HL enablement, HIP-3 collateral swaps, fills/orders, copy-trade PnL, referral rewards |
| Promotion & Partners | trending_list, trending_options, trending_register, trending_booking_status, get_retailers | Paid trending slots and retailer partners |
| Account | oauth_login, associate_email | Google sign-in and linking an email to a wallet |
MCP Documentation Tools (8 tools)
search_gdex_docs | Search documentation by keyword |
get_sdk_pattern | TypeScript code patterns by operation |
get_api_info | API endpoint details (URL, method, params) |
explain_workflow | Step-by-step trading workflows |
get_chain_info | Supported chains and capabilities |
get_trading_guide | Spot, perp, or limit trading guides |
get_copy_trade_guide | Copy trading guides (Solana / HL) |
get_component_guide | React UI component patterns |
📦 SDK Installation
npm install github:GemachDAO/gdex-skill
Installs the SDK straight from GitHub (it builds on install). Imports stay
from '@gemachdao/gdex-skill'. Pin a release with
npm install github:GemachDAO/gdex-skill#v4.1.1.
The install script displays a quick-start banner in your terminal. Optional peer dependencies for wallet signing (only needed for user-specific wallet auth):
npm install ethers
npm install bs58 tweetnacl
🚀 Quick Start
import { GdexSkill, GDEX_API_KEY_PRIMARY } from '@gemachdao/gdex-skill';
const skill = new GdexSkill();
skill.loginWithApiKey(GDEX_API_KEY_PRIMARY);
const trade = await skill.buyToken({
chain: 'solana',
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: '0.1',
slippage: 1,
});
console.log('Trade submitted:', trade.jobId, '— status:', trade.status);
const pos = await skill.openPerpPosition({
coin: 'BTC',
side: 'long',
sizeUsd: '1000',
leverage: 10,
takeProfitPrice: '110000',
stopLossPrice: '95000',
});
const trending = await skill.getTrendingTokens({ chain: 'solana', period: '24h', limit: 5 });
console.log('Trending:', trending.map(t => t.symbol).join(', '));
✅ Verify (offline)
Confirm the SDK is installed and configured correctly — no network connection or API key required:
npm run verify
Sample output:
1. SDK import
✓ SDK imported from ../dist/index.js
2. API keys
✓ GDEX_API_KEY_PRIMARY = 9b4e1c73...
✓ GDEX_API_KEY_SECONDARY = 2c8f0a91...
✓ GDEX_API_KEYS array = 2 keys
3. GdexSkill instantiation
✓ new GdexSkill() — default config
✓ new GdexSkill({ apiUrl, timeout, maxRetries }) — custom config
4. Authentication state (offline)
✓ isAuthenticated() = false before login
✓ loginWithApiKey(GDEX_API_KEY_PRIMARY) → isAuthenticated() = true
✓ logout() → isAuthenticated() = false
...
All 20 checks passed ✓
SDK is ready — no network token required.
🔑 Authentication
Shared API Keys (recommended for agents)
Two shared keys are pre-configured in the package — agents do not need to sign wallet transactions:
import {
GdexSkill,
GDEX_API_KEY_PRIMARY,
GDEX_API_KEY_SECONDARY,
GDEX_API_KEYS,
} from '@gemachdao/gdex-skill';
const skill = new GdexSkill();
skill.loginWithApiKey(GDEX_API_KEY_PRIMARY);
skill.loginWithApiKey(GDEX_API_KEY_SECONDARY);
skill.loginWithApiKey(GDEX_API_KEYS[0]);
skill.isAuthenticated();
skill.logout();
Note: Read-only endpoints (getTrendingTokens, getTokenDetails, getOHLCV, getTopTraders) do not require authentication.
Wallet-based Auth (advanced)
For user-owned wallets or custom signers (hardware wallets, browser extensions):
await skill.authenticate({
type: 'evm',
address: '0xYourAddress',
privateKey: '0xPrivateKey',
});
await skill.authenticate({
type: 'solana',
address: 'YourSolanaAddress',
privateKey: 'base58EncodedPrivateKey',
});
await skill.authenticate({
type: 'evm',
address: accounts[0],
signer: async (message) =>
window.ethereum.request({ method: 'personal_sign', params: [message, accounts[0]] }),
});
Configuration
const skill = new GdexSkill({
apiUrl: 'https://trade-api.gemach.io/v1',
timeout: 30000,
maxRetries: 3,
debug: false,
});
The SDK does not read environment variables directly. If you choose to use env vars,
read them in your application (for example via process.env) and pass their values into
the GdexSkill constructor as shown above.
Recommended environment variables for your own app:
GDEX_API_URL | Backend base URL to pass as apiUrl | https://trade-api.gemach.io/v1 |
GDEX_API_KEY | API key for AES encryption in managed-custody flow | — |
GDEX_TIMEOUT | Request timeout (ms) to pass as timeout | 30000 |
GDEX_MAX_RETRIES | Retry attempts to pass as maxRetries | 3 |
GDEX_DEBUG | Enable debug logging to pass as debug | false |
GDEX_CONTROL_WALLET | Control wallet address (userId) for managed custody | — |
GDEX_SESSION_PRIVATE | Session private key (hex, 0x-prefixed) for managed custody | — |
GDEX_MANAGED_CHAIN_ID | Chain ID for managed trades (622112261=Solana, 42161=Arbitrum for perps) | 622112261 |
CONFIRM_LIVE_TRADE | Set to YES to submit real trades | — |
🔒 Managed-Custody Trading
All trading on GDEX goes through server-side managed wallets. Your control wallet (EVM or Solana) is only used to authenticate (sign-in) — actual on-chain execution is handled by GDEX backend trade workers.
How It Works
- Generate a session keypair — secp256k1 key used to sign trades after auth
- Sign-in — control wallet signs a message, encrypted as
computedData → POST /v1/sign_in
- Resolve user — GET
/v1/user with encrypted session key to see managed wallets
- Trade — ABI-encode trade data, sign with session key, encrypt as
computedData → POST /v1/purchase_v2 or /v1/sell_v2
- Poll status — GET
/v1/trade-status/:requestId until completed/failed
Encryption
All authenticated payloads use AES-256-CBC with a deterministic key/IV derived from the API key (no random IV):
- Key = first 32 bytes of
SHA256(apiKey) hex
- IV = first 16 bytes of
SHA256(SHA256(apiKey)) hex
- Trade/sign-in payloads:
JSON.stringify({ userId, data, signature, apiKey }) → UTF-8 → encrypt → hex
- Session key (
/v1/user): hex-decoded raw bytes → encrypt (not UTF-8 string)
- The API key is included inside the encrypted JSON payload (not just used for encryption)
WARNING: Do NOT use random IVs or the iv:ciphertext format. The backend uses deterministic AES derived from the API key hash chain.
Signatures
Spot trade signatures (purchase/sell) use raw keccak256 + secp256k1 (no EIP-191 prefix):
- Message:
"<action>-<lowercaseUserId>-<dataHexWithout0x>"
- Digest:
keccak256(utf8Bytes(message))
- Output:
r(64 hex) + s(64 hex) + v(2 hex) = 130 chars, no 0x prefix
- v = raw recovery parameter (
00 or 01), NOT EIP-155 (1b/1c)
HL perp signatures use the same algorithm but with HL-specific action prefixes (hl_deposit, hl_withdraw, hl_create_order, etc.) and different ABI schemas. See the HL Managed-Custody Reference section.
Sign-in signatures use EIP-191 personal_sign with the control wallet — this is the ONLY operation that uses EIP-191. All post-sign-in operations use raw keccak256.
Nonce Generation
Nonces are client-generated (not fetched from the server):
const nonce = String(Math.floor(Date.now() / 1000) + Math.floor(Math.random() * 1000));
Quick Example
import {
GdexSkill,
GDEX_API_KEY_PRIMARY,
generateGdexSessionKeyPair,
buildGdexSignInMessage,
buildGdexSignInComputedData,
buildGdexManagedTradeComputedData,
buildGdexUserSessionData,
} from '@gemachdao/gdex-skill';
const skill = new GdexSkill();
skill.loginWithApiKey(GDEX_API_KEY_PRIMARY);
const apiKey = GDEX_API_KEY_PRIMARY;
const { sessionPrivateKey, sessionKey } = generateGdexSessionKeyPair();
const userId = '0xYourControlWallet';
const nonce = String(Math.floor(Date.now() / 1000) + Math.floor(Math.random() * 1000));
const message = buildGdexSignInMessage(userId, nonce, sessionKey);
const signature = ;
const signInPayload = buildGdexSignInComputedData({ apiKey, userId, sessionKey, nonce, signature });
await skill.signInWithComputedData({ computedData: signInPayload.computedData, chainId: 900 });
const trade = buildGdexManagedTradeComputedData({
apiKey, action: 'purchase', userId,
tokenAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
amount: '100000',
nonce: String(Math.floor(Date.now() / 1000) + Math.floor(Math.random() * 1000)),
sessionPrivateKey,
});
const result = await skill.submitManagedPurchase({
computedData: trade.computedData, chainId: 900, slippage: 1,
});
if (result.requestId) {
const status = await skill.getManagedTradeStatus(result.requestId);
console.log(status.status, status.hash);
}
Managed-Custody Helpers
generateGdexSessionKeyPair() | Generate secp256k1 session keypair |
buildGdexSignInMessage(userId, nonce, sessionKey) | Build the sign-in message for wallet signing |
encodeGdexSignInData(sessionKey, nonce, refCode?) | ABI-encode sign-in data (['bytes','string','string']) |
buildGdexSignInComputedData({...}) | Build encrypted sign-in payload |
buildGdexUserSessionData(sessionKey, apiKey) | Encrypt session key (raw hex bytes) for /v1/user |
encodeGdexTradeData(tokenAddr, amount, nonce?) | ABI-encode trade data (['string','uint256','string']) |
signGdexTradeMessageWithSessionKey(action, userId, data, privKey) | Sign trade with session key (v = raw recoveryParam 00/01) |
buildGdexManagedTradeComputedData({...}) | Build encrypted trade payload |
encodeLimitOrderData(action, params) | ABI-encode limit order data (buy/sell/update schemas) |
signLimitOrderMessage(action, userId, data, privKey) | Sign limit order with session key |
buildLimitOrderComputedData({...}) | Build encrypted limit order payload |
encodeCopyTradeData(action, params) | ABI-encode copy trade data (create: 12 fields, update: 16 fields, chainId is uint256) |
signCopyTradeMessage(action, userId, data, privKey) | Sign copy trade with session key |
buildCopyTradeComputedData({...}) | Build encrypted copy trade payload |
buildEncryptedGdexPayload({...}) | Encrypt JSON {userId, data, signature} for computedData |
encryptGdexComputedData(plaintext, apiKey) | AES-256-CBC encrypt UTF-8 plaintext |
encryptGdexHexData(hexData, apiKey) | AES-256-CBC encrypt raw hex-decoded bytes |
decryptGdexComputedData(cipherHex, apiKey) | AES-256-CBC decrypt to UTF-8 plaintext |
deriveGdexAesMaterial(apiKey) | Get raw AES key/IV from API key |
Verify Managed Flow (offline)
npm run verify:managed
This generates all payloads (session keypair, sign-in, user lookup, trade), validates encrypt/decrypt roundtrips, and prints the full execution plan — without making any live API calls.
📚 API Reference
Spot Trading
buyToken(params)
Buy a token on any supported chain.
chain | string | ChainId | ✅ | Chain name or numeric ID |
tokenAddress | string | ✅ | Token contract address |
amount | string | ✅ | Native token input amount |
slippage | number | | Max slippage % (default: 1) |
dex | string | | Force specific DEX |
walletAddress | string | | Override wallet address |
priorityFee | number | | Solana priority fee (lamports) |
const result = await skill.buyToken({
chain: 'solana',
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: '0.1',
slippage: 1,
});
sellToken(params)
await skill.sellToken({ chain: 8453, tokenAddress: '0x...', amount: '100', slippage: 0.5 });
await skill.sellToken({ chain: 'solana', tokenAddress: '...', amount: '50%' });
Perpetual Futures (HyperLiquid)
Critical: HyperLiquid deposits/withdrawals/orders use a different crypto flow than spot trades. See the HL Managed-Custody Reference section below for exact specifications.
openPerpPosition(params)
coin | string | | Asset symbol (e.g., 'BTC', 'ETH') |
side | 'long' | 'short' | | Direction |
sizeUsd | string | | Collateral in USD |
leverage | number | 5 | 1–50× |
takeProfitPrice | string | | Optional TP price |
stopLossPrice | string | | Optional SL price |
marginMode | 'cross' | 'isolated' | 'cross' | Margin mode |
const pos = await skill.openPerpPosition({
coin: 'BTC', side: 'long', sizeUsd: '1000', leverage: 10,
takeProfitPrice: '110000', stopLossPrice: '95000',
});
closePerpPosition(params)
await skill.closePerpPosition({ coin: 'BTC' });
await skill.closePerpPosition({ coin: 'ETH', closePercent: 50 });
Other perp methods
await skill.setPerpLeverage({ coin: 'BTC', leverage: 20 });
await skill.hlUpdateLeverage({
coin: 'BTC',
leverage: 40,
isCross: true,
apiKey,
walletAddress,
sessionPrivateKey,
});
await skill.perpDeposit({ amount: '10' });
await skill.perpWithdraw({ amount: '5' });
const positions = await skill.getPerpPositions({ walletAddress: '0x...' });
HL Deposit notes: Amount is in human-readable USDC (e.g., '10' for 10 USDC). The SDK automatically converts to smallest unit (6 decimals). Minimum deposit is 10 USDC. Your managed wallet must have the deposit amount + 1% fee buffer in USDC on Arbitrum. After the on-chain tx confirms, HyperLiquid takes ~10 minutes to credit the deposit.
Direct HyperLiquid Execution (via @gdexsdk/hyper-liquid-trader)
For direct trading on HyperLiquid L1 without managed custody — use your own private key:
const result = await skill.hlExecuteCrossPerp(
process.env.PRIVATE_KEY!,
{ coin: 'BTC', isLong: true, price: '100000', positionSize: '0.001', leverage: 10 },
);
const result2 = await skill.hlExecuteIsolatedPerp(
process.env.PRIVATE_KEY!,
{ coin: 'ETH', isLong: false, price: '3000', positionSize: '1', leverage: 5 },
);
const result3 = await skill.hlExecuteSpot(
process.env.PRIVATE_KEY!,
{ coin: 'PURR', isBuy: true, price: '0.50', size: '100' },
);
await skill.hlDirectCancelOrder(process.env.PRIVATE_KEY!, 'BTC', orderId);
const mids = await skill.getHlAllMids();
const trades = await skill.getHlTradeHistory('0xYourWallet');
const leverage = await skill.getHlTraderLeverageContext('0xTraderWallet', 'BTC');
const trader = await skill.createHlTrader(['wss://custom-ws.example.com']);
Limit Orders
Endpoints: limit_buy / limit_sell / update_order / orders — NOT orders/create or orders/cancel.
const buyResult = await skill.limitBuy({
apiKey: GDEX_API_KEY_PRIMARY,
userId: '0x53D029a671bd1CF61a2fB1F4F6e4bD830BFBb2eD',
sessionPrivateKey: '<session-key-hex>',
chainId: 622112261,
tokenAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm',
amount: '10000000',
triggerPrice: '0.50',
profitPercent: '50',
lossPercent: '25',
});
const sellResult = await skill.limitSell({
apiKey: GDEX_API_KEY_PRIMARY,
userId: '0x53D029a671bd1CF61a2fB1F4F6e4bD830BFBb2eD',
sessionPrivateKey: '<session-key-hex>',
chainId: 622112261,
tokenAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm',
amount: '100000',
triggerPrice: '999.99',
});
await skill.updateOrder({
apiKey: GDEX_API_KEY_PRIMARY,
userId: '0x53D029a671bd1CF61a2fB1F4F6e4bD830BFBb2eD',
sessionPrivateKey: '<session-key-hex>',
chainId: 622112261,
orderId: '<64-char-hex-order-id>',
isDelete: true,
});
const { count, orders } = await skill.getLimitOrders({
userId: '0x53D029a671bd1CF61a2fB1F4F6e4bD830BFBb2eD',
data: encryptedSessionKey,
chainId: 622112261,
});
Copy Trading
Solana-only for write operations. Sign-in must use chainId: 622112261. The ABI encodes chainId as uint256.
Discovery (no auth)
const topWallets = await skill.getCopyTradeWallets();
const customWallets = await skill.getCopyTradeCustomWallets();
const gems = await skill.getCopyTradeGems();
const dexes = await skill.getCopyTradeDexes(622112261);
Read (session-key auth)
import { buildGdexUserSessionData } from '@gemachdao/gdex-skill';
const data = buildGdexUserSessionData(sessionKey, apiKey);
const { allCopyTrades, dexes } = await skill.getCopyTradeList({ userId, data });
const { txes } = await skill.getCopyTradeTxList({ userId, data });
Create (computedData auth)
import {
GDEX_API_KEY_PRIMARY,
buildCopyTradeComputedData,
generateGdexSessionKeyPair,
buildGdexSignInMessage,
buildGdexSignInComputedData,
} from '@gemachdao/gdex-skill';
const result = await skill.createCopyTrade({
apiKey: GDEX_API_KEY_PRIMARY,
userId: controlWalletAddress,
sessionPrivateKey,
chainId: 622112261,
traderWallet: 'SolanaTraderAddress',
copyTradeName: 'Alpha Trader',
buyMode: 1,
copyBuyAmount: '0.001',
lossPercent: '50',
profitPercent: '100',
copySell: true,
isBuyExistingToken: false,
excludedDexNumbers: [],
});
Delete (computedData auth)
const deleteResult = await skill.updateCopyTrade({
apiKey: GDEX_API_KEY_PRIMARY,
userId: controlWalletAddress,
sessionPrivateKey,
chainId: 622112261,
copyTradeId: '<64-char-hex-id>',
traderWallet: 'SolanaTraderAddress',
copyTradeName: 'Alpha Trader',
buyMode: 1,
copyBuyAmount: '0.001',
lossPercent: '50',
profitPercent: '100',
isDelete: true,
});
WARNING: Both isDelete: true and isChangeStatus: true permanently delete the copy trade.
There is no toggle/pause functionality on the current backend. Boolean fields use '' for false
and '1' for true internally; string '0' is truthy in JS and will trigger deletion.
Copy Trade ABI Reference
create_copy_trade | 12 | ['string','string','uint256','string'×9] |
update_copy_trade | 16 | ['string','string','uint256','string'×13] |
Create field order: [traderWallet, copyTradeName, chainId, gasPrice, buyMode, copyBuyAmount, isBuyExistingToken, lossPercent, profitPercent, nonce, copySell, excludedDexNumbers]
Update field order: [traderWallet, copyTradeName, chainId, gasPrice, buyMode, copyBuyAmount, isBuyExistingToken, lossPercent, profitPercent, nonce, copySell, excludedDexNumbers, copyTradeId, isDelete, isChangeStatus, excludedProgramIds]
chainId at position 2 is uint256, all other fields are string. Nonce is auto-generated by the SDK.
HL Perp Copy Trading
Completely separate from Solana copy trading above. This copies perpetual futures positions (long/short) on HyperLiquid. Sign-in uses chainId: 1. All ABI fields are strings.
Discovery (no auth)
const topByVolume = await skill.getHlTopTraders('volume');
const topByPnl = await skill.getHlTopTradersByPnl();
const stats = await skill.getHlUserStats('0xManagedWalletAddress');
const dexes = await skill.getHlPerpDexes();
const assets = await skill.getHlAllAssets();
const tokens = await skill.getHlDepositTokens();
const state = await skill.getHlClearinghouseState('0xAddress');
const orders = await skill.getHlOpenOrdersForCopy('0xAddress');
const balance = await skill.getHlUsdcBalanceForCopy('0xAddress');
Read (session-key auth)
import { buildGdexUserSessionData } from '@gemachdao/gdex-skill';
const data = buildGdexUserSessionData(sessionKey, apiKey);
const { allCopyTrades } = await skill.getHlCopyTradeList({ userId, data });
const { txes, totalCount } = await skill.getHlCopyTradeTxList({ userId, data, page: '1', limit: '20' });
Create (computedData auth)
await skill.createHlCopyTrade({
apiKey,
userId: controlWalletAddress,
sessionPrivateKey,
traderWallet: '0xTraderEvmAddress',
copyTradeName: 'BTC Whale',
copyMode: 1,
fixedAmountCostPerOrder: '50',
lossPercent: '25',
profitPercent: '100',
oppositeCopy: false,
});
Update / Delete (computedData auth)
await skill.updateHlCopyTrade({
apiKey, userId: controlWalletAddress, sessionPrivateKey,
copyTradeId: 'abc123...',
traderWallet: '0xTraderEvmAddress',
copyTradeName: 'Updated Name',
copyMode: 2, fixedAmountCostPerOrder: '0.5',
lossPercent: '30', profitPercent: '150',
oppositeCopy: true,
});
await skill.updateHlCopyTrade({ ...existingParams, isDelete: true });
await skill.updateHlCopyTrade({ ...existingParams, isChangeStatus: true });
HL Perp Copy Trade ABI Reference
hl_create | 8 | ['string' × 8] |
hl_update | 11 | ['string' × 11] |
Create field order: [traderWallet, copyTradeName, copyMode, fixedAmountCostPerOrder, lossPercent, profitPercent, nonce, oppositeCopy]
Update field order: [traderWallet, copyTradeName, copyMode, fixedAmountCostPerOrder, lossPercent, profitPercent, nonce, isDelete, isChangeStatus, copyTradeId, oppositeCopy]
All fields are string. Boolean fields: '1' = true, '' = false. Nonce auto-generated by SDK.
Note: copyMode and oppositeCopy in responses contain ABI byte-offsets (e.g., 416), not actual values. Both isDelete and isChangeStatus permanently delete the trade.
Portfolio
const portfolio = await skill.getPortfolio({ walletAddress: '0x...' });
const balances = await skill.getBalances({ walletAddress: '0x...', chain: ChainId.ETHEREUM });
const history = await skill.getTradeHistory({
walletAddress: '0x...', page: 1, limit: 20,
startTime: 1700000000, endTime: 1700086400,
});
Token Information
🔓 No authentication required for these endpoints.
const token = await skill.getTokenDetails({
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
chain: 'solana',
});
const trending = await skill.getTrendingTokens({
chain: 'solana',
period: '24h',
limit: 20,
minLiquidity: 50000,
});
const ohlcv = await skill.getOHLCV({
tokenAddress: 'So11111111111111111111111111111111111111112',
chain: 'solana',
resolution: '60',
from: Math.floor(Date.now() / 1000) - 86400,
to: Math.floor(Date.now() / 1000),
});
Top Traders
🔓 No authentication required.
const traders = await skill.getTopTraders({
chain: 'solana',
period: '7d',
limit: 10,
sortBy: 'pnl',
});
Bridge
const quote = await skill.getBridgeQuote({
fromChain: 'solana',
toChain: ChainId.ETHEREUM,
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: '100',
});
console.log('Output:', quote.outputAmount, '— fee (USD):', quote.feeUsd);
const result = await skill.bridge({
fromChain: 'solana',
toChain: ChainId.ETHEREUM,
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: '100',
destinationAddress: '0xYourEthAddress',
slippage: 0.5,
});
Wallet Info
const info = await skill.getWalletInfo({ walletAddress: '...', chain: 'solana' });
Wallet Generation
🔓 No authentication required. Keys are generated locally and never transmitted.
When a user doesn't have a wallet yet, generate an EVM control wallet for them. Then use the managed-custody sign-in flow to authenticate, and the Gbot backend automatically provisions a full trading wallet — including a Solana address and other chain-specific keys — server-side. No separate Solana wallet generation is needed.
generateEvmWallet() — your control wallet
Generates a new EVM-compatible wallet (Ethereum, Base, Arbitrum, BSC, etc.) using ethers.Wallet.createRandom().
import {
GdexSkill,
generateEvmWallet,
generateGdexSessionKeyPair,
buildGdexSignInMessage,
GDEX_API_KEY_PRIMARY,
} from '@gemachdao/gdex-skill';
const wallet = generateEvmWallet();
console.log(wallet.address);
const { sessionPrivateKey, sessionKey } = generateGdexSessionKeyPair();
const message = buildGdexSignInMessage(wallet.address, String(Date.now()), sessionKey);
🌐 Supported Chains
| Ethereum | 1 | ETH | Uniswap V2/V3, Odos |
| Optimism | 10 | ETH | Uniswap V3, Odos |
| BNB Smart Chain | 56 | BNB | PancakeSwap, Odos |
| Sonic | 146 | S | — |
| Fraxtal | 252 | frxETH | Uniswap V3 |
| Nibiru | 6900 | NIBI | — |
| Base | 8453 | ETH | Uniswap V3, Odos, Arcadia |
| Arbitrum One | 42161 | ETH | Uniswap V3, Odos |
| Berachain | 80094 | BERA | — |
| Solana | 622112261 | SOL | Raydium, Raydium V2, Orca |
| Sui | 1313131213 | SUI | Cetus, Bluefin |
| HyperLiquid | perps only | USDC | Native perp engine |
The ChainId enum is wider than this table. It also defines Avalanche (43114), Polygon
(137), zkSync Era (324), Linea (59144), Blast (81457) and Scroll (534352). The backend's
supportedChainIds does not include them, so calls against those ids will not route. Trade
only the chains listed above.
import { ChainId } from '@gemachdao/gdex-skill';
ChainId.ETHEREUM
ChainId.OPTIMISM
ChainId.BSC
ChainId.SONIC
ChainId.FRAXTAL
ChainId.NIBIRU
ChainId.BASE
ChainId.ARBITRUM
ChainId.BERACHAIN
ChainId.SOLANA
ChainId.SUI
⚠️ Error Handling
import {
GdexAuthError,
GdexValidationError,
GdexApiError,
GdexNetworkError,
GdexRateLimitError,
} from '@gemachdao/gdex-skill';
try {
await skill.buyToken({ ... });
} catch (err) {
if (err instanceof GdexRateLimitError) {
console.log(`Rate limited — retry after ${err.retryAfter}s`);
await new Promise(r => setTimeout(r, err.retryAfter * 1000));
} else if (err instanceof GdexAuthError) {
skill.loginWithApiKey(GDEX_API_KEY_PRIMARY);
} else if (err instanceof GdexValidationError) {
console.error(`Bad param "${err.field}": ${err.message}`);
} else if (err instanceof GdexApiError) {
console.error(`API ${err.statusCode}: ${err.message}`);
} else if (err instanceof GdexNetworkError) {
console.error(`Network (${err.code}): ${err.message}`);
}
}
GdexAuthError | 401/403, invalid credentials |
GdexValidationError | Invalid address, amount, chain, slippage |
GdexApiError | Non-success HTTP (4xx/5xx) |
GdexNetworkError | Connection refused, ECONNABORTED, timeout |
GdexRateLimitError | HTTP 429 (has .retryAfter in seconds) |
🛠 Utility Functions
import {
getChainName,
getNativeToken,
formatTokenAmount,
formatUsd,
formatPercentChange,
shortenAddress,
validateAddress,
validateAmount,
validateChain,
} from '@gemachdao/gdex-skill';
🧪 Testing
All 103 tests run with mocked HTTP — no real API key or network connection required:
npm test
npm run test:coverage
npm run verify
npm run verify:managed
Test suites:
tests/client/auth.test.ts — API key auth, EVM/Solana wallet signing
tests/actions/spotTrade.test.ts — buy/sell, slippage, validation
tests/actions/perpTrade.test.ts — open/close positions, leverage, deposits
tests/actions/portfolio.test.ts — balances, history, wallet info
tests/actions/tokenInfo.test.ts — trending, OHLCV, token details, top traders
tests/utils/walletGeneration.test.ts — EVM control wallet generation (offline)
tests/utils/gdexManagedCrypto.test.ts — managed-custody crypto helpers (AES, signing, ABI encoding)
🏗 Architecture
AI Agent (Claude Code / Cursor / Codex / ...)
│
│ npx skills add GemachDAO/gdex-skill
│ ──────────────────────────────────
│ SKILL.md → agent skill directory
│
▼
@gemachdao/gdex-skill (this package)
│ TypeScript methods with full type safety
│ @gdexsdk/hyper-liquid-trader for HyperLiquid L1 queries & direct execution
│ Managed-custody: AES-256-CBC encryption + secp256k1 session signing
│ computedData payloads for all trade operations
│ Auto-retry with exponential backoff
│
│ Control Wallet (EVM / Solana)
│ └─ signs once for /v1/sign_in → session keypair
│
▼
Gbot Backend API (https://trade-api.gemach.io/v1)
│ Decrypt computedData → verify signature → resolve nonce (gRPC)
│ NATS JetStream trade queue
│ Server-side managed wallets (custody)
│ DEX aggregation engine
▼
Blockchains (Solana · Sui · Ethereum · Base · Arbitrum · …)
HL Managed-Custody Reference
HyperLiquid perp operations use a distinct crypto pipeline from spot trades. Getting any detail wrong produces a 400 Unauthorized (code 103) error. This section documents the exact specification.
HL Deposit Flow
The backend custodially executes the on-chain deposit: it loads the user's server-side private key, constructs an Arbitrum transaction, and sends USDC to the HyperLiquid bridge receiver. The agent only provides an authorization signature.
Agent SDK Backend
──────── ───────
1. ABI-encode deposit params ──► 2. AES-decrypt computedData
(uint64 chainId, address, 3. ABI-decode data
uint256 amount, string nonce) 4. Verify chainId == 42161
2. Sign with session key ──► 5. Verify signature vs stored sessionKey
3. AES-encrypt as computedData ──► 6. Validate token, balance, min deposit
4. POST /v1/hl/deposit 7. Execute ERC-20 transfer on Arbitrum
HL ABI Schemas (CRITICAL)
hl_deposit | ['uint64', 'address', 'uint256', 'string'] | [chainId, tokenAddress, amount, nonce] |
hl_withdraw | ['string', 'string'] | [amount, nonce] |
hl_create_order | ['string', 'bool', 'string', 'string', 'bool', 'string', 'string', 'string', 'bool'] | [coin, isLong, price, size, reduceOnly, nonce, tpPrice, slPrice, isMarket] |
hl_place_order | ['string', 'bool', 'string', 'string', 'bool', 'string'] | [coin, isLong, price, size, reduceOnly, nonce] |
hl_close_all | ['string'] | [nonce] |
hl_cancel_order | ['string', 'string', 'string'] | [nonce, coin, orderId] |
hl_cancel_all_orders | ['string'] | [nonce] |
hl_update_leverage | ['string', 'uint32', 'bool', 'string'] | [coin, leverage, isCross, nonce] |
WARNING: The hl_deposit chainId uses uint64, NOT uint256. This is the single most common cause of "Unauthorized" errors. The backend re-encodes with uint64 for signature verification — if you encode with uint256, the hex differs, signature recovery fails, and you get code 103.
HL Signature Format
All HL write operations sign with the session private key (from sign-in), NOT the control wallet key:
const msg = `${action}-${userId.toLowerCase()}-${dataHex}`;
const digest = keccak256(toUtf8Bytes(msg));
const sig = new SigningKey(sessionPrivateKey).sign(digest);
HL Deposit Constraints
| Chain | Arbitrum only (chainId 42161) |
| Token | USDC only (0xaf88d065e77c8cC2239327C5EDb3A432268e5831) |
| Amount | In smallest unit (6 decimals): 10 USDC = 10000000 |
| Min deposit | 10 USDC |
| Fee buffer | Balance must cover amount × 1.01 |
| Delivery time | ~10 minutes after Arbitrum tx confirms |
| Bridge receiver | 0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7 |
| userId | Control wallet address (from sign-in), NOT managed wallet |
HL Error Codes
| 101 | Missing params | computedData not in request body |
| 102 | Invalid chainId | chainId is not 42161 |
| 102 | Invalid params | Nonce already used, or token not supported |
| 103 | Unauthorized | Signature verification failed — check ABI types (uint64!), userId, and that you're signing with the session key registered during sign-in |
| — | Insufficient balance | Managed wallet doesn't have enough USDC + fee on Arbitrum |
| — | Too low amount | Amount < 10 USDC |
The backend sits behind Cloudflare. Requests MUST include browser-like headers:
{
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Authorization': 'Bearer <apiKey>',
}
WARNING: Using a non-browser User-Agent (e.g., axios/1.x or any string containing bot identifiers) will result in Cloudflare 403 "Access denied". The SDK handles this automatically.
Complete HL Deposit Example
import {
GdexSkill,
GDEX_API_KEY_PRIMARY,
generateGdexSessionKeyPair,
buildGdexSignInMessage,
buildGdexSignInComputedData,
buildHlComputedData,
} from '@gemachdao/gdex-skill';
import { ethers } from 'ethers';
const apiKey = GDEX_API_KEY_PRIMARY;
const skill = new GdexSkill();
skill.loginWithApiKey(apiKey);
const { sessionPrivateKey, sessionKey } = generateGdexSessionKeyPair();
const controlWallet = new ethers.Wallet('0xYourPrivateKey');
const nonce = String(Date.now());
const message = buildGdexSignInMessage(controlWallet.address, nonce, sessionKey);
const signature = await controlWallet.signMessage(message);
const signInPayload = buildGdexSignInComputedData({
apiKey, userId: controlWallet.address, sessionKey, nonce,
signature: signature.replace(/^0x/, ''),
});
await skill.signInWithComputedData({
computedData: signInPayload.computedData, chainId: 42161,
});
const computedData = buildHlComputedData({
action: 'hl_deposit',
apiKey,
walletAddress: controlWallet.address,
sessionPrivateKey,
actionParams: {
chainId: 42161,
tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
amount: '10000000',
},
});
const result = await skill.client.post('/v1/hl/deposit', { computedData });
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Run
npm test && npm run build && npm run verify
- Submit a pull request
License
MIT © GemachDAO