Node API
The Node API provides Lightning Network channel management and payment operations.
Endpoints
Peer Management
Channel Operations
- Estimate Open Channel Fee
- Open Channel
- Estimate Deposit Channel Fee
- Deposit Channel
- Estimate Withdraw Channel Fee
- Withdraw Channel
- Estimate Close Channel Fee
- Close Channel
- Estimate Force Close Channel Fee
- Force Close Channel
- Estimate Redeem Closed Channel Fee
- Redeem Closed Channel
- Wait for Active Asset Channel
- Allow Dual Funded Channel
Payment Operations
Invoice Management
- Create Invoice
- Decode Invoice
- Estimate Pay Invoice Fee
- Pay Invoice
- Estimate Pay Empty Invoice Fee
- Pay Empty Invoice
- Resolve Hashlock Payment
- Reject Payment
Connect to Peer
Connect to a Lightning Network peer.
Method: ConnectToPeer
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to connect on |
peer_url | string | YES | Peer 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: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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_api::node_service_client::NodeServiceClient;
use hydra_api::{ConnectToPeerRequest, Network};
let mut client = NodeServiceClient::new(channel);
let request = tonic::Request::new(ConnectToPeerRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to query |
Response:
| Field | Type | Description |
|---|---|---|
node_ids | string[] | Array of connected node IDs |
Example Request:
TypeScript
const request = new GetConnectedPeersRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
const response = await client.getConnectedPeers(request, {})
const peers = response.getNodeIdsList()
console.log('Connected peers:', peers)
Go
req := &pb.GetConnectedPeersRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
}
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
});
let response = client.get_connected_peers(request).await?;
let peers = response.into_inner().node_ids;
println!("Connected peers: {:?}", peers);
Estimate Open Channel Fee
Estimate the onchain fee for opening a new channel.
Method: EstimateOpenChannelFee
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to open channel on |
node_id | string | YES | Peer's node ID |
asset_amounts | map<string, SendAmount> | YES | Map of asset_id → amount to allocate |
fee_rate | FeeRate | YES | Transaction fee rate |
SendAmount Object (one of):
| Field | Type | Description |
|---|---|---|
value | DecimalString | Exact amount |
max | - | Use maximum available |
FeeRate Object (one of):
| Field | Type | Description |
|---|---|---|
absolute | DecimalString | Absolute fee amount |
relative | DecimalString | Relative fee (e.g., sat/vB) |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated onchain fee |
Example Request:
TypeScript
const request = new EstimateOpenChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to open on |
node_id | string | YES | Peer's node ID |
asset_amounts | map<string, SendAmount> | YES | Assets to allocate |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Funding transaction ID |
channel_id | string | Channel identifier |
Example Request:
TypeScript
const request = new OpenChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to deposit to |
asset_amounts | map<string, SendAmount> | YES | Assets to deposit |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated fee |
Example Request:
TypeScript
const request = new EstimateDepositChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to deposit to |
asset_amounts | map<string, SendAmount> | YES | Assets to deposit |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Deposit transaction ID |
Example Request:
TypeScript
const request = new DepositChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to withdraw from |
asset_amounts | map<string, WithdrawAmount> | YES | Amounts to withdraw |
fee_rate | FeeRate | YES | Transaction fee rate |
WithdrawAmount Object:
| Field | Type | Description |
|---|---|---|
self_withdrawal | SendAmount | Amount you withdraw |
counterparty_withdrawal | SendAmount | Amount counterparty withdraws |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated fee |
Example Request:
TypeScript
const request = new EstimateWithdrawChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to withdraw from |
asset_amounts | map<string, WithdrawAmount> | YES | Amounts to withdraw |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Withdrawal transaction ID |
Example Request:
TypeScript
const request = new WithdrawChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to close |
asset_ids | string[] | YES | Assets to settle |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated fee |
Example Request:
TypeScript
const request = new EstimateCloseChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to close |
asset_ids | string[] | YES | Assets to settle |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Closing transaction ID |
Example Request:
TypeScript
const request = new CloseChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to force close |
asset_ids | string[] | YES | Assets to settle |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated fee |
Example Request:
TypeScript
const request = new EstimateForceCloseChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to force close |
asset_ids | string[] | YES | Assets to settle |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Force close transaction ID |
Example Request:
TypeScript
const request = new ForceCloseChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Closed channel |
asset_ids | string[] | YES | Assets to redeem |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated fee |
Example Request:
TypeScript
const request = new EstimateRedeemClosedChannelFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Closed channel |
asset_ids | string[] | YES | Assets to redeem |
fee_rate | FeeRate | YES | Transaction fee rate |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Redemption transaction ID |
Example Request:
TypeScript
const request = new RedeemClosedChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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);
Wait for Active Asset Channel
Wait for a channel's asset to become active for sending/receiving.
Method: WaitForActiveAssetChannel
Use Cases:
- Wait after opening channel before sending payment
- Ensure channel is ready after deposit
- Verify channel state before operations
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to monitor |
asset_id | string | YES | Asset to check |
active_sending | bool | YES | Wait for sending capability |
active_receiving | bool | YES | Wait for receiving capability |
updatable | bool | YES | Wait for updatable state |
Response: Empty (resolves when conditions met)
Example Request:
TypeScript
const request = new WaitForActiveAssetChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setChannelId('ch_abc123')
request.setAssetId('BTC')
request.setActiveSending(true)
request.setActiveReceiving(true)
request.setUpdatable(true)
console.log('Waiting for channel to be ready...')
await client.waitForActiveAssetChannel(request, {})
console.log('Channel is now active!')
Go
req := &pb.WaitForActiveAssetChannelRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
ChannelId: "ch_abc123",
AssetId: "BTC",
ActiveSending: true,
ActiveReceiving: true,
Updatable: true,
}
log.Println("Waiting for channel to be ready...")
_, err := client.WaitForActiveAssetChannel(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Println("Channel is now active!")
Rust
let request = tonic::Request::new(WaitForActiveAssetChannelRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
channel_id: "ch_abc123".to_string(),
asset_id: "BTC".to_string(),
active_sending: true,
active_receiving: true,
updatable: true,
});
println!("Waiting for channel to be ready...");
client.wait_for_active_asset_channel(request).await?;
println!("Channel is now active!");
Allow Dual Funded Channel
Whitelist asset amounts for dual-funded channel opening.
Method: AllowDualFundedChannel
Benefits:
- Single transaction for both parties
- Lower total fees
- Faster channel setup
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
node_id | string | YES | Peer's node ID |
asset_amounts | map<string, DualFundAmount> | YES | Whitelisted amounts |
DualFundAmount Object:
| Field | Type | Description |
|---|---|---|
self_amount | SendAmount | Your contribution |
counterparty_amount | SendAmount | Peer's contribution |
Response: Empty (success confirmation)
Example Request:
TypeScript
const request = new AllowDualFundedChannelRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setNodeId('02abc123...')
request.setAssetAmountsMap({
'BTC': {
selfAmount: { value: '5000000' }, // You: 0.05 BTC
counterpartyAmount: { value: '10000000' } // Peer: 0.1 BTC
}
})
await client.allowDualFundedChannel(request, {})
console.log('Dual-fund whitelist updated')
Go
req := &pb.AllowDualFundedChannelRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
NodeId: "02abc123...",
AssetAmounts: map[string]*pb.DualFundAmount{
"BTC": {
SelfAmount: &pb.SendAmount{Value: "5000000"}, // You: 0.05 BTC
CounterpartyAmount: &pb.SendAmount{Value: "10000000"}, // Peer: 0.1 BTC
},
},
}
_, err := client.AllowDualFundedChannel(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Println("Dual-fund whitelist updated")
Rust
let mut asset_amounts = HashMap::new();
asset_amounts.insert("BTC".to_string(), DualFundAmount {
self_amount: Some(SendAmount {
value: "5000000".to_string(), // You: 0.05 BTC
}),
counterparty_amount: Some(SendAmount {
value: "10000000".to_string(), // Peer: 0.1 BTC
}),
});
let request = tonic::Request::new(AllowDualFundedChannelRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
node_id: "02abc123...".to_string(),
asset_amounts,
});
client.allow_dual_funded_channel(request).await?;
println!("Dual-fund whitelist updated");
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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
channel_id | string | YES | Channel to use |
asset_amounts | map<string, SendAmount> | YES | Assets to send |
hashlock | Hashlock | NO | Optional hashlock for HTLC |
expiry_timeout_secs | uint64 | NO | Payment expiry timeout |
Hashlock Object:
| Field | Type | Description |
|---|---|---|
payment_hash | string | Payment hash (32 bytes hex) |
Response:
| Field | Type | Description |
|---|---|---|
payment_id | string | Payment identifier |
Example Request:
TypeScript
const request = new SendChannelPaymentRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setChannelId('ch_abc123')
request.setAssetAmountsMap({
'BTC': { value: '100000' } // 0.001 BTC
})
// Optional: set hashlock for HTLC
// request.setHashlock({ paymentHash: 'abc123...' })
request.setExpiryTimeoutSecs(3600) // 1 hour
const response = await client.sendChannelPayment(request, {})
console.log('Payment sent:', response.getPaymentId())
Go
req := &pb.SendChannelPaymentRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
ChannelId: "ch_abc123",
AssetAmounts: map[string]*pb.SendAmount{
"BTC": {Value: "100000"}, // 0.001 BTC
},
// Optional: set hashlock for HTLC
// Hashlock: &pb.Hashlock{PaymentHash: "abc123..."},
ExpiryTimeoutSecs: 3600, // 1 hour
}
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(), SendAmount {
value: "100000".to_string(), // 0.001 BTC
});
let request = tonic::Request::new(SendChannelPaymentRequest {
network: Some(Network {
protocol: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
channel_id: "ch_abc123".to_string(),
asset_amounts,
// Optional: set hashlock for HTLC
// hashlock: Some(Hashlock {
// payment_hash: "abc123...".to_string(),
// }),
expiry_timeout_secs: 3600, // 1 hour
});
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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
recipient_node_id | string | YES | Recipient's node ID |
asset_amounts | map<string, SendAmount> | YES | Assets to send |
hashlock | Hashlock | NO | Optional hashlock |
expiry_timeout_secs | uint64 | NO | Payment timeout |
Response:
| Field | Type | Description |
|---|---|---|
fees | map<string, DecimalString> | Fees per asset |
Example Request:
TypeScript
const request = new EstimateSendPaymentFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
RecipientNodeId: "03def456...",
AssetAmounts: map[string]*pb.SendAmount{
"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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
recipient_node_id: "03def456...".to_string(),
asset_amounts,
hashlock: None,
expiry_timeout_secs: 0,
});
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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
recipient_node_id | string | YES | Recipient's node ID |
asset_amounts | map<string, SendAmount> | YES | Assets to send |
hashlock | Hashlock | NO | Optional hashlock |
expiry_timeout_secs | uint64 | NO | Payment timeout |
Response:
| Field | Type | Description |
|---|---|---|
payment_id | string | Payment identifier |
Example Request:
TypeScript
const request = new SendPaymentRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setRecipientNodeId('03def456...')
request.setAssetAmountsMap({
'BTC': { value: '500000' }
})
request.setExpiryTimeoutSecs(3600)
const response = await client.sendPayment(request, {})
console.log('Payment routed:', response.getPaymentId())
Go
req := &pb.SendPaymentRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
RecipientNodeId: "03def456...",
AssetAmounts: map[string]*pb.SendAmount{
"BTC": {Value: "500000"},
},
ExpiryTimeoutSecs: 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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
recipient_node_id: "03def456...".to_string(),
asset_amounts,
hashlock: None,
expiry_timeout_secs: 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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
asset_id | string | YES | Asset to receive |
amount | DecimalString | NO | Amount (omit for zero-amount invoice) |
hashlock | Hashlock | NO | Optional hashlock |
expiry_timeout_secs | uint64 | NO | Invoice expiry |
Response:
| Field | Type | Description |
|---|---|---|
invoice | Invoice | Invoice details |
Invoice Object:
| Field | Type | Description |
|---|---|---|
payment_request | string | Encoded payment request string |
payment_hash | string | Payment hash |
amount | DecimalString | Invoice amount (optional) |
asset_id | string | Asset identifier |
expiry | Timestamp | Expiry time |
Example Request:
TypeScript
const request = new CreateInvoiceRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_request | string | YES | Encoded payment request |
Response:
| Field | Type | Description |
|---|---|---|
invoice | Invoice | Decoded invoice details |
Example Request:
TypeScript
const request = new DecodeInvoiceRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_request | string | YES | Invoice to pay |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated routing fee |
Example Request:
TypeScript
const request = new EstimatePayInvoiceFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setPaymentRequest('lnbc10m1...')
const response = await client.estimatePayInvoiceFee(request, {})
console.log('Routing fee:', response.getFee(), 'sats')
Go
req := &pb.EstimatePayInvoiceFeeRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_request | string | YES | Invoice to pay |
Response:
| Field | Type | Description |
|---|---|---|
payment_id | string | Payment identifier |
Example Request:
TypeScript
const request = new PayInvoiceRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setPaymentRequest('lnbc10m1...')
const response = await client.payInvoice(request, {})
console.log('Payment sent:', response.getPaymentId())
Go
req := &pb.PayInvoiceRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_request | string | YES | Zero-amount invoice |
amount | SendAmount | YES | Amount to pay |
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated routing fee |
Example Request:
TypeScript
const request = new EstimatePayEmptyInvoiceFeeRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_request | string | YES | Zero-amount invoice |
amount | SendAmount | YES | Amount to pay |
Response:
| Field | Type | Description |
|---|---|---|
payment_id | string | Payment identifier |
Example Request:
TypeScript
const request = new PayEmptyInvoiceRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
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: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_id | string | YES | Payment to resolve |
payment_preimage | string | YES | Preimage (32 bytes hex) |
Response: Empty (success confirmation)
Example Request:
TypeScript
const request = new ResolveHashlockPaymentRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setPaymentId('payment_abc123')
request.setPaymentPreimage('0123456789abcdef...')
await client.resolveHashlockPayment(request, {})
console.log('Payment claimed')
Go
req := &pb.ResolveHashlockPaymentRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".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:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network |
payment_id | string | YES | Payment to reject |
Response: Empty (success confirmation)
Example Request:
TypeScript
const request = new RejectPaymentRequest()
request.setNetwork({
protocol: 0,
chainId: '0',
name: 'Bitcoin'
})
request.setPaymentId('payment_abc123')
await client.rejectPayment(request, {})
console.log('Payment rejected')
Go
req := &pb.RejectPaymentRequest{
Network: &pb.Network{
Protocol: 0,
ChainId: "0",
Name: "Bitcoin",
},
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: 0,
chain_id: "0".to_string(),
name: "Bitcoin".to_string(),
}),
payment_id: "payment_abc123".to_string(),
});
client.reject_payment(request).await?;
println!("Payment rejected");
Common Workflows
Open channel and wait for activation
TypeScript
async function openAndWaitForChannel(
client: NodeServiceClient,
network: Network,
nodeId: string,
assetId: string,
amount: string
) {
// 1. Open channel
const openReq = new OpenChannelRequest()
openReq.setNetwork(network)
openReq.setNodeId(nodeId)
openReq.setAssetAmountsMap({
[assetId]: { value: amount }
})
openReq.setFeeRate({ relative: '5' })
const openResp = await client.openChannel(openReq, {})
const channelId = openResp.getChannelId()
console.log('Channel opened:', channelId)
// 2. Wait for activation
const waitReq = new WaitForActiveAssetChannelRequest()
waitReq.setNetwork(network)
waitReq.setChannelId(channelId)
waitReq.setAssetId(assetId)
waitReq.setActiveSending(true)
waitReq.setActiveReceiving(true)
waitReq.setUpdatable(true)
console.log('Waiting for channel to become active...')
await client.waitForActiveAssetChannel(waitReq, {})
console.log('Channel is ready for payments!')
return channelId
}
Go
func openAndWaitForChannel(
client pb.NodeServiceClient,
network *pb.Network,
nodeId string,
assetId string,
amount string,
) (string, error) {
// 1. Open channel
openReq := &pb.OpenChannelRequest{
Network: network,
NodeId: nodeId,
AssetAmounts: map[string]*pb.SendAmount{
assetId: {Value: amount},
},
FeeRate: &pb.FeeRate{Relative: "5"},
}
openResp, err := client.OpenChannel(context.Background(), openReq)
if err != nil {
return "", err
}
channelId := openResp.ChannelId
log.Println("Channel opened:", channelId)
// 2. Wait for activation
waitReq := &pb.WaitForActiveAssetChannelRequest{
Network: network,
ChannelId: channelId,
AssetId: assetId,
ActiveSending: true,
ActiveReceiving: true,
Updatable: true,
}
log.Println("Waiting for channel to become active...")
_, err = client.WaitForActiveAssetChannel(context.Background(), waitReq)
if err != nil {
return "", err
}
log.Println("Channel is ready for payments!")
return channelId, nil
}
Rust
async fn open_and_wait_for_channel(
client: &mut NodeServiceClient<Channel>,
network: Network,
node_id: String,
asset_id: String,
amount: String,
) -> Result<String, Box<dyn std::error::Error>> {
// 1. Open channel
let mut asset_amounts = HashMap::new();
asset_amounts.insert(asset_id.clone(), SendAmount {
value: amount,
});
let open_req = tonic::Request::new(OpenChannelRequest {
network: Some(network.clone()),
node_id,
asset_amounts,
fee_rate: Some(FeeRate {
relative: "5".to_string(),
}),
});
let open_resp = client.open_channel(open_req).await?;
let channel_id = open_resp.into_inner().channel_id;
println!("Channel opened: {}", channel_id);
// 2. Wait for activation
let wait_req = tonic::Request::new(WaitForActiveAssetChannelRequest {
network: Some(network),
channel_id: channel_id.clone(),
asset_id,
active_sending: true,
active_receiving: true,
updatable: true,
});
println!("Waiting for channel to become active...");
client.wait_for_active_asset_channel(wait_req).await?;
println!("Channel is ready for payments!");
Ok(channel_id)
}
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
- Always wait for channels to be active - Use
WaitForActiveAssetChannelbefore sending - Estimate fees first - Call estimate methods before operations
- Use cooperative close - Try
CloseChannelbeforeForceCloseChannel - Set reasonable timeouts - 1-24 hours for invoice expiry
- Monitor channel capacity - Ensure sufficient balance before sending
- Handle hashlock payments carefully - Keep preimage secret until ready
- Dual-fund when possible - More efficient than two separate channels
Channel Lifecycle
1. ConnectToPeer
↓
2. OpenChannel / AllowDualFundedChannel
↓
3. WaitForActiveAssetChannel
↓
4. SendPayment / CreateInvoice
↓
5. DepositChannel (if needed)
↓
6. WithdrawChannel (optional)
↓
7. CloseChannel / ForceCloseChannel
↓
8. RedeemClosedChannel (if force closed)