Rental API
The Rental API allows you to rent inbound liquidity from liquidity providers without managing channels yourself.
Endpoints
Get Rental Node Info
Get general rental configuration from the node including supported assets and duration limits.
Method: GetRentalNodeInfo
Parameters: None
Response:
| Field | Type | Description |
|---|---|---|
min_duration_seconds | uint64 | Minimum rental duration in seconds |
max_duration_seconds | uint64 | Maximum rental duration in seconds |
min_capacity_usd | DecimalString | Minimum rental capacity in USD |
max_capacity_usd | DecimalString | Maximum rental capacity in USD |
rental_assets | RentalAssetConfig[] | Available assets for rental |
RentalAssetConfig:
| Field | Type | Description |
|---|---|---|
network | Network | Network where asset exists |
asset_id | string | Asset identifier |
rental_fee_ratio | DecimalString | Fee ratio (e.g., "0.001" = 0.1%) |
Example Request:
TypeScript
import { RentalServiceClient } from './proto/RentalServiceClientPb'
import { GetRentalNodeInfoRequest } from './proto/rental_pb'
const client = new RentalServiceClient('http://localhost:50051')
const request = new GetRentalNodeInfoRequest()
const response = await client.getRentalNodeInfo(request, {})
console.log('Duration range:',
response.getMinDurationSeconds(),
'to',
response.getMaxDurationSeconds(),
'seconds'
)
const assets = response.getRentalAssetsList()
assets.forEach(asset => {
console.log('Asset:', asset.getAssetId(), 'Fee:', asset.getRentalFeeRatio())
})
Go
import (
"context"
"fmt"
"log"
pb "github.com/hydra/hydra-go/proto"
"google.golang.org/grpc"
)
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewRentalServiceClient(conn)
req := &pb.GetRentalNodeInfoRequest{}
resp, err := client.GetRentalNodeInfo(context.Background(), req)
if err != nil {
log.Fatalf("GetRentalNodeInfo failed: %v", err)
}
fmt.Printf("Duration range: %d to %d seconds\n",
resp.MinDurationSeconds,
resp.MaxDurationSeconds)
for _, asset := range resp.RentalAssets {
fmt.Printf("Asset: %s, Fee: %s\n", asset.AssetId, asset.RentalFeeRatio)
}
Rust
use hydra_api::rental_service_client::RentalServiceClient;
use hydra_api::GetRentalNodeInfoRequest;
use tonic::transport::Channel;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel = Channel::from_static("http://localhost:50051")
.connect()
.await?;
let mut client = RentalServiceClient::new(channel);
let request = tonic::Request::new(GetRentalNodeInfoRequest {});
let response = client.get_rental_node_info(request).await?;
let info = response.into_inner();
println!("Duration range: {} to {} seconds",
info.min_duration_seconds,
info.max_duration_seconds);
for asset in info.rental_assets {
println!("Asset: {}, Fee: {}", asset.asset_id, asset.rental_fee_ratio);
}
Ok(())
}
Example Response:
{
"min_duration_seconds": "86400",
"max_duration_seconds": "7776000",
"min_capacity_usd": "100",
"max_capacity_usd": "10000",
"rental_assets": [
{
"network": {
"protocol": 0,
"chain_id": "0",
"name": "Bitcoin"
},
"asset_id": "BTC",
"rental_fee_ratio": "0.001"
},
{
"network": {
"protocol": 1,
"chain_id": "1",
"name": "Ethereum"
},
"asset_id": "0x0000000000000000000000000000000000000000",
"rental_fee_ratio": "0.0012"
}
]
}
Get Rentable Asset Info
Get detailed rental information for a specific asset.
Method: GetRentableAssetInfo
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to query |
asset_id | string | YES | Asset identifier |
Response:
| Field | Type | Description |
|---|---|---|
available_liquidity | DecimalString | Currently available liquidity |
min_capacity | DecimalString | Minimum rentable amount |
max_capacity | DecimalString | Maximum rentable amount |
min_duration_seconds | uint64 | Minimum rental duration in seconds |
max_duration_seconds | uint64 | Maximum rental duration in seconds |
rental_fee_ratio | DecimalString | Fee as ratio of amount |
Example Request:
TypeScript
const request = new GetRentableAssetInfoRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setAssetId('BTC')
const response = await client.getRentableAssetInfo(request, {})
console.log('Available:', response.getAvailableLiquidity())
console.log('Min:', response.getMinCapacity())
console.log('Max:', response.getMaxCapacity())
console.log('Fee ratio:', response.getRentalFeeRatio())
Go
req := &pb.GetRentableAssetInfoRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
AssetId: "BTC",
}
resp, err := client.GetRentableAssetInfo(context.Background(), req)
if err != nil {
log.Fatalf("GetRentableAssetInfo failed: %v", err)
}
fmt.Printf("Available: %s\n", resp.AvailableLiquidity)
fmt.Printf("Min: %s\n", resp.MinCapacity)
fmt.Printf("Max: %s\n", resp.MaxCapacity)
fmt.Printf("Fee ratio: %s\n", resp.RentalFeeRatio)
Rust
use hydra_api::GetRentableAssetInfoRequest;
let request = tonic::Request::new(GetRentableAssetInfoRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
asset_id: "BTC".to_string(),
});
let response = client.get_rentable_asset_info(request).await?;
let info = response.into_inner();
println!("Available: {}", info.available_liquidity);
println!("Min: {}", info.min_capacity);
println!("Max: {}", info.max_capacity);
println!("Fee ratio: {}", info.rental_fee_ratio);
Example Response:
{
"available_liquidity": "500000000",
"min_capacity": "1000000",
"max_capacity": "100000000",
"min_duration_seconds": "86400",
"max_duration_seconds": "2592000",
"rental_fee_ratio": "0.001"
}
Estimate Rent Channel Fee
Estimate the rental fee before executing.
Method: EstimateRentChannelFee
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to rent on |
asset_id | string | YES | Asset to rent |
lifetime_seconds | uint64 | YES | Rental lifetime in seconds |
amount | DecimalString | YES | Amount of inbound liquidity |
rental_option | RentalOption | YES | Payment or DualFund option |
RentalOption (one of):
Payment Option
Use for ONCHAIN or OFFCHAIN payment methods.
| Field | Type | Description |
|---|---|---|
rental_tx_fee_rate | FeeRate | Transaction fee rate (for ONCHAIN) |
payment_network | Network | Network for fee payment |
payment_asset_id | string | Asset used for fee payment |
payment_method | PaymentMethod | ONCHAIN (0) or OFFCHAIN (1) |
PaymentMethod Enum:
| Value | Description | Status |
|---|---|---|
ONCHAIN (0) | Pay rental fee with onchain transaction | ⚠️ Not currently supported |
OFFCHAIN (1) | Pay rental fee with Lightning payment | ✅ Supported |
DualFund Option
Use for dual-funded channel rental.
| Field | Type | Description |
|---|---|---|
self_amount | DecimalString | Your contribution to the channel |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated rental fee |
Example Request (OFFCHAIN Payment):
TypeScript
const request = new EstimateRentChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setAssetId('BTC')
request.setLifetimeSeconds(2592000) // 30 days
request.setAmount('10000000') // 0.1 BTC
// Using Payment option with OFFCHAIN method
request.setRentalOption({
payment: {
paymentNetwork: { protocol: 0, chainId: '0', name: 'Bitcoin' },
paymentAssetId: 'BTC',
paymentMethod: 1 // OFFCHAIN
}
})
const response = await client.estimateRentChannelFee(request, {})
console.log('Rental fee:', response.getFee())
Go
req := &pb.EstimateRentChannelFeeRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
AssetId: "BTC",
LifetimeSeconds: 2592000, // 30 days
Amount: "10000000", // 0.1 BTC
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_Payment{
Payment: &pb.RentalOption_PaymentOption{
PaymentNetwork: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
PaymentAssetId: "BTC",
PaymentMethod: pb.PaymentMethod_OFFCHAIN,
},
},
},
}
resp, err := client.EstimateRentChannelFee(context.Background(), req)
if err != nil {
log.Fatalf("EstimateRentChannelFee failed: %v", err)
}
fmt.Printf("Rental fee: %s\n", resp.Fee)
Rust
use hydra_api::{EstimateRentChannelFeeRequest, RentalOption, rental_option, PaymentMethod};
let request = tonic::Request::new(EstimateRentChannelFeeRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
asset_id: "BTC".to_string(),
lifetime_seconds: 2592000, // 30 days
amount: "10000000".to_string(), // 0.1 BTC
rental_option: Some(RentalOption {
option: Some(rental_option::Option::Payment(
rental_option::PaymentOption {
payment_network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
payment_asset_id: "BTC".to_string(),
payment_method: PaymentMethod::Offchain as i32,
..Default::default()
}
)),
}),
});
let response = client.estimate_rent_channel_fee(request).await?;
println!("Rental fee: {}", response.into_inner().fee);
Example Request (DualFund):
TypeScript
const request = new EstimateRentChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setAssetId('BTC')
request.setLifetimeSeconds(2592000)
request.setAmount('10000000')
// Using DualFund option
request.setRentalOption({
dualFund: {
selfAmount: '1000000' // Your 0.01 BTC contribution
}
})
const response = await client.estimateRentChannelFee(request, {})
console.log('Rental fee:', response.getFee())
Go
req := &pb.EstimateRentChannelFeeRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
AssetId: "BTC",
LifetimeSeconds: 2592000,
Amount: "10000000",
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_DualFund{
DualFund: &pb.RentalOption_DualFundOption{
SelfAmount: "1000000", // Your 0.01 BTC contribution
},
},
},
}
resp, err := client.EstimateRentChannelFee(context.Background(), req)
if err != nil {
log.Fatalf("EstimateRentChannelFee failed: %v", err)
}
fmt.Printf("Rental fee: %s\n", resp.Fee)
Rust
use hydra_api::{EstimateRentChannelFeeRequest, RentalOption, rental_option};
let request = tonic::Request::new(EstimateRentChannelFeeRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
asset_id: "BTC".to_string(),
lifetime_seconds: 2592000,
amount: "10000000".to_string(),
rental_option: Some(RentalOption {
option: Some(rental_option::Option::DualFund(
rental_option::DualFundOption {
self_amount: "1000000".to_string(), // Your 0.01 BTC contribution
}
)),
}),
});
let response = client.estimate_rent_channel_fee(request).await?;
println!("Rental fee: {}", response.into_inner().fee);
Example Response:
{
"fee": "10000"
}
Rent Channel
Execute a channel rental to receive inbound liquidity.
Method: RentChannel
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to rent on |
asset_id | string | YES | Asset to rent |
lifetime_seconds | uint64 | YES | Rental lifetime in seconds |
amount | DecimalString | YES | Amount of inbound liquidity |
rental_option | RentalOption | YES | Payment or DualFund option |
Response:
| Field | Type | Description |
|---|---|---|
rental_txid | string | Rental channel funding transaction ID |
rented_channel_id | string | ID of the rented channel |
rental_payment | RentalPayment | Payment details |
RentalPayment (one of):
Onchain Payment
| Field | Type | Description |
|---|---|---|
payment_txid | string | Payment transaction ID |
deposit_address | string | Deposit address for payment |
fee_amount | DecimalString | Fee amount paid |
metadata | bytes | Payment metadata |
Offchain Payment
| Field | Type | Description |
|---|---|---|
payment_id | string | Lightning payment ID |
payment_request | string | Lightning invoice (if applicable) |
Example Request (OFFCHAIN):
TypeScript
const request = new RentChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setAssetId('BTC')
request.setLifetimeSeconds(2592000) // 30 days
request.setAmount('10000000') // 0.1 BTC
request.setRentalOption({
payment: {
paymentNetwork: { protocol: 0, chainId: '0', name: 'Bitcoin' },
paymentAssetId: 'BTC',
paymentMethod: 1 // OFFCHAIN
}
})
const response = await client.rentChannel(request, {})
console.log('Rented channel ID:', response.getRentedChannelId())
console.log('Rental TX:', response.getRentalTxid())
const payment = response.getRentalPayment()
if (payment.hasOffchain()) {
console.log('Payment ID:', payment.getOffchain()?.getPaymentId())
}
Go
req := &pb.RentChannelRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
AssetId: "BTC",
LifetimeSeconds: 2592000, // 30 days
Amount: "10000000", // 0.1 BTC
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_Payment{
Payment: &pb.RentalOption_PaymentOption{
PaymentNetwork: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
PaymentAssetId: "BTC",
PaymentMethod: pb.PaymentMethod_OFFCHAIN,
},
},
},
}
resp, err := client.RentChannel(context.Background(), req)
if err != nil {
log.Fatalf("RentChannel failed: %v", err)
}
fmt.Printf("Rented channel ID: %s\n", resp.RentedChannelId)
fmt.Printf("Rental TX: %s\n", resp.RentalTxid)
if offchain := resp.RentalPayment.GetOffchain(); offchain != nil {
fmt.Printf("Payment ID: %s\n", offchain.PaymentId)
}
Rust
use hydra_api::{RentChannelRequest, RentalOption, rental_option, PaymentMethod};
let request = tonic::Request::new(RentChannelRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
asset_id: "BTC".to_string(),
lifetime_seconds: 2592000, // 30 days
amount: "10000000".to_string(), // 0.1 BTC
rental_option: Some(RentalOption {
option: Some(rental_option::Option::Payment(
rental_option::PaymentOption {
payment_network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
payment_asset_id: "BTC".to_string(),
payment_method: PaymentMethod::Offchain as i32,
..Default::default()
}
)),
}),
});
let response = client.rent_channel(request).await?;
let rental = response.into_inner();
println!("Rented channel ID: {}", rental.rented_channel_id);
println!("Rental TX: {}", rental.rental_txid);
if let Some(payment) = rental.rental_payment {
if let Some(offchain) = payment.offchain {
println!("Payment ID: {}", offchain.payment_id);
}
}
Example Request (DualFund):
TypeScript
const request = new RentChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setAssetId('BTC')
request.setLifetimeSeconds(2592000)
request.setAmount('10000000')
request.setRentalOption({
dualFund: {
selfAmount: '1000000' // Your contribution
}
})
const response = await client.rentChannel(request, {})
console.log('Dual-funded channel ID:', response.getRentedChannelId())
console.log('Funding TX:', response.getRentalTxid())
Go
req := &pb.RentChannelRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
AssetId: "BTC",
LifetimeSeconds: 2592000,
Amount: "10000000",
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_DualFund{
DualFund: &pb.RentalOption_DualFundOption{
SelfAmount: "1000000", // Your contribution
},
},
},
}
resp, err := client.RentChannel(context.Background(), req)
if err != nil {
log.Fatalf("RentChannel failed: %v", err)
}
fmt.Printf("Dual-funded channel ID: %s\n", resp.RentedChannelId)
fmt.Printf("Funding TX: %s\n", resp.RentalTxid)
Rust
use hydra_api::{RentChannelRequest, RentalOption, rental_option};
let request = tonic::Request::new(RentChannelRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
asset_id: "BTC".to_string(),
lifetime_seconds: 2592000,
amount: "10000000".to_string(),
rental_option: Some(RentalOption {
option: Some(rental_option::Option::DualFund(
rental_option::DualFundOption {
self_amount: "1000000".to_string(), // Your contribution
}
)),
}),
});
let response = client.rent_channel(request).await?;
let rental = response.into_inner();
println!("Dual-funded channel ID: {}", rental.rented_channel_id);
println!("Funding TX: {}", rental.rental_txid);
Example Response:
{
"rental_txid": "abc123...",
"rented_channel_id": "ch_def456",
"rental_payment": {
"offchain": {
"payment_id": "payment_xyz789",
"payment_request": ""
}
}
}
Rental Duration Guide
Duration is specified in seconds.
Common Durations:
| Duration | Seconds | Calculation |
|---|---|---|
| 1 hour | 3,600 | 60 × 60 |
| 1 day | 86,400 | 24 × 60 × 60 |
| 1 week | 604,800 | 7 × 24 × 60 × 60 |
| 30 days | 2,592,000 | 30 × 24 × 60 × 60 |
| 90 days | 7,776,000 | 90 × 24 × 60 × 60 |
Calculation Formula:
TypeScript
const lifetimeSeconds = days * 24 * 60 * 60
// Examples
const oneDay = 1 * 24 * 60 * 60 // 86,400
const oneWeek = 7 * 24 * 60 * 60 // 604,800
const thirtyDays = 30 * 24 * 60 * 60 // 2,592,000
Go
lifetimeSeconds := days * 24 * 60 * 60
// Examples
oneDay := 1 * 24 * 60 * 60 // 86,400
oneWeek := 7 * 24 * 60 * 60 // 604,800
thirtyDays := 30 * 24 * 60 * 60 // 2,592,000
Rust
let lifetime_seconds = days * 24 * 60 * 60;
// Examples
let one_day = 1 * 24 * 60 * 60; // 86,400
let one_week = 7 * 24 * 60 * 60; // 604,800
let thirty_days = 30 * 24 * 60 * 60; // 2,592,000
Payment Methods
⚠️ Currently, only OFFCHAIN payment is supported.
OFFCHAIN Payment (✅ Supported)
- How it works: Fee is paid via Lightning payment
- Pros: Instant, lower fees
- Cons: Requires existing offchain balance
- Use when: You have Lightning channels with sufficient balance
DualFund Option (✅ Supported)
- How it works: You and the provider both contribute to the channel
- Pros: Efficient use of capital, shared funding
- Use when: You want to contribute some funds to the rental channel
ONCHAIN Payment (⚠️ Not Currently Supported)
- Status: Not yet implemented
- Future availability: Will be added in a future release
Common Workflows
Rent a channel with OFFCHAIN payment
TypeScript
async function rentChannelOffchain(
client: RentalServiceClient,
network: Network,
assetId: string,
amount: string,
durationDays: number
) {
// 1. Check availability
const infoReq = new GetRentableAssetInfoRequest()
infoReq.setNetwork(network)
infoReq.setAssetId(assetId)
const info = await client.getRentableAssetInfo(infoReq, {})
if (BigInt(amount) > BigInt(info.getAvailableLiquidity())) {
throw new Error('Insufficient liquidity available')
}
// 2. Calculate duration in seconds
const lifetimeSeconds = durationDays * 24 * 60 * 60
// 3. Estimate fee
const estimateReq = new EstimateRentChannelFeeRequest()
estimateReq.setNetwork(network)
estimateReq.setAssetId(assetId)
estimateReq.setLifetimeSeconds(lifetimeSeconds)
estimateReq.setAmount(amount)
estimateReq.setRentalOption({
payment: {
paymentNetwork: network,
paymentAssetId: assetId,
paymentMethod: 1 // OFFCHAIN
}
})
const estimate = await client.estimateRentChannelFee(estimateReq, {})
console.log('Rental fee:', estimate.getFee())
// 4. Execute rental
const rentReq = new RentChannelRequest()
rentReq.setNetwork(network)
rentReq.setAssetId(assetId)
rentReq.setLifetimeSeconds(lifetimeSeconds)
rentReq.setAmount(amount)
rentReq.setRentalOption({
payment: {
paymentNetwork: network,
paymentAssetId: assetId,
paymentMethod: 1 // OFFCHAIN
}
})
const result = await client.rentChannel(rentReq, {})
return {
channelId: result.getRentedChannelId(),
txid: result.getRentalTxid(),
paymentId: result.getRentalPayment()?.getOffchain()?.getPaymentId()
}
}
// Usage: Rent 0.1 BTC for 30 days
const rental = await rentChannelOffchain(
client,
{ protocol: 0, chainId: '0', name: 'Bitcoin' },
'BTC',
'10000000', // 0.1 BTC
30 // 30 days
)
console.log('Channel rented:', rental.channelId)
Go
func rentChannelOffchain(
client pb.RentalServiceClient,
network *pb.Network,
assetId string,
amount string,
durationDays int,
) (map[string]string, error) {
// 1. Check availability
infoReq := &pb.GetRentableAssetInfoRequest{
Network: network,
AssetId: assetId,
}
info, err := client.GetRentableAssetInfo(context.Background(), infoReq)
if err != nil {
return nil, err
}
amountBig, _ := new(big.Int).SetString(amount, 10)
availBig, _ := new(big.Int).SetString(info.AvailableLiquidity, 10)
if amountBig.Cmp(availBig) > 0 {
return nil, fmt.Errorf("insufficient liquidity available")
}
// 2. Calculate duration in seconds
lifetimeSeconds := uint64(durationDays * 24 * 60 * 60)
// 3. Estimate fee
estimateReq := &pb.EstimateRentChannelFeeRequest{
Network: network,
AssetId: assetId,
LifetimeSeconds: lifetimeSeconds,
Amount: amount,
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_Payment{
Payment: &pb.RentalOption_PaymentOption{
PaymentNetwork: network,
PaymentAssetId: assetId,
PaymentMethod: pb.PaymentMethod_OFFCHAIN,
},
},
},
}
estimate, err := client.EstimateRentChannelFee(context.Background(), estimateReq)
if err != nil {
return nil, err
}
fmt.Printf("Rental fee: %s\n", estimate.Fee)
// 4. Execute rental
rentReq := &pb.RentChannelRequest{
Network: network,
AssetId: assetId,
LifetimeSeconds: lifetimeSeconds,
Amount: amount,
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_Payment{
Payment: &pb.RentalOption_PaymentOption{
PaymentNetwork: network,
PaymentAssetId: assetId,
PaymentMethod: pb.PaymentMethod_OFFCHAIN,
},
},
},
}
result, err := client.RentChannel(context.Background(), rentReq)
if err != nil {
return nil, err
}
return map[string]string{
"channelId": result.RentedChannelId,
"txid": result.RentalTxid,
"paymentId": result.RentalPayment.GetOffchain().GetPaymentId(),
}, nil
}
// Usage: Rent 0.1 BTC for 30 days
rental, err := rentChannelOffchain(
client,
&pb.Network{Protocol: 0, ChainId: "0", Name: "Bitcoin"},
"BTC",
"10000000", // 0.1 BTC
30, // 30 days
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Channel rented: %s\n", rental["channelId"])
Rust
use hydra_api::{RentalServiceClient, GetRentableAssetInfoRequest, EstimateRentChannelFeeRequest, RentChannelRequest, Network, RentalOption, rental_option, PaymentMethod};
use std::collections::HashMap;
async fn rent_channel_offchain(
client: &mut RentalServiceClient<Channel>,
network: Network,
asset_id: String,
amount: String,
duration_days: u64,
) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
// 1. Check availability
let info_req = tonic::Request::new(GetRentableAssetInfoRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
});
let info = client.get_rentable_asset_info(info_req).await?.into_inner();
let amount_big: u64 = amount.parse()?;
let avail_big: u64 = info.available_liquidity.parse()?;
if amount_big > avail_big {
return Err("Insufficient liquidity available".into());
}
// 2. Calculate duration in seconds
let lifetime_seconds = duration_days * 24 * 60 * 60;
// 3. Estimate fee
let estimate_req = tonic::Request::new(EstimateRentChannelFeeRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
lifetime_seconds,
amount: amount.clone(),
rental_option: Some(RentalOption {
option: Some(rental_option::Option::Payment(
rental_option::PaymentOption {
payment_network: Some(network.clone()),
payment_asset_id: asset_id.clone(),
payment_method: PaymentMethod::Offchain as i32,
..Default::default()
}
)),
}),
});
let estimate = client.estimate_rent_channel_fee(estimate_req).await?.into_inner();
println!("Rental fee: {}", estimate.fee);
// 4. Execute rental
let rent_req = tonic::Request::new(RentChannelRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
lifetime_seconds,
amount: amount.clone(),
rental_option: Some(RentalOption {
option: Some(rental_option::Option::Payment(
rental_option::PaymentOption {
payment_network: Some(network),
payment_asset_id: asset_id,
payment_method: PaymentMethod::Offchain as i32,
..Default::default()
}
)),
}),
});
let result = client.rent_channel(rent_req).await?.into_inner();
let mut rental = HashMap::new();
rental.insert("channelId".to_string(), result.rented_channel_id);
rental.insert("txid".to_string(), result.rental_txid);
if let Some(payment) = result.rental_payment {
if let Some(offchain) = payment.offchain {
rental.insert("paymentId".to_string(), offchain.payment_id);
}
}
Ok(rental)
}
// Usage: Rent 0.1 BTC for 30 days
let rental = rent_channel_offchain(
&mut client,
Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
},
"BTC".to_string(),
"10000000".to_string(), // 0.1 BTC
30, // 30 days
).await?;
println!("Channel rented: {}", rental.get("channelId").unwrap());
Rent with dual-funded channel
TypeScript
async function rentChannelDualFund(
client: RentalServiceClient,
network: Network,
assetId: string,
amount: string,
selfContribution: string,
durationDays: number
) {
const lifetimeSeconds = durationDays * 24 * 60 * 60
// 1. Estimate fee
const estimateReq = new EstimateRentChannelFeeRequest()
estimateReq.setNetwork(network)
estimateReq.setAssetId(assetId)
estimateReq.setLifetimeSeconds(lifetimeSeconds)
estimateReq.setAmount(amount)
estimateReq.setRentalOption({
dualFund: {
selfAmount: selfContribution
}
})
const estimate = await client.estimateRentChannelFee(estimateReq, {})
console.log('Rental fee:', estimate.getFee())
// 2. Execute rental
const rentReq = new RentChannelRequest()
rentReq.setNetwork(network)
rentReq.setAssetId(assetId)
rentReq.setLifetimeSeconds(lifetimeSeconds)
rentReq.setAmount(amount)
rentReq.setRentalOption({
dualFund: {
selfAmount: selfContribution
}
})
const result = await client.rentChannel(rentReq, {})
return {
channelId: result.getRentedChannelId(),
txid: result.getRentalTxid()
}
}
// Usage: Rent 0.1 BTC, contribute 0.01 BTC yourself
const rental = await rentChannelDualFund(
client,
{ protocol: 0, chainId: '0', name: 'Bitcoin' },
'BTC',
'10000000', // 0.1 BTC total inbound
'1000000', // 0.01 BTC your contribution
30 // 30 days
)
Go
func rentChannelDualFund(
client pb.RentalServiceClient,
network *pb.Network,
assetId string,
amount string,
selfContribution string,
durationDays int,
) (map[string]string, error) {
lifetimeSeconds := uint64(durationDays * 24 * 60 * 60)
// 1. Estimate fee
estimateReq := &pb.EstimateRentChannelFeeRequest{
Network: network,
AssetId: assetId,
LifetimeSeconds: lifetimeSeconds,
Amount: amount,
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_DualFund{
DualFund: &pb.RentalOption_DualFundOption{
SelfAmount: selfContribution,
},
},
},
}
estimate, err := client.EstimateRentChannelFee(context.Background(), estimateReq)
if err != nil {
return nil, err
}
fmt.Printf("Rental fee: %s\n", estimate.Fee)
// 2. Execute rental
rentReq := &pb.RentChannelRequest{
Network: network,
AssetId: assetId,
LifetimeSeconds: lifetimeSeconds,
Amount: amount,
RentalOption: &pb.RentalOption{
Option: &pb.RentalOption_DualFund{
DualFund: &pb.RentalOption_DualFundOption{
SelfAmount: selfContribution,
},
},
},
}
result, err := client.RentChannel(context.Background(), rentReq)
if err != nil {
return nil, err
}
return map[string]string{
"channelId": result.RentedChannelId,
"txid": result.RentalTxid,
}, nil
}
// Usage: Rent 0.1 BTC, contribute 0.01 BTC yourself
rental, err := rentChannelDualFund(
client,
&pb.Network{Protocol: 0, ChainId: "0", Name: "Bitcoin"},
"BTC",
"10000000", // 0.1 BTC total inbound
"1000000", // 0.01 BTC your contribution
30, // 30 days
)
if err != nil {
log.Fatal(err)
}
Rust
use hydra_api::{RentalServiceClient, EstimateRentChannelFeeRequest, RentChannelRequest, Network, RentalOption, rental_option};
use std::collections::HashMap;
async fn rent_channel_dual_fund(
client: &mut RentalServiceClient<Channel>,
network: Network,
asset_id: String,
amount: String,
self_contribution: String,
duration_days: u64,
) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let lifetime_seconds = duration_days * 24 * 60 * 60;
// 1. Estimate fee
let estimate_req = tonic::Request::new(EstimateRentChannelFeeRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
lifetime_seconds,
amount: amount.clone(),
rental_option: Some(RentalOption {
option: Some(rental_option::Option::DualFund(
rental_option::DualFundOption {
self_amount: self_contribution.clone(),
}
)),
}),
});
let estimate = client.estimate_rent_channel_fee(estimate_req).await?.into_inner();
println!("Rental fee: {}", estimate.fee);
// 2. Execute rental
let rent_req = tonic::Request::new(RentChannelRequest {
network: Some(network),
asset_id,
lifetime_seconds,
amount,
rental_option: Some(RentalOption {
option: Some(rental_option::Option::DualFund(
rental_option::DualFundOption {
self_amount: self_contribution,
}
)),
}),
});
let result = client.rent_channel(rent_req).await?.into_inner();
let mut rental = HashMap::new();
rental.insert("channelId".to_string(), result.rented_channel_id);
rental.insert("txid".to_string(), result.rental_txid);
Ok(rental)
}
// Usage: Rent 0.1 BTC, contribute 0.01 BTC yourself
let rental = rent_channel_dual_fund(
&mut client,
Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
},
"BTC".to_string(),
"10000000".to_string(), // 0.1 BTC total inbound
"1000000".to_string(), // 0.01 BTC your contribution
30, // 30 days
).await?;
Calculate rental cost
TypeScript
function calculateRentalCost(
amount: string,
feeRatio: string
): string {
const amountBigInt = BigInt(amount)
const feeRatioBigInt = BigInt(parseFloat(feeRatio) * 1000000)
const rentalFee = (amountBigInt * feeRatioBigInt) / BigInt(1000000)
return rentalFee.toString()
}
// Example: 0.1 BTC at 0.1% fee
const fee = calculateRentalCost('10000000', '0.001')
console.log('Rental fee:', fee, 'satoshis') // 10000 satoshis
Go
import "math/big"
func calculateRentalCost(amount string, feeRatio string) (string, error) {
amountBig, ok := new(big.Int).SetString(amount, 10)
if !ok {
return "", fmt.Errorf("invalid amount")
}
feeRatioFloat, err := strconv.ParseFloat(feeRatio, 64)
if err != nil {
return "", err
}
feeRatioBig := big.NewInt(int64(feeRatioFloat * 1000000))
rentalFee := new(big.Int).Mul(amountBig, feeRatioBig)
rentalFee.Div(rentalFee, big.NewInt(1000000))
return rentalFee.String(), nil
}
// Example: 0.1 BTC at 0.1% fee
fee, err := calculateRentalCost("10000000", "0.001")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Rental fee: %s satoshis\n", fee) // 10000 satoshis
Rust
fn calculate_rental_cost(amount: &str, fee_ratio: &str) -> Result<String, Box<dyn std::error::Error>> {
let amount_u64: u64 = amount.parse()?;
let fee_ratio_f64: f64 = fee_ratio.parse()?;
let fee_ratio_scaled = (fee_ratio_f64 * 1_000_000.0) as u64;
let rental_fee = (amount_u64 * fee_ratio_scaled) / 1_000_000;
Ok(rental_fee.to_string())
}
// Example: 0.1 BTC at 0.1% fee
let fee = calculate_rental_cost("10000000", "0.001")?;
println!("Rental fee: {} satoshis", fee); // 10000 satoshis
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Invalid asset_id or parameters | Check asset ID format and parameter ranges |
RESOURCE_EXHAUSTED | Insufficient liquidity available | Reduce amount or try different asset |
FAILED_PRECONDITION | Duration outside allowed range | Check min/max duration from GetRentableAssetInfo |
UNAVAILABLE | Rental service temporarily unavailable | Retry with exponential backoff |
Best Practices
- Always estimate first - Call
EstimateRentChannelFeebeforeRentChannel - Check availability - Verify
available_liquiditybefore renting large amounts - Choose appropriate duration - Longer rentals have better fee efficiency
- Use OFFCHAIN when possible - Cheaper and faster than ONCHAIN (when available)
- Consider dual-funding - Efficient use of capital if you have funds
- Monitor channel status - Use Node API to track channel becoming active
- Plan for expiry - Rental channels close after lifetime expires
Fee Calculation Example
TypeScript
// Asset: BTC
// Amount: 10,000,000 satoshis (0.1 BTC)
// Lifetime: 2,592,000 seconds (30 days)
// Fee Ratio: 0.001 (0.1%)
// Payment: OFFCHAIN
const rentalFee = 10_000_000 * 0.001 = 10_000 sats
const totalCost = 10_000 sats (~$10.00 at $100k BTC)
// Effective rate: 0.1% for 30 days = 1.2% annualized
Go
// Asset: BTC
// Amount: 10,000,000 satoshis (0.1 BTC)
// Lifetime: 2,592,000 seconds (30 days)
// Fee Ratio: 0.001 (0.1%)
// Payment: OFFCHAIN
rentalFee := 10_000_000 * 0.001 // 10_000 sats
totalCost := 10_000 // sats (~$10.00 at $100k BTC)
// Effective rate: 0.1% for 30 days = 1.2% annualized
Rust
// Asset: BTC
// Amount: 10,000,000 satoshis (0.1 BTC)
// Lifetime: 2,592,000 seconds (30 days)
// Fee Ratio: 0.001 (0.1%)
// Payment: OFFCHAIN
let rental_fee = 10_000_000.0 * 0.001; // 10_000 sats
let total_cost = 10_000; // sats (~$10.00 at $100k BTC)
// Effective rate: 0.1% for 30 days = 1.2% annualized
RentalOption Selection Guide
Choose the appropriate rental option based on your situation:
| Situation | Rental Option | Why |
|---|---|---|
| Have Lightning balance | Payment (OFFCHAIN) | Fast, low fees |
| Want to contribute funds | DualFund | Efficient capital use |
| No Lightning balance | Not currently supported | ONCHAIN coming soon |