Asset API
The Asset API exposes Hydra App's AssetService — the registry of assets the node tracks on each network. Every network has one native asset; tokens are whatever the operator has registered, from config.yaml at boot or with AddToken at runtime.
JSON-RPC namespace: asset
Endpoints
Get Assets
Get all registered assets for a specific network.
Service: AssetServiceMethod: GetAssets
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to query assets for |
Response:
| Field | Type | Description |
|---|---|---|
assets | Asset[] | Array of registered assets |
Asset Object:
| Field | Type | Description |
|---|---|---|
id | string | Asset identifier — see Asset ID formats |
name | string | Human-readable name, e.g. "Bitcoin" |
symbol | string | Ticker, e.g. "BTC". The declared symbol when the operator set one, otherwise the chain's |
decimals | uint32 | Decimal places in the asset's smallest unit. Always read from the chain |
There is no
is_nativeflag. An earlier revision of this page listed one; the proto has never carried it. An asset is native exactly when itsidis its protocol's zero address — or ask for it directly withGetNativeAsset.
Example Request:
TypeScript
const assets = await hydraGrpcClient.getAssets({
network: { protocol: 2, id: '11155111' } // Ethereum Sepolia
})
Rust
let request = tonic::Request::new(GetAssetsRequest {
network: Some(Network {
protocol: Protocol::Evm as i32,
id: "11155111".to_string(),
}),
});
let response = client.get_assets(request).await?;
let assets = response.into_inner().assets;
Get Asset
Get information about a specific asset.
Service: AssetServiceMethod: GetAsset
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network the asset is on |
asset_id | string | YES | Asset identifier |
Response:
| Field | Type | Description |
|---|---|---|
asset | Asset | Asset details |
Example Request:
TypeScript
const asset = await hydraGrpcClient.getAsset({
network: { protocol: 2, id: '11155111' },
assetId: 'erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0' // USDC on Sepolia
})
Rust
let request = tonic::Request::new(GetAssetRequest {
network: Some(Network {
protocol: Protocol::Evm as i32,
id: "11155111".to_string(),
}),
asset_id: "erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0".to_string(),
});
let response = client.get_asset(request).await?;
let asset = response.into_inner().asset;
Get Native Asset
Get the native asset for a network (e.g., BTC for Bitcoin, ETH for Ethereum).
Service: AssetServiceMethod: GetNativeAsset
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to get native asset for |
Response:
| Field | Type | Description |
|---|---|---|
asset | Asset | Native asset details |
Example Request:
TypeScript
const nativeAsset = await hydraGrpcClient.getNativeAsset({
network: { protocol: 1, id: '0a03cf40' } // Bitcoin Signet
})
// → { id: "0x0000…0000", name: "Bitcoin", symbol: "BTC", decimals: 8 }
Rust
let request = tonic::Request::new(GetNativeAssetRequest {
network: Some(Network {
protocol: Protocol::Bitcoin as i32,
id: "0a03cf40".to_string(),
}),
});
let response = client.get_native_asset(request).await?;
let asset = response.into_inner().asset;
Add Token
Register a new token to the asset registry.
Service: AssetServiceMethod: AddToken
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network the token is on |
token_id | string | YES | The token identifier — an ERC-20 / TRC-20 contract address |
symbol | string | no | (2026-09-03) Ticker to show in place of the one the contract reports |
name | string | no | (2026-09-03) Name to show in place of the one the contract reports |
The request field is
token_id, nottoken_address. Older revisions of this page saidtoken_address; that name has never been on the wire.
Response:
| Field | Type | Description |
|---|---|---|
asset | Asset | Added asset details, fetched from the blockchain |
Declaring a symbol and name
A contract's symbol() is its deployer's choice — a bridged or test deployment often carries a variant of the asset's real ticker — while every consumer of your node expects the asset's own identity. Setting symbol / name replaces the chain's wherever the node hands the token out.
The overrides are re-applied on every registration of that token, so changing them is a matter of calling AddToken again. Omit either to keep the chain's own value.
Decimals are never declared. They define the token's units and are only ever read from the chain. There is no field for them, by design — a wrong decimals value would silently misprice every amount.
Constraints, enforced at boot when declared in config.yaml and at the call otherwise: a symbol must be a single word with no whitespace; a name must be non-empty with no leading or trailing whitespace.
The same overrides can be set declaratively per network — see the Setup Guide:
tokens:
- "erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" # USDC — keeps the chain's symbol
- id: "erc20:0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"
symbol: USDT # contract reports "USD₮0"
name: "Tether USD"
Example Request:
TypeScript
// Add USDC token on Ethereum Sepolia
const asset = await hydraGrpcClient.addToken({
network: { protocol: 2, id: '11155111' },
tokenId: 'erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0'
})
// With display overrides — LayerZero-bridged USDT on Arbitrum reports "USD₮0"
const usdt = await hydraGrpcClient.addToken({
network: { protocol: 2, id: '42161' },
tokenId: 'erc20:0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
symbol: 'USDT',
name: 'Tether USD'
})
Rust
let request = tonic::Request::new(AddTokenRequest {
network: Some(Network {
protocol: Protocol::Evm as i32,
id: "11155111".to_string(),
}),
token_id: "erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0".to_string(),
symbol: None,
name: None,
});
let response = client.add_token(request).await?;
let asset = response.into_inner().asset;
Common Patterns
Get all assets including native
Rust
async fn get_all_network_assets(
client: &mut AssetServiceClient<Channel>,
network: Network,
) -> Result<Vec<Asset>, Box<dyn std::error::Error>> {
let response = client
.get_assets(GetAssetsRequest {
network: Some(network),
})
.await?;
Ok(response.into_inner().assets)
}
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Invalid network or asset ID | Verify parameters |
NOT_FOUND | Asset not found | Check asset exists on this network |
ALREADY_EXISTS | Token already registered | Use GetAsset instead |
Best Practices
- Cache the registry. It changes only when the operator adds a token or restarts the node — fetch it once at start and refresh on demand.
- Verify a contract before adding it.
AddTokenreads whatever metadata the contract reports; a lookalike contract is registered just as happily as the real one. - Use
decimalsfor display only. Amounts on the wire are already whole-unitDecimalStrings — see Amount precision.decimalsis what you need to convert to and from chain-native values, not to read the API. - Fetch the native asset for fee display —
GetNativeAsset. Every on-chain fee is quoted in it, whatever asset the transaction moves. - Register a token before using it. Balances, sends, channels and orders all reject an asset the node does not hold in its registry.