Api

Node API

Channel and payment management

The Node API provides Lightning Network channel management and payment operations.

JSON-RPC namespace: node

Endpoints

Peer Management

Zero-Conf Whitelist

Channel Operations

Batch Channel Operations

Payment Operations

Invoice Management

Funding Allowances


Connect to Peer

Connect to a Lightning Network peer.

Method: ConnectToPeer

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to connect on
peer_urlstringYESPeer connection URL

Peer URL Format:

node_id@host:port

Response: Empty (success confirmation)

Example Request:

TypeScript
import { NodeServiceClient } from './proto/NodeServiceClientPb'
import { ConnectToPeerRequest } from './proto/node_pb'

const client = new NodeServiceClient('http://localhost:5001')

const request = new ConnectToPeerRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPeerUrl('02abc123...@lightning.example.com:9735')

await client.connectToPeer(request, {})
console.log('Connected to peer')
Go
import (
    pb "github.com/hydra/hydra-go/proto"
)

client := pb.NewNodeServiceClient(conn)

req := &pb.ConnectToPeerRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PeerUrl: "02abc123...@lightning.example.com:9735",
}

_, err := client.ConnectToPeer(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Connected to peer")
Rust
use hydra_app::node_service_client::NodeServiceClient;
use hydra_app::{ConnectToPeerRequest, Network};

let mut client = NodeServiceClient::new(channel);

let request = tonic::Request::new(ConnectToPeerRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    peer_url: "02abc123...@lightning.example.com:9735".to_string(),
});

client.connect_to_peer(request).await?;
println!("Connected to peer");

Get Connected Peers

Get list of currently connected peers.

Method: GetConnectedPeers

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to query

Response:

FieldTypeDescription
node_idsstring[]Array of connected node IDs

Example Request:

TypeScript
const request = new GetConnectedPeersRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })

const response = await client.getConnectedPeers(request, {})
const peers = response.getNodeIdsList()
console.log('Connected peers:', peers)
Go
req := &pb.GetConnectedPeersRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
}

resp, err := client.GetConnectedPeers(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

peers := resp.NodeIds
log.Println("Connected peers:", peers)
Rust
let request = tonic::Request::new(GetConnectedPeersRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
});

let response = client.get_connected_peers(request).await?;
let peers = response.into_inner().node_ids;
println!("Connected peers: {:?}", peers);

Disconnect from Peer

Disconnect from a connected peer.

Method: DisconnectFromPeer

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key (hex)

Response: Empty.

Example Request:

import { DisconnectFromPeerRequest } from './proto/node_pb'

const request = new DisconnectFromPeerRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

await client.disconnectFromPeer(request, {})

Is Peer Connected

Check whether a specific peer is currently connected.

Method: IsPeerConnected

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key (hex)

Response:

FieldTypeDescription
is_connectedboolTrue if currently connected

Example Request:

import { IsPeerConnectedRequest } from './proto/node_pb'

const request = new IsPeerConnectedRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

const response = await client.isPeerConnected(request, {})
console.log('Connected:', response.getIsConnected())

Get Zero-Conf Whitelist

Returns the list of peers permitted to use zero-confirmation channels with this node.

Method: GetZeroConfWhitelist

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network

Response:

FieldTypeDescription
node_idsstring[]Hex-encoded public keys of whitelisted peers

Example Request:

import { GetZeroConfWhitelistRequest } from './proto/node_pb'

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

const response = await client.getZeroConfWhitelist(request, {})
console.log('Whitelisted:', response.getNodeIdsList())

Add Peer To Zero-Conf Whitelist

Adds a peer to the zero-confirmation whitelist. Whitelisted peers can use channels before the funding transaction confirms on-chain.

Method: AddPeerToZeroConfWhitelist

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key to whitelist

Response: Empty.

Example Request:

import { AddPeerToZeroConfWhitelistRequest } from './proto/node_pb'

const request = new AddPeerToZeroConfWhitelistRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

await client.addPeerToZeroConfWhitelist(request, {})

Remove Peer From Zero-Conf Whitelist

Removes a peer from the zero-confirmation whitelist.

Method: RemovePeerFromZeroConfWhitelist

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key to remove

Response: Empty.

Example Request:

import { RemovePeerFromZeroConfWhitelistRequest } from './proto/node_pb'

const request = new RemovePeerFromZeroConfWhitelistRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

await client.removePeerFromZeroConfWhitelist(request, {})

Estimate Open Channel Fee

Estimate the onchain fee for opening a new channel.

Method: EstimateOpenChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to open channel on
node_idstringYESPeer's node ID
asset_amountsmap<string, SendAmount>YESMap of asset_id → amount to allocate
fee_rateFeeRateYESTransaction fee rate

SendAmount Object (one of):

FieldTypeDescription
valueDecimalStringExact amount
max-Use maximum available

FeeRate Object (one of):

FieldTypeDescription
absoluteDecimalStringAbsolute fee amount
relativeDecimalStringRelative fee (e.g., sat/vB)

Response:

FieldTypeDescription
feeDecimalStringEstimated onchain fee

Example Request:

TypeScript
const request = new EstimateOpenChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')
request.setAssetAmountsMap({
  'BTC': { value: '10000000' } // 0.1 BTC
})
request.setFeeRate({ relative: '5' }) // 5 sat/vB

const response = await client.estimateOpenChannelFee(request, {})
console.log('Estimated fee:', response.getFee(), 'sats')
Go
req := &pb.EstimateOpenChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    NodeId: "02abc123...",
    AssetAmounts: map[string]*pb.SendAmount{
        "BTC": {Value: "10000000"}, // 0.1 BTC
    },
    FeeRate: &pb.FeeRate{Relative: "5"}, // 5 sat/vB
}

resp, err := client.EstimateOpenChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Estimated fee:", resp.Fee, "sats")
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "10000000".to_string(), // 0.1 BTC
});

let request = tonic::Request::new(EstimateOpenChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    node_id: "02abc123...".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(), // 5 sat/vB
    }),
});

let response = client.estimate_open_channel_fee(request).await?;
println!("Estimated fee: {} sats", response.into_inner().fee);

Open Channel

Open a new Lightning channel with a peer.

Method: OpenChannel

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork to open on
node_idstringYESPeer's node ID
asset_amountsmap<string, SendAmount>YESAssets to allocate
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringFunding transaction ID
channel_idstringChannel identifier

Example Request:

TypeScript
const request = new OpenChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')
request.setAssetAmountsMap({
  'BTC': { value: '10000000' }
})
request.setFeeRate({ relative: '5' })

const response = await client.openChannel(request, {})
console.log('Channel opened!')
console.log('  TX:', response.getTxid())
console.log('  Channel ID:', response.getChannelId())
Go
req := &pb.OpenChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    NodeId: "02abc123...",
    AssetAmounts: map[string]*pb.SendAmount{
        "BTC": {Value: "10000000"},
    },
    FeeRate: &pb.FeeRate{Relative: "5"},
}

resp, err := client.OpenChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Channel opened!")
log.Println("  TX:", resp.Txid)
log.Println("  Channel ID:", resp.ChannelId)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "10000000".to_string(),
});

let request = tonic::Request::new(OpenChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    node_id: "02abc123...".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.open_channel(request).await?;
let inner = response.into_inner();
println!("Channel opened!");
println!("  TX: {}", inner.txid);
println!("  Channel ID: {}", inner.channel_id);

Estimate Deposit Channel Fee

Estimate fee for depositing assets into an existing channel.

Method: EstimateDepositChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to deposit to
asset_amountsmap<string, SendAmount>YESAssets to deposit
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
feeDecimalStringEstimated fee

Example Request:

TypeScript
const request = new EstimateDepositChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
  'BTC': { value: '5000000' } // Add 0.05 BTC
})
request.setFeeRate({ relative: '5' })

const response = await client.estimateDepositChannelFee(request, {})
console.log('Deposit fee:', response.getFee())
Go
req := &pb.EstimateDepositChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetAmounts: map[string]*pb.SendAmount{
        "BTC": {Value: "5000000"}, // Add 0.05 BTC
    },
    FeeRate: &pb.FeeRate{Relative: "5"},
}

resp, err := client.EstimateDepositChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Deposit fee:", resp.Fee)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "5000000".to_string(), // Add 0.05 BTC
});

let request = tonic::Request::new(EstimateDepositChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.estimate_deposit_channel_fee(request).await?;
println!("Deposit fee: {}", response.into_inner().fee);

Deposit Channel

Deposit additional assets into an existing channel.

Method: DepositChannel

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to deposit to
asset_amountsmap<string, SendAmount>YESAssets to deposit
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringDeposit transaction ID

Example Request:

TypeScript
const request = new DepositChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
  'BTC': { value: '5000000' }
})
request.setFeeRate({ relative: '5' })

const response = await client.depositChannel(request, {})
console.log('Deposit TX:', response.getTxid())
Go
req := &pb.DepositChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetAmounts: map[string]*pb.SendAmount{
        "BTC": {Value: "5000000"},
    },
    FeeRate: &pb.FeeRate{Relative: "5"},
}

resp, err := client.DepositChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Deposit TX:", resp.Txid)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "5000000".to_string(),
});

let request = tonic::Request::new(DepositChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.deposit_channel(request).await?;
println!("Deposit TX: {}", response.into_inner().txid);

Estimate Withdraw Channel Fee

Estimate fee for withdrawing assets from a channel.

Method: EstimateWithdrawChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to withdraw from
asset_amountsmap<string, WithdrawAmount>YESAmounts to withdraw
fee_rateFeeRateYESTransaction fee rate

WithdrawAmount Object:

FieldTypeDescription
self_withdrawalSendAmountAmount you withdraw
counterparty_withdrawalSendAmountAmount counterparty withdraws

Response:

FieldTypeDescription
feeDecimalStringEstimated fee

Example Request:

TypeScript
const request = new EstimateWithdrawChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
  'BTC': {
    selfWithdrawal: { value: '3000000' },
    counterpartyWithdrawal: { value: '2000000' }
  }
})
request.setFeeRate({ relative: '5' })

const response = await client.estimateWithdrawChannelFee(request, {})
console.log('Withdrawal fee:', response.getFee())
Go
req := &pb.EstimateWithdrawChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetAmounts: map[string]*pb.WithdrawAmount{
        "BTC": {
            SelfWithdrawal:         &pb.SendAmount{Value: "3000000"},
            CounterpartyWithdrawal: &pb.SendAmount{Value: "2000000"},
        },
    },
    FeeRate: &pb.FeeRate{Relative: "5"},
}

resp, err := client.EstimateWithdrawChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Withdrawal fee:", resp.Fee)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), WithdrawAmount {
    self_withdrawal: Some(SendAmount {
        value: "3000000".to_string(),
    }),
    counterparty_withdrawal: Some(SendAmount {
        value: "2000000".to_string(),
    }),
});

let request = tonic::Request::new(EstimateWithdrawChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.estimate_withdraw_channel_fee(request).await?;
println!("Withdrawal fee: {}", response.into_inner().fee);

Withdraw Channel

Withdraw assets from an existing channel.

Method: WithdrawChannel

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to withdraw from
asset_amountsmap<string, WithdrawAmount>YESAmounts to withdraw
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringWithdrawal transaction ID

Example Request:

TypeScript
const request = new WithdrawChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
  'BTC': {
    selfWithdrawal: { value: '3000000' },
    counterpartyWithdrawal: { value: '2000000' }
  }
})
request.setFeeRate({ relative: '5' })

const response = await client.withdrawChannel(request, {})
console.log('Withdrawal TX:', response.getTxid())
Go
req := &pb.WithdrawChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetAmounts: map[string]*pb.WithdrawAmount{
        "BTC": {
            SelfWithdrawal:         &pb.SendAmount{Value: "3000000"},
            CounterpartyWithdrawal: &pb.SendAmount{Value: "2000000"},
        },
    },
    FeeRate: &pb.FeeRate{Relative: "5"},
}

resp, err := client.WithdrawChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Withdrawal TX:", resp.Txid)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), WithdrawAmount {
    self_withdrawal: Some(SendAmount {
        value: "3000000".to_string(),
    }),
    counterparty_withdrawal: Some(SendAmount {
        value: "2000000".to_string(),
    }),
});

let request = tonic::Request::new(WithdrawChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_amounts,
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.withdraw_channel(request).await?;
println!("Withdrawal TX: {}", response.into_inner().txid);

Estimate Close Channel Fee

Estimate fee for cooperatively closing a channel.

Method: EstimateCloseChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to close
asset_idsstring[]YESAssets to settle
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
feeDecimalStringEstimated fee

Example Request:

TypeScript
const request = new EstimateCloseChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '5' })

const response = await client.estimateCloseChannelFee(request, {})
console.log('Close fee:', response.getFee())
Go
req := &pb.EstimateCloseChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "5"},
}

resp, err := client.EstimateCloseChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Close fee:", resp.Fee)
Rust
let request = tonic::Request::new(EstimateCloseChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.estimate_close_channel_fee(request).await?;
println!("Close fee: {}", response.into_inner().fee);

Close Channel

Cooperatively close a channel with your peer.

Method: CloseChannel

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to close
asset_idsstring[]YESAssets to settle
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringClosing transaction ID

Example Request:

TypeScript
const request = new CloseChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '5' })

const response = await client.closeChannel(request, {})
console.log('Channel closed, TX:', response.getTxid())
Go
req := &pb.CloseChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "5"},
}

resp, err := client.CloseChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Channel closed, TX:", resp.Txid)
Rust
let request = tonic::Request::new(CloseChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.close_channel(request).await?;
println!("Channel closed, TX: {}", response.into_inner().txid);

Estimate Force Close Channel Fee

Estimate fee for force-closing an unresponsive channel.

Method: EstimateForceCloseChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to force close
asset_idsstring[]YESAssets to settle
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
feeDecimalStringEstimated fee

Example Request:

TypeScript
const request = new EstimateForceCloseChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '5' })

const response = await client.estimateForceCloseChannelFee(request, {})
console.log('Force close fee:', response.getFee())
Go
req := &pb.EstimateForceCloseChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "5"},
}

resp, err := client.EstimateForceCloseChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Force close fee:", resp.Fee)
Rust
let request = tonic::Request::new(EstimateForceCloseChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.estimate_force_close_channel_fee(request).await?;
println!("Force close fee: {}", response.into_inner().fee);

Force Close Channel

Force close a channel without peer cooperation.

Method: ForceCloseChannel

Important Notes:

  • Use only when peer is unresponsive
  • Assets locked until dispute period expires
  • May require additional RedeemClosedChannel transaction
  • Higher fees than cooperative close

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to force close
asset_idsstring[]YESAssets to settle
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringForce close transaction ID

Example Request:

TypeScript
const request = new ForceCloseChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '10' }) // Higher fee recommended

const response = await client.forceCloseChannel(request, {})
console.log('Force close initiated, TX:', response.getTxid())
console.log('Assets will be spendable after dispute period')
Go
req := &pb.ForceCloseChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "10"}, // Higher fee recommended
}

resp, err := client.ForceCloseChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Force close initiated, TX:", resp.Txid)
log.Println("Assets will be spendable after dispute period")
Rust
let request = tonic::Request::new(ForceCloseChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "10".to_string(), // Higher fee recommended
    }),
});

let response = client.force_close_channel(request).await?;
println!("Force close initiated, TX: {}", response.into_inner().txid);
println!("Assets will be spendable after dispute period");

Estimate Redeem Closed Channel Fee

Estimate fee for redeeming a force-closed channel.

Method: EstimateRedeemClosedChannelFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESClosed channel
asset_idsstring[]YESAssets to redeem
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
feeDecimalStringEstimated fee

Example Request:

TypeScript
const request = new EstimateRedeemClosedChannelFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '5' })

const response = await client.estimateRedeemClosedChannelFee(request, {})
console.log('Redemption fee:', response.getFee())
Go
req := &pb.EstimateRedeemClosedChannelFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "5"},
}

resp, err := client.EstimateRedeemClosedChannelFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Redemption fee:", resp.Fee)
Rust
let request = tonic::Request::new(EstimateRedeemClosedChannelFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.estimate_redeem_closed_channel_fee(request).await?;
println!("Redemption fee: {}", response.into_inner().fee);

Redeem Closed Channel

Redeem assets from a force-closed channel after dispute period.

Method: RedeemClosedChannel

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESClosed channel
asset_idsstring[]YESAssets to redeem
fee_rateFeeRateYESTransaction fee rate

Response:

FieldTypeDescription
txidstringRedemption transaction ID

Example Request:

TypeScript
const request = new RedeemClosedChannelRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetIdsList(['BTC'])
request.setFeeRate({ relative: '5' })

const response = await client.redeemClosedChannel(request, {})
console.log('Assets redeemed, TX:', response.getTxid())
Go
req := &pb.RedeemClosedChannelRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetIds:  []string{"BTC"},
    FeeRate:   &pb.FeeRate{Relative: "5"},
}

resp, err := client.RedeemClosedChannel(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Assets redeemed, TX:", resp.Txid)
Rust
let request = tonic::Request::new(RedeemClosedChannelRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_ids: vec!["BTC".to_string()],
    fee_rate: Some(FeeRate {
        relative: "5".to_string(),
    }),
});

let response = client.redeem_closed_channel(request).await?;
println!("Assets redeemed, TX: {}", response.into_inner().txid);

Batch Channel Operations

Executes a list of channel operations atomically in a single transaction. Each operation can be an open, deposit, withdraw, close, or force-close.

Method: BatchChannelOperations

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
operationsBatchChannelOperation[]YESOperations to execute atomically
tx_fee_rateFeeRateYESChain fee rate

Response:

FieldTypeDescription
txidstringSingle transaction ID for the entire batch
opened_channelsBatchOpenedChannel[]Channels created by Open operations (carries the input index of the originating operation)

Example Request:

import { BatchChannelOperationsRequest } from './proto/node_pb'

const request = new BatchChannelOperationsRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setOperationsList([/* BatchChannelOperation entries */])
request.setTxFeeRate({
  maxFeePerUnit: { value: '50' },
  priorityFeePerUnit: { value: '5' }
})

const response = await client.batchChannelOperations(request, {})
console.log('Batch TX:', response.getTxid())

Estimate Batch Channel Operations Fee

Estimates fees for a batch operation before executing it.

Method: EstimateBatchChannelOperationsFee

Parameters: Same as Batch Channel Operations.

Response:

FieldTypeDescription
fee_estimateFeeEstimateTotal fee estimate at low/medium/high priority

Example:

const response = await client.estimateBatchChannelOperationsFee(request, {})
console.log('Fee estimate:', response.getFeeEstimate()?.toObject())

Estimate Simulated Channel Operation Fee

Estimates the fee for a single hypothetical operation against a simulated channel snapshot — useful for UI fee previews where the channel doesn't yet exist.

Method: EstimateSimulatedChannelOperationFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
channelSimulatedChannelYESHypothetical channel snapshot
operationSimulatedChannelOpYESOperation to simulate
tx_fee_rateFeeRateYESChain fee rate

Response:

FieldTypeDescription
fee_estimateFeeEstimateEstimated fee

Example:

import { EstimateSimulatedChannelOperationFeeRequest } from './proto/node_pb'

const request = new EstimateSimulatedChannelOperationFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannel(/* SimulatedChannel */)
request.setOperation(/* SimulatedChannelOp */)
request.setTxFeeRate({
  maxFeePerUnit: { value: '50' },
  priorityFeePerUnit: { value: '5' }
})

const response = await client.estimateSimulatedChannelOperationFee(request, {})
console.log('Estimate:', response.getFeeEstimate()?.toObject())

Estimate Simulated Channel Operations Fee

Estimates the total fee for a list of hypothetical operations across simulated channels — the multi-operation counterpart to the previous RPC.

Method: EstimateSimulatedChannelOperationsFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
operationsSimulatedChannelOp[]YESSimulated operations to estimate
tx_fee_rateFeeRateYESChain fee rate

Response:

FieldTypeDescription
fee_estimateFeeEstimateAggregate estimated fee

Example:

import { EstimateSimulatedChannelOperationsFeeRequest } from './proto/node_pb'

const request = new EstimateSimulatedChannelOperationsFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setOperationsList([/* SimulatedChannelOp entries */])
request.setTxFeeRate({
  maxFeePerUnit: { value: '50' },
  priorityFeePerUnit: { value: '5' }
})

const response = await client.estimateSimulatedChannelOperationsFee(request, {})

Send Channel Payment

Send a direct payment through a specific channel.

Method: SendChannelPayment

Use Cases:

  • Direct peer payments
  • Testing channel functionality
  • Hashlock payments (HTLC)

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
channel_idstringYESChannel to use
asset_amountsmap<string, Amount>YESAssets to send
hashlockHashlockNOOptional hashlock for HTLC
cltv_buffer_secsuint64NORenamed from expiry_timeout_secs (2026-05-10). Optional HTLC effective lifetime in seconds (BOLT-11 tag c semantic). KeySend has no separate invoice-validity knob — this buffer alone bounds the HTLC.

Hashlock Object:

FieldTypeDescription
payment_hashstringPayment hash (32 bytes hex)

Response:

FieldTypeDescription
payment_idstringPayment identifier

Example Request:

TypeScript
const request = new SendChannelPaymentRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
  'BTC': { value: '100000' } // 0.001 BTC
})
// Optional: set hashlock for HTLC
// request.setHashlock({ paymentHash: 'abc123...' })
request.setCltvBufferSecs(3600) // HTLC lifetime, seconds

const response = await client.sendChannelPayment(request, {})
console.log('Payment sent:', response.getPaymentId())
Go
req := &pb.SendChannelPaymentRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    ChannelId: "ch_abc123",
    AssetAmounts: map[string]*pb.Amount{
        "BTC": {Value: "100000"}, // 0.001 BTC
    },
    // Optional: set hashlock for HTLC
    // Hashlock: &pb.Hashlock{PaymentHash: "abc123..."},
    CltvBufferSecs: 3600, // HTLC lifetime, seconds
}

resp, err := client.SendChannelPayment(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment sent:", resp.PaymentId)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), Amount {
    value: "100000".to_string(), // 0.001 BTC
});

let request = tonic::Request::new(SendChannelPaymentRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    channel_id: "ch_abc123".to_string(),
    asset_amounts,
    // Optional: set hashlock for HTLC
    // hashlock: Some(Hashlock {
    //     payment_hash: "abc123...".to_string(),
    // }),
    cltv_buffer_secs: Some(3600), // HTLC lifetime, seconds
});

let response = client.send_channel_payment(request).await?;
println!("Payment sent: {}", response.into_inner().payment_id);

Estimate Send Payment Fee

Estimate fee for a routed Lightning payment.

Method: EstimateSendPaymentFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
recipient_node_idstringYESRecipient's node ID
asset_amountsmap<string, SendAmount>YESAssets to send
hashlockHashlockNOOptional hashlock
cltv_buffer_secsuint64NORenamed from expiry_timeout_secs (2026-05-10). HTLC effective lifetime, seconds (BOLT-11 c).
max_total_cltv_secsuint64NOAdded 2026-05-10. Optional clamp on the route's max-total-CLTV (seconds). For atomic-swap-correct timing pass the invoice's cltv_buffer_secs; for permissive multi-hop routing omit.

Response:

FieldTypeDescription
feesmap<string, DecimalString>Fees per asset

Example Request:

TypeScript
const request = new EstimateSendPaymentFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setRecipientNodeId('03def456...')
request.setAssetAmountsMap({
  'BTC': { value: '500000' }
})

const response = await client.estimateSendPaymentFee(request, {})
const fees = response.getFeesMap()
console.log('Routing fee:', fees.get('BTC'), 'sats')
Go
req := &pb.EstimateSendPaymentFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    RecipientNodeId: "03def456...",
    AssetAmounts: map[string]*pb.Amount{
        "BTC": {Value: "500000"},
    },
}

resp, err := client.EstimateSendPaymentFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

fees := resp.Fees
log.Println("Routing fee:", fees["BTC"], "sats")
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "500000".to_string(),
});

let request = tonic::Request::new(EstimateSendPaymentFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    recipient_node_id: "03def456...".to_string(),
    asset_amounts,
    hashlock: None,
    cltv_buffer_secs: None,
});

let response = client.estimate_send_payment_fee(request).await?;
let fees = response.into_inner().fees;
println!("Routing fee: {} sats", fees.get("BTC").unwrap_or(&"0".to_string()));

Send Payment

Send a routed Lightning payment through the network.

Method: SendPayment

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
recipient_node_idstringYESRecipient's node ID
asset_amountsmap<string, SendAmount>YESAssets to send
hashlockHashlockNOOptional hashlock
cltv_buffer_secsuint64NORenamed from expiry_timeout_secs (2026-05-10). HTLC effective lifetime, seconds (BOLT-11 c).
max_total_cltv_secsuint64NOAdded 2026-05-10. Optional clamp on the route's max-total-CLTV (seconds). For atomic-swap-correct timing pass the invoice's cltv_buffer_secs; for permissive multi-hop routing omit.

Response:

FieldTypeDescription
payment_idstringPayment identifier

Example Request:

TypeScript
const request = new SendPaymentRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setRecipientNodeId('03def456...')
request.setAssetAmountsMap({
  'BTC': { value: '500000' }
})
request.setCltvBufferSecs(3600)

const response = await client.sendPayment(request, {})
console.log('Payment routed:', response.getPaymentId())
Go
req := &pb.SendPaymentRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    RecipientNodeId:   "03def456...",
    AssetAmounts: map[string]*pb.Amount{
        "BTC": {Value: "500000"},
    },
    CltvBufferSecs: 3600,
}

resp, err := client.SendPayment(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment routed:", resp.PaymentId)
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), SendAmount {
    value: "500000".to_string(),
});

let request = tonic::Request::new(SendPaymentRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    recipient_node_id: "03def456...".to_string(),
    asset_amounts,
    hashlock: None,
    cltv_buffer_secs: Some(3600),
});

let response = client.send_payment(request).await?;
println!("Payment routed: {}", response.into_inner().payment_id);

Create Invoice

Create a Lightning invoice for receiving payment.

Method: CreateInvoice

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
asset_idstringYESAsset to receive
amountDecimalStringNOAmount (omit for zero-amount invoice)
hashlockHashlockNOOptional hashlock
expiry_timeout_secsuint64NOInvoice validity window in seconds (BOLT-11 x). Unchanged — only the payment-send RPCs renamed this field.
cltv_buffer_secsuint64NOAdded 2026-05-10. Extra HTLC lifetime past invoice expiry the receiver requires for safe settlement (BOLT-11 c).

Response:

FieldTypeDescription
invoiceInvoiceInvoice details

Invoice Object:

FieldTypeDescription
payment_requeststringEncoded payment request string
payment_hashstringPayment hash
amountDecimalStringInvoice amount (optional)
asset_idstringAsset identifier
expiryTimestampInvoice validity expiry
min_final_cltv_expiry_secsuint64Added 2026-05-10. Minimum CLTV buffer (seconds) the receiver requires for the incoming HTLC. The HTLC's effective deadline is expiry + min_final_cltv_expiry_secs.

Example Request:

TypeScript
const request = new CreateInvoiceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setAssetId('BTC')
request.setAmount('1000000') // 0.01 BTC
request.setExpiryTimeoutSecs(3600)

const response = await client.createInvoice(request, {})
const invoice = response.getInvoice()
console.log('Payment request:', invoice.getPaymentRequest())
console.log('Share this with the payer')
Go
req := &pb.CreateInvoiceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    AssetId:           "BTC",
    Amount:            "1000000", // 0.01 BTC
    ExpiryTimeoutSecs: 3600,
}

resp, err := client.CreateInvoice(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

invoice := resp.Invoice
log.Println("Payment request:", invoice.PaymentRequest)
log.Println("Share this with the payer")
Rust
let request = tonic::Request::new(CreateInvoiceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    asset_id: "BTC".to_string(),
    amount: "1000000".to_string(), // 0.01 BTC
    hashlock: None,
    expiry_timeout_secs: 3600,
});

let response = client.create_invoice(request).await?;
let invoice = response.into_inner().invoice.unwrap();
println!("Payment request: {}", invoice.payment_request);
println!("Share this with the payer");

Example (Zero-amount invoice):

TypeScript
const request = new CreateInvoiceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setAssetId('BTC')
// No amount set - payer decides

const response = await client.createInvoice(request, {})
const invoice = response.getInvoice()
console.log('Zero-amount invoice:', invoice.getPaymentRequest())
Go
req := &pb.CreateInvoiceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    AssetId: "BTC",
    // No amount set - payer decides
}

resp, err := client.CreateInvoice(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

invoice := resp.Invoice
log.Println("Zero-amount invoice:", invoice.PaymentRequest)
Rust
let request = tonic::Request::new(CreateInvoiceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    asset_id: "BTC".to_string(),
    amount: String::new(), // No amount set - payer decides
    hashlock: None,
    expiry_timeout_secs: 0,
});

let response = client.create_invoice(request).await?;
let invoice = response.into_inner().invoice.unwrap();
println!("Zero-amount invoice: {}", invoice.payment_request);

Decode Invoice

Decode a Lightning invoice to view its details.

Method: DecodeInvoice

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_requeststringYESEncoded payment request

Response:

FieldTypeDescription
invoiceInvoiceDecoded invoice details

Example Request:

TypeScript
const request = new DecodeInvoiceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentRequest('lnbc10m1...')

const response = await client.decodeInvoice(request, {})
const invoice = response.getInvoice()
console.log('Amount:', invoice.getAmount())
console.log('Asset:', invoice.getAssetId())
console.log('Expires:', invoice.getExpiry())
Go
req := &pb.DecodeInvoiceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentRequest: "lnbc10m1...",
}

resp, err := client.DecodeInvoice(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

invoice := resp.Invoice
log.Println("Amount:", invoice.Amount)
log.Println("Asset:", invoice.AssetId)
log.Println("Expires:", invoice.Expiry)
Rust
let request = tonic::Request::new(DecodeInvoiceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_request: "lnbc10m1...".to_string(),
});

let response = client.decode_invoice(request).await?;
let invoice = response.into_inner().invoice.unwrap();
println!("Amount: {}", invoice.amount);
println!("Asset: {}", invoice.asset_id);
println!("Expires: {:?}", invoice.expiry);

Estimate Pay Invoice Fee

Estimate routing fee for paying an invoice.

Method: EstimatePayInvoiceFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_requeststringYESInvoice to pay

Response:

FieldTypeDescription
feeDecimalStringEstimated routing fee

Example Request:

TypeScript
const request = new EstimatePayInvoiceFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentRequest('lnbc10m1...')

const response = await client.estimatePayInvoiceFee(request, {})
console.log('Routing fee:', response.getFee(), 'sats')
Go
req := &pb.EstimatePayInvoiceFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentRequest: "lnbc10m1...",
}

resp, err := client.EstimatePayInvoiceFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Routing fee:", resp.Fee, "sats")
Rust
let request = tonic::Request::new(EstimatePayInvoiceFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_request: "lnbc10m1...".to_string(),
});

let response = client.estimate_pay_invoice_fee(request).await?;
println!("Routing fee: {} sats", response.into_inner().fee);

Pay Invoice

Pay a Lightning invoice.

Method: PayInvoice

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_requeststringYESInvoice to pay

Response:

FieldTypeDescription
payment_idstringPayment identifier

Example Request:

TypeScript
const request = new PayInvoiceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentRequest('lnbc10m1...')

const response = await client.payInvoice(request, {})
console.log('Payment sent:', response.getPaymentId())
Go
req := &pb.PayInvoiceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentRequest: "lnbc10m1...",
}

resp, err := client.PayInvoice(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment sent:", resp.PaymentId)
Rust
let request = tonic::Request::new(PayInvoiceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_request: "lnbc10m1...".to_string(),
});

let response = client.pay_invoice(request).await?;
println!("Payment sent: {}", response.into_inner().payment_id);

Estimate Pay Empty Invoice Fee

Estimate fee for paying a zero-amount invoice with a specific amount.

Method: EstimatePayEmptyInvoiceFee

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_requeststringYESZero-amount invoice
amountSendAmountYESAmount to pay

Response:

FieldTypeDescription
feeDecimalStringEstimated routing fee

Example Request:

TypeScript
const request = new EstimatePayEmptyInvoiceFeeRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentRequest('lnbc1...')
request.setAmount({ value: '2000000' }) // 0.02 BTC

const response = await client.estimatePayEmptyInvoiceFee(request, {})
console.log('Fee:', response.getFee())
Go
req := &pb.EstimatePayEmptyInvoiceFeeRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentRequest: "lnbc1...",
    Amount:         &pb.SendAmount{Value: "2000000"}, // 0.02 BTC
}

resp, err := client.EstimatePayEmptyInvoiceFee(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Fee:", resp.Fee)
Rust
let request = tonic::Request::new(EstimatePayEmptyInvoiceFeeRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_request: "lnbc1...".to_string(),
    amount: Some(SendAmount {
        value: "2000000".to_string(), // 0.02 BTC
    }),
});

let response = client.estimate_pay_empty_invoice_fee(request).await?;
println!("Fee: {}", response.into_inner().fee);

Pay Empty Invoice

Pay a zero-amount invoice with a specific amount.

Method: PayEmptyInvoice

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_requeststringYESZero-amount invoice
amountSendAmountYESAmount to pay

Response:

FieldTypeDescription
payment_idstringPayment identifier

Example Request:

TypeScript
const request = new PayEmptyInvoiceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentRequest('lnbc1...')
request.setAmount({ value: '2000000' })

const response = await client.payEmptyInvoice(request, {})
console.log('Payment sent:', response.getPaymentId())
Go
req := &pb.PayEmptyInvoiceRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentRequest: "lnbc1...",
    Amount:         &pb.SendAmount{Value: "2000000"},
}

resp, err := client.PayEmptyInvoice(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment sent:", resp.PaymentId)
Rust
let request = tonic::Request::new(PayEmptyInvoiceRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_request: "lnbc1...".to_string(),
    amount: Some(SendAmount {
        value: "2000000".to_string(),
    }),
});

let response = client.pay_empty_invoice(request).await?;
println!("Payment sent: {}", response.into_inner().payment_id);

Resolve Hashlock Payment

Claim a hashlock payment by revealing the preimage.

Method: ResolveHashlockPayment

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_idstringYESPayment to resolve
payment_preimagestringYESPreimage (32 bytes hex)

Response: Empty (success confirmation)

Example Request:

TypeScript
const request = new ResolveHashlockPaymentRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentId('payment_abc123')
request.setPaymentPreimage('0123456789abcdef...')

await client.resolveHashlockPayment(request, {})
console.log('Payment claimed')
Go
req := &pb.ResolveHashlockPaymentRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentId:       "payment_abc123",
    PaymentPreimage: "0123456789abcdef...",
}

_, err := client.ResolveHashlockPayment(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment claimed")
Rust
let request = tonic::Request::new(ResolveHashlockPaymentRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_id: "payment_abc123".to_string(),
    payment_preimage: "0123456789abcdef...".to_string(),
});

client.resolve_hashlock_payment(request).await?;
println!("Payment claimed");

Reject Payment

Reject an incoming payment.

Method: RejectPayment

Parameters:

NameTypeRequiredDescription
networkNetworkYESNetwork
payment_idstringYESPayment to reject

Response: Empty (success confirmation)

Example Request:

TypeScript
const request = new RejectPaymentRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentId('payment_abc123')

await client.rejectPayment(request, {})
console.log('Payment rejected')
Go
req := &pb.RejectPaymentRequest{
    Network: &pb.Network{
        Protocol: pb.Protocol_PROTOCOL_BITCOIN,
        Id:       "0a03cf40",
    },
    PaymentId: "payment_abc123",
}

_, err := client.RejectPayment(context.Background(), req)
if err != nil {
    log.Fatal(err)
}

log.Println("Payment rejected")
Rust
let request = tonic::Request::new(RejectPaymentRequest {
    network: Some(Network {
        protocol: Protocol::Bitcoin as i32,
        id: "0a03cf40".to_string(),
    }),
    payment_id: "payment_abc123".to_string(),
});

client.reject_payment(request).await?;
println!("Payment rejected");

Register Preimage

Pre-registers a payment preimage with the node so incoming HTLCs that match the preimage's hash can be settled automatically. Useful for split-receive flows or external invoice systems.

Method: RegisterPreimage

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
payment_preimagestringYESHex-encoded preimage to register

Response: Empty.

Example Request:

import { RegisterPreimageRequest } from './proto/node_pb'

const request = new RegisterPreimageRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentPreimage('9f8e7d6c...')

await client.registerPreimage(request, {})

Set Funding Allowance

Sets the per-asset funding allowance for a peer — the maximum amount they can deposit to and the maximum payment they can claim from that deposit. Replaces any existing allowance.

Funding allowances let you accept incoming channel deposits or hashlock-funded payments from a known peer without manually approving each one.

Method: SetFundingAllowance

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key
asset_allowancesmap<string, FundingAllowance>YESPer-asset allowances

FundingAllowance:

FieldTypeDescription
allowed_depositDecimalStringMaximum deposit the peer may initiate
allowed_paymentDecimalStringMaximum payment the peer may claim

Response: Empty.

Example Request:

import { SetFundingAllowanceRequest } from './proto/node_pb'

const request = new SetFundingAllowanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')
request.getAssetAllowancesMap().set('BTC', {
  allowedDeposit: { value: '1.0' },
  allowedPayment: { value: '0.5' }
})

await client.setFundingAllowance(request, {})

Get Funding Allowance

Returns the current remaining funding allowances for a peer.

Method: GetFundingAllowance

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key

Response:

FieldTypeDescription
asset_allowancesmap<string, FundingAllowance>Remaining allowance per asset

Example Request:

import { GetFundingAllowanceRequest } from './proto/node_pb'

const request = new GetFundingAllowanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

const response = await client.getFundingAllowance(request, {})
const map = response.getAssetAllowancesMap()
map.forEach((allowance, assetId) => {
  console.log(assetId, allowance.toObject())
})

Increase Funding Allowance

Increases an existing funding allowance for a peer by a delta. Each entry in asset_allowances is added to the current allowance for that asset.

Method: IncreaseFundingAllowance

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key
asset_allowancesmap<string, FundingAllowance>YESDeltas to add

Response: Empty.

Example Request:

import { IncreaseFundingAllowanceRequest } from './proto/node_pb'

const request = new IncreaseFundingAllowanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')
request.getAssetAllowancesMap().set('BTC', {
  allowedDeposit: { value: '0.5' },
  allowedPayment: { value: '0.25' }
})

await client.increaseFundingAllowance(request, {})

Decrease Funding Allowance

Decreases an existing funding allowance for a peer by a delta. Subtracts each entry from the current allowance.

Method: DecreaseFundingAllowance

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key
asset_allowancesmap<string, FundingAllowance>YESDeltas to subtract

Response: Empty.

Example Request:

import { DecreaseFundingAllowanceRequest } from './proto/node_pb'

const request = new DecreaseFundingAllowanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')
request.getAssetAllowancesMap().set('BTC', {
  allowedDeposit: { value: '0.1' },
  allowedPayment: { value: '0.05' }
})

await client.decreaseFundingAllowance(request, {})

Revoke Funding Allowance

Revokes all funding allowances for a peer. Equivalent to setting every asset's allowance to zero.

Method: RevokeFundingAllowance

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
node_idstringYESPeer public key

Response: Empty.

Example Request:

import { RevokeFundingAllowanceRequest } from './proto/node_pb'

const request = new RevokeFundingAllowanceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setNodeId('02abc123...')

await client.revokeFundingAllowance(request, {})

Common Workflows

Open channel and wait for activation

There is no WaitForActiveAssetChannel RPC — that pattern was replaced by event subscriptions. Subscribe to event.SubscribeNodeEvents before calling OpenChannel, then watch for the channel-active update for your new channel_id.

import {
  OpenChannelRequest,
  EstimateOpenChannelFeeRequest,
} from './proto/node_pb'
import { SubscribeNodeEventsRequest } from './proto/event_pb'

async function openAndWaitForChannel(
  node: NodeServiceClient,
  events: EventServiceClient,
  network: { protocol: number; id: string },
  peerNodeId: string,
  assetId: string,
  amountDecimal: string,
) {
  // 1. Subscribe BEFORE opening so we don't miss the activation event.
  const sub = new SubscribeNodeEventsRequest()
  sub.setNetwork(network)
  const stream = events.subscribeNodeEvents(sub, {})

  // 2. Open the channel.
  const open = new OpenChannelRequest()
  open.setNetwork(network)
  open.setNodeId(peerNodeId)
  open.getAssetAmountsMap().set(assetId, {
    exact: { amount: { value: amountDecimal } }   // DepositAmount oneof
  })
  open.setFeeOption({ medium: {} })

  const opened = await node.openChannel(open, {})
  const channelId = opened.getChannelId()
  console.log('OpenChannel broadcast, channel:', channelId, 'tx:', opened.getTxid())

  // 3. Wait for the channel to go active for this asset.
  await new Promise<void>((resolve, reject) => {
    stream.on('data', (evt: any) => {
      const upd = evt.getChannelUpdate?.()
      if (!upd) return
      if (upd.getChannelId?.() !== channelId) return
      // Asset-channel status `active` means it's ready to send & receive.
      const assetChannel = upd.getAssetChannelsMap?.().get(assetId)
      if (assetChannel?.getStatus?.()?.getActive?.()) {
        stream.cancel()
        resolve()
      }
    })
    stream.on('error', reject)
  })
  console.log('Channel is active for', assetId)
  return channelId
}

The exact field paths above (channel_update, asset_channels, Active) match the current event.proto and channel.proto. If you regenerate code from a newer proto and the names change, follow the proto rather than this snippet.

Create and pay invoice

TypeScript
async function createAndPayInvoice(
  client: NodeServiceClient,
  network: Network,
  assetId: string,
  amount: string
) {
  // Create invoice
  const createReq = new CreateInvoiceRequest()
  createReq.setNetwork(network)
  createReq.setAssetId(assetId)
  createReq.setAmount(amount)
  createReq.setExpiryTimeoutSecs(3600)

  const createResp = await client.createInvoice(createReq, {})
  const invoice = createResp.getInvoice()
  const paymentRequest = invoice.getPaymentRequest()

  console.log('Invoice created:', paymentRequest)

  // Decode to verify
  const decodeReq = new DecodeInvoiceRequest()
  decodeReq.setNetwork(network)
  decodeReq.setPaymentRequest(paymentRequest)

  const decoded = await client.decodeInvoice(decodeReq, {})
  console.log('Amount:', decoded.getInvoice()?.getAmount())

  // Pay invoice
  const payReq = new PayInvoiceRequest()
  payReq.setNetwork(network)
  payReq.setPaymentRequest(paymentRequest)

  const payResp = await client.payInvoice(payReq, {})
  console.log('Payment sent:', payResp.getPaymentId())
}
Go
func createAndPayInvoice(
    client pb.NodeServiceClient,
    network *pb.Network,
    assetId string,
    amount string,
) error {
    // Create invoice
    createReq := &pb.CreateInvoiceRequest{
        Network:           network,
        AssetId:           assetId,
        Amount:            amount,
        ExpiryTimeoutSecs: 3600,
    }

    createResp, err := client.CreateInvoice(context.Background(), createReq)
    if err != nil {
        return err
    }

    invoice := createResp.Invoice
    paymentRequest := invoice.PaymentRequest
    log.Println("Invoice created:", paymentRequest)

    // Decode to verify
    decodeReq := &pb.DecodeInvoiceRequest{
        Network:        network,
        PaymentRequest: paymentRequest,
    }

    decoded, err := client.DecodeInvoice(context.Background(), decodeReq)
    if err != nil {
        return err
    }

    log.Println("Amount:", decoded.Invoice.Amount)

    // Pay invoice
    payReq := &pb.PayInvoiceRequest{
        Network:        network,
        PaymentRequest: paymentRequest,
    }

    payResp, err := client.PayInvoice(context.Background(), payReq)
    if err != nil {
        return err
    }

    log.Println("Payment sent:", payResp.PaymentId)
    return nil
}
Rust
async fn create_and_pay_invoice(
    client: &mut NodeServiceClient<Channel>,
    network: Network,
    asset_id: String,
    amount: String,
) -> Result<(), Box<dyn std::error::Error>> {
    // Create invoice
    let create_req = tonic::Request::new(CreateInvoiceRequest {
        network: Some(network.clone()),
        asset_id,
        amount,
        hashlock: None,
        expiry_timeout_secs: 3600,
    });

    let create_resp = client.create_invoice(create_req).await?;
    let invoice = create_resp.into_inner().invoice.unwrap();
    let payment_request = invoice.payment_request.clone();

    println!("Invoice created: {}", payment_request);

    // Decode to verify
    let decode_req = tonic::Request::new(DecodeInvoiceRequest {
        network: Some(network.clone()),
        payment_request: payment_request.clone(),
    });

    let decoded = client.decode_invoice(decode_req).await?;
    println!("Amount: {}", decoded.into_inner().invoice.unwrap().amount);

    // Pay invoice
    let pay_req = tonic::Request::new(PayInvoiceRequest {
        network: Some(network),
        payment_request,
    });

    let pay_resp = client.pay_invoice(pay_req).await?;
    println!("Payment sent: {}", pay_resp.into_inner().payment_id);

    Ok(())
}

Safe channel closure

TypeScript
async function safeCloseChannel(
  client: NodeServiceClient,
  network: Network,
  channelId: string,
  assetIds: string[]
) {
  // Try cooperative close first
  try {
    const closeReq = new CloseChannelRequest()
    closeReq.setNetwork(network)
    closeReq.setChannelId(channelId)
    closeReq.setAssetIdsList(assetIds)
    closeReq.setFeeRate({ relative: '5' })

    const response = await client.closeChannel(closeReq, {})
    console.log('Channel closed cooperatively:', response.getTxid())
    return
  } catch (error) {
    console.log('Cooperative close failed, trying force close...')
  }

  // Force close if cooperative fails
  const forceReq = new ForceCloseChannelRequest()
  forceReq.setNetwork(network)
  forceReq.setChannelId(channelId)
  forceReq.setAssetIdsList(assetIds)
  forceReq.setFeeRate({ relative: '10' })

  const forceResp = await client.forceCloseChannel(forceReq, {})
  console.log('Force close initiated:', forceResp.getTxid())
  console.log('Wait for dispute period before redeeming')
}
Go
func safeCloseChannel(
    client pb.NodeServiceClient,
    network *pb.Network,
    channelId string,
    assetIds []string,
) error {
    // Try cooperative close first
    closeReq := &pb.CloseChannelRequest{
        Network:   network,
        ChannelId: channelId,
        AssetIds:  assetIds,
        FeeRate:   &pb.FeeRate{Relative: "5"},
    }

    closeResp, err := client.CloseChannel(context.Background(), closeReq)
    if err == nil {
        log.Println("Channel closed cooperatively:", closeResp.Txid)
        return nil
    }

    log.Println("Cooperative close failed, trying force close...")

    // Force close if cooperative fails
    forceReq := &pb.ForceCloseChannelRequest{
        Network:   network,
        ChannelId: channelId,
        AssetIds:  assetIds,
        FeeRate:   &pb.FeeRate{Relative: "10"},
    }

    forceResp, err := client.ForceCloseChannel(context.Background(), forceReq)
    if err != nil {
        return err
    }

    log.Println("Force close initiated:", forceResp.Txid)
    log.Println("Wait for dispute period before redeeming")
    return nil
}
Rust
async fn safe_close_channel(
    client: &mut NodeServiceClient<Channel>,
    network: Network,
    channel_id: String,
    asset_ids: Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    // Try cooperative close first
    let close_req = tonic::Request::new(CloseChannelRequest {
        network: Some(network.clone()),
        channel_id: channel_id.clone(),
        asset_ids: asset_ids.clone(),
        fee_rate: Some(FeeRate {
            relative: "5".to_string(),
        }),
    });

    match client.close_channel(close_req).await {
        Ok(response) => {
            println!("Channel closed cooperatively: {}", response.into_inner().txid);
            return Ok(());
        }
        Err(_) => {
            println!("Cooperative close failed, trying force close...");
        }
    }

    // Force close if cooperative fails
    let force_req = tonic::Request::new(ForceCloseChannelRequest {
        network: Some(network),
        channel_id,
        asset_ids,
        fee_rate: Some(FeeRate {
            relative: "10".to_string(),
        }),
    });

    let force_resp = client.force_close_channel(force_req).await?;
    println!("Force close initiated: {}", force_resp.into_inner().txid);
    println!("Wait for dispute period before redeeming");

    Ok(())
}

Best Practices

  1. Subscribe to event.SubscribeNodeEvents before opening or paying — channel-active and payment-completed signals come through the stream, not as a return value of the opening RPC.
  2. Estimate fees first — every channel-mutating RPC has a paired Estimate* variant that takes the same request and returns just the fee.
  3. Try CloseChannel before ForceCloseChannel — cooperative close is cheaper and faster.
  4. Pick reasonable invoice expirations — 1–24 hours is the common range; very short expirations stress retry logic.
  5. Check capacity before sending — call EstimateSendPaymentFee or watchOnlyNode.GetChannel to confirm the asset side has the balance you need.
  6. Keep hashlock preimages secret until you're ready to release the funds — see RegisterPreimage and ResolveHashlockPayment.
  7. Reuse channels via DepositChannel rather than opening a new one for the same peer / asset.

Channel Lifecycle

1. ConnectToPeer
   ↓
2. (optionally) AddPeerToZeroConfWhitelist
   ↓
3. SubscribeNodeEvents     ← keep this stream open for steps 4–9
   ↓
4. OpenChannel
   ↓
5. wait for ChannelUpdate { asset_channels[…].status = Active }
   ↓
6. SendPayment / CreateInvoice + PayInvoice / SendChannelPayment
   ↓
7. DepositChannel       (top up either side)
   ↓
8. WithdrawChannel      (pull funds out without closing)
   ↓
9. CloseChannel  →  RedeemClosedChannel  (if force closed)

Service-backed alternative for steps 4–5 and 7–9: use the Lease API (liquidity.RequestChannelLiquidity / RequestChannelRelease) instead of OpenChannel/DepositChannel/WithdrawChannel/CloseChannel. The lease flow handles the same lifecycle but the liquidity service signs and broadcasts the underlying transactions for you.


← Back to API Reference | Next: Lease API →


Copyright © 2025