Client API
The Client API exposes Hydra App's ClientService — on-chain transaction creation, signing, and broadcasting for native assets, tokens, fee bumps, and token allowances.
JSON-RPC namespace: client
The service exposes two parallel flows:
- Build → sign → finalize —
Create*Transactionreturns an unsignedTransactionRequest. Sign it withsigner.SignTransaction, then submit withFinalizeAndBroadcastTransaction. Use this when you want to inspect the transaction or use an external signer. - One-shot send —
SendTransaction,SendTokenTransaction,BumpTransaction,SetTokenAllowancebuild, sign, and broadcast in a single call.
Looking for
GetDepositAddress? That moved to the Wallet API.
Endpoints
Build → sign → finalize
- Create Send Transaction
- Create Bump Transaction
- Create Token Send Transaction
- Create Set Token Allowance Transaction
- Finalize And Broadcast Transaction
One-shot
Create Send Transaction
Builds an unsigned native asset send transaction. Returns a generic TransactionRequest that can be inspected, signed via signer.SignTransaction, and then submitted via FinalizeAndBroadcastTransaction.
Method: CreateSendTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
to | string | YES | Recipient address |
amount | Amount | YES | Amount to send (exact or all-available) |
fee_option | FeeOption | YES | Fee selection |
Response:
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | Unsigned transaction request |
Example Request:
import { CreateSendTransactionRequest } from './proto/client_pb'
const request = new CreateSendTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTo('bc1qrecipient...')
request.setAmount({ exact: { value: '0.01' } })
request.setFeeOption({ /* see Fee API for options */ })
const response = await client.createSendTransaction(request, {})
const txRequest = response.getTransactionRequest()
// → pass txRequest to signer.SignTransaction
Create Bump Transaction
Builds an unsigned fee-bump (RBF / speed-up) transaction request for an existing unconfirmed transaction.
Method: CreateBumpTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
txid | string | YES | Transaction ID to replace |
fee_option | FeeOption | YES | New (higher) fee option |
Response:
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | Unsigned bump transaction |
Example Request:
import { CreateBumpTransactionRequest } from './proto/client_pb'
const request = new CreateBumpTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTxid('abc123...')
request.setFeeOption({ /* higher fee */ })
const response = await client.createBumpTransaction(request, {})
const txRequest = response.getTransactionRequest()
Create Token Send Transaction
Builds an unsigned ERC-20 / token transfer transaction. For native asset transfers use Create Send Transaction.
Method: CreateTokenSendTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network (must support tokens, e.g. EVM) |
to | string | YES | Recipient address |
token_id | string | YES | Token contract address / identifier |
amount | Amount | YES | Amount of tokens |
fee_option | FeeOption | YES | Fee selection |
Response:
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | Unsigned token transfer transaction |
Example Request:
import { CreateTokenSendTransactionRequest } from './proto/client_pb'
const request = new CreateTokenSendTransactionRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setTo('0xrecipient...')
request.setTokenId('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48') // USDC
request.setAmount({ exact: { value: '100' } })
request.setFeeOption({ /* ... */ })
const response = await client.createTokenSendTransaction(request, {})
const txRequest = response.getTransactionRequest()
Create Set Token Allowance Transaction
Builds an unsigned token allowance approval transaction. Supports ERC-20, ERC-721, and ERC-1155.
Method: CreateSetTokenAllowanceTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network (must support tokens, e.g. EVM) |
spender | string | YES | Spender address to approve |
allowance | SetTokenAllowance | YES | Token type, amount, and approval status |
fee_option | FeeOption | YES | Fee selection |
Response:
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | Unsigned allowance approval transaction |
Example Request:
import { CreateSetTokenAllowanceTransactionRequest } from './proto/client_pb'
const request = new CreateSetTokenAllowanceTransactionRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setSpender('0xspender...')
request.setAllowance({ /* SetTokenAllowance */ })
request.setFeeOption({ /* ... */ })
const response = await client.createSetTokenAllowanceTransaction(request, {})
const txRequest = response.getTransactionRequest()
Finalize And Broadcast Transaction
Submits a signed transaction to the network. Accepts a SignedTransactionRequest (as returned by signer.SignTransaction) and returns the resulting transaction ID.
Method: FinalizeAndBroadcastTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Target network |
signed_tx | SignedTransactionRequest | YES | Signed transaction to finalize and broadcast |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Broadcast transaction ID |
Example Request:
import { FinalizeAndBroadcastTransactionRequest } from './proto/client_pb'
// signedTx is the SignedTransactionRequest returned by signer.SignTransaction
const request = new FinalizeAndBroadcastTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setSignedTx(signedTx)
const response = await client.finalizeAndBroadcastTransaction(request, {})
console.log('Broadcast TXID:', response.getTxid())
Example Response:
{ "txid": "abc123def456..." }
Send Transaction
Send the native asset of a network to another address.
Service: ClientServiceMethod: SendTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to send transaction on |
to_address | string | YES | Recipient address |
amount | Amount | YES | Amount to send |
fee_option | FeeOption | YES | Fee option for the transaction |
Amount Object:
| Field | Type | Description |
|---|---|---|
value | DecimalString | Amount as decimal string |
asset_id | string | Asset identifier |
FeeOption Enum:
| Option | Description |
|---|---|
LOW | Low fee, slower confirmation |
MEDIUM | Medium fee, moderate confirmation time |
HIGH | High fee, faster confirmation |
CUSTOM | Custom fee rate (sats/vB for Bitcoin, gwei for EVM) |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Transaction ID |
Example Request:
TypeScript
import { FeeOption } from './proto/models'
const response = await hydraGrpcClient.sendTransaction({
network: {
protocol: Protocol.BITCOIN,
id: '0a03cf40'
},
toAddress: 'tb1q...',
amount: {
value: '0.001',
assetId: 'BTC'
},
feeOption: FeeOption.MEDIUM
})
console.log('Transaction ID:', response.txid)
Go
req := &pb.SendTransactionRequest{
Network: &pb.Network{
Protocol: pb.Protocol_BITCOIN,
Id: "0a03cf40",
},
To: "tb1q...",
Amount: &pb.Amount{
Value: "0.001",
AssetId: "BTC",
},
FeeOption: pb.FeeOption_MEDIUM,
}
resp, err := client.SendTransaction(context.Background(), req)
if err != nil {
log.Fatal(err)
}
txid := resp.Txid
Rust
let request = tonic::Request::new(SendTransactionRequest {
network: Some(Network {
protocol: Protocol::Bitcoin as i32,
id: "0a03cf40".to_string(),
}),
to_address: "tb1q...".to_string(),
amount: Some(Amount {
value: "0.001".to_string(),
asset_id: "BTC".to_string(),
}),
fee_option: FeeOption::Medium as i32,
});
let response = client.send_transaction(request).await?;
let txid = response.into_inner().txid;
Example Response:
{
"txid": "abc123..."
}
Send Token Transaction
Send a token (non-native asset) to another address on a network.
Service: ClientServiceMethod: SendTokenTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to send transaction on |
to_address | string | YES | Recipient address |
token_id | string | YES | Token contract address or asset ID |
amount | Amount | YES | Amount to send |
fee_option | FeeOption | YES | Fee option for the transaction |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Transaction ID |
Example Request:
TypeScript
// Send USDC on Ethereum Sepolia
const response = await hydraGrpcClient.sendTokenTransaction({
network: {
protocol: Protocol.EVM,
id: '11155111'
},
toAddress: '0x...',
tokenId: '0x...', // USDC contract address
amount: {
value: '100.0',
assetId: 'USDC'
},
feeOption: FeeOption.MEDIUM
})
Go
req := &pb.SendTokenTransactionRequest{
Network: &pb.Network{
Protocol: pb.Protocol_EVM,
Id: "11155111",
},
To: "0x...",
TokenId: "0x...",
Amount: &pb.Amount{
Value: "100.0",
AssetId: "USDC",
},
FeeOption: pb.FeeOption_MEDIUM,
}
resp, err := client.SendTokenTransaction(context.Background(), req)
Rust
let request = tonic::Request::new(SendTokenTransactionRequest {
network: Some(Network {
protocol: Protocol::Evm as i32,
id: "11155111".to_string(),
}),
to_address: "0x...".to_string(),
token_id: "0x...".to_string(),
amount: Some(Amount {
value: "100.0".to_string(),
asset_id: "USDC".to_string(),
}),
fee_option: FeeOption::Medium as i32,
});
let response = client.send_token_transaction(request).await?;
Example Response:
{
"txid": "0x..."
}
Bump Transaction
Increase the fee of an existing unconfirmed transaction to speed up confirmation.
Service: ClientServiceMethod: BumpTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network where transaction exists |
txid | string | YES | Transaction ID to bump |
fee_option | FeeOption | YES | New fee option (must be higher than original) |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | New transaction ID (or same if RBF) |
Example Request:
TypeScript
const response = await hydraGrpcClient.bumpTransaction({
network: {
protocol: Protocol.BITCOIN,
id: '0a03cf40'
},
txid: 'abc123...',
feeOption: FeeOption.HIGH
})
Go
req := &pb.BumpTransactionRequest{
Network: &pb.Network{
Protocol: pb.Protocol_BITCOIN,
Id: "0a03cf40",
},
Txid: "abc123...",
FeeOption: pb.FeeOption_HIGH,
}
resp, err := client.BumpTransaction(context.Background(), req)
Rust
let request = tonic::Request::new(BumpTransactionRequest {
network: Some(Network {
protocol: Protocol::Bitcoin as i32,
id: "0a03cf40".to_string(),
}),
txid: "abc123...".to_string(),
fee_option: FeeOption::High as i32,
});
let response = client.bump_transaction(request).await?;
Example Response:
{
"txid": "def456..."
}
Set Token Allowance
Set the allowance for a spender to use a certain amount of a token on behalf of the owner (EVM only).
Service: ClientServiceMethod: SetTokenAllowance
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | EVM network |
spender | string | YES | Spender address |
allowance | SetTokenAllowance | YES | Allowance details |
fee_option | FeeOption | YES | Fee option for the transaction |
SetTokenAllowance Object:
| Field | Type | Description |
|---|---|---|
token_id | string | Token contract address |
amount | DecimalString | Amount to allow |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Transaction ID |
Example Request:
TypeScript
const response = await hydraGrpcClient.setTokenAllowance({
network: {
protocol: Protocol.EVM,
id: '11155111'
},
spender: '0x...', // Contract or address to approve
allowance: {
tokenId: '0x...', // Token contract
amount: '1000.0'
},
feeOption: FeeOption.MEDIUM
})
Go
req := &pb.SetTokenAllowanceRequest{
Network: &pb.Network{
Protocol: pb.Protocol_EVM,
Id: "11155111",
},
Spender: "0x...",
Allowance: &pb.SetTokenAllowance{
TokenId: "0x...",
Amount: "1000.0",
},
FeeOption: pb.FeeOption_MEDIUM,
}
resp, err := client.SetTokenAllowance(context.Background(), req)
Rust
let request = tonic::Request::new(SetTokenAllowanceRequest {
network: Some(Network {
protocol: Protocol::Evm as i32,
id: "11155111".to_string(),
}),
spender: "0x...".to_string(),
allowance: Some(SetTokenAllowance {
token_id: "0x...".to_string(),
amount: "1000.0".to_string(),
}),
fee_option: FeeOption::Medium as i32,
});
let response = client.set_token_allowance(request).await?;
Example Response:
{
"txid": "0x..."
}
Network ID Formats
Different networks use different ID formats:
Bitcoin Networks
- Mainnet:
0a03cf40 - Testnet3:
0b110907 - Testnet4:
1c163f28 - Signet:
0a03cf40 - Regtest:
fabfb5da
EVM Networks
- Ethereum Mainnet:
1 - Ethereum Sepolia:
11155111 - Arbitrum One:
42161 - Arbitrum Sepolia:
421614 - Optimism:
10 - Polygon:
137
Common Patterns
Get deposit address for all configured networks
TypeScript
async function getAllDepositAddresses() {
const networks = await hydraGrpcClient.getNetworks()
const addresses: Record<string, string> = {}
for (const network of networks) {
try {
const address = await hydraGrpcClient.getDepositAddress(network)
addresses[network.id] = address
} catch (error) {
console.error(`Failed to get address for ${network.id}:`, error)
}
}
return addresses
}
Go
func getAllDepositAddresses(client pb.ClientServiceClient) (map[string]string, error) {
networksResp, err := appClient.GetNetworks(context.Background(), &pb.GetNetworksRequest{})
if err != nil {
return nil, err
}
addresses := make(map[string]string)
for _, network := range networksResp.Networks {
resp, err := client.GetDepositAddress(context.Background(), &pb.GetDepositAddressRequest{
Network: network,
})
if err != nil {
log.Printf("Failed to get address for %s: %v", network.Id, err)
continue
}
addresses[network.Id] = resp.Address
}
return addresses, nil
}
Rust
async fn get_all_deposit_addresses(
client: &mut ClientServiceClient<Channel>,
app_client: &mut AppServiceClient<Channel>,
) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let networks = app_client
.get_networks(GetNetworksRequest {})
.await?
.into_inner()
.networks;
let mut addresses = HashMap::new();
for network in networks {
match client
.get_deposit_address(GetDepositAddressRequest {
network: Some(network.clone()),
})
.await
{
Ok(resp) => {
addresses.insert(network.id.clone(), resp.into_inner().address);
}
Err(e) => {
eprintln!("Failed to get address for {}: {}", network.id, e);
}
}
}
Ok(addresses)
}
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Invalid network, address, or amount | Verify all parameters are correctly formatted |
FAILED_PRECONDITION | Insufficient balance or wallet not initialized | Check balance and wallet status |
NOT_FOUND | Transaction not found (for BumpTransaction) | Verify transaction exists and is unconfirmed |
UNAVAILABLE | Service temporarily unavailable | Retry with exponential backoff |
Best Practices
- Validate addresses - Always validate recipient addresses before sending
- Check balances - Verify sufficient balance before sending transactions
- Use appropriate fees - Choose fee option based on urgency (LOW for non-urgent, HIGH for urgent)
- Cache deposit addresses - Deposit addresses don't change frequently
- Monitor transactions - Use the Wallet API to monitor transaction confirmations
- Handle errors gracefully - Network issues can occur, implement retry logic