Date: 2026-07-04
Contact: @luckysitara (GitHub) / Discord available on request
Environment: Devnet — https://devnet.multihopper.com/api/v1API Key: mh_test_...53b (redacted)
Source Wallet: 3avqNjYEG7bteokEvp8rMT27MPQr8QvYkAW32DZ8qD3ZTest Repository: Private — TypeScript agent harness using @solana/web3.js v1.98, bip39, ed25519-hd-key; all signing client-side, no private keys sent to API.
Test transfers created: 785–793 (9 total). Transfers 790 and 791 were fully deployed and are active on devnet.
BAD_REQUEST Instead of Documented Structured CodesSeverity: 🔴 High — Blocks agentic error handling
A TypeScript agent harness (multihopper-tests/harness.ts) that exercises the full documented API flow. The harness uses the MultiHopper REST API directly with fetch(), plus @solana/web3.js for client-side signing and broadcasting. All signing is done client-side; API calls are standard HTTP.
# 1. Invalid wallet address — docs promise MH_011
curl -s -X POST <https://devnet.multihopper.com/api/v1/transfers> \
-H "x-api-key: mh_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"tokenMint": "So11111111111111111111111111111111111111112",
"amountRaw": "10000000", "amountTokens": "0.01",
"tokenDecimals": 9, "tokenSymbol": "SOL",
"sourceOwner": "not-a-valid-base58-address!!!",
"recipientWallet": "3avqNjYEG7bteokEvp8rMT27MPQr8QvYkAW32DZ8qD3Z",
"hops": 3, "arrivalSeconds": 300
}'
# Response (400):
# {"error":{"code":"BAD_REQUEST","message":"sourceOwner: String must contain at least 32 character(s)"}}
# Expected: {"error":{"code":"MH_011","message":"Invalid wallet address"}}
# 2. Hops below minimum — docs promise MH_013
# Same as above but with "hops": 1
# Response (400):
# {"error":{"code":"BAD_REQUEST","message":"hops: Number must be greater than or equal to 3"}}
# 3. Invalid externalId format — docs promise MH_015
# Same as above but with "externalId": "has spaces and stuff!"
# Response (400):
# {"error":{"code":"BAD_REQUEST","message":"externalId: externalId may only contain letters, digits, '.', '_' and '-' (1-128 chars)"}}
The following error codes from the API reference never appear in actual API responses for their documented scenarios:
| Documented Code | Scenario | Actual Response |
|---|---|---|
MH_011 |
Invalid wallet address | BAD_REQUEST + Zod prose |
MH_013 |
Hops out of range (3–10) | BAD_REQUEST + Zod prose |
MH_015 |
Invalid external ID format | BAD_REQUEST + Zod prose |
Meanwhile, these codes do work correctly: MH_030 (not found), MH_033 (duplicate externalId), MH_036 (sig not on chain), MH_039 (missing keeper sig), MH_070 (bad idempotency key), MH_071 (key reuse with diff body).
Critical for agent reliability. An AI agent's error-handling logic built against the documented API would contain:
switch (error.code) {
case "MH_011": return "Invalid wallet address — check sourceOwner format";
case "MH_013": return "Hops must be between 3 and 10";
case "MH_015": return "externalId contains invalid characters";
}
None of these branches would ever execute. The agent would fall through to a generic error handler, losing the ability to provide actionable feedback or auto-correct. This directly undermines the stated goal of making "agent-driven on-chain routing safer, clearer and more reliable."