diff --git a/common/features.c b/common/features.c index 7b6ed6cb5458..c775ec13e409 100644 --- a/common/features.c +++ b/common/features.c @@ -144,6 +144,8 @@ static const struct feature_style feature_styles[] = { .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT} }, + { OPT_BOLT11_REQUEST, + .copy_style = { [BOLT12_OFFER_FEATURE] = FEATURE_REPRESENT } }, }; struct dependency { diff --git a/common/features.h b/common/features.h index 2861573647ea..54bf0c11a23a 100644 --- a/common/features.h +++ b/common/features.h @@ -157,4 +157,9 @@ struct feature_set *feature_set_dup(const tal_t *ctx, #define OPT_SHUTDOWN_WRONG_FUNDING 104 +/* BOLT-fetch-bolt11 #9: + * | 2/3 | `option_bolt11_request` | Invoice request for a bolt11 invoice | 2 | | [BOLT #12][bolt12-bolt11-req] | + */ +#define OPT_BOLT11_REQUEST 2 + #endif /* LIGHTNING_COMMON_FEATURES_H */ diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index 2424bdab68dd..8b40449f32b4 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -13907,6 +13907,13 @@ ], "added": "v25.02" }, + "bolt11": { + "type": "boolean", + "description": [ + "Indicates we should fetch a bolt11 invoice instead (EXPERIMENTAL)" + ], + "added": "v26.09" + }, "dev_reply_path": { "hidden": true }, @@ -13925,7 +13932,7 @@ "invoice": { "type": "string", "description": [ - "The BOLT12 invoice we fetched." + "The BOLT12 invoice we fetched (or BOLT11 if `bolt11` parameter was set)." ] }, "changes": { @@ -41740,6 +41747,13 @@ "BOLT #12 `invoice` payload." ] }, + "bolt11_invoice": { + "type": "string", + "added": "v26.09", + "description": [ + "BOLT #11 invoice payload." + ] + }, "invoice_error": { "type": "hex", "description": [ @@ -41925,6 +41939,13 @@ "BOLT #12 `invoice` payload." ] }, + "bolt11_invoice": { + "type": "string", + "added": "v26.09", + "description": [ + "BOLT #11 invoice payload." + ] + }, "invoice_error": { "type": "hex", "description": [ diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 5d5ced691bac..797d088900d5 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -752,7 +752,7 @@ def feerates(self, style, urgent=None, normal=None, slow=None): def fetchinvoice(self, offer, amount_msat=None, quantity=None, recurrence_counter=None, recurrence_start=None, recurrence_label=None, timeout=None, payer_note=None, - payer_metadata=None, bip353=None): + payer_metadata=None, bip353=None, bolt11=None): """ Fetch an invoice for an offer. """ @@ -767,6 +767,7 @@ def fetchinvoice(self, offer, amount_msat=None, quantity=None, recurrence_counte "payer_note": payer_note, "payer_metadata": payer_metadata, "bip353": bip353, + "bolt11": bolt11, } return self.call("fetchinvoice", payload) diff --git a/doc/schemas/fetchinvoice.json b/doc/schemas/fetchinvoice.json index a69442a37f9d..18db970ba235 100644 --- a/doc/schemas/fetchinvoice.json +++ b/doc/schemas/fetchinvoice.json @@ -76,6 +76,13 @@ ], "added": "v25.02" }, + "bolt11": { + "type": "boolean", + "description": [ + "Indicates we should fetch a bolt11 invoice instead (EXPERIMENTAL)" + ], + "added": "v26.09" + }, "dev_reply_path": { "hidden": true }, @@ -94,7 +101,7 @@ "invoice": { "type": "string", "description": [ - "The BOLT12 invoice we fetched." + "The BOLT12 invoice we fetched (or BOLT11 if `bolt11` parameter was set)." ] }, "changes": { diff --git a/doc/schemas/hook/onion_message_recv.json b/doc/schemas/hook/onion_message_recv.json index 81515883bc6c..98a95ac067d2 100644 --- a/doc/schemas/hook/onion_message_recv.json +++ b/doc/schemas/hook/onion_message_recv.json @@ -113,6 +113,13 @@ "BOLT #12 `invoice` payload." ] }, + "bolt11_invoice": { + "type": "string", + "added": "v26.09", + "description": [ + "BOLT #11 invoice payload." + ] + }, "invoice_error": { "type": "hex", "description": [ diff --git a/doc/schemas/hook/onion_message_recv_secret.json b/doc/schemas/hook/onion_message_recv_secret.json index 92e0b4665482..76e82389c905 100644 --- a/doc/schemas/hook/onion_message_recv_secret.json +++ b/doc/schemas/hook/onion_message_recv_secret.json @@ -127,6 +127,13 @@ "BOLT #12 `invoice` payload." ] }, + "bolt11_invoice": { + "type": "string", + "added": "v26.09", + "description": [ + "BOLT #11 invoice payload." + ] + }, "invoice_error": { "type": "hex", "description": [ diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 28173383d23d..edd2f66794b5 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -1125,7 +1125,6 @@ static struct command_result *json_invoice(struct command *cmd, struct jsonrpc_request *req; struct plugin *plugin; bool *hashonly; - const size_t inv_max_label_len = 128; const jsmntok_t *dev_routes; info = tal(cmd, struct invoice_info); @@ -1151,11 +1150,6 @@ static struct command_result *json_invoice(struct command *cmd, return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "dev-routes requires --developer"); - if (strlen(info->label->s) > inv_max_label_len) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Label '%s' over %zu bytes", info->label->s, inv_max_label_len); - } - if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Description greater than %d bytes " diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index 70efb66e53d1..b00b209d7231 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -59,6 +59,11 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload, if (payload->om->invoice) json_add_hex_talarr(stream, "invoice", payload->om->invoice); + if (payload->om->bolt11_invoice) + json_add_stringn(stream, "bolt11_invoice", + payload->om->bolt11_invoice, + tal_bytelen(payload->om->bolt11_invoice)); + if (payload->om->invoice_error) json_add_hex_talarr(stream, "invoice_error", payload->om->invoice_error); diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 8ff374d5715b..ebb030420422 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -171,7 +171,12 @@ static struct command_result *handle_invreq_response(struct command *cmd, u64 *expected_amount; const struct recurrence *recurrence; - invtok = json_get_member(buf, om, "invoice"); + /* Handle bolt11 case separately */ + if (sent->invreq->invreq_bolt11) { + invtok = json_get_member(buf, om, "bolt11_invoice"); + } else { + invtok = json_get_member(buf, om, "invoice"); + } if (!invtok) { plugin_log(cmd->plugin, LOG_UNUSUAL, "Neither invoice nor invoice_request_failed in reply %.*s", @@ -185,6 +190,27 @@ static struct command_result *handle_invreq_response(struct command *cmd, return command_hook_success(cmd); } + if (sent->invreq->invreq_bolt11) { + const char *bolt11, *err; + + /* Sanity check that they sent us a valid reply */ + bolt11 = json_strdup(tmpctx, buf, invtok); + if (!bolt11_decode(tmpctx, bolt11, + plugin_feature_set(cmd->plugin), + NULL, + chainparams, + &err)) { + badfield = tal_fmt(tmpctx, "invoice: %s", err); + goto badinv; + } + out = jsonrpc_stream_success(sent->cmd); + json_add_string(out, "invoice", bolt11); + json_object_start(out, "changes"); + json_object_end(out); + discard_result(command_finished(sent->cmd, out)); + return command_hook_success(cmd); + } + invbin = json_tok_bin_from_hex(cmd, buf, invtok); cursor = invbin; len = tal_bytelen(invbin); @@ -929,6 +955,7 @@ struct command_result *json_fetchinvoice(struct command *cmd, u32 *timeout; u64 *quantity; u32 *recurrence_counter, *recurrence_start; + bool *bolt11; if (!param_check(cmd, buffer, params, p_req("offer", param_offer, &sent->offer), @@ -941,6 +968,7 @@ struct command_result *json_fetchinvoice(struct command *cmd, p_opt("payer_note", param_string, &payer_note), p_opt("payer_metadata", param_bin_from_hex, &payer_metadata), p_opt("bip353", param_bip353, &bip353), + p_opt_def("bolt11", param_bool, &bolt11, false), p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd), p_opt("dev_reply_path", param_dev_reply_path, &sent->dev_reply_path), NULL)) @@ -1148,6 +1176,13 @@ struct command_result *json_fetchinvoice(struct command *cmd, strlen(payer_note), 0); + if (*bolt11) { + if (!feature_offered(invreq->offer_features, OPT_BOLT11_REQUEST)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Cannot request bolt11: offer does not implement it"); + invreq->invreq_bolt11 = tal(invreq, struct tlv_invoice_request_invreq_bolt11); + } + /* If only checking, we're done now */ if (command_check_only(cmd)) return command_check_done(cmd); diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index f9672b0680a6..8b1a850da4b9 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -224,9 +224,12 @@ static struct command_result *createinvoice_error(struct command *cmd, JSON_SCAN(json_to_u32, &code), JSON_SCAN_TAL(tmpctx, json_strdup, &status)) == NULL && code == INVOICE_LABEL_ALREADY_EXISTS) { - if (streq(status, "unpaid")) - return createinvoice_done(cmd, method, buf, - json_get_member(buf, err, "data"), ir); + if (streq(status, "unpaid")) { + const jsmntok_t *data = json_get_member(buf, err, "data"); + if (!json_get_member(buf, data, "bolt12")) + return fail_invreq(cmd, ir, "invoice previously requested as a bolt11"); + return createinvoice_done(cmd, method, buf, data, ir); + } if (streq(status, "expired")) return fail_invreq(cmd, ir, "invoice expired (cancelled?)"); } @@ -823,6 +826,78 @@ static struct command_result *convert_currency(struct command *cmd, return send_outreq(req); } +static struct command_result *bolt11_invoice_done(struct command *cmd, + const char *method, + const char *buf, + const jsmntok_t *result, + struct invreq *ir) +{ + struct tlv_onionmsg_tlv *payload; + const jsmntok_t *t = json_get_member(buf, result, "bolt11"); + + /* In case it was a bolt12 last time! */ + if (!t) + return fail_invreq(cmd, ir, "invoice previously requested as a bolt12"); + + payload = tlv_onionmsg_tlv_new(tmpctx); + /* Not nul-terminated! */ + payload->bolt11_invoice = tal_dup_arr(payload, char, + buf + t->start, + t->end - t->start, + 0); + return send_onion_reply(cmd, ir->reply_path, payload); +} + +static struct command_result *bolt11_listinvoices_done(struct command *cmd, + const char *method, + const char *buf, + const jsmntok_t *result, + struct invreq *ir) +{ + const jsmntok_t *arr = json_get_member(buf, result, "invoices"), *inv, *status; + + if (arr->size == 0) + return fail_internalerr(cmd, ir, "Listing invoices gave no invoice?"); + + inv = arr + 1; + status = json_get_member(buf, inv, "status"); + if (!json_tok_streq(buf, status, "unpaid")) { + return fail_invreq(cmd, ir, "Invoice %.*s", + status->end - status->start, + buf + status->start); + } + + /* If we hand it the first entry, it's exactly like we just generated it */ + return bolt11_invoice_done(cmd, method, buf, inv, ir); +} + +/* This can fail because they've already asked for it */ +static struct command_result *bolt11_invoice_error(struct command *cmd, + const char *method, + const char *buf, + const jsmntok_t *err, + struct invreq *ir) +{ + struct out_req *req; + u32 code; + if (json_scan(tmpctx, buf, err, + "{code:%}", + JSON_SCAN(json_to_u32, &code)) != NULL + || code != INVOICE_LABEL_ALREADY_EXISTS) { + return fail_internalerr(cmd, ir, + "Bad invoice error %.*s", + json_tok_full_len(err), + json_tok_full(buf, err)); + } + + req = jsonrpc_request_start(cmd, "listinvoices", + bolt11_listinvoices_done, + error, + ir); + json_add_label(req->js, &ir->offer_id, ir->invreq->invreq_payer_id, 0); + return send_outreq(req); +} + static struct command_result *listoffers_done(struct command *cmd, const char *method, const char *buf, @@ -1043,6 +1118,46 @@ static struct command_result *listoffers_done(struct command *cmd, return err; } + /* BOLT-fetch-bolt11 #12: + * - if `invreq_bolt11` is present: + * - if `offer_features` does not contain `option_bolt11_request`: + * - MUST reject the invoice request. + * - if `invreq_quantity` is present: + * - MUST reject the invoice request. + * - SHOULD create a BOLT 11 invoice and place it in the + * `bolt11_invoice` field of a response `onionmsg_tlv`, sent using + * the `onionmsg_tlv` `reply_path`. + */ + if (ir->invreq->invreq_bolt11) { + struct out_req *req; + if (!feature_offered(ir->invreq->offer_features, OPT_BOLT11_REQUEST)) + return fail_invreq(cmd, ir, "This offer is not bolt11 compatible"); + if (ir->invreq->invreq_quantity) + return fail_invreq(cmd, ir, "Cannot have quantity with bolt11"); + if (ir->invreq->offer_amount) { + if (ir->invreq->invreq_amount + && *ir->invreq->offer_amount != *ir->invreq->invreq_amount) + return fail_invreq(cmd, ir, "Requested amount %s != offer amount %s", + fmt_amount_msat(tmpctx, amount_msat(*ir->invreq->invreq_amount)), + fmt_amount_msat(tmpctx, amount_msat(*ir->invreq->offer_amount))); + amt = amount_msat(*ir->invreq->offer_amount); + } else { + if (!ir->invreq->invreq_amount) + return fail_invreq(cmd, ir, "Missing invreq_amount"); + amt = amount_msat(*ir->invreq->invreq_amount); + } + req = jsonrpc_request_start(cmd, "invoice", + bolt11_invoice_done, + bolt11_invoice_error, + ir); + json_add_amount_msat(req->js, "amount_msat", amt); + json_add_label(req->js, &ir->offer_id, ir->invreq->invreq_payer_id, 0); + json_add_stringn(req->js, "description", + ir->invreq->offer_description, + tal_bytelen(ir->invreq->offer_description)); + return send_outreq(req); + } + /* BOLT #12: * A writer of an invoice: *... diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 59570e2608a6..7124ed96e21d 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -472,6 +472,21 @@ static struct command_result *param_pubkey_arr(struct command *cmd, return NULL; } +/* Simple offers can be turned into bolt11 invoices */ +static bool offer_is_bolt11_compatible(const struct tlv_offer *offer) +{ + /* No currency conversion */ + if (offer->offer_currency) + return false; + /* No quantities */ + if (offer->offer_quantity_max) + return false; + /* No recurrence */ + if (offer_recurrence(offer)) + return false; + return true; +} + struct command_result *json_offer(struct command *cmd, const char *buffer, const jsmntok_t *params) @@ -521,10 +536,9 @@ struct command_result *json_offer(struct command *cmd, /* If they don't specify explicitly, use config (if any) */ if (!offinfo->fronting_nodes) offinfo->fronting_nodes = od->fronting_nodes; - else if (tal_count(offinfo->fronting_nodes) == 0) { + else if (tal_count(offinfo->fronting_nodes) == 0) /* [] means "no fronting" */ offinfo->fronting_nodes = tal_free(offinfo->fronting_nodes); - } /* BOLT #12: * @@ -611,6 +625,16 @@ struct command_result *json_offer(struct command *cmd, */ offer->offer_issuer_id = tal_dup(offer, struct pubkey, &od->id); + /* BOLT11 can do some fronting, but it's not as good (especially if we + * ever obscure our own node id when fronting!). So we act + * conservatively and disable it. */ + if (offer_is_bolt11_compatible(offer) + && !paths + && !offinfo->fronting_nodes) { + offer->offer_features = tal_arr(offer, u8, 0); + set_feature_bit(&offer->offer_features, OPTIONAL_FEATURE(OPT_BOLT11_REQUEST)); + } + /* Now rest of offer will not change: we use pathless offer to create secret. */ if (paths) { struct secret blinding_path_secret; diff --git a/tests/test_pay.py b/tests/test_pay.py index 76aff48dfb05..6b09673fd146 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -7329,3 +7329,70 @@ def test_createproof_include(node_factory, bitcoind): # Unknown name is an error. with pytest.raises(RpcError, match=r'Unknown field name'): l1.rpc.call('createproof', {'invstring': inv, 'include': ['no_such_field']}) + + +def test_fetchinvoice_bolt11(node_factory, bitcoind): + """Test fetchinvoice bolt11=True fetches a bolt11 invoice via onion message.""" + l1, l2 = node_factory.line_graph(2, wait_for_announce=True, + opts={'dev-allow-localhost': None}) + + # Simple offer should automatically have option_bolt11_request set. + offer = l2.rpc.offer(amount='2msat', description='bolt11 test') + assert offer['created'] is True + + # Fetch a bolt11 invoice. + ret = l1.rpc.fetchinvoice(offer=offer['bolt12'], + payer_metadata='0001020304050607', + bolt11=True) + assert ret['invoice'].startswith('lnb') + decoded = l1.rpc.decode(ret['invoice']) + assert decoded['type'] == 'bolt11 invoice' + assert decoded['amount_msat'] == 2 + + # Requesting bolt11 again with same payer key returns the same invoice. + ret2 = l1.rpc.fetchinvoice(offer=offer['bolt12'], + payer_metadata='0001020304050607', + bolt11=True) + assert ret2['invoice'] == ret['invoice'] + + # Requesting non-bolt11 fails. + with pytest.raises(RpcError, match="invoice previously requested as a bolt11"): + l1.rpc.fetchinvoice(offer=offer['bolt12'], payer_metadata='0001020304050607') + + # We can pay it. + l1.rpc.xpay(ret['invoice']) + + # Don't hit onion ratelimits! + time.sleep(1) + + # Cannot fetch it again with same payer key. + with pytest.raises(RpcError, match="Invoice paid"): + l1.rpc.fetchinvoice(offer=offer['bolt12'], + payer_metadata='0001020304050607', + bolt11=True) + + # Offer with quantity_max has no option_bolt11_request; rejects bolt11 param. + offer_qty = l2.rpc.offer(amount='1msat', + description='qty offer', + quantity_max=5) + with pytest.raises(RpcError, match="Cannot request bolt11: offer does not implement it"): + l1.rpc.fetchinvoice(offer=offer_qty['bolt12'], quantity=1, bolt11=True) + + # Variable-amount offer works with amount_msat. + offer_any = l2.rpc.offer(amount='any', description='any amount') + ret = l1.rpc.fetchinvoice(offer=offer_any['bolt12'], + bolt11=True, + amount_msat=1000) + assert ret['invoice'].startswith('lnb') + decoded = l1.rpc.decode(ret['invoice']) + assert decoded['amount_msat'] == 1000 + + # Cannot request bolt11 after bolt12 with same payer key. + l1.rpc.fetchinvoice(offer=offer_any['bolt12'], + amount_msat=1, + payer_metadata='0001020304050608') + with pytest.raises(RpcError, match="invoice previously requested as a bolt12"): + l1.rpc.fetchinvoice(offer=offer_any['bolt12'], + amount_msat=1, + payer_metadata='0001020304050608', + bolt11=True) diff --git a/wire/bolt12_wire.csv b/wire/bolt12_wire.csv index 3810f9727d4d..2d3c941e3ba3 100644 --- a/wire/bolt12_wire.csv +++ b/wire/bolt12_wire.csv @@ -89,6 +89,7 @@ tlvtype,invoice_request,invreq_paths,90 tlvdata,invoice_request,invreq_paths,paths,blinded_path,... tlvtype,invoice_request,invreq_bip_353_name,91 tlvdata,invoice_request,invreq_bip_353_name,bip_353_name,bip_353_name, +tlvtype,invoice_request,invreq_bolt11,92 tlvtype,invoice_request,invreq_recurrence_counter,2000000092 tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, tlvtype,invoice_request,invreq_recurrence_start,2000000093 diff --git a/wire/extracted_bolt12_03_fetch_bolt11.patch b/wire/extracted_bolt12_03_fetch_bolt11.patch new file mode 100644 index 000000000000..96a8e96f10fb --- /dev/null +++ b/wire/extracted_bolt12_03_fetch_bolt11.patch @@ -0,0 +1,12 @@ +diff --git a/wire/bolt12_wire.csv b/wire/bolt12_wire.csv +index 3810f9727d..2d3c941e3b 100644 +--- a/wire/bolt12_wire.csv ++++ b/wire/bolt12_wire.csv +@@ -89,6 +89,7 @@ tlvtype,invoice_request,invreq_paths,90 + tlvdata,invoice_request,invreq_paths,paths,blinded_path,... + tlvtype,invoice_request,invreq_bip_353_name,91 + tlvdata,invoice_request,invreq_bip_353_name,bip_353_name,bip_353_name, ++tlvtype,invoice_request,invreq_bolt11,92 + tlvtype,invoice_request,invreq_recurrence_counter,2000000092 + tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, + tlvtype,invoice_request,invreq_recurrence_start,2000000093 diff --git a/wire/extracted_onion_01_bolt11_fetch.patch b/wire/extracted_onion_01_bolt11_fetch.patch new file mode 100644 index 000000000000..0eac45ae2914 --- /dev/null +++ b/wire/extracted_onion_01_bolt11_fetch.patch @@ -0,0 +1,10 @@ +diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv +index e3f313cf6a..6c49cd8d06 100644 +--- a/wire/onion_wire.csv ++++ b/wire/onion_wire.csv +@@ -102,3 +102,5 @@ tlvtype,onionmsg_tlv,invoice,66 + tlvdata,onionmsg_tlv,invoice,inv,tlv_invoice, + tlvtype,onionmsg_tlv,invoice_error,68 + tlvdata,onionmsg_tlv,invoice_error,inverr,tlv_invoice_error, ++tlvtype,onionmsg_tlv,bolt11_invoice,70 ++tlvdata,onionmsg_tlv,bolt11_invoice,bolt11,utf8,... diff --git a/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch b/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch index 82e0b2e280b2..6a98b37f0be0 100644 --- a/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch +++ b/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch @@ -4,7 +4,7 @@ diff --git b/wire/onion_wire.csv a/wire/onion_wire.csv index 5c52fe9a1..2ac0c4cff 100644 --- b/wire/onion_wire.csv +++ a/wire/onion_wire.csv -@@ -51,8 +29,8 @@ tlvdata,onionmsg_tlv,reply_path,path,onionmsg_path,... +@@ -51,10 +29,10 @@ tlvdata,onionmsg_tlv,reply_path,path,onionmsg_path,... tlvtype,onionmsg_tlv,encrypted_data_tlv,4 tlvdata,onionmsg_tlv,encrypted_data_tlv,encrypted_data_tlv,byte,... tlvtype,onionmsg_tlv,invoice_request,64 @@ -16,3 +16,5 @@ index 5c52fe9a1..2ac0c4cff 100644 tlvtype,onionmsg_tlv,invoice_error,68 -tlvdata,onionmsg_tlv,invoice_error,inverr,tlv_invoice_error, +tlvdata,onionmsg_tlv,invoice_error,inverr,byte,... + tlvtype,onionmsg_tlv,bolt11_invoice,70 + tlvdata,onionmsg_tlv,bolt11_invoice,bolt11,utf8,... diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv index e3f313cf6a65..6c49cd8d0653 100644 --- a/wire/onion_wire.csv +++ b/wire/onion_wire.csv @@ -102,3 +102,5 @@ tlvtype,onionmsg_tlv,invoice,66 tlvdata,onionmsg_tlv,invoice,inv,byte,... tlvtype,onionmsg_tlv,invoice_error,68 tlvdata,onionmsg_tlv,invoice_error,inverr,byte,... +tlvtype,onionmsg_tlv,bolt11_invoice,70 +tlvdata,onionmsg_tlv,bolt11_invoice,bolt11,utf8,...