-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Conor Okus
committed
Mar 24, 2023
1 parent
869cb87
commit 4bd2cde
Showing
2 changed files
with
171 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
docs/tutorials/building-a-node-with-ldk/sending-payments.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Sending Payments | ||
|
||
Lightning payments are used to pay invoices, which are typically encoded as a | ||
string in accordance with BOLT 11. After parsing the invoice, you'll need to | ||
find a route from your node to the recipient and then make the payment using | ||
`ChannelManager`. | ||
|
||
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}"> | ||
<template v-slot:rust> | ||
|
||
```rust | ||
// Parse the invoice. | ||
let invoice = Invoice::from_str(encoded_invoice) | ||
.expect("ERROR: failed to parse invoice"); | ||
|
||
let amt_pico_btc = invoice.amount_pico_btc() | ||
.expect("ERROR: invalid invoice: must contain amount to pay"); | ||
let amt_msat = amt_pico_btc / 10; | ||
let payer_pubkey = channel_manager.get_our_node_id(); | ||
let network_graph = router.network_graph.read().unwrap(); | ||
let payee_pubkey = invoice.recover_payee_pub_key(); | ||
let payee_features = invoice.features().cloned(); | ||
let first_hops = channel_manager.list_usable_channels(); | ||
let last_hops = invoice.route_hints(); | ||
let final_cltv = invoice.min_final_cltv_expiry() as u32; | ||
|
||
// Find a route and send the payment. | ||
let route = router::get_route( | ||
&payer_pubkey, &network_graph, &payee_pubkey, payee_features, | ||
Some(&first_hops.iter().collect::<Vec<_>>()), &last_hops, | ||
amt_msat, final_cltv, logger.clone(), | ||
).expect("ERROR: failed to find route"); | ||
|
||
let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner()); | ||
let payment_secret = invoice.payment_secret().cloned(); | ||
|
||
channel_manager.send_payment(&route, payment_hash, &payment_secret) | ||
.expect("ERROR: failed to send payment"); | ||
``` | ||
|
||
</template> | ||
<template v-slot:java> | ||
|
||
```java | ||
String invoice_str = // get an invoice from the payee | ||
Result_InvoiceNoneZ parsed_invoice = Invoice.from_str(invoice_str); | ||
|
||
if (parsed_invoice instanceof Result_InvoiceNoneZ.Result_InvoiceNoneZ_OK) { | ||
Invoice invoice = ((Result_InvoiceNoneZ.Result_InvoiceNoneZ_OK) parsed_invoice).res; | ||
long amt_msat = 0; | ||
if (invoice.amount_pico_btc() instanceof Option_u64Z.Some) { | ||
amt_msat = ((Option_u64Z.Some)invoice.amount_pico_btc()).some / 10; | ||
} | ||
if (amt_msat == 0) { | ||
// <Handle a zero-value invoice> | ||
} | ||
|
||
Route route; | ||
try (LockedNetworkGraph netgraph = router.read_locked_graph()) { | ||
NetworkGraph graph = netgraph.graph(); | ||
Result_RouteLightningErrorZ route_res = UtilMethods.get_route( | ||
channel_manager.get_our_node_id(), | ||
graph, invoice.recover_payee_pub_key(), invoice.features(), | ||
channel_manager.list_usable_channels(), invoice.route_hints(), | ||
amt_msat, invoice.min_final_cltv_expiry(), logger); | ||
assert route_res instanceof Result_RouteLightningErrorZ.Result_RouteLightningErrorZ_OK; | ||
route = ((Result_RouteLightningErrorZ.Result_RouteLightningErrorZ_OK) route_res).res; | ||
} | ||
|
||
Result_NonePaymentSendFailureZ payment_res = channel_manager.send_payment( | ||
route, invoice.payment_hash(), invoice.payment_secret()); | ||
assert payment_res instanceof Result_NonePaymentSendFailureZ.Result_NonePaymentSendFailureZ_OK; | ||
} | ||
``` | ||
|
||
</template> | ||
<template v-slot:kotlin> | ||
|
||
```java | ||
// Get an invoice from the recipient/payee | ||
val parsedInvoice = Invoice.from_str(recipientInvoice) | ||
if (!parsedInvoice.is_ok) { | ||
// Unable to parse invoice | ||
} | ||
|
||
val invoice = (parsedInvoice as Result_InvoiceParseOrSemanticErrorZ.Result_InvoiceParseOrSemanticErrorZ_OK).res | ||
|
||
var amountSats: Long = 0 | ||
if (invoice.amount_milli_satoshis() is Option_u64Z.Some) { | ||
amountSats = (invoice.amount_milli_satoshis() as Option_u64Z.Some).some * 1000 | ||
} | ||
|
||
if (amountSats == 0L) { | ||
// Handle a zero-value invoice | ||
} | ||
|
||
val res = channelManagerConstructor.payer.pay_invoice(invoice) | ||
|
||
|
||
|
||
if (parsed_invoice instanceof Result_InvoiceNoneZ.Result_InvoiceNoneZ_OK) { | ||
Invoice invoice = ((Result_InvoiceNoneZ.Result_InvoiceNoneZ_OK) parsed_invoice).res; | ||
long amt_msat = 0; | ||
if (invoice.amount_pico_btc() instanceof Option_u64Z.Some) { | ||
amt_msat = ((Option_u64Z.Some)invoice.amount_pico_btc()).some / 10; | ||
} | ||
if (amt_msat == 0) { | ||
// <Handle a zero-value invoice> | ||
} | ||
|
||
Route route; | ||
try (LockedNetworkGraph netgraph = router.read_locked_graph()) { | ||
NetworkGraph graph = netgraph.graph(); | ||
Result_RouteLightningErrorZ route_res = UtilMethods.get_route( | ||
channel_manager.get_our_node_id(), | ||
graph, invoice.recover_payee_pub_key(), invoice.features(), | ||
channel_manager.list_usable_channels(), invoice.route_hints(), | ||
amt_msat, invoice.min_final_cltv_expiry(), logger); | ||
assert route_res instanceof Result_RouteLightningErrorZ.Result_RouteLightningErrorZ_OK; | ||
route = ((Result_RouteLightningErrorZ.Result_RouteLightningErrorZ_OK) route_res).res; | ||
} | ||
|
||
Result_NonePaymentSendFailureZ payment_res = channel_manager.send_payment( | ||
route, invoice.payment_hash(), invoice.payment_secret()); | ||
assert payment_res instanceof Result_NonePaymentSendFailureZ.Result_NonePaymentSendFailureZ_OK; | ||
} | ||
``` | ||
|
||
</template> | ||
</CodeSwitcher> | ||
|
||
An event is generated once a payment has completed. Successful payments result | ||
in a `PaymentSent` event with the preimage of the payment hash. Be sure to look | ||
out for a `PaymentFailed` event, if the payment fails for some reason, and act | ||
accordingly. | ||
|
||
<CodeSwitcher :languages="{rust:'Rust', java:'Java'}"> | ||
<template v-slot:rust> | ||
|
||
```rust | ||
// In the event handler passed to BackgroundProcessor::start | ||
match event { | ||
Event::PaymentSent { payment_preimage } => { | ||
// Handle successful payment | ||
} | ||
Event::PaymentFailed { payment_hash, rejected_by_dest } => { | ||
// Handle failed payment | ||
} | ||
// ... | ||
} | ||
``` | ||
|
||
</template> | ||
<template v-slot:java> | ||
|
||
```java | ||
// In the `handle_event` method of ChannelManagerPersister implementation | ||
else if (e instanceof Event.PaymentSent) { | ||
// Handle successful payment | ||
Event.PaymentSent event = ((Event.PaymentSent) e); | ||
} | ||
else if (e instanceof Event.PaymentFailed) { | ||
// Handle failed payment | ||
Event.PaymentFailed event = ((Event.PaymentFailed) e); | ||
} | ||
``` | ||
|
||
</template> | ||
</CodeSwitcher> |