plugins/fetchinvoice: implement `sendinvoice` command.
As fetchinvoice handles normal offers, sendinvoice handles send_invoice offers. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
c1b38afcd3
commit
dc2ba6f9d9
|
@ -1,4 +1,5 @@
|
|||
#include <bitcoin/chainparams.h>
|
||||
#include <bitcoin/preimage.h>
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/json_out/json_out.h>
|
||||
#include <ccan/mem/mem.h>
|
||||
|
@ -34,10 +35,15 @@ struct sent {
|
|||
struct pubkey reply_blinding;
|
||||
/* The command which sent us. */
|
||||
struct command *cmd;
|
||||
/* The offer we are trying to get an invoice for. */
|
||||
/* The offer we are trying to get an invoice/payment for. */
|
||||
struct tlv_offer *offer;
|
||||
/* The invreq we sent. */
|
||||
|
||||
/* The invreq we sent, OR the invoice we sent */
|
||||
struct tlv_invoice_request *invreq;
|
||||
|
||||
struct tlv_invoice *inv;
|
||||
struct preimage inv_preimage;
|
||||
struct json_escape *inv_label;
|
||||
};
|
||||
|
||||
static struct sent *find_sent(const struct pubkey *blinding)
|
||||
|
@ -80,87 +86,80 @@ static void discard_result(struct command_result *ret)
|
|||
{
|
||||
}
|
||||
|
||||
static struct command_result *recv_onion_message(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
/* Returns NULL if it wasn't an error. */
|
||||
static struct command_result *handle_error(struct command *cmd,
|
||||
struct sent *sent,
|
||||
const char *buf,
|
||||
const jsmntok_t *om)
|
||||
{
|
||||
const u8 *data;
|
||||
size_t dlen;
|
||||
struct tlv_invoice_error *err;
|
||||
struct json_out *details;
|
||||
const jsmntok_t *errtok;
|
||||
|
||||
errtok = json_get_member(buf, om, "invoice_error");
|
||||
if (!errtok)
|
||||
return NULL;
|
||||
|
||||
data = json_tok_bin_from_hex(cmd, buf, errtok);
|
||||
dlen = tal_bytelen(data);
|
||||
err = tlv_invoice_error_new(cmd);
|
||||
details = json_out_new(cmd);
|
||||
|
||||
plugin_log(cmd->plugin, LOG_DBG, "errtok = %.*s",
|
||||
json_tok_full_len(errtok),
|
||||
json_tok_full(buf, errtok));
|
||||
json_out_start(details, NULL, '{');
|
||||
if (!fromwire_invoice_error(&data, &dlen, err)) {
|
||||
plugin_log(cmd->plugin, LOG_DBG,
|
||||
"Invalid invoice_error %.*s",
|
||||
json_tok_full_len(errtok),
|
||||
json_tok_full(buf, errtok));
|
||||
json_out_addstr(details, "invoice_error_hex",
|
||||
tal_strndup(tmpctx,
|
||||
buf + errtok->start,
|
||||
errtok->end - errtok->start));
|
||||
} else {
|
||||
char *failstr;
|
||||
|
||||
/* FIXME: with a bit more generate-wire.py support,
|
||||
* we could have fieldnames and even types. */
|
||||
if (err->erroneous_field)
|
||||
json_out_add(details, "erroneous_field", false,
|
||||
"%"PRIu64, *err->erroneous_field);
|
||||
if (err->suggested_value)
|
||||
json_out_addstr(details, "suggested_value",
|
||||
tal_hex(tmpctx,
|
||||
err->suggested_value));
|
||||
/* If they don't include this, it'll be empty */
|
||||
failstr = tal_strndup(tmpctx,
|
||||
err->error,
|
||||
tal_bytelen(err->error));
|
||||
json_out_addstr(details, "error", failstr);
|
||||
}
|
||||
json_out_end(details, '}');
|
||||
discard_result(command_done_err(sent->cmd,
|
||||
OFFER_BAD_INVREQ_REPLY,
|
||||
"Remote node sent failure message",
|
||||
details));
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *handle_invreq_response(struct command *cmd,
|
||||
struct sent *sent,
|
||||
const char *buf,
|
||||
const jsmntok_t *om)
|
||||
{
|
||||
const jsmntok_t *om, *invtok, *errtok, *blindingtok;
|
||||
const u8 *invbin;
|
||||
const jsmntok_t *invtok;
|
||||
size_t len;
|
||||
struct tlv_invoice *inv;
|
||||
struct sent *sent;
|
||||
struct sha256 merkle, sighash;
|
||||
struct json_stream *out;
|
||||
const char *badfield;
|
||||
struct pubkey blinding;
|
||||
u64 *expected_amount;
|
||||
|
||||
plugin_log(cmd->plugin, LOG_INFORM, "Received onion message: %.*s",
|
||||
json_tok_full_len(params),
|
||||
json_tok_full(buf, params));
|
||||
|
||||
om = json_get_member(buf, params, "onion_message");
|
||||
blindingtok = json_get_member(buf, om, "blinding_in");
|
||||
if (!blindingtok || !json_to_pubkey(buf, blindingtok, &blinding))
|
||||
return command_hook_success(cmd);
|
||||
|
||||
sent = find_sent(&blinding);
|
||||
if (!sent) {
|
||||
plugin_log(cmd->plugin, LOG_DBG,
|
||||
"No match for onion %.*s",
|
||||
json_tok_full_len(om),
|
||||
json_tok_full(buf, om));
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
/* From here on, we know it's genuine, so we will fail the
|
||||
* fetchinvoice command if the invoice is invalid */
|
||||
errtok = json_get_member(buf, om, "invoice_error");
|
||||
if (errtok) {
|
||||
const u8 *data = json_tok_bin_from_hex(cmd, buf, errtok);
|
||||
size_t dlen = tal_bytelen(data);
|
||||
struct tlv_invoice_error *err = tlv_invoice_error_new(cmd);
|
||||
struct json_out *details = json_out_new(cmd);
|
||||
|
||||
plugin_log(cmd->plugin, LOG_DBG, "errtok = %.*s",
|
||||
json_tok_full_len(errtok),
|
||||
json_tok_full(buf, errtok));
|
||||
json_out_start(details, NULL, '{');
|
||||
if (!fromwire_invoice_error(&data, &dlen, err)) {
|
||||
plugin_log(cmd->plugin, LOG_DBG,
|
||||
"Invalid invoice_error %.*s",
|
||||
json_tok_full_len(errtok),
|
||||
json_tok_full(buf, errtok));
|
||||
json_out_addstr(details, "invoice_error_hex",
|
||||
tal_strndup(tmpctx,
|
||||
buf + errtok->start,
|
||||
errtok->end - errtok->start));
|
||||
} else {
|
||||
char *failstr;
|
||||
|
||||
/* FIXME: with a bit more generate-wire.py support,
|
||||
* we could have fieldnames and even types. */
|
||||
if (err->erroneous_field)
|
||||
json_out_add(details, "erroneous_field", false,
|
||||
"%"PRIu64, *err->erroneous_field);
|
||||
if (err->suggested_value)
|
||||
json_out_addstr(details, "suggested_value",
|
||||
tal_hex(tmpctx,
|
||||
err->suggested_value));
|
||||
/* If they don't include this, it'll be empty */
|
||||
failstr = tal_strndup(tmpctx,
|
||||
err->error,
|
||||
tal_bytelen(err->error));
|
||||
json_out_addstr(details, "error", failstr);
|
||||
}
|
||||
json_out_end(details, '}');
|
||||
discard_result(command_done_err(sent->cmd,
|
||||
OFFER_BAD_INVREQ_REPLY,
|
||||
"Remote node sent failure message",
|
||||
details));
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
invtok = json_get_member(buf, om, "invoice");
|
||||
if (!invtok) {
|
||||
plugin_log(cmd->plugin, LOG_UNUSUAL,
|
||||
|
@ -372,6 +371,54 @@ badinv:
|
|||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *handle_inv_response(struct command *cmd,
|
||||
struct sent *sent,
|
||||
const char *buf,
|
||||
const jsmntok_t *om)
|
||||
{
|
||||
/* FIXME: Report error. */
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *recv_onion_message(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
const jsmntok_t *om, *blindingtok;
|
||||
struct sent *sent;
|
||||
struct pubkey blinding;
|
||||
struct command_result *err;
|
||||
|
||||
om = json_get_member(buf, params, "onion_message");
|
||||
blindingtok = json_get_member(buf, om, "blinding_in");
|
||||
if (!blindingtok || !json_to_pubkey(buf, blindingtok, &blinding))
|
||||
return command_hook_success(cmd);
|
||||
|
||||
sent = find_sent(&blinding);
|
||||
if (!sent) {
|
||||
plugin_log(cmd->plugin, LOG_DBG,
|
||||
"No match for onion %.*s",
|
||||
json_tok_full_len(om),
|
||||
json_tok_full(buf, om));
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
plugin_log(cmd->plugin, LOG_DBG, "Received onion message: %.*s",
|
||||
json_tok_full_len(params),
|
||||
json_tok_full(buf, params));
|
||||
|
||||
err = handle_error(cmd, sent, buf, om);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (sent->invreq)
|
||||
return handle_invreq_response(cmd, sent, buf, om);
|
||||
else {
|
||||
assert(sent->inv);
|
||||
return handle_inv_response(cmd, sent, buf, om);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy_sent(struct sent *sent)
|
||||
{
|
||||
list_del(&sent->list);
|
||||
|
@ -493,7 +540,12 @@ static const struct pubkey *route_backwards(const tal_t *ctx,
|
|||
static struct command_result *send_message(struct command *cmd,
|
||||
struct sent *sent,
|
||||
const char *msgfield,
|
||||
const u8 *msgval)
|
||||
const u8 *msgval,
|
||||
struct command_result *(*done)
|
||||
(struct command *cmd,
|
||||
const char *buf UNUSED,
|
||||
const jsmntok_t *result UNUSED,
|
||||
struct sent *sent))
|
||||
{
|
||||
const struct dijkstra *dij;
|
||||
const struct gossmap_node *dst, *src;
|
||||
|
@ -535,8 +587,8 @@ static struct command_result *send_message(struct command *cmd,
|
|||
&sent->reply_blinding);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage",
|
||||
&sendonionmsg_done,
|
||||
&forward_error,
|
||||
done,
|
||||
forward_error,
|
||||
sent);
|
||||
json_array_start(req->js, "hops");
|
||||
for (size_t i = 0; i < tal_count(r); i++) {
|
||||
|
@ -595,6 +647,7 @@ static struct command_result *invreq_done(struct command *cmd,
|
|||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
|
||||
sent->inv = NULL;
|
||||
sent->invreq = invrequest_decode(sent,
|
||||
buf + t->start,
|
||||
t->end - t->start,
|
||||
|
@ -610,7 +663,8 @@ static struct command_result *invreq_done(struct command *cmd,
|
|||
|
||||
rawinvreq = tal_arr(tmpctx, u8, 0);
|
||||
towire_invoice_request(&rawinvreq, sent->invreq);
|
||||
return send_message(cmd, sent, "invoice_request", rawinvreq);
|
||||
return send_message(cmd, sent, "invoice_request", rawinvreq,
|
||||
sendonionmsg_done);
|
||||
}
|
||||
|
||||
/* Fetches an invoice for this offer, and makes sure it corresponds. */
|
||||
|
@ -797,13 +851,331 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
|
|||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static const struct plugin_command commands[] = { {
|
||||
"fetchinvoice",
|
||||
"payment",
|
||||
"Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
|
||||
NULL,
|
||||
json_fetchinvoice,
|
||||
static struct command_result *createinvoice_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *invtok = json_get_member(buf, result, "bolt12");
|
||||
char *fail;
|
||||
u8 *rawinv;
|
||||
|
||||
/* Replace invoice with signed one */
|
||||
tal_free(sent->inv);
|
||||
sent->inv = invoice_decode(sent,
|
||||
buf + invtok->start,
|
||||
invtok->end - invtok->start,
|
||||
plugin_feature_set(cmd->plugin),
|
||||
chainparams,
|
||||
&fail);
|
||||
if (!sent->inv) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Bad createinvoice %.*s: %s",
|
||||
json_tok_full_len(invtok),
|
||||
json_tok_full(buf, invtok),
|
||||
fail);
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Bad createinvoice response %s", fail);
|
||||
}
|
||||
|
||||
rawinv = tal_arr(tmpctx, u8, 0);
|
||||
towire_invoice(&rawinv, sent->inv);
|
||||
return send_message(cmd, sent, "invoice", rawinv, sendonionmsg_done);
|
||||
}
|
||||
|
||||
static struct command_result *sign_invoice(struct command *cmd,
|
||||
struct sent *sent)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
/* Get invoice signature and put in db so we can receive payment */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice",
|
||||
&createinvoice_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_string(req->js, "invstring", invoice_encode(tmpctx, sent->inv));
|
||||
json_add_preimage(req->js, "preimage", &sent->inv_preimage);
|
||||
json_add_escaped_string(req->js, "label", sent->inv_label);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static bool json_to_bip340sig(const char *buffer, const jsmntok_t *tok,
|
||||
struct bip340sig *sig)
|
||||
{
|
||||
return hex_decode(buffer + tok->start, tok->end - tok->start,
|
||||
sig->u8, sizeof(sig->u8));
|
||||
}
|
||||
|
||||
static struct command_result *payersign_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *sig;
|
||||
|
||||
sent->inv->refund_signature = tal(sent->inv, struct bip340sig);
|
||||
sig = json_get_member(buf, result, "signature");
|
||||
json_to_bip340sig(buf, sig, sent->inv->refund_signature);
|
||||
|
||||
return sign_invoice(cmd, sent);
|
||||
}
|
||||
|
||||
/* They're offering a refund, so we need to sign with same key as used
|
||||
* in initial payment. */
|
||||
static struct command_result *listsendpays_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *t, *arr = json_get_member(buf, result, "payments");
|
||||
size_t i;
|
||||
const u8 *public_tweak = NULL, *p;
|
||||
u8 *msg;
|
||||
size_t len;
|
||||
struct sha256 merkle;
|
||||
struct out_req *req;
|
||||
|
||||
/* Linearize populates ->fields */
|
||||
msg = tal_arr(tmpctx, u8, 0);
|
||||
towire_invoice(&msg, sent->inv);
|
||||
p = msg;
|
||||
len = tal_bytelen(msg);
|
||||
sent->inv = tlv_invoice_new(cmd);
|
||||
if (!fromwire_invoice(&p, &len, sent->inv))
|
||||
plugin_err(cmd->plugin,
|
||||
"Could not remarshall %s", tal_hex(tmpctx, msg));
|
||||
|
||||
merkle_tlv(sent->inv->fields, &merkle);
|
||||
|
||||
json_for_each_arr(i, t, arr) {
|
||||
const jsmntok_t *b12tok;
|
||||
struct tlv_invoice *inv;
|
||||
char *fail;
|
||||
|
||||
b12tok = json_get_member(buf, t, "bolt12");
|
||||
if (!b12tok) {
|
||||
/* This could happen if they try to refund a bolt11 */
|
||||
plugin_log(cmd->plugin, LOG_UNUSUAL,
|
||||
"Not bolt12 string in %.*s?",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
continue;
|
||||
}
|
||||
|
||||
inv = invoice_decode(tmpctx, buf + b12tok->start,
|
||||
b12tok->end - b12tok->start,
|
||||
plugin_feature_set(cmd->plugin),
|
||||
chainparams,
|
||||
&fail);
|
||||
if (!inv) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Bad bolt12 string in %.*s?",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
continue;
|
||||
}
|
||||
|
||||
public_tweak = inv->payer_info;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!public_tweak)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Cannot find invoice %s for refund",
|
||||
type_to_string(tmpctx, struct sha256,
|
||||
sent->offer->refund_for));
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `refund_signature` to the signature of the
|
||||
* `refunded_payment_hash` using prefix `refund_signature` and the
|
||||
* `payer_key` from the to-be-refunded invoice.
|
||||
*/
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "payersign",
|
||||
&payersign_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_string(req->js, "messagename", "invoice");
|
||||
json_add_string(req->js, "fieldname", "refund_signature");
|
||||
json_add_sha256(req->js, "merkle", &merkle);
|
||||
json_add_hex_talarr(req->js, "tweak", public_tweak);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *json_sendinvoice(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct amount_msat *msat;
|
||||
struct out_req *req;
|
||||
struct sent *sent = tal(cmd, struct sent);
|
||||
|
||||
sent->inv = tlv_invoice_new(cmd);
|
||||
sent->invreq = NULL;
|
||||
sent->cmd = cmd;
|
||||
|
||||
/* FIXME: Support recurring send_invoice offers? */
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("offer", param_offer, &sent->offer),
|
||||
p_req("label", param_label, &sent->inv_label),
|
||||
p_opt("msatoshi", param_msat, &msat),
|
||||
p_opt("quantity", param_u64, &sent->inv->quantity),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
/* Check they are really trying to send us money. */
|
||||
if (!sent->offer->send_invoice)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer wants an invoice_request, not invoice");
|
||||
|
||||
/* If they don't tell us how much, base it on offer. */
|
||||
if (!msat) {
|
||||
if (sent->offer->currency)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer in different currency: need amount");
|
||||
if (!sent->offer->amount)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer did not specify: need amount");
|
||||
sent->inv->amount = tal_dup(sent->inv, u64, sent->offer->amount);
|
||||
if (sent->inv->quantity)
|
||||
*sent->inv->amount *= *sent->inv->quantity;
|
||||
} else
|
||||
sent->inv->amount = tal_dup(sent->inv, u64,
|
||||
&msat->millisatoshis); /* Raw: tlv */
|
||||
|
||||
/* FIXME: Support blinded paths, in which case use fake nodeid */
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - otherwise (responding to a `send_invoice` offer):
|
||||
* - MUST set `node_id` to the id of the node to send payment to.
|
||||
* - MUST set `description` the same as the offer.
|
||||
*/
|
||||
sent->inv->node_id = tal(sent->inv, struct pubkey32);
|
||||
if (!pubkey32_from_node_id(sent->inv->node_id, &local_id))
|
||||
plugin_err(cmd->plugin, "Invalid local_id %s?",
|
||||
type_to_string(tmpctx, struct node_id, &local_id));
|
||||
|
||||
sent->inv->description
|
||||
= tal_dup_talarr(sent->inv, char, sent->offer->description);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set (or not set) `send_invoice` the same as the offer.
|
||||
*/
|
||||
sent->inv->send_invoice = tal(sent->inv, struct tlv_invoice_send_invoice);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `offer_id` to the id of the offer.
|
||||
*/
|
||||
sent->inv->offer_id = tal(sent->inv, struct sha256);
|
||||
merkle_tlv(sent->offer->fields, sent->inv->offer_id);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - SHOULD not respond to an offer if the current time is after
|
||||
* `absolute_expiry`.
|
||||
*/
|
||||
if (sent->offer->absolute_expiry
|
||||
&& time_now().ts.tv_sec > *sent->offer->absolute_expiry)
|
||||
return command_fail(cmd, OFFER_EXPIRED, "Offer expired");
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - otherwise (responding to a `send_invoice` offer):
|
||||
*...
|
||||
* - if the offer had a `quantity_min` or `quantity_max` field:
|
||||
* - MUST set `quantity`
|
||||
* - MUST set it within that (inclusive) range.
|
||||
* - otherwise:
|
||||
* - MUST NOT set `quantity`
|
||||
*/
|
||||
if (sent->offer->quantity_min || sent->offer->quantity_max) {
|
||||
if (!sent->inv->quantity)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity parameter required");
|
||||
if (sent->offer->quantity_min
|
||||
&& *sent->inv->quantity < *sent->offer->quantity_min)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity must be >= %"PRIu64,
|
||||
*sent->offer->quantity_min);
|
||||
if (sent->offer->quantity_max
|
||||
&& *sent->inv->quantity > *sent->offer->quantity_max)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity must be <= %"PRIu64,
|
||||
*sent->offer->quantity_max);
|
||||
} else {
|
||||
if (sent->inv->quantity)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity parameter unnecessary");
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `payer_key` to the `node_id` of the offer.
|
||||
*/
|
||||
sent->inv->payer_key = sent->offer->node_id;
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - FIXME: recurrence!
|
||||
*/
|
||||
if (sent->offer->recurrence)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"FIXME: handle recurring send_invoice offer!");
|
||||
|
||||
/* BOLT-offers #12:
|
||||
*
|
||||
* - if the chain for the invoice is not solely bitcoin:
|
||||
* - MUST specify `chains` the offer is valid for.
|
||||
* - otherwise:
|
||||
* - the bitcoin chain is implied as the first and only entry.
|
||||
*/
|
||||
if (!streq(chainparams->network_name, "bitcoin")) {
|
||||
sent->inv->chains = tal_arr(sent->inv, struct bitcoin_blkid, 1);
|
||||
sent->inv->chains[0] = chainparams->genesis_blockhash;
|
||||
}
|
||||
|
||||
sent->inv->features
|
||||
= plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE];
|
||||
|
||||
randombytes_buf(&sent->inv_preimage, sizeof(sent->inv_preimage));
|
||||
sent->inv->payment_hash = tal(sent->inv, struct sha256);
|
||||
sha256(sent->inv->payment_hash,
|
||||
&sent->inv_preimage, sizeof(sent->inv_preimage));
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set (or not set) `refund_for` exactly as the offer did.
|
||||
* - if it sets `refund_for`:
|
||||
* - MUST set `refund_signature` to the signature of the
|
||||
* `refunded_payment_hash` using prefix `refund_signature` and
|
||||
* the `payer_key` from the to-be-refunded invoice.
|
||||
* - otherwise:
|
||||
* - MUST NOT set `refund_signature`
|
||||
*/
|
||||
if (sent->offer->refund_for) {
|
||||
sent->inv->refund_for = sent->offer->refund_for;
|
||||
/* Find original payment invoice */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays",
|
||||
&listsendpays_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_sha256(req->js, "payment_hash",
|
||||
sent->offer->refund_for);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
return sign_invoice(cmd, sent);
|
||||
}
|
||||
|
||||
static const struct plugin_command commands[] = {
|
||||
{
|
||||
"fetchinvoice",
|
||||
"payment",
|
||||
"Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
|
||||
NULL,
|
||||
json_fetchinvoice,
|
||||
},
|
||||
{
|
||||
"sendinvoice",
|
||||
"payment",
|
||||
"Request remote node for to pay this send_invoice {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
|
||||
NULL,
|
||||
json_sendinvoice,
|
||||
},
|
||||
};
|
||||
|
||||
static void init(struct plugin *p, const char *buf UNUSED,
|
||||
|
|
Loading…
Reference in New Issue