MultiHopper API Bug Bounty Formal Submission

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.


Finding #1: Validation Errors Use Generic BAD_REQUEST Instead of Documented Structured Codes

Severity: 🔴 High — Blocks agentic error handling

What I Built

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.

Steps to Reproduce

# 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)"}}

Evidence

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).

Impact on Agentic Usage

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."

Proposed Fix