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
Token permits (2026-09-11)
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: { amount: { 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: { amount: { 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. Builds, signs and broadcasts in one call.
Method: SendTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to send on |
to | string | YES | Recipient address (or a Bitcoin script) |
amount | Amount | YES | Amount to send |
fee_option | FeeOption | YES | Fee selection |
Amount
Amount is a oneof, not a struct with a value. Exactly one arm is set, and it carries no asset id — the asset is the network's native one for SendTransaction, and token_id for SendTokenTransaction.
| Arm | Payload | Meaning |
|---|---|---|
all | (empty) | Send the entire available balance |
exact | { amount: DecimalString } | Send exactly this much, in whole units |
FeeOption
FeeOption is also a oneof message, not an enum. Three arms select a network estimate; the fourth carries an explicit rate.
| Arm | Payload | Meaning |
|---|---|---|
low | (empty) | The network's low-priority estimate |
medium | (empty) | The standard estimate |
high | (empty) | The fastest estimate |
custom | { fee_rate: FeeRate } | Your own max_fee_per_unit / priority_fee_per_unit |
There is no
FeeOption.MEDIUMconstant. Older revisions of this page showedFeeOptionas an enum withLOW/MEDIUM/HIGH/CUSTOMvalues andAmountas{ value, asset_id }. Neither has ever existed in the proto — see Fee structures for the real shapes.
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Broadcast transaction ID |
Example Request:
import { SendTransactionRequest } from './proto/client_pb'
const request = new SendTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTo('tb1q...')
request.setAmount({ exact: { amount: { value: '0.001' } } })
request.setFeeOption({ medium: {} })
const response = await client.sendTransaction(request, {})
console.log('Transaction ID:', response.getTxid())
Example Response:
{ "txid": "abc123..." }
Send Token Transaction
Send a token (non-native asset) to another address. For native transfers use Send Transaction.
Method: SendTokenTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to send on (must support tokens — EVM or Tron) |
to | string | YES | Recipient address |
token_id | string | YES | Token identifier, e.g. erc20:0x… |
amount | Amount | YES | Amount to send, in the token's whole units |
fee_option | FeeOption | YES | Fee selection |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Broadcast transaction ID |
Example Request:
// Send 100 USDC on Ethereum Sepolia
const request = new SendTokenTransactionRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setTo('0xrecipient...')
request.setTokenId('erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0')
request.setAmount({ exact: { amount: { value: '100' } } })
request.setFeeOption({ medium: {} })
const response = await client.sendTokenTransaction(request, {})
Example Response:
{ "txid": "0x..." }
The token must be registered on the node — listed under the network's
tokensinconfig.yaml, or added at runtime withasset.AddToken. An unregisteredtoken_idis rejected.
Bump Transaction
Increase the fee of an existing unconfirmed transaction to speed up confirmation (RBF on Bitcoin, a same-nonce replacement on EVM).
Method: BumpTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network where the transaction exists |
txid | string | YES | Transaction ID to replace |
fee_option | FeeOption | YES | New fee, which must price above the original |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | The replacement transaction's ID |
Example Request:
const request = new BumpTransactionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setTxid('abc123...')
request.setFeeOption({ high: {} })
const response = await client.bumpTransaction(request, {})
Example Response:
{ "txid": "def456..." }
Set Token Allowance
Approve a spender to move tokens on the wallet's behalf. Builds, signs and broadcasts in one call.
Method: SetTokenAllowance
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network (must support tokens — EVM or Tron) |
spender | string | YES | Address being approved |
allowance | SetTokenAllowance | YES | Which approval to set — see below |
fee_option | FeeOption | YES | Fee selection |
SetTokenAllowance
A oneof over the three approval shapes the token standards use:
| Arm | Payload | Standard |
|---|---|---|
token | { token_id: string, amount: AllowanceAmount } | ERC-20 / TRC-20 approve |
unique_token | { token_id: string, approved: bool } | ERC-721 approve |
contract | { contract_address: string, token_variant: string, approved: bool } | ERC-1155 setApprovalForAll |
AllowanceAmount is itself a oneof: unlimited (empty) or exact { amount: DecimalString }.
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Broadcast transaction ID |
Example Request:
const request = new SetTokenAllowanceRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setSpender('0xspender...')
request.setAllowance({
token: {
token_id: 'erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0',
amount: { exact: { amount: { value: '1000' } } },
},
})
request.setFeeOption({ medium: {} })
const response = await client.setTokenAllowance(request, {})
Example Response:
{ "txid": "0x..." }
Read the current state back with
blockchain.GetTokenAllowance.
Token permits
Added 2026-09-11.
A token permit is an allowance its owner authorises with an off-chain signature instead of an on-chain approve. Whoever submits the permit pays the gas — so a wallet that holds a token but no native asset can still grant an allowance, as long as someone else sends the transaction.
The three RPCs below are the submitting side: this wallet sends a permit that some owner signed, and pays for it. owner and this wallet are usually different parties.
The flow:
- Read the token's terms with
blockchain.GetTokenPermitTerms. No terms ⇒ the token offers no signature-authorised allowance to that owner, and the permit path is unavailable — fall back toSetTokenAllowance. - Price the submission with
EstimateTokenPermitFee. The permit does not exist yet — the owner signs once the price is known. - The owner signs the terms, producing a
TokenPermit. - Submit it with
SubmitTokenPermit, or build it unsigned withCreateTokenPermitTransactionfor an external signer.
TokenPermit
| Field | Type | Description |
|---|---|---|
token_id | string | The token the permit is for |
owner | string | The address granting the allowance and signing the permit |
spender | string | The address the allowance is granted to |
value | U256String | The allowance, in the token's smallest unit |
deadline | uint64 | Unix seconds after which the authorisation lapses |
terms | bytes | The terms the token disclosed when the owner signed, serialised by the network's protocol — the same bytes GetTokenPermitTerms returns |
signature | bytes | The owner's raw signature bytes |
⚠️TokenPermit.valueis the one amount that is not in whole unitsIt is a
U256Stringin the token's smallest unit — the value that goes on the wire to the contract — because that is what the owner's signature commits to, and re-deriving it from a decimal would risk signing for a different number. Everywhere else in this API an amount is a whole-unitDecimalString;EstimateTokenPermitFee.valuebelow is one of those.
Estimate Token Permit Fee
What submitting a permit would cost this wallet, priced against the owner's live allowance and permit nonce on the token. Nothing is signed or sent.
Method: EstimateTokenPermitFee
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network (must support tokens, e.g. EVM) |
asset_id | string | YES | The token the permit is for |
owner | string | YES | The wallet whose permit would be submitted |
spender | string | YES | The address the permit would allow to spend |
value | DecimalString | YES | The allowance the permit would grant, in whole units, rounded up to the token's precision |
fee_option | FeeOption | YES | The fee option to price the submission at |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | What the submission would cost, in the network's native asset |
Call this before the owner signs. Pricing needs the owner's live state on the token — an allowance already at the target value, or a different permit nonce, changes what the submission costs — which is exactly why it takes no signature.
Create Token Permit Transaction
Builds the unsigned transaction that submits a signed permit, for inspection or an external signer. The permit's owner authorised the allowance off-chain; this wallet sends the transaction and pays the fee.
Method: CreateTokenPermitTransaction
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network (must support tokens, e.g. EVM) |
permit | TokenPermit | YES | The permit to submit |
fee_option | FeeOption | YES | Fee for the submitting transaction |
Response:
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | Unsigned transaction — sign it, then FinalizeAndBroadcastTransaction |
Submit Token Permit
Creates, signs and broadcasts the transaction that sets the allowance the permit's owner authorised.
Method: SubmitTokenPermit
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network (must support tokens, e.g. EVM) |
permit | TokenPermit | YES | The permit to submit |
fee_option | FeeOption | YES | Fee for the submitting transaction |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Broadcast transaction ID |
Example Request:
import { SubmitTokenPermitRequest } from './proto/client_pb'
const request = new SubmitTokenPermitRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setPermit({
token_id: 'erc20:0x8cd0da3d001b013336918b8bc4e56d9dda1347e0',
owner: '0xowner...',
spender: '0xspender...',
value: { value: '1000000000' }, // smallest unit
deadline: Math.floor(Date.now() / 1000) + 3600,
terms: termsBytes, // from blockchain.GetTokenPermitTerms
signature: signatureBytes, // the owner's signature over those terms
})
request.setFeeOption({ medium: {} })
const response = await client.submitTokenPermit(request, {})
console.log('permit submitted:', response.getTxid())
Example Response:
{ "txid": "0x..." }
A permit whose
deadlinehas passed, whose signature does not matchowner, or whose nonce the token has already consumed is rejected by the token contract — the transaction reverts and the fee is still spent. Submit promptly after signing, and re-read the terms if a submission fails.
The same rail is what the liquidity service uses to fund a channel for a wallet that holds the token but no gas — see
SponsoredDepositFeePaymentand the swapDepositRailin Swap API.
Network ID Formats
Different networks use different ID formats:
Bitcoin Networks
- Mainnet:
f9beb4d9 - Testnet4:
1c163f28 - Testnet3:
0b110907 - Signet:
0a03cf40 - Regtest:
fabfb5da
EVM Networks
- Ethereum Mainnet:
1 - Ethereum Sepolia:
11155111 - Arbitrum One:
42161 - Arbitrum Sepolia:
421614 - Optimism:
10 - Polygon:
137
Common Patterns
Build → sign → broadcast with an external signer
// 1. Build the unsigned transaction.
const build = new CreateSendTransactionRequest()
build.setNetwork({ protocol: 1, id: '0a03cf40' })
build.setTo('tb1q...')
build.setAmount({ exact: { amount: { value: '0.001' } } })
build.setFeeOption({ medium: {} })
const txRequest = (await client.createSendTransaction(build, {})).getTransactionRequest()
// 2. `signable_data` is the wallet-standard payload for the network:
// EVM → UTF-8 JSON eth_sendTransaction params; Bitcoin → base64 BIP-174 PSBT.
// Hand it to signer.SignTransaction, or to your own signer.
const signed = await signerClient.signTransaction(/* … txRequest … */)
// 3. Finalize + broadcast.
const fin = new FinalizeAndBroadcastTransactionRequest()
fin.setNetwork({ protocol: 1, id: '0a03cf40' })
fin.setSignedTx(signed)
console.log('txid:', (await client.finalizeAndBroadcastTransaction(fin, {})).getTxid())
TransactionRequestcarries the estimate too —fee_units(gas for EVM, weight units for Bitcoin) and an optionalfee. Check them before signing; the one-shotSend*calls give you no such window.
Deposit addresses
Deposit addresses are on the Wallet API, not here — wallet.GetDepositAddress for the wallet's standing address, wallet.GetUniqueDepositAddress for a fresh one per invoice on Bitcoin.
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 before sending. An on-chain send is irreversible; the API does not second-guess a well-formed address.
- Check the balance first —
wallet.GetBalance. The native asset also has to cover the fee, which is a separate resource from the token you are moving. - Pick the fee by urgency —
lowwhen you can wait,highwhen you cannot,customwhen you have your own estimate. - Prefer build → sign → finalize when the amount is large. It is the only flow that lets you inspect
fee_unitsandfeebefore committing. - Follow the transaction afterwards —
wallet.GetTransaction, or theTransactionUpdateonSubscribeClientEvents. - Bump rather than resend. A second send is a second transaction;
BumpTransactionreplaces the first.