Api

Wallet API

Deposit addresses, balances, and transaction history

The Wallet API exposes Hydra App's WalletService — deposit address management, on-chain and off-chain balances, and transaction history per network.

JSON-RPC namespace: wallet

Endpoints


Get Deposit Address

Returns a deposit address for the specified network. The address can be shared with others to receive on-chain funds. Each call may return the same or a new address depending on the protocol.

Method: GetDepositAddress

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network

Response:

FieldTypeDescription
addressstringDeposit address for this wallet

Example Request:

import { WalletServiceClient } from './proto/WalletServiceClientPb'
import { GetDepositAddressRequest } from './proto/wallet_pb'

const client = new WalletServiceClient('http://localhost:5001')

const request = new GetDepositAddressRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })

const response = await client.getDepositAddress(request, {})
console.log('Deposit address:', response.getAddress())

Example Response:

{ "address": "bc1qxyz..." }

Get Unique Deposit Address

Returns a unique, never-before-used deposit address. Only supported on UTXO-based protocols (e.g., Bitcoin). Each call returns a fresh address from the HD derivation chain. If there are previously released addresses in the free pool, the lowest available index is reused; otherwise a new derivation index is revealed.

Method: GetUniqueDepositAddress

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network (must be UTXO-based)

Response:

FieldTypeDescription
addressstringA fresh address guaranteed to be unique

Example Request:

import { GetUniqueDepositAddressRequest } from './proto/wallet_pb'

const request = new GetUniqueDepositAddressRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })

const response = await client.getUniqueDepositAddress(request, {})
console.log('Fresh address:', response.getAddress())

Example Response:

{ "address": "bc1qfreshaddress..." }

Release Deposit Address

Releases a previously acquired unique deposit address back to the pool. Must only be called when the associated invoice has expired without receiving any on-chain payment. The server verifies that no funds have been received at the address before reinserting it into the free pool.

Returns an error if the address has received funds or was not previously acquired via GetUniqueDepositAddress.

Method: ReleaseDepositAddress

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
addressstringYESThe address to release back to the free pool

Response: Empty.

Example Request:

import { ReleaseDepositAddressRequest } from './proto/wallet_pb'

const request = new ReleaseDepositAddressRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setAddress('bc1qfreshaddress...')

await client.releaseDepositAddress(request, {})
console.log('Address released')

Example Response:

{}

Get Balances

Get all asset balances for a specific network.

Method: GetBalances

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to query balances for

Network Object:

FieldTypeDescription
protocolProtocol enumPROTOCOL_BITCOIN = 1, PROTOCOL_EVM = 2
idstringMagic bytes (hex) for Bitcoin, decimal chain ID for EVM

Response:

FieldTypeDescription
balancesmap<string, Balance>Map of asset_id → Balance

Balance Object:

FieldTypeDescription
onchainOnchainBalanceOnchain balance details
offchainOffchainBalanceOffchain (Lightning) balance details

OnchainBalance:

FieldTypeDescription
confirmedDecimalStringConfirmed balance
unconfirmedDecimalStringUnconfirmed balance
reservedDecimalStringReserved for channel operations

OffchainBalance:

FieldTypeDescription
localDecimalStringBalance you can send
remoteDecimalStringBalance you can receive

Example Request:

TypeScript
import { WalletServiceClient } from './proto/WalletServiceClientPb'
import { GetBalancesRequest } from './proto/wallet_pb'

const client = new WalletServiceClient('http://localhost:5001')

const request = new GetBalancesRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })

const response = await client.getBalances(request, {})
const balances = response.getBalancesMap()
Go
import (
    pb "github.com/hydra/hydra-go/proto"
)

client := pb.NewWalletServiceClient(conn)

req := &pb.GetBalancesRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
}

resp, err := client.GetBalances(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

balances := resp.Balances
Rust
use hydra_app::wallet_service_client::WalletServiceClient;
use hydra_app::{GetBalancesRequest, Network};

let mut client = WalletServiceClient::new(channel);

let request = tonic::Request::new(GetBalancesRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
});

let response = client.get_balances(request).await?;
let balances = response.into_inner().balances;

Example Response:

{
  "balances": {
    "BTC": {
      "onchain": {
        "confirmed": "50000000",
        "unconfirmed": "0",
        "reserved": "5000000"
      },
      "offchain": {
        "local": "10000000",
        "remote": "5000000"
      }
    },
    "RGB:abc123": {
      "onchain": {
        "confirmed": "1000",
        "unconfirmed": "0",
        "reserved": "0"
      },
      "offchain": {
        "local": "500",
        "remote": "500"
      }
    }
  }
}

Get Balance

Get the balance of a specific asset on a network.

Method: GetBalance

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to query
asset_idstringYESAsset identifier (e.g., "BTC", "0x...")

Response:

FieldTypeDescription
balanceBalanceBalance details for the asset

Example Request:

TypeScript
const request = new GetBalanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setAssetId('BTC')

const response = await client.getBalance(request, {})
const balance = response.getBalance()
Go
req := &pb.GetBalanceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    AssetId: "BTC",
}

resp, err := client.GetBalance(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

balance := resp.Balance
Rust
let request = tonic::Request::new(GetBalanceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    asset_id: "BTC".to_string(),
});

let response = client.get_balance(request).await?;
let balance = response.into_inner().balance;

Example Response:

{
  "balance": {
    "onchain": {
      "confirmed": "50000000",
      "unconfirmed": "0",
      "reserved": "5000000"
    },
    "offchain": {
      "local": "10000000",
      "remote": "5000000"
    }
  }
}

Get Transactions

Get all transactions for a network.

Method: GetTransactions

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to query transactions for

Response:

FieldTypeDescription
transactionsTransaction[]Array of transactions

Transaction Object:

FieldTypeDescription
txidstringTransaction ID
timestampTimestampTransaction timestamp
confirmationsuint32Number of confirmations
asset_transfersAssetTransfer[]Asset movements in this transaction

Example Request:

TypeScript
const request = new GetTransactionsRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })

const response = await client.getTransactions(request, {})
const transactions = response.getTransactionsList()
Go
req := &pb.GetTransactionsRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
}

resp, err := client.GetTransactions(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

transactions := resp.Transactions
Rust
let request = tonic::Request::new(GetTransactionsRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
});

let response = client.get_transactions(request).await?;
let transactions = response.into_inner().transactions;

Example Response:

{
  "transactions": [
    {
      "txid": "abc123...",
      "timestamp": "2025-10-03T10:30:00Z",
      "confirmations": 6,
      "asset_transfers": [
        {
          "asset_id": "BTC",
          "amount": "1000000",
          "direction": "OUTGOING"
        }
      ]
    },
    {
      "txid": "def456...",
      "timestamp": "2025-10-02T14:20:00Z",
      "confirmations": 144,
      "asset_transfers": [
        {
          "asset_id": "BTC",
          "amount": "5000000",
          "direction": "INCOMING"
        }
      ]
    }
  ]
}

Get Transaction

Get details of a specific transaction by its ID.

Method: GetTransaction

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork where transaction occurred
txidstringYESTransaction ID

Response:

FieldTypeDescription
transactionTransactionTransaction details

Example Request:

TypeScript
const request = new GetTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTxid('abc123...')

const response = await client.getTransaction(request, {})
const transaction = response.getTransaction()
Go
req := &pb.GetTransactionRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    Txid: "abc123...",
}

resp, err := client.GetTransaction(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

transaction := resp.Transaction
Rust
let request = tonic::Request::new(GetTransactionRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    txid: "abc123...".to_string(),
});

let response = client.get_transaction(request).await?;
let transaction = response.into_inner().transaction;

Example Response:

{
  "transaction": {
    "txid": "abc123...",
    "timestamp": "2025-10-03T10:30:00Z",
    "confirmations": 6,
    "fee": "2500",
    "asset_transfers": [
      {
        "asset_id": "BTC",
        "amount": "1000000",
        "direction": "OUTGOING"
      }
    ]
  }
}

Asset ID Formats

Different networks use different asset ID formats:

Bitcoin

  • Native: "BTC"
  • RGB Assets: "RGB:contract_id"

Ethereum/EVM

  • Native: "0x0000000000000000000000000000000000000000" (42-character zero address)
  • ERC20 Tokens: "ERC20:0x..." (contract address)

Common Patterns

Check if user has sufficient balance

TypeScript
async function hasSufficientBalance(
  client: WalletServiceClient,
  network: Network,
  assetId: string,
  requiredAmount: string,
  balanceType: 'onchain' | 'offchain'
): Promise<boolean> {
  const request = new GetBalanceRequest()
  request.setNetwork(network)
  request.setAssetId(assetId)

  const response = await client.getBalance(request, {})
  const balance = response.getBalance()

  const available = balanceType === 'onchain'
    ? balance.getOnchain()?.getConfirmed()
    : balance.getOffchain()?.getLocal()

  return BigInt(available || '0') >= BigInt(requiredAmount)
}
Go
func hasSufficientBalance(
    client pb.WalletServiceClient,
    network *pb.Network,
    assetId string,
    requiredAmount string,
    balanceType string,
) (bool, error) {
    req := &pb.GetBalanceRequest{
        Network: network,
        AssetId: assetId,
    }

    resp, err := client.GetBalance(context.Background(), req)
    if err != nil {
        return false, err
    }

    balance := resp.Balance
    var available string

    if balanceType == "onchain" {
        available = balance.Onchain.Confirmed
    } else {
        available = balance.Offchain.Local
    }

    requiredBig := new(big.Int)
    requiredBig.SetString(requiredAmount, 10)

    availableBig := new(big.Int)
    availableBig.SetString(available, 10)

    return availableBig.Cmp(requiredBig) >= 0, nil
}
Rust
async fn has_sufficient_balance(
    client: &mut WalletServiceClient<Channel>,
    network: Network,
    asset_id: String,
    required_amount: String,
    balance_type: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GetBalanceRequest {
        network: Some(network),
        asset_id,
    });

    let response = client.get_balance(request).await?;
    let balance = response.into_inner().balance.unwrap();

    let available = if balance_type == "onchain" {
        balance.onchain.unwrap().confirmed
    } else {
        balance.offchain.unwrap().local
    };

    let required: u64 = required_amount.parse()?;
    let available_amount: u64 = available.parse()?;

    Ok(available_amount >= required)
}

Monitor transaction confirmations

TypeScript
async function waitForConfirmations(
  client: WalletServiceClient,
  network: Network,
  txid: string,
  requiredConfirmations: number
): Promise<void> {
  while (true) {
    const request = new GetTransactionRequest()
    request.setNetwork(network)
    request.setTxid(txid)

    const response = await client.getTransaction(request, {})
    const tx = response.getTransaction()

    if (tx.getConfirmations() >= requiredConfirmations) {
      return
    }

    await new Promise(resolve => setTimeout(resolve, 30000)) // Wait 30s
  }
}
Go
func waitForConfirmations(
    client pb.WalletServiceClient,
    network *pb.Network,
    txid string,
    requiredConfirmations uint32,
) error {
    for {
        req := &pb.GetTransactionRequest{
            Network: network,
            Txid:    txid,
        }

        resp, err := client.GetTransaction(context.Background(), req)
        if err != nil {
            return err
        }

        tx := resp.Transaction

        if tx.Confirmations >= requiredConfirmations {
            return nil
        }

        time.Sleep(30 * time.Second) // Wait 30s
    }
}
Rust
async fn wait_for_confirmations(
    client: &mut WalletServiceClient<Channel>,
    network: Network,
    txid: String,
    required_confirmations: u32,
) -> Result<(), Box<dyn std::error::Error>> {
    loop {
        let request = tonic::Request::new(GetTransactionRequest {
            network: Some(network.clone()),
            txid: txid.clone(),
        });

        let response = client.get_transaction(request).await?;
        let tx = response.into_inner().transaction.unwrap();

        if tx.confirmations >= required_confirmations {
            return Ok(());
        }

        tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; // Wait 30s
    }
}

Error Handling

Error CodeDescriptionSolution
INVALID_ARGUMENTInvalid network or asset_idCheck asset ID format for the network
NOT_FOUNDTransaction not foundVerify txid is correct
UNAVAILABLEService temporarily unavailableRetry with exponential backoff

Best Practices

  1. Cache balance queries - Balances don't change instantly, cache for 10-30 seconds
  2. Use GetBalances for multiple assets - More efficient than multiple GetBalance calls
  3. Check both onchain and offchain - Lightning payments require offchain balance
  4. Monitor reserved balance - Reserved funds cannot be spent until channel operations complete
  5. Handle unconfirmed balance - Don't rely on unconfirmed funds for critical operations

← Back to API Reference | Next: Pricing API →


Copyright © 2025