Skip to content

Commit 162bdbe

Browse files
committed
xpay: add sendamount command
Changelog-Added: sendamount command, to make a payment specifying the desired amount to send instead of the amount to be received. Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
1 parent b581b7e commit 162bdbe

2 files changed

Lines changed: 381 additions & 7 deletions

File tree

‎plugins/xpay/xpay.c‎

Lines changed: 294 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ static struct command_result *xpay_core(struct command *cmd,
191191
u32 maxdelay,
192192
bool as_pay);
193193

194+
static struct command_result *
195+
sendamount_core(struct command *cmd, const char *invstring TAKES,
196+
const struct amount_msat sendamount_msat,
197+
const struct amount_msat invoiceamount_msat,
198+
const struct amount_msat *maxfee, const char **layers,
199+
u32 retryfor, u32 maxdelay);
200+
194201
/* Wrapper for pending commands (ignores return) */
195202
static void was_pending(const struct command_result *res)
196203
{
@@ -1862,10 +1869,10 @@ check_offer_sendamount_payable(struct command *cmd, const char *offerstr)
18621869
"Cannot pay offer in different currency %s",
18631870
b12offer->offer_currency);
18641871
/* Can only be applied to *any amount* offers. */
1865-
if (b12offer->offer_amount) {
1872+
if (b12offer->offer_amount)
18661873
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
1867-
"Offer does not allow any amount.");
1868-
}
1874+
"Expecting an offer with no amount.");
1875+
18691876
/* Not recurrence, one time only. */
18701877
if (offer_recurrence(b12offer))
18711878
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
@@ -1892,10 +1899,17 @@ invoice_fetched(struct command *cmd,
18921899
char *inv;
18931900

18941901
inv = json_strdup(NULL, buf, json_get_member(buf, result, "invoice"));
1895-
return xpay_core(cmd, take(to_canonical_invstr(NULL, take(inv))),
1896-
NULL, params->maxfee, params->layers,
1897-
params->retryfor, params->partial, params->maxdelay,
1898-
false);
1902+
if (params->includefees_msat) {
1903+
return sendamount_core(
1904+
cmd, take(to_canonical_invstr(NULL, take(inv))),
1905+
*params->includefees_msat, *params->msat, params->maxfee,
1906+
params->layers, params->retryfor, params->maxdelay);
1907+
} else {
1908+
return xpay_core(
1909+
cmd, take(to_canonical_invstr(NULL, take(inv))), NULL,
1910+
params->maxfee, params->layers, params->retryfor,
1911+
params->partial, params->maxdelay, false);
1912+
}
18991913
}
19001914

19011915
static struct command_result *
@@ -2223,6 +2237,187 @@ static struct command_result *xpay_core(struct command *cmd,
22232237
return send_outreq(req);
22242238
}
22252239

2240+
/* Same pattern as xpay_core, but specific to sendamount. */
2241+
static struct command_result *
2242+
sendamount_core(struct command *cmd, const char *invstring TAKES,
2243+
const struct amount_msat sendamount_msat,
2244+
const struct amount_msat invoiceamount_msat,
2245+
const struct amount_msat *maxfee, const char **layers,
2246+
u32 retryfor, u32 maxdelay)
2247+
{
2248+
struct payment *payment = tal(cmd, struct payment);
2249+
struct xpay *xpay = xpay_of(cmd->plugin);
2250+
struct gossmap *gossmap = get_gossmap(xpay);
2251+
struct amount_msat diff_msat;
2252+
char *err;
2253+
struct out_req *req;
2254+
u64 now, invexpiry;
2255+
2256+
list_head_init(&payment->current_attempts);
2257+
list_head_init(&payment->past_attempts);
2258+
payment->plugin = cmd->plugin;
2259+
payment->cmd = cmd;
2260+
payment->amount_being_routed = AMOUNT_MSAT(0);
2261+
payment->group_id = pseudorand(INT64_MAX);
2262+
payment->total_num_attempts = payment->num_failures = 0;
2263+
payment->requests = tal_arr(payment, struct out_req *, 0);
2264+
payment->prior_results = tal_strdup(payment, "");
2265+
payment->deadline = timemono_add(time_mono(), time_from_sec(retryfor));
2266+
payment->start_time = clock_time();
2267+
payment->start_blockheight = xpay->blockheight;
2268+
payment->pay_compat = false;
2269+
payment->invstring = tal_strdup(payment, invstring);
2270+
payment->includefees = true;
2271+
payment->maxdelay = maxdelay;
2272+
payment->disable_mpp = true;
2273+
payment->maxparts = 1;
2274+
if (layers)
2275+
payment->layers = tal_dup_talarr(payment, const char *, layers);
2276+
else
2277+
payment->layers = NULL;
2278+
2279+
payment->amount = sendamount_msat;
2280+
payment->mpp_amount = AMOUNT_MSAT(0);
2281+
2282+
if (amount_msat_is_zero(sendamount_msat))
2283+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
2284+
"Cannot send zero amount");
2285+
2286+
if (!maxfee) {
2287+
/* The default maximum fee function is piecewise linear and
2288+
* continuous. It always satisfies maxfee <= sendamount/2. */
2289+
if (amount_msat_less_eq(sendamount_msat, AMOUNT_MSAT(10000))) {
2290+
/* Very small amounts (<=10sats) we use a very high
2291+
* relative fee but never above 50%. */
2292+
payment->maxfee = amount_msat_div(sendamount_msat, 2);
2293+
} else if (amount_msat_less_eq(sendamount_msat,
2294+
AMOUNT_MSAT(500000))) {
2295+
/* In this medium range we fix the maximum fee to 5sats.
2296+
*/
2297+
payment->maxfee = AMOUNT_MSAT(5000);
2298+
} else {
2299+
/* For high value payments (>=500sat), the default is
2300+
* 1%.*/
2301+
payment->maxfee = amount_msat_div(sendamount_msat, 100);
2302+
}
2303+
} else
2304+
payment->maxfee = *maxfee;
2305+
2306+
if (amount_msat_sub(&diff_msat, sendamount_msat, invoiceamount_msat)) {
2307+
/* Even if maxfee is specified, we cap it at half_amount because
2308+
* we cannot pay more than half_amount in fees otherwise the
2309+
* received amount will be smaller than the invoice amount. */
2310+
payment->maxfee = amount_msat_min(payment->maxfee, diff_msat);
2311+
} else {
2312+
return command_fail(
2313+
cmd, JSONRPC2_INVALID_PARAMS,
2314+
"Invoice amount (%s) is greater than send amount (%s).",
2315+
fmt_amount_msat(tmpctx, invoiceamount_msat),
2316+
fmt_amount_msat(tmpctx, sendamount_msat));
2317+
}
2318+
2319+
if (bolt12_has_prefix(payment->invstring)) {
2320+
// bolt12 invoice
2321+
struct tlv_invoice *b12inv = invoice_decode(
2322+
tmpctx, payment->invstring, strlen(payment->invstring),
2323+
plugin_feature_set(cmd->plugin), chainparams, &err);
2324+
if (!b12inv)
2325+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
2326+
"Invalid bolt12 invoice: %s", err);
2327+
2328+
invexpiry = invoice_expiry(b12inv);
2329+
if (!amount_msat_eq(invoiceamount_msat,
2330+
amount_msat(*b12inv->invoice_amount)))
2331+
return command_fail(
2332+
cmd, JSONRPC2_INVALID_PARAMS,
2333+
"Expecting a bolt12 invoice with amount %s, got %s "
2334+
"instead",
2335+
fmt_amount_msat(tmpctx, invoiceamount_msat),
2336+
fmt_amount_msat(
2337+
tmpctx, amount_msat(*b12inv->invoice_amount)));
2338+
2339+
payment->route_hints = NULL;
2340+
payment->payment_secret = NULL;
2341+
payment->payment_metadata = NULL;
2342+
payment->paths = tal_steal(payment, b12inv->invoice_paths);
2343+
payment->payinfos =
2344+
tal_steal(payment, b12inv->invoice_blindedpay);
2345+
payment->payment_hash = *b12inv->invoice_payment_hash;
2346+
payment->destination = *b12inv->invoice_node_id;
2347+
/* Resolve introduction points if possible */
2348+
for (size_t i = 0; i < tal_count(payment->paths); i++) {
2349+
if (!gossmap_scidd_pubkey(
2350+
gossmap, &payment->paths[i]->first_node_id)) {
2351+
payment_log(
2352+
payment, LOG_UNUSUAL,
2353+
"Could not resolve blinded path start %s: "
2354+
"discarding",
2355+
fmt_sciddir_or_pubkey(
2356+
tmpctx,
2357+
&payment->paths[i]->first_node_id));
2358+
tal_arr_remove(&payment->paths, i);
2359+
tal_arr_remove(&payment->payinfos, i);
2360+
i--;
2361+
}
2362+
}
2363+
/* In case we remove them all! */
2364+
if (tal_count(payment->paths) == 0)
2365+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
2366+
"Could not resolve any paths: "
2367+
"unknown short_channel_id");
2368+
2369+
/* We don't actually know the final_cltv for blinded
2370+
* paths, we just know the cltv we use to enter the
2371+
* final hop. */
2372+
payment->final_cltv = 0;
2373+
} else {
2374+
// bolt11 invoice
2375+
struct bolt11 *b11 = bolt11_decode(
2376+
tmpctx, payment->invstring, plugin_feature_set(cmd->plugin),
2377+
NULL, chainparams, &err);
2378+
if (!b11)
2379+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
2380+
"Invalid bolt11 invoice: %s", err);
2381+
payment->route_hints = tal_steal(payment, b11->routes);
2382+
payment->paths = NULL;
2383+
payment->payinfos = NULL;
2384+
if (!pubkey_from_node_id(&payment->destination,
2385+
&b11->receiver_id))
2386+
return command_fail(
2387+
cmd, JSONRPC2_INVALID_PARAMS,
2388+
"Invalid destination id %s",
2389+
fmt_node_id(tmpctx, &b11->receiver_id));
2390+
2391+
payment->final_cltv = b11->min_final_cltv_expiry;
2392+
payment->payment_hash = b11->payment_hash;
2393+
payment->payment_secret =
2394+
tal_steal(payment, b11->payment_secret);
2395+
if (!b11->payment_secret)
2396+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
2397+
"need payment_secret");
2398+
payment->payment_metadata = tal_steal(payment, b11->metadata);
2399+
if (b11->msat)
2400+
return command_fail(
2401+
cmd, JSONRPC2_INVALID_PARAMS,
2402+
"Expecting a Bolt11 invoice with no amount.");
2403+
2404+
invexpiry = b11->timestamp + b11->expiry;
2405+
}
2406+
2407+
now = clock_time().ts.tv_sec;
2408+
if (now > invexpiry)
2409+
return command_fail(cmd, PAY_INVOICE_EXPIRED,
2410+
"Invoice expired %" PRIu64 " seconds ago",
2411+
now - invexpiry);
2412+
2413+
/* Now preapprove, then start payment. */
2414+
req = jsonrpc_request_start(cmd, "preapproveinvoice",
2415+
&preapproveinvoice_succeed, &forward_error,
2416+
payment);
2417+
json_add_string(req->js, "bolt11", payment->invstring);
2418+
return send_outreq(req);
2419+
}
2420+
22262421
static struct command_result *json_xpay(struct command *cmd,
22272422
const char *buffer,
22282423
const jsmntok_t *params)
@@ -2237,6 +2432,94 @@ static struct command_result *json_xpay_as_pay(struct command *cmd,
22372432
return json_xpay_params(cmd, buffer, params, true);
22382433
}
22392434

2435+
/* When to use this?
2436+
* If we want to be specific about the amount we send and not about the amount
2437+
* the other end receives.
2438+
* Same pattern as json_xpay. */
2439+
static struct command_result *json_sendamount(struct command *cmd,
2440+
const char *buffer,
2441+
const jsmntok_t *params)
2442+
{
2443+
struct amount_msat *send_msat, *maxfee;
2444+
struct amount_msat invoice_msat;
2445+
const char *invstring;
2446+
const char **layers;
2447+
u32 *maxdelay;
2448+
const char *payer_note;
2449+
unsigned int *retryfor;
2450+
struct xpay_params *xparams;
2451+
struct out_req *req;
2452+
2453+
if (!param_check(cmd, buffer, params,
2454+
p_req("invstring", param_invstring, &invstring),
2455+
p_req("sendamount_msat", param_msat, &send_msat),
2456+
p_opt("maxfee", param_msat, &maxfee),
2457+
p_opt("layers", param_string_array, &layers),
2458+
p_opt_def("retry_for", param_number, &retryfor, 60),
2459+
p_opt_def("maxdelay", param_u32, &maxdelay, 2016),
2460+
p_opt("payer_note", param_string, &payer_note), NULL))
2461+
return command_param_failed();
2462+
2463+
// FIXME: why does xpay returns this only after
2464+
// preapproveinvoice_succeed?
2465+
if (command_check_only(cmd))
2466+
return command_check_done(cmd);
2467+
2468+
/* We need the recepient to generate an invoice for send_msat/2
2469+
* so that we can deliver any value from send_msat/2 up to
2470+
* send_msat. We don't know the fees in advance. We could use
2471+
* getroutes to estimate some fees though, but the use of
2472+
* blinded paths already invalidates this procedure: we can't
2473+
* use getroutes until we have an invoice. */
2474+
invoice_msat = amount_msat_div_ceil(*send_msat, 2);
2475+
// FIXME: could this work with invoice_msat = 0?
2476+
2477+
/* Bolt12 offer */
2478+
if (bolt12_has_prefix(invstring)) {
2479+
struct command_result *ret;
2480+
ret = check_offer_sendamount_payable(cmd, invstring);
2481+
if (ret)
2482+
return ret;
2483+
xparams = tal(cmd, struct xpay_params);
2484+
xparams->msat =
2485+
tal_dup(xparams, struct amount_msat, &invoice_msat);
2486+
xparams->maxfee = maxfee;
2487+
xparams->partial = NULL;
2488+
xparams->includefees_msat = send_msat;
2489+
xparams->layers = layers;
2490+
xparams->retryfor = *retryfor;
2491+
xparams->maxdelay = *maxdelay;
2492+
xparams->bip353 = NULL;
2493+
xparams->payer_note = payer_note;
2494+
2495+
return do_fetchinvoice(cmd, invstring, xparams);
2496+
}
2497+
2498+
/* BIP353 */
2499+
if (strchr(invstring, '@')) {
2500+
xparams = tal(cmd, struct xpay_params);
2501+
xparams->msat =
2502+
tal_dup(xparams, struct amount_msat, &invoice_msat);
2503+
xparams->maxfee = maxfee;
2504+
xparams->partial = NULL;
2505+
xparams->includefees_msat = send_msat;
2506+
xparams->layers = layers;
2507+
xparams->retryfor = *retryfor;
2508+
xparams->maxdelay = *maxdelay;
2509+
xparams->bip353 = invstring;
2510+
xparams->payer_note = payer_note;
2511+
2512+
req = jsonrpc_request_start(cmd, "fetchbip353", bip353_fetched,
2513+
forward_error, xparams);
2514+
json_add_string(req->js, "address", invstring);
2515+
return send_outreq(req);
2516+
}
2517+
2518+
/* Probably a bolt11, an invoice already. */
2519+
return sendamount_core(cmd, invstring, *send_msat, invoice_msat, maxfee,
2520+
layers, *retryfor, *maxdelay);
2521+
}
2522+
22402523
static struct command_result *getchaininfo_done(struct command *aux_cmd,
22412524
const char *method,
22422525
const char *buf,
@@ -2371,6 +2654,10 @@ static const struct plugin_command commands[] = {
23712654
"xpay-as-pay",
23722655
json_xpay_as_pay,
23732656
},
2657+
{
2658+
"sendamount",
2659+
json_sendamount,
2660+
},
23742661
};
23752662

23762663
static struct command_result *handle_block_added(struct command *cmd,

0 commit comments

Comments
 (0)