TrackOnion
TrackOnion allows callers to query whether or not a payment dispatched via SendOnion succeeded or failed.
This RPC is designed to be called after a successful dispatch has been explicitly confirmed via the SendOnion RPC. It should not be used to determine whether an HTLC dispatch was received in an ambiguous network scenario. That ambiguity must be resolved by retrying the idempotent SendOnion RPC until a definitive acknowledgement is received from the Switch RPC server.
Once dispatch is confirmed, TrackOnion provides the mechanism to wait for the result of the in-flight HTLC. A client must handle three distinct categories of outcomes:
PAYMENT SUCCESS (gRPC code OK, preimage set): The payment settled successfully. The response contains the payment preimage.
PAYMENT FAILURE (gRPC code OK, failure_details set): The payment failed at the application level. The failure_details oneof describes the specific failure type: a ForwardingFailure from a remote hop, a LocalFailure from the local node, an UnreadableFailure if the error could not be decoded, or a SwitchError for other switch-level failures. When encrypted_error_data is populated, the client may perform its own decryption.
QUERY FAILURE (gRPC error status): The query itself could not be completed. NotFound indicates the payment ID is unknown. Unavailable signals the switch is shutting down. Canceled or DeadlineExceeded reflects client-side timeouts. These are safe to retry.
Source: switchrpc/switch.proto
gRPC
rpc TrackOnion (TrackOnionRequest) returns (TrackOnionResponse);
REST
| HTTP Method | Path |
|---|---|
| POST | /v2/switch/track |
Code Samples
- gRPC
- REST
- 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', 'switchrpc/switch.proto'], loaderOptions);
const switchrpc = grpc.loadPackageDefinition(packageDefinition).switchrpc;
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 switchrpc.Switch(GRPC_HOST, creds);
let request = {
payment_hash: <bytes>,
attempt_id: <uint64>,
session_key: <bytes>,
hop_pubkeys: <bytes>,
};
client.trackOnion(request, function(err, response) {
console.log(response);
});
// Console output:
// {
// "preimage": <bytes>,
// "failure_details": <FailureDetails>,
// }
import codecs, grpc, os
# Generate the following 2 modules by compiling the switchrpc/switch.proto with the grpcio-tools.
# See https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/python.md for instructions.
import switch_pb2 as switchrpc, switch_pb2_grpc as switchstub
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 = switchstub.SwitchStub(channel)
request = switchrpc.TrackOnionRequest(
payment_hash=<bytes>,
attempt_id=<uint64>,
session_key=<bytes>,
hop_pubkeys=<bytes>,
)
response = stub.TrackOnion(request)
print(response)
# {
# "preimage": <bytes>,
# "failure_details": <FailureDetails>,
# }
# 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 switchrpc/switch.proto \
-cacert $TLS_PATH \
-H "macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "payment_hash": BASE64_ENCODED_VALUE, "attempt_id": <uint64>, "session_key": BASE64_ENCODED_VALUE, "hop_pubkeys": BASE64_ENCODED_VALUE }' \
$GRPC_HOST \
switchrpc.Switch/TrackOnion
- 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)
attempt_id: <string>, // <uint64>
session_key: <string>, // <bytes> (base64 encoded)
hop_pubkeys: <array>, // <bytes> (base64 encoded)
};
let options = {
url: `https://${REST_HOST}/v2/switch/track`,
// 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:
// {
// "preimage": <string>, // <bytes>
// "failure_details": <object>, // <FailureDetails>
// }
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/switch/track'
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
headers = {'Grpc-Metadata-macaroon': macaroon}
data = {
'payment_hash': base64.b64encode(<bytes>),
'attempt_id': <uint64>,
'session_key': base64.b64encode(<bytes>),
'hop_pubkeys': base64.b64encode(<bytes>),
}
r = requests.post(url, headers=headers, data=json.dumps(data), verify=TLS_PATH)
print(r.json())
# {
# "preimage": <bytes>,
# "failure_details": <FailureDetails>,
# }
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, "attempt_id": <uint64>, "session_key": BASE64_ENCODED_VALUE, "hop_pubkeys": BASE64_ENCODED_VALUE }' \
https://$REST_HOST/v2/switch/track
Messages
switchrpc.TrackOnionRequest
Source: switchrpc/switch.proto
| Field | gRPC Type | REST Type | REST Placement |
|---|---|---|---|
payment_hash | bytes | string | body |
attempt_id | uint64 | string | body |
session_key | bytes | string | body |
hop_pubkeys | bytes[] | array | body |
switchrpc.TrackOnionResponse
Source: switchrpc/switch.proto
| Field | gRPC Type | REST Type |
|---|---|---|
preimage | bytes | string |
failure_details | FailureDetails | object |
Nested Messages
switchrpc.FailureDetails
| Field | gRPC Type | REST Type |
|---|---|---|
error_message | string | string |
local_failure | LocalFailure | object |
forwarding_failure | ForwardingFailure | object |
encrypted_error_data | bytes | string |
unreadable_failure | UnreadableFailure | object |
switch_error | SwitchError | object |
switchrpc.ForwardingFailure
| Field | gRPC Type | REST Type |
|---|---|---|
failure_source_index | uint32 | integer |
wire_message | bytes | string |
switchrpc.LocalFailure
| Field | gRPC Type | REST Type |
|---|---|---|
wire_message | bytes | string |
switchrpc.SwitchError
This response has no parameters.
switchrpc.UnreadableFailure
This response has no parameters.