Api

Client API

On-chain transaction creation, signing, and broadcasting

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*Transaction returns an unsigned TransactionRequest. Sign it with signer.SignTransaction, then submit with FinalizeAndBroadcastTransaction. Use this when you want to inspect the transaction or use an external signer.
  • One-shot send — SendTransaction, SendTokenTransaction, BumpTransaction, SetTokenAllowance build, sign, and broadcast in a single call.

Looking for GetDepositAddress? That moved to the Wallet API.

Endpoints

Build → sign → finalize

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:

NameTypeRequiredDescription
networkNetworkYESTarget network
tostringYESRecipient address
amountAmountYESAmount to send (exact or all-available)
fee_optionFeeOptionYESFee selection

Response:

FieldTypeDescription
transaction_requestTransactionRequestUnsigned 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

Get Bump Eligibility

Reports whether an unconfirmed transaction's fee can be raised, by which mechanism, and from what floor. Ask this before offering a speed-up control: it answers from state the node already holds, so a chain with no replacement mechanism — and a transaction that is already mined, is not yours, or has nothing left to pay with — is refused without building anything.

Method: GetBumpEligibility

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
txidstringYESTransaction ID to ask about

Response:

FieldTypeDescription
eligibilityBumpEligibilityExactly one arm is set: yes (a BumpQuote) or no (a BumpRefusal)

BumpQuote carries mechanism and minimum_fee_rate:

BumpMechanismWhat it means for the transaction id
BUMP_MECHANISM_REPLACEThe transaction is rebroadcast at a higher fee and supersedes the original. The id changes.
BUMP_MECHANISM_CHILD_PAYSA child spending one of its outputs pays the shortfall for both. The original keeps its id.
BUMP_MECHANISM_RENEGOTIATEA fresh round with the channel counterparty double-spends the shared input. The id changes.

minimum_fee_rate is the lowest rate the bump would be accepted at, and is absent under BUMP_MECHANISM_RENEGOTIATE, whose floor belongs to a negotiation round that has not been opened yet. It is a floor, not a ceiling: a rate above it can still fail to build if the wallet cannot cover the increase. Do not derive the floor from the transaction's own rate — a child's floor is the rate of the whole unconfirmed package it drags along, which is a different number.

A BumpRefusal says why the fee cannot be raised. Every value means something on any chain, so a client can render all of them without knowing which chain answered:

BumpRefusalMeaning
BUMP_REFUSAL_CHAIN_HAS_NO_MECHANISMThe chain has no fee-replacement mechanism at all; never offer the operation on it
BUMP_REFUSAL_NOT_FOUNDNeither the wallet nor the chain has a record of the transaction
BUMP_REFUSAL_NO_LONGER_PENDINGMined or otherwise settled; paying more buys nothing
BUMP_REFUSAL_NOT_OURSSomebody else sent it
BUMP_REFUSAL_PINNED_BY_PROTOCOLA protocol above the chain commits to this exact transaction
BUMP_REFUSAL_UNSUPPORTED_FORMThe wallet cannot rebuild a transaction of this form faithfully
BUMP_REFUSAL_NO_FUNDS_TO_BUMPThe mechanism applies, but nothing is available to pay the increase with

Example Request:

import { GetBumpEligibilityRequest } from './proto/client_pb'

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

const response = await client.getBumpEligibility(request, {})
const quote = response.getEligibility()?.getYes()
// quote?.getMechanism(), quote?.getMinimumFeeRate()

Create Bump Transaction

Builds an unsigned fee-bump (RBF / speed-up) transaction request for an existing unconfirmed transaction. Errors when the transaction cannot be bumped, or when the rate is below the floor Get Bump Eligibility reported — ask it first.

Method: CreateBumpTransaction

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
txidstringYESTransaction ID to replace
fee_optionFeeOptionYESNew (higher) fee option

Response:

FieldTypeDescription
transaction_requestTransactionRequestUnsigned 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:

NameTypeRequiredDescription
networkNetworkYESTarget network (must support tokens, e.g. EVM)
tostringYESRecipient address
token_idstringYESToken contract address / identifier
amountAmountYESAmount of tokens
fee_optionFeeOptionYESFee selection

Response:

FieldTypeDescription
transaction_requestTransactionRequestUnsigned 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:

NameTypeRequiredDescription
networkNetworkYESTarget network (must support tokens, e.g. EVM)
spenderstringYESSpender address to approve
allowanceSetTokenAllowanceYESToken type, amount, and approval status
fee_optionFeeOptionYESFee selection

Response:

FieldTypeDescription
transaction_requestTransactionRequestUnsigned 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:

NameTypeRequiredDescription
networkNetworkYESTarget network
signed_txSignedTransactionRequestYESSigned transaction to finalize and broadcast

Response:

FieldTypeDescription
txidstringBroadcast 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:

NameTypeRequiredDescription
networkNetworkYESNetwork to send on
tostringYESRecipient address (or a Bitcoin script)
amountAmountYESAmount to send
fee_optionFeeOptionYESFee 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.

ArmPayloadMeaning
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.

ArmPayloadMeaning
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.MEDIUM constant. Older revisions of this page showed FeeOption as an enum with LOW / MEDIUM / HIGH / CUSTOM values and Amount as { value, asset_id }. Neither has ever existed in the proto — see Fee structures for the real shapes.

Response:

FieldTypeDescription
txidstringBroadcast 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:

NameTypeRequiredDescription
networkNetworkYESNetwork to send on (must support tokens — EVM or Tron)
tostringYESRecipient address
token_idstringYESToken identifier, e.g. erc20:0x…
amountAmountYESAmount to send, in the token's whole units
fee_optionFeeOptionYESFee selection

Response:

FieldTypeDescription
txidstringBroadcast 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 tokens in config.yaml, or added at runtime with asset.AddToken. An unregistered token_id is 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:

NameTypeRequiredDescription
networkNetworkYESNetwork where the transaction exists
txidstringYESTransaction ID to replace
fee_optionFeeOptionYESNew fee, which must price above the original

Response:

FieldTypeDescription
txidstringThe 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:

NameTypeRequiredDescription
networkNetworkYESNetwork (must support tokens — EVM or Tron)
spenderstringYESAddress being approved
allowanceSetTokenAllowanceYESWhich approval to set — see below
fee_optionFeeOptionYESFee selection

SetTokenAllowance

A oneof over the three approval shapes the token standards use:

ArmPayloadStandard
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:

FieldTypeDescription
txidstringBroadcast 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:

  1. 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 to SetTokenAllowance.
  2. Price the submission with EstimateTokenPermitFee. The permit does not exist yet — the owner signs once the price is known.
  3. The owner signs the terms, producing a TokenPermit.
  4. Submit it with SubmitTokenPermit, or build it unsigned with CreateTokenPermitTransaction for an external signer.

TokenPermit

FieldTypeDescription
token_idstringThe token the permit is for
ownerstringThe address granting the allowance and signing the permit
spenderstringThe address the allowance is granted to
valueU256StringThe allowance, in the token's smallest unit
deadlineuint64Unix seconds after which the authorisation lapses
termsbytesThe terms the token disclosed when the owner signed, serialised by the network's protocol — the same bytes GetTokenPermitTerms returns
signaturebytesThe owner's raw signature bytes

⚠️ TokenPermit.value is the one amount that is not in whole units

It is a U256String in 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-unit DecimalString; EstimateTokenPermitFee.value below 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:

NameTypeRequiredDescription
networkNetworkYESNetwork (must support tokens, e.g. EVM)
asset_idstringYESThe token the permit is for
ownerstringYESThe wallet whose permit would be submitted
spenderstringYESThe address the permit would allow to spend
valueDecimalStringYESThe allowance the permit would grant, in whole units, rounded up to the token's precision
fee_optionFeeOptionYESThe fee option to price the submission at

Response:

FieldTypeDescription
feeDecimalStringWhat 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:

NameTypeRequiredDescription
networkNetworkYESNetwork (must support tokens, e.g. EVM)
permitTokenPermitYESThe permit to submit
fee_optionFeeOptionYESFee for the submitting transaction

Response:

FieldTypeDescription
transaction_requestTransactionRequestUnsigned 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:

NameTypeRequiredDescription
networkNetworkYESNetwork (must support tokens, e.g. EVM)
permitTokenPermitYESThe permit to submit
fee_optionFeeOptionYESFee for the submitting transaction

Response:

FieldTypeDescription
txidstringBroadcast 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 deadline has passed, whose signature does not match owner, 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 SponsoredDepositFeePayment and the swap DepositRail in 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())

TransactionRequest carries the estimate too — fee_units (gas for EVM, weight units for Bitcoin) and an optional fee. Check them before signing; the one-shot Send* 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 CodeDescriptionSolution
INVALID_ARGUMENTInvalid network, address, or amountVerify all parameters are correctly formatted
FAILED_PRECONDITIONInsufficient balance or wallet not initializedCheck balance and wallet status
NOT_FOUNDTransaction not found (for BumpTransaction)Verify transaction exists and is unconfirmed
UNAVAILABLEService temporarily unavailableRetry with exponential backoff

Best Practices

  1. Validate addresses before sending. An on-chain send is irreversible; the API does not second-guess a well-formed address.
  2. 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.
  3. Pick the fee by urgency — low when you can wait, high when you cannot, custom when you have your own estimate.
  4. Prefer build → sign → finalize when the amount is large. It is the only flow that lets you inspect fee_units and fee before committing.
  5. Follow the transaction afterwards — wallet.GetTransaction, or the TransactionUpdate on SubscribeClientEvents.
  6. Bump rather than resend. A second send is a second transaction; BumpTransaction replaces the first.

← Back to API Reference | Next: Asset API →


Copyright © 2025