Install
openclaw skills install xenodiaEnables this agent to authenticate with and use the Xenodia Multimodal AI Gateway. Covers two wallet identity modes (local keypair OR CDP Server Wallet), balance checking, model availability queries, and switching your LLM provider to Xenodia.
openclaw skills install xenodiaXenodia is a unified AI Gateway with a standard OpenAI-compatible API. It uses EVM wallet identity + EIP-191 signatures for authentication — no static API keys.
Gateway Base URL: https://api.xenodia.xyz (or XENODIA_BASE_URL env var)
Helper scripts in this skill folder:
xenodia_client.py — local keypair mode (no CDP)xenodia_cdp_client.py — CDP Server Wallet mode (MPC, no raw private key)| Situation | Use |
|---|---|
| You have a local EVM private key | xenodia_client.py |
| No private key, using Coinbase CDP | xenodia_cdp_client.py |
If unsure, use CDP mode — it's more secure (key never leaves Coinbase).
echo "0xYOUR_HEX_PRIVATE_KEY" > .xenodia_agent_key
python3 xenodia_client.py check-wallet
python3 xenodia_client.py init
Prints your new wallet address. Tell your owner to bind it (see Step: Bind Address below).
You need 3 values from portal.cdp.coinbase.com. Tell your owner:
"I need you to get 3 things from portal.cdp.coinbase.com:
- CDP_API_KEY_ID and CDP_API_KEY_SECRET: Go to portal → top-left menu → API Keys → Create API Key → After creation, copy the
"id"field (= CDP_API_KEY_ID) and the"privateKey"field (= CDP_API_KEY_SECRET, a base64 string ~88 chars)- CDP_WALLET_SECRET: Go to portal → top-left menu → Server Wallet → Click "Generate Wallet Secret" → Copy the value immediately — it's shown only once → It's a longer base64 string (~180 chars), starts with
MIGHAgEA...These 3 values are different — do NOT reuse the same value for API secret and Wallet Secret."
pip install cdp-sdk requests
export CDP_API_KEY_ID="your-uuid"
export CDP_API_KEY_SECRET="your-base64-ed25519-key" # ~88 chars
export CDP_WALLET_SECRET="MIGHAgEAMBMGByqGSM49..." # ~180 chars, starts MIGHAgEA
export CDP_WALLET_NAME="xenodia-agent" # optional, default: "xenodia-agent"
⚠️ Critical:
CDP_API_KEY_SECRETandCDP_WALLET_SECRETare completely different keys. Using the same value for both will fail with "EC key deserialization error".
python3 xenodia_cdp_client.py init
Expected output:
============================================================
CDP SERVER WALLET INITIALIZED
============================================================
Account Name : xenodia-agent
Address : 0xd39211a61a7194d5C188981c3ec14846385dDA22
Signing Mode : CDP MPC (no private key stored locally)
► NEXT STEP:
Tell your human owner to bind this address:
→ https://xenodia.xyz/settings (AGENT_BINDINGS section)
============================================================
Tell your owner:
"Please go to https://xenodia.xyz/settings → AGENT_BINDINGS section, and add this wallet address:
0x...(the address printed byinit). Without this, I can't authenticate with Xenodia."
python3 xenodia_cdp_client.py balance
# Expected: Balance: 10.000000 USDC (10000000 micro-USDC)
If you get [!] Wallet not bound → owner hasn't completed Step 2 yet.
Once the balance check passes, ask the agent to save the credentials to ~/.zshrc so they're available in every future session without prompting:
"验证成功了,帮我把这三个 CDP 变量写到 ~/.zshrc 里保存起来。"
The agent will append:
export CDP_API_KEY_ID="..."
export CDP_API_KEY_SECRET="..."
export CDP_WALLET_SECRET="..."
After this, future sessions will pick up the credentials automatically from the environment — no need to paste them again.
python3 xenodia_cdp_client.py init # Create/load CDP account
python3 xenodia_cdp_client.py check-wallet # Show wallet address
python3 xenodia_cdp_client.py balance # Check Xenodia credit balance
python3 xenodia_cdp_client.py models # List available models
python3 xenodia_cdp_client.py check-model <name> # Check specific model
python3 xenodia_cdp_client.py get-token # Print JWT to stdout
python3 xenodia_cdp_client.py chat <model> "<prompt>" # Make LLM call
| Error | Cause | Fix |
|---|---|---|
EC key deserialization error | CDP_WALLET_SECRET is wrong format | Get the real Wallet Secret from portal → Server Wallet page |
Missing CDP credentials | Env vars not set | Export all 3 env vars before running |
Wallet Secret not configured | CDP_WALLET_SECRET missing | Account doesn't exist yet, need wallet secret to create it |
invalid_signature | Wrong signing method or address not bound | Ensure address is bound in Xenodia settings |
Wallet not bound | Address not added to Xenodia | Ask owner to bind address at https://xenodia.xyz/settings |
RuntimeError: Timeout should be used inside a task | Python 3.14 asyncio bug | Script auto-workarounds this via thread-based event loop |
python3 xenodia_client.py balance # local wallet
python3 xenodia_cdp_client.py balance # CDP wallet
# → Balance: 10.000000 USDC (10000000 micro-USDC)
python3 xenodia_client.py models # shows live list from gateway
python3 xenodia_client.py check-model gpt-4o
Models are configured server-side in gateway-svc/models.yaml — always query dynamically,
don't hardcode a list in your code.
Xenodia supports TWO authentication methods depending on your use-case:
If you are helping configure ~/.openclaw/openclaw.json or another static configuration file for your owner, you should generate a long-term validity API Key and provide it to them.
# Generate a static, long-term API Key
python3 xenodia_cdp_client.py get-api-key # or xenodia_client.py
Instruction for the Agent:
eyJ...).python3 xenodia_cdp_client.py models. The /models endpoint returns balance info./models endpoint which triggers an x402 payment, or ask the owner to top up their account via the website after binding the wallet).~/.openclaw/openclaw.json or other config files, offer to modify the file directly:
apiKey to the generated long-term key.baseUrl to https://api.xenodia.xyz/v1 (or the Xenodia host).model to one of the Xenodia models (e.g., claude-3-7-sonnet, deepseek-v3). No provider prefixes needed!Xenodia's default tokens are JWTs that expire every hour. If you are writing a standalone script, you should fetch a fresh one at runtime.
❌ Common Mistake: Do not save get-token output into a static XENODIA_API_KEY environment variable. It will expire in exactly 1 hour.
✅ Correct Script Pattern:
import subprocess
from openai import OpenAI
# Always fetch a fresh token at runtime (don't cache to env var)
token = subprocess.check_output(
["python3", "xenodia_cdp_client.py", "get-token"] # or xenodia_client.py
).decode().strip()
# Use just like OpenAI SDK — only base_url and api_key change
client = OpenAI(api_key=token, base_url="https://api.xenodia.xyz/v1")
response = client.chat.completions.create(
model="deepseek-v3", # short model ID, no provider prefix
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
TOKEN=$(python3 xenodia_cdp_client.py get-token)
curl -X POST https://api.xenodia.xyz/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'
Signing (CDP mode): Use cdp.evm.sign_message(address, message) — pass the raw message string, it returns a 0x... hex signature directly. Do NOT use account.sign_message(SignableMessage) from EvmServerAccount — that method has a different internal flow and produces a signature that doesn't recover to the correct address.
asyncio (Python 3.14): asyncio.run() crashes on shutdown due to shutdown_default_executor trying to use a timeout context manager outside a task. Workaround: run the event loop in a fresh thread via concurrent.futures.ThreadPoolExecutor.
| Command (local) | Command (CDP) | Description |
|---|---|---|
xenodia_client.py init | xenodia_cdp_client.py init | Initialize wallet |
xenodia_client.py check-wallet | xenodia_cdp_client.py check-wallet | Show address |
xenodia_client.py balance | xenodia_cdp_client.py balance | Check credits |
xenodia_client.py models | xenodia_cdp_client.py models | List models |
xenodia_client.py check-model X | xenodia_cdp_client.py check-model X | Check model |
xenodia_client.py get-token | xenodia_cdp_client.py get-token | Get 1-hr JWT |
xenodia_client.py get-api-key | xenodia_cdp_client.py get-api-key | Get long-term API Key |
xenodia_client.py chat M "P" | xenodia_cdp_client.py chat M "P" | Chat call |