Api

Error Codes

Complete error reference

This document provides a comprehensive reference for all error codes in the Hydra API.

gRPC Status Codes

Hydra uses standard gRPC status codes. All errors follow this format:

{
  "code": 3,
  "message": "invalid asset id",
  "details": []
}

Common Status Codes

OK (0)

Description: Success - no error

When it occurs: Successful operation

Action: None required


CANCELLED (1)

Description: Operation was cancelled

When it occurs:

  • Client cancelled request
  • Stream was closed

Action: Retry if needed


UNKNOWN (2)

Description: Unknown error

When it occurs:

  • Unexpected server error
  • Unhandled exception

Action: Check request parameters, contact support if persists


INVALID_ARGUMENT (3)

Description: Client specified an invalid argument

Common causes:

  • Invalid asset ID format
  • Invalid network parameters
  • Malformed request data
  • Out-of-range values

Examples:

// Error: invalid asset id
{
  "code": 3,
  "message": "invalid asset id"
}

// Error: invalid network
{
  "code": 3,
  "message": "unsupported network protocol"
}

// Error: invalid amount
{
  "code": 3,
  "message": "amount must be positive"
}

Action:

  • Verify asset ID format (zero-padded hash for native assets, contract address for ERC-20s — see asset.GetNativeAsset / asset.GetAssets)
  • Check Network shape: { protocol, id } only — not chain_id/name
  • Validate all numeric values (DecimalString is human-readable, not base units)
  • Ensure required fields and one-of variants are present (e.g. exactly one fee_payment on Lease requests)

DEADLINE_EXCEEDED (4)

Description: Operation timed out

When it occurs:

  • Request took too long (>10 seconds for most operations)
  • Network latency issues

Action:

  • Retry with exponential backoff
  • Check network connectivity
  • For long operations, use streaming endpoints

NOT_FOUND (5)

Description: Requested resource not found

Common causes:

  • Transaction doesn't exist
  • Order not found
  • Channel doesn't exist
  • Payment not found

Examples:

// Error: transaction not found
{
  "code": 5,
  "message": "transaction not found: abc123..."
}

// Error: order not found
{
  "code": 5,
  "message": "order not found: order_xyz789"
}

Action:

  • Verify resource ID is correct
  • Check that resource exists on the specified network
  • Ensure resource hasn't been deleted

ALREADY_EXISTS (6)

Description: Resource already exists

When it occurs:

  • Attempting to create duplicate resource
  • Market already initialized
  • Channel already opened

Examples:

{
  "code": 6,
  "message": "market already initialized"
}

Action:

  • Check if resource already exists
  • Use update/modify operations instead of create

PERMISSION_DENIED (7)

Description: Caller doesn't have permission

When it occurs:

  • Authentication failure
  • Insufficient permissions
  • Attempting to access another user's resource

Action:

  • Verify authentication credentials
  • Check API key permissions
  • Ensure you own the resource

RESOURCE_EXHAUSTED (8)

Description: Resource has been exhausted

Common causes:

  • Insufficient balance
  • No liquidity available
  • Rental capacity exceeded
  • Channel capacity full

Examples:

// Error: insufficient balance
{
  "code": 8,
  "message": "insufficient onchain balance"
}

// Error: no liquidity
{
  "code": 8,
  "message": "no liquidity available in orderbook"
}

// Error: rental capacity
{
  "code": 8,
  "message": "rental amount exceeds available liquidity"
}

Action:

  • Check balance before operations
  • Add funds to wallet
  • Try smaller amount
  • Wait for liquidity to become available

FAILED_PRECONDITION (9)

Description: Operation rejected due to system state

Common causes:

  • Channel not active yet
  • Market not initialized
  • Asset not supported
  • Duration out of allowed range

Examples:

// Error: channel not ready
{
  "code": 9,
  "message": "channel not active for sending"
}

// Error: market not initialized
{
  "code": 9,
  "message": "market not initialized for this pair"
}

// Error: duration invalid
{
  "code": 9,
  "message": "rental duration below minimum"
}

Action:

  • Wait for prerequisite conditions
  • Initialize required resources first
  • Check state before operations
  • Verify parameter ranges

ABORTED (10)

Description: Operation was aborted

When it occurs:

  • Concurrent modification conflict
  • Transaction conflict
  • Order already cancelled

Action: Retry operation


OUT_OF_RANGE (11)

Description: Operation outside valid range

Common causes:

  • Amount too large or too small
  • Fee rate invalid
  • Duration out of range

Action:

  • Check minimum/maximum values
  • Adjust parameters to valid range

UNIMPLEMENTED (12)

Description: Operation not implemented

When it occurs:

  • Feature not yet available
  • Endpoint deprecated
  • Network not supported

Action:

  • Check API documentation for supported features
  • Use alternative endpoint

INTERNAL (13)

Description: Internal server error

When it occurs:

  • Database error
  • Backend service failure
  • Unexpected server condition

Action:

  • Retry with exponential backoff
  • Contact support if persists

UNAVAILABLE (14)

Description: Service temporarily unavailable

Common causes:

  • Service maintenance
  • Network issues
  • Backend overload

Action:

  • Retry with exponential backoff
  • Check status page
  • Wait and retry

DATA_LOSS (15)

Description: Unrecoverable data loss

When it occurs: Rare - serious server issue

Action: Contact support immediately


UNAUTHENTICATED (16)

Description: Request lacks valid authentication

When it occurs:

  • Missing authentication
  • Invalid credentials
  • Expired session

Action:

  • Provide authentication credentials
  • Refresh authentication token
  • Check API key is valid

Error Handling Best Practices

1. Implement Retry Logic — for idempotent RPCs only

⚠️ Use this helper only for Get*, Estimate*, and stream-reconnect calls. Wrapping SendTransaction, OpenChannel, CreateOrder, etc. in a blind retry can cause double-execution. See Idempotency & Retry Safety for the full rules.

async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation()
    } catch (error: any) {
      const code = error.code

      // Don't retry client errors (INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED,
      // OUT_OF_RANGE, UNIMPLEMENTED, UNAUTHENTICATED, FAILED_PRECONDITION)
      if ([3, 5, 7, 9, 11, 12, 16].includes(code)) {
        throw error
      }

      // Retry on transient errors (DEADLINE_EXCEEDED, UNAVAILABLE) and INTERNAL
      if ([4, 13, 14].includes(code)) {
        const delay = baseDelay * Math.pow(2, i) + Math.floor(Math.random() * 500)
        console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`)
        await new Promise(resolve => setTimeout(resolve, delay))
        continue
      }

      throw error
    }
  }

  throw new Error('Max retries exceeded')
}

// ✅ Safe — Get* is idempotent
const balance = await withRetry(() => walletClient.getBalance(request, {}))

// ❌ DANGEROUS — SendTransaction is NOT idempotent.
//    A DEADLINE_EXCEEDED retry can broadcast twice.
//    Use the verify-before-retry pattern from the Idempotency section.
// const tx = await withRetry(() => clientClient.sendTransaction(request, {}))

2. Graceful Error Handling

async function safeGetBalance(
  client: WalletServiceClient,
  network: Network,
  assetId: string
): Promise<Balance | null> {
  try {
    const request = new GetBalanceRequest()
    request.setNetwork(network)
    request.setAssetId(assetId)

    const response = await client.getBalance(request, {})
    return response.getBalance()
  } catch (error) {
    switch (error.code) {
      case 3:
        console.error('Invalid asset ID:', assetId)
        return null

      case 5:
        console.error('Asset not found:', assetId)
        return null

      case 14:
        console.warn('Service unavailable, retrying...')
        // Implement retry logic
        return null

      default:
        console.error('Unexpected error:', error)
        throw error
    }
  }
}

3. Check Preconditions

async function sendPaymentSafely(
  walletClient: WalletServiceClient,
  nodeClient: NodeServiceClient,
  network: Network,
  assetId: string,
  amount: string
) {
  // Check balance first
  const balanceReq = new GetBalanceRequest()
  balanceReq.setNetwork(network)
  balanceReq.setAssetId(assetId)

  const balanceResp = await walletClient.getBalance(balanceReq, {})
  const available = balanceResp.getBalance()?.getOffchain()?.getLocal()

  if (BigInt(available || '0') < BigInt(amount)) {
    throw new Error('Insufficient balance')
  }

  // Proceed with payment
  const paymentReq = new SendPaymentRequest()
  // ... configure payment
  return await nodeClient.sendPayment(paymentReq, {})
}

4. User-Friendly Messages

function getUserFriendlyError(error: any): string {
  switch (error.code) {
    case 3:
      return 'Invalid input. Please check your entries.'

    case 5:
      return 'Resource not found. Please verify the ID.'

    case 8:
      if (error.message.includes('balance')) {
        return 'Insufficient funds. Please add more to your wallet.'
      }
      if (error.message.includes('liquidity')) {
        return 'Not enough liquidity available. Try a smaller amount.'
      }
      return 'Resource limit exceeded.'

    case 9:
      return 'Operation cannot be completed right now. Please try again later.'

    case 14:
      return 'Service temporarily unavailable. Please try again in a moment.'

    default:
      return 'An unexpected error occurred. Please try again.'
  }
}

// Usage in UI
try {
  await client.someOperation(request, {})
} catch (error) {
  alert(getUserFriendlyError(error))
}

Service-Specific Errors

Wallet Service

ErrorCauseSolution
INVALID_ARGUMENT: invalid asset idWrong asset ID formatCheck asset ID for network type
NOT_FOUND: transaction not foundTransaction doesn't existVerify txid is correct

Orderbook Service

ErrorCauseSolution
RESOURCE_EXHAUSTED: no liquidityEmpty orderbookWait or provide liquidity
FAILED_PRECONDITION: market not initializedMarket doesn't existCall InitMarket first
INVALID_ARGUMENT: amount below minimumAmount too smallIncrease to min_base_amount

Swap Service

ErrorCauseSolution
RESOURCE_EXHAUSTED: insufficient balanceNot enough offchain fundsUse SimpleSwap instead
FAILED_PRECONDITION: price exceeded tolerancePrice moved too muchIncrease tolerance or retry

Node Service

ErrorCauseSolution
FAILED_PRECONDITION: channel not activeChannel still opening / confirmingWait for the Active event from SubscribeNodeEvents
NOT_FOUND: channel not foundInvalid channel_idVerify with watchOnlyNode.GetChannels
RESOURCE_EXHAUSTED: insufficient capacityChannel too smallDeposit more funds via node.DepositChannel or liquidity.RequestChannelLiquidity (deposit op)
INVALID_ARGUMENT: peer not in zero-conf whitelistTried zero-conf with non-whitelisted peerAdd peer via node.AddPeerToZeroConfWhitelist first
FAILED_PRECONDITION: funding allowance exceededPeer-initiated deposit/payment exceeds set allowanceRaise via node.IncreaseFundingAllowance

Liquidity Service (Lease API)

ErrorCauseSolution
INVALID_ARGUMENT: lease_duration_seconds requiredserver_amount > 0 without a durationSet lease_duration_seconds, or set server_amount = "0"
INVALID_ARGUMENT: dual_fund_fee_payment not allowedUsed dual_fund_fee_payment on Release or Lease ExtensionSwitch to onchain_fee_payment or offchain_fee_payment
INVALID_ARGUMENT: exactly one fee_payment requiredNone or multiple fee_payment variants setSet exactly one
RESOURCE_EXHAUSTED: insufficient liquidityProvider has no inventory for that assetReduce server_amount, try a different asset, or self-fund via node.OpenChannel
FAILED_PRECONDITION: channel does not existBad channel_id on Release / Lease ExtensionVerify with watchOnlyNode.GetChannel

Watch-Only Node Service

ErrorCauseSolution
NOT_FOUND: channel not foundBad channel_idUse GetChannels to enumerate
NOT_FOUND: payment not foundBad payment_idUse GetPayments / GetPaymentsByHash to look up

Client Service

ErrorCauseSolution
INVALID_ARGUMENT: invalid amountAmount oneof unset, or DecimalString not a numberSet exact or all; ensure value parses
RESOURCE_EXHAUSTED: insufficient onchain balanceWallet under-funded for the send + feeWait for incoming deposits or reduce amount
FAILED_PRECONDITION: token allowance too lowEVM token send needs higher allowanceCall client.SetTokenAllowance first

Pricing Service

ErrorCauseSolution
NOT_FOUND: price unavailableProvider has no quote for that asset/currencyResponse field price is empty, not an error — only some asset/symbol combos return a price. Treat empty as "unknown."

Idempotency & Retry Safety

A bot must know which RPCs are safe to retry blindly after a DEADLINE_EXCEEDED or transient UNAVAILABLE.

Safe to retry (read-only, idempotent)

RPC familySafe to retry?
All Get* queries on every service✅ Always
Estimate* RPCs (e.g. EstimateOpenChannelFee, EstimateRequestChannelLiquidityFee)✅ Always
Subscribe* (re-establishing after a drop)✅ Always — reconnect with backoff

Retry only after checking server state first

These RPCs are not idempotent. After a DEADLINE_EXCEEDED you may have triggered the action without seeing the response. Verify before retrying:

RPCHow to verify before retry
client.SendTransaction, client.SendTokenTransaction, client.BumpTransactionQuery wallet.GetTransactions for a recent tx with the same recipient / amount
client.SetTokenAllowanceQuery blockchain.GetTokenAllowance for the new value
client.FinalizeAndBroadcastTransactionQuery wallet.GetTransaction for the txid
node.OpenChannel, node.DepositChannel, node.WithdrawChannel, node.CloseChannel, node.ForceCloseChannel, node.RedeemClosedChannelWatch SubscribeNodeEvents — events for the channel will fire if the operation went through
node.SendChannelPayment, node.SendPayment, node.PayInvoice, node.PayEmptyInvoiceQuery watchOnlyNode.GetPaymentsByHash (you must know the payment hash)
liquidity.RequestChannelLiquidityWatch SubscribeNodeEvents for new channel creation
liquidity.RequestChannelReleaseWatch SubscribeNodeEvents for the channel state change
liquidity.RequestChannelLeaseExtensionQuery watchOnlyNode.GetChannel and check the new expiry
orderbook.CreateOrderQuery orderbook.GetAllOwnOrders and look for the same (base, quote, side, amount, price) shape — note: not perfectly unique, see below
orderbook.CancelOrderQuery orderbook.GetOrder — if status is cancelled, the request succeeded
swap.Swap, swap.SimpleSwapswap.SubscribeSimpleSwaps will emit progress; otherwise orderbook.GetAllOwnOrders

Never auto-retry

StatusWhy
INVALID_ARGUMENTYour request is wrong — fix the code, don't retry
FAILED_PRECONDITIONState doesn't allow this — wait for state change, then re-evaluate (don't blindly retry)
PERMISSION_DENIED / UNAUTHENTICATEDAuth issue — re-authenticate first
OUT_OF_RANGENumerical input outside permitted range — fix
UNIMPLEMENTEDRPC doesn't exist on this server version

Application-level idempotency keys

Hydra's RPCs don't accept idempotency tokens. To make your bot truly idempotent across retries, you need to track an action ID at the application level:

  1. Generate a UUID for each high-level action (e.g. "place 0.1 BTC sell at $67k").
  2. Persist (action_id, status: pending|done|failed) on disk before calling the RPC.
  3. After the RPC returns or after a verification check, update the status.
  4. On startup, replay any pending actions: query the relevant Get* RPC, decide if the action completed, and update.

For order placement specifically: include a unique client tag in your in-memory mapping of action_id → submitted_order_request. After a retry, if GetAllOwnOrders shows two orders matching the request shape, you've double-submitted — cancel one with CancelOrder.


Debugging Tips

  1. Enable verbose logging - Log all requests and responses
  2. Check error.message - Contains detailed error information
  3. Verify request format - Use protobuf debugging tools
  4. Test with small amounts - Use minimal values when debugging
  5. Check network status - Ensure blockchain is synced
  6. Monitor rate limits - Avoid excessive requests

← Back to API Reference | Next: API Reference →


Copyright © 2025