Blockchain API
The Blockchain API exposes Hydra App's BlockchainService — read-only methods to query on-chain data and a single broadcast method for fully-signed raw transactions.
JSON-RPC namespace: blockchain
Endpoints
- Get Block Number
- Get Block Header
- Get Transaction By Id
- Get Fee Estimates
- Broadcast Raw Transaction
- Get Address Balance
- Get Token Balance
- Get Token Allowance
- Get Token Permit Terms
Get Block Number
Get the current block number for a network.
Service: BlockchainServiceMethod: GetBlockNumber
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to query |
Response:
| Field | Type | Description |
|---|---|---|
block_number | uint64 | Current block number/height |
Example Request:
TypeScript
const blockNumber = await hydraGrpcClient.getBlockNumber({
network: { protocol: 1, id: '0a03cf40' } // Bitcoin Signet
})
Rust
let request = tonic::Request::new(GetBlockNumberRequest {
network: Some(Network {
protocol: Protocol::Bitcoin as i32,
id: "0a03cf40".to_string(),
}),
});
let response = client.get_block_number(request).await?;
let block_number = response.into_inner().block_number;
Get Block Header
Get block header information for a specific block.
Service: BlockchainServiceMethod: GetBlockHeader
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to query |
block_number | uint64 | YES | Block height to read |
block_numberis not optional. It is a plainuint64, so leaving it out sends0and you get the genesis block, not the tip. For the tip, callGetBlockNumberfirst and pass what it returns.
Response:
| Field | Type | Description |
|---|---|---|
block_header | BlockHeader | Block header data |
BlockHeader Object:
| Field | Type | Description |
|---|---|---|
number | uint64 | Block height |
hash | string | Hex-encoded block hash |
timestamp | Timestamp | When the block was produced |
prev_hash | string | Hex-encoded hash of the previous block |
Example Request:
TypeScript
const network = { protocol: 2, id: '11155111' }
// The tip: read its height first, then the header at that height.
const tip = await hydraGrpcClient.getBlockNumber({ network })
const latest = await hydraGrpcClient.getBlockHeader({ network, blockNumber: tip.blockNumber })
// A specific block.
const header = await hydraGrpcClient.getBlockHeader({ network, blockNumber: 1000000 })
Rust
let network = Network { protocol: Protocol::Evm as i32, id: "11155111".to_string() };
// The tip: read its height first, then the header at that height.
let tip = client.get_block_number(tonic::Request::new(GetBlockNumberRequest {
network: Some(network.clone()),
})).await?.into_inner().block_number;
let request = tonic::Request::new(GetBlockHeaderRequest {
network: Some(network),
block_number: tip,
});
let block_header = client.get_block_header(request).await?.into_inner().block_header;
Get Transaction By Id
Returns the details of a specific on-chain transaction by its ID.
Method: GetTransactionById
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
txid | string | YES | Transaction ID to query |
Response:
| Field | Type | Description |
|---|---|---|
transaction | ChainTransaction | On-chain transaction details |
Example Request:
import { GetTransactionByIdRequest } from './proto/blockchain_pb'
const request = new GetTransactionByIdRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTxid('abc123def456...')
const response = await client.getTransactionById(request, {})
console.log('Transaction:', response.getTransaction()?.toObject())
Get Fee Estimates
Current fee estimates at three priority levels. Use these to build a FeeOption.custom, or to show the user what low / medium / high currently cost before picking one.
Method: GetFeeEstimates
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to get fees for |
Response:
| Field | Type | Description |
|---|---|---|
fee_estimate | FeeEstimate | The three estimates |
FeeEstimate — low, medium and high, each a ChainFee:
| Field | Type | Description |
|---|---|---|
base_fee_per_unit | U256String | The network's current base fee per gas / weight unit |
fee_rate | FeeRate | max_fee_per_unit + priority_fee_per_unit — the pair you would put on a transaction |
effective_fee_per_unit | U256String | What a transaction at this level would actually pay per unit |
The response is not three decimal strings. An earlier revision of this page showed
{ low, medium, high }asDecimalStrings; every level is a fullChainFee, and its numbers areU256Strings per unit — sat/vbyte on Bitcoin, wei per gas on EVM — not whole-asset amounts. See Fee structures.
Example Request:
const { feeEstimate } = await hydraGrpcClient.getFeeEstimates({
network: { protocol: 1, id: '0a03cf40' }
})
for (const level of ['low', 'medium', 'high'] as const) {
console.log(`${level}: ${feeEstimate[level].feeRate.maxFeePerUnit.value} sat/vB`)
}
Example Response:
{
"feeEstimate": {
"low": {
"baseFeePerUnit": { "value": "1" },
"feeRate": { "maxFeePerUnit": { "value": "2" }, "priorityFeePerUnit": { "value": "0" } },
"effectiveFeePerUnit": { "value": "1" }
},
"medium": {
"baseFeePerUnit": { "value": "1" },
"feeRate": { "maxFeePerUnit": { "value": "5" }, "priorityFeePerUnit": { "value": "0" } },
"effectiveFeePerUnit": { "value": "3" }
},
"high": {
"baseFeePerUnit": { "value": "1" },
"feeRate": { "maxFeePerUnit": { "value": "12" }, "priorityFeePerUnit": { "value": "0" } },
"effectiveFeePerUnit": { "value": "8" }
}
}
}
This is the JSON-RPC wire shape: camelCase keys, and every
U256String/DecimalStringas a{ "value": … }object. Over gRPC the field names are the proto's own snake_case. See Wire encoding.
Broadcast Raw Transaction
Broadcasts a fully signed raw transaction to the specified network. Use this when you have a pre-signed transaction in serialized form (e.g. produced offline or by an external signer).
Method: BroadcastRawTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
raw_tx | bytes | YES | Fully signed raw transaction bytes |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Resulting transaction ID |
Example Request:
import { BroadcastRawTransactionRequest } from './proto/blockchain_pb'
const request = new BroadcastRawTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setRawTx(Buffer.from('0200000001...', 'hex'))
const response = await client.broadcastRawTransaction(request, {})
console.log('Broadcast TXID:', response.getTxid())
Example Response:
{ "txid": "abc123def456..." }
Get Address Balance
Returns the native asset balance for an arbitrary address (not necessarily this wallet's).
Method: GetAddressBalance
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
address | string | YES | Address to query |
Response:
| Field | Type | Description |
|---|---|---|
balance | DecimalString | Native asset balance |
Example Request:
import { GetAddressBalanceRequest } from './proto/blockchain_pb'
const request = new GetAddressBalanceRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setAddress('0xabc...')
const response = await client.getAddressBalance(request, {})
console.log('Balance:', response.getBalance()?.getValue())
Example Response:
{ "balance": { "value": "1.234567" } }
Get Token Balance
Returns the token balance for the given token contract and address (EVM only).
Method: GetTokenBalance
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | EVM network |
token_id | string | YES | Token contract address |
address | string | YES | Holder address |
Response:
| Field | Type | Description |
|---|---|---|
balance | DecimalString | Token balance |
Example Request:
import { GetTokenBalanceRequest } from './proto/blockchain_pb'
const request = new GetTokenBalanceRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setTokenId('erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') // USDC
request.setAddress('0xabc...')
const response = await client.getTokenBalance(request, {})
console.log('Token balance:', response.getBalance()?.getValue())
Example Response:
{ "balance": { "value": "1500" } }
Get Token Allowance
Returns the current token allowance granted by an owner to a spender on the specified network. EVM only.
Method: GetTokenAllowance
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | EVM network |
token_id | string | YES | Token contract address |
owner | string | YES | Token owner address |
spender | string | YES | Approved spender address |
Response:
| Field | Type | Description |
|---|---|---|
allowance | TokenAllowance | Allowance details (token type + amount + approval status) |
Example Request:
import { GetTokenAllowanceRequest } from './proto/blockchain_pb'
const request = new GetTokenAllowanceRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setTokenId('erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48')
request.setOwner('0xowner...')
request.setSpender('0xspender...')
const response = await client.getTokenAllowance(request, {})
console.log('Allowance:', response.getAllowance()?.toObject())
Get Token Permit Terms
Added 2026-09-11.
Returns what a token discloses before an owner signs a permit for it — the terms a TokenPermit carries. EVM and Tron only.
Reading the terms is how a caller learns whether a permit is available at all: a token that offers no signature-authorised allowance to this owner returns no terms, and the only way to set an allowance for it is an on-chain SetTokenAllowance.
Method: GetTokenPermitTerms
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | EVM or Tron network |
token_id | string | YES | The token, e.g. erc20:0x… |
owner | string | YES | The address that would sign the permit |
Response:
| Field | Type | Description |
|---|---|---|
terms | bytes (optional) | The terms, serialised by the network's protocol — exactly the bytes that go into TokenPermit.terms. Absent when the token offers no permit to this owner |
The terms are owner-specific. They fold in the owner's current permit nonce, so terms read for one owner are not valid for another, and terms read before a permit is consumed are not valid after. Read them immediately before signing.
Example Request:
import { GetTokenPermitTermsRequest } from './proto/blockchain_pb'
const request = new GetTokenPermitTermsRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setTokenId('erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0')
request.setOwner('0xowner...')
const terms = (await client.getTokenPermitTerms(request, {})).getTerms_asU8()
if (!terms || terms.length === 0) {
// No permit available — fall back to an on-chain approve.
}
Example Response:
{ "terms": "eyJkb21haW4iOnsibmFtZSI6IlVTRC..." }
bytesis base64 over JSON-RPC and raw bytes over gRPC. The full permit flow — estimate, sign, submit — is on the Client API.
Common Patterns
Monitor block confirmations
Rust
async fn wait_for_confirmations(
client: &mut BlockchainServiceClient<Channel>,
network: Network,
tx_block: u64,
required_confirmations: u64,
) -> Result<(), Box<dyn std::error::Error>> {
loop {
let current_block = client
.get_block_number(GetBlockNumberRequest {
network: Some(network.clone()),
})
.await?
.into_inner()
.block_number;
let confirmations = current_block.saturating_sub(tx_block);
if confirmations >= required_confirmations {
return Ok(());
}
tokio::time::sleep(tokio::time::Duration::from_secs(15)).await;
}
}
Get appropriate fee for urgency
Rust
async fn get_fee_for_urgency(
client: &mut BlockchainServiceClient<Channel>,
network: Network,
urgent: bool,
) -> Result<String, Box<dyn std::error::Error>> {
let fees = client
.get_fee_estimates(GetFeeEstimatesRequest {
network: Some(network),
})
.await?
.into_inner();
Ok(if urgent { fees.high } else { fees.medium })
}
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Invalid network, address, or token id | Verify all parameters. A token_id needs its standard prefix — erc20:0x…, not a bare address |
NOT_FOUND | Block or transaction not found | Check the height / txid exists on this network |
UNIMPLEMENTED | A token call on Bitcoin | GetTokenBalance / GetTokenAllowance / GetTokenPermitTerms exist only on EVM and Tron |
UNAVAILABLE | Chain backend temporarily unavailable | Retry with backoff |
Best Practices
- Cache fee estimates. Refresh every 30–60 seconds, not once per transaction — the backing estimator does not move faster than that.
- Prefer the node's own view for your own transactions. These calls go to the chain; for a transaction this wallet sent,
wallet.GetTransactionalready carries the wallet-level status and confirmations. - Pass
token_idwith its standard prefix —erc20:0x…,trc20:T…. A bare contract address is rejected. - Read permit terms immediately before signing. They encode the owner's current nonce — see Get Token Permit Terms.
- Allow for reorgs on short confirmation counts: a header at a height you read a moment ago may not be the header at that height now.