SendToRouteV2
SendToRouteV2 attempts to make a payment via the specified route. This method differs from SendPayment in that it allows users to specify a full route manually. This can be used for things like rebalancing, and atomic swaps.
Source: routerrpc/router.proto
gRPC
rpc SendToRouteV2 (SendToRouteRequest) returns (HTLCAttempt);
REST
| HTTP Method | Path |
|---|---|
| POST | /v2/router/route/send |
Code Samples
- gRPC
- REST
- lncli
- Javascript
- Python
- grpcurl
const fs = require('fs');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const GRPC_HOST = 'localhost:10009'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
const TLS_PATH = 'LND_DIR/tls.cert'
const loaderOptions = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
};
const packageDefinition = protoLoader.loadSync(['lightning.proto', 'routerrpc/router.proto'], loaderOptions);
const routerrpc = grpc.loadPackageDefinition(packageDefinition).routerrpc;
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA';
const tlsCert = fs.readFileSync(TLS_PATH);
const sslCreds = grpc.credentials.createSsl(tlsCert);
const macaroon = fs.readFileSync(MACAROON_PATH).toString('hex');
const macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args, callback) {
let metadata = new grpc.Metadata();
metadata.add('macaroon', macaroon);
callback(null, metadata);
});
let creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
let client = new routerrpc.Router(GRPC_HOST, creds);
let request = {
payment_hash: <bytes>,
route: <Route>,
skip_temp_err: <bool>,
first_hop_custom_records: <FirstHopCustomRecordsEntry>,
};
client.sendToRouteV2(request, function(err, response) {
console.log(response);
});
// Console output:
// {
// "attempt_id": <uint64>,
// "status": <HTLCStatus>,
// "route": <Route>,
// "attempt_time_ns": <int64>,
// "resolve_time_ns": <int64>,
// "failure": <Failure>,
// "preimage": <bytes>,
// }
import codecs, grpc, os
# Generate the following 2 modules by compiling the routerrpc/router.proto with the grpcio-tools.
# See https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/python.md for instructions.
import router_pb2 as routerrpc, router_pb2_grpc as routerstub
GRPC_HOST = 'localhost:10009'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
# create macaroon credentials
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
def metadata_callback(context, callback):
callback([('macaroon', macaroon)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
# create SSL credentials
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(TLS_PATH, 'rb').read()
ssl_creds = grpc.ssl_channel_credentials(cert)
# combine macaroon and SSL credentials
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
# make the request
channel = grpc.secure_channel(GRPC_HOST, combined_creds)
stub = routerstub.RouterStub(channel)
request = routerrpc.SendToRouteRequest(
payment_hash=<bytes>,
route=<Route>,
skip_temp_err=<bool>,
first_hop_custom_records=<FirstHopCustomRecordsEntry>,
)
response = stub.SendToRouteV2(request)
print(response)
# {
# "attempt_id": <uint64>,
# "status": <HTLCStatus>,
# "route": <Route>,
# "attempt_time_ns": <int64>,
# "resolve_time_ns": <int64>,
# "failure": <Failure>,
# "preimage": <bytes>,
# }
# grpcurl docs: https://github.com/fullstorydev/grpcurl
# Proto source: https://github.com/lightningnetwork/lnd
GRPC_HOST=localhost:10009
LND_DIR=~/.lnd
LND_SOURCE=path/to/lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
grpcurl \
-import-path $LND_SOURCE/lnrpc/ \
-proto routerrpc/router.proto \
-cacert $TLS_PATH \
-H "macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "payment_hash": BASE64_ENCODED_VALUE, "route": <Route>, "skip_temp_err": <bool>, "first_hop_custom_records": <FirstHopCustomRecordsEntry> }' \
$GRPC_HOST \
routerrpc.Router/SendToRouteV2
- Javascript
- Python
- curl
const fs = require('fs');
const request = require('request');
const REST_HOST = 'localhost:8080'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
let requestBody = {
payment_hash: <string>, // <bytes> (base64 encoded)
route: <object>, // <Route>
skip_temp_err: <boolean>, // <bool>
first_hop_custom_records: <object>, // <FirstHopCustomRecordsEntry>
};
let options = {
url: `https://${REST_HOST}/v2/router/route/send`,
// Work-around for self-signed certificates.
rejectUnauthorized: false,
json: true,
headers: {
'Grpc-Metadata-macaroon': fs.readFileSync(MACAROON_PATH).toString('hex'),
},
form: JSON.stringify(requestBody),
}
request.post(options, function(error, response, body) {
console.log(body);
});
// Console output:
// {
// "attempt_id": <string>, // <uint64>
// "status": <string>, // <HTLCStatus>
// "route": <object>, // <Route>
// "attempt_time_ns": <string>, // <int64>
// "resolve_time_ns": <string>, // <int64>
// "failure": <object>, // <Failure>
// "preimage": <string>, // <bytes>
// }
import base64, codecs, json, requests
REST_HOST = 'localhost:8080'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
url = f'https://{REST_HOST}/v2/router/route/send'
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
headers = {'Grpc-Metadata-macaroon': macaroon}
data = {
'payment_hash': base64.b64encode(<bytes>),
'route': <Route>,
'skip_temp_err': <bool>,
'first_hop_custom_records': <FirstHopCustomRecordsEntry>,
}
r = requests.post(url, headers=headers, data=json.dumps(data), verify=TLS_PATH)
print(r.json())
# {
# "attempt_id": <uint64>,
# "status": <HTLCStatus>,
# "route": <Route>,
# "attempt_time_ns": <int64>,
# "resolve_time_ns": <int64>,
# "failure": <Failure>,
# "preimage": <bytes>,
# }
REST_HOST=localhost:8080
LND_DIR=~/.lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
curl -X POST \
--cacert $TLS_PATH \
-H "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "payment_hash": BASE64_ENCODED_VALUE, "route": <Route>, "skip_temp_err": <bool>, "first_hop_custom_records": <FirstHopCustomRecordsEntry> }' \
https://$REST_HOST/v2/router/route/send
$ lncli sendtoroute --help
NAME:
lncli sendtoroute - Send a payment over a predefined route.
USAGE:
lncli sendtoroute [command options] [arguments...]
CATEGORY:
Payments
DESCRIPTION:
Send a payment over Lightning using a specific route. One must specify
the route to attempt and the payment hash. This command can even
be chained with the response to queryroutes or buildroute. This command
can be used to implement channel rebalancing by crafting a self-route,
or even atomic swaps using a self-route that crosses multiple chains.
There are three ways to specify a route:
* using the --routes parameter to manually specify a JSON encoded
route in the format of the return value of queryroutes or
buildroute:
(lncli sendtoroute --payment_hash=<pay_hash> --routes=<route>)
* passing the route as a positional argument:
(lncli sendtoroute --payment_hash=pay_hash <route>)
* or reading in the route from stdin, which can allow chaining the
response from queryroutes or buildroute, or even read in a file
with a pre-computed route:
(lncli queryroutes --args.. | lncli sendtoroute --payment_hash= -
notice the '-' at the end, which signals that lncli should read
the route in from stdin
OPTIONS:
--payment_hash value, --pay_hash value the hash to use within the payment's HTLC
--routes value, -r value a json array string in the format of the response of queryroutes that denotes which routes to use
--skip_temp_err Whether the payment should be marked as failed when a temporary error occurred. Set it to true so the payment won't be failed unless a terminal error has occurred.
Messages
routerrpc.SendToRouteRequest
Source: routerrpc/router.proto
| Field | gRPC Type | REST Type | REST Placement |
|---|---|---|---|
payment_hash | bytes | string | body |
route | Route | object | body |
skip_temp_err | bool | boolean | body |
first_hop_custom_records | FirstHopCustomRecordsEntry[] | object | body |
lnrpc.HTLCAttempt
Source: lightning.proto
| Field | gRPC Type | REST Type |
|---|---|---|
attempt_id | uint64 | string |
status | HTLCStatus | string |
route | Route | object |
attempt_time_ns | int64 | string |
resolve_time_ns | int64 | string |
failure | Failure | object |
preimage | bytes | string |
Nested Messages
lnrpc.AMPRecord
| Field | gRPC Type | REST Type |
|---|---|---|
root_share | bytes | string |
set_id | bytes | string |
child_index | uint32 | integer |
lnrpc.ChannelUpdate
| Field | gRPC Type | REST Type |
|---|---|---|
signature | bytes | string |
chain_hash | bytes | string |
chan_id | uint64 | string |
timestamp | uint32 | integer |
message_flags | uint32 | integer |
channel_flags | uint32 | integer |
time_lock_delta | uint32 | integer |
htlc_minimum_msat | uint64 | string |
base_fee | uint32 | integer |
fee_rate | uint32 | integer |
htlc_maximum_msat | uint64 | string |
extra_opaque_data | bytes | string |
lnrpc.Failure
| Field | gRPC Type | REST Type |
|---|---|---|
code | FailureCode | string |
channel_update | ChannelUpdate | object |
htlc_msat | uint64 | string |
onion_sha_256 | bytes | string |
cltv_expiry | uint32 | integer |
flags | uint32 | integer |
failure_source_index | uint32 | integer |
height | uint32 | integer |
lnrpc.Hop
| Field | gRPC Type | REST Type |
|---|---|---|
chan_id | uint64 | string |
chan_capacity | int64 | string |
amt_to_forward | int64 | string |
fee | int64 | string |
expiry | uint32 | integer |
amt_to_forward_msat | int64 | string |
fee_msat | int64 | string |
pub_key | string | string |
tlv_payload | bool | boolean |
mpp_record | MPPRecord | object |
amp_record | AMPRecord | object |
custom_records | CustomRecordsEntry[] | object |
metadata | bytes | string |
blinding_point | bytes | string |
encrypted_data | bytes | string |
total_amt_msat | uint64 | string |
lnrpc.Hop.CustomRecordsEntry
| Field | gRPC Type | REST Type |
|---|---|---|
key | uint64 | unknown |
value | bytes | unknown |
lnrpc.MPPRecord
| Field | gRPC Type | REST Type |
|---|---|---|
payment_addr | bytes | string |
total_amt_msat | int64 | string |
lnrpc.Route
| Field | gRPC Type | REST Type |
|---|---|---|
total_time_lock | uint32 | integer |
total_fees | int64 | string |
total_amt | int64 | string |
hops | Hop[] | array |
total_fees_msat | int64 | string |
total_amt_msat | int64 | string |
first_hop_amount_msat | int64 | string |
custom_channel_data | bytes | string |
routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry
| Field | gRPC Type | REST Type |
|---|---|---|
key | uint64 | unknown |
value | bytes | unknown |
Enums
lnrpc.Failure.FailureCode
| Name | Number |
|---|---|
RESERVED | 0 |
INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS | 1 |
INCORRECT_PAYMENT_AMOUNT | 2 |
FINAL_INCORRECT_CLTV_EXPIRY | 3 |
FINAL_INCORRECT_HTLC_AMOUNT | 4 |
FINAL_EXPIRY_TOO_SOON | 5 |
INVALID_REALM | 6 |
EXPIRY_TOO_SOON | 7 |
INVALID_ONION_VERSION | 8 |
INVALID_ONION_HMAC | 9 |
INVALID_ONION_KEY | 10 |
AMOUNT_BELOW_MINIMUM | 11 |
FEE_INSUFFICIENT | 12 |
INCORRECT_CLTV_EXPIRY | 13 |
CHANNEL_DISABLED | 14 |
TEMPORARY_CHANNEL_FAILURE | 15 |
REQUIRED_NODE_FEATURE_MISSING | 16 |
REQUIRED_CHANNEL_FEATURE_MISSING | 17 |
UNKNOWN_NEXT_PEER | 18 |
TEMPORARY_NODE_FAILURE | 19 |
PERMANENT_NODE_FAILURE | 20 |
PERMANENT_CHANNEL_FAILURE | 21 |
EXPIRY_TOO_FAR | 22 |
MPP_TIMEOUT | 23 |
INVALID_ONION_PAYLOAD | 24 |
INVALID_ONION_BLINDING | 25 |
INTERNAL_FAILURE | 997 |
UNKNOWN_FAILURE | 998 |
UNREADABLE_FAILURE | 999 |
lnrpc.HTLCAttempt.HTLCStatus
| Name | Number |
|---|---|
IN_FLIGHT | 0 |
SUCCEEDED | 1 |
FAILED | 2 |