mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
offer: allow offers in other currencies if we can convert.
This avoids a footgun where they create an offer then we can't create the invoice because they don't have a converter plugin. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
2de467274e
commit
9681d491df
@ -249,22 +249,57 @@ static struct command_result *param_invoice_payment_hash(struct command *cmd,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct offer_info {
|
||||||
|
const struct tlv_offer *offer;
|
||||||
|
const char *label;
|
||||||
|
bool *single_use;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct command_result *create_offer(struct command *cmd,
|
||||||
|
struct offer_info *offinfo)
|
||||||
|
{
|
||||||
|
struct out_req *req;
|
||||||
|
|
||||||
|
/* We simply pass this through. */
|
||||||
|
req = jsonrpc_request_start(cmd->plugin, cmd, "createoffer",
|
||||||
|
forward_result, forward_error,
|
||||||
|
offinfo);
|
||||||
|
json_add_string(req->js, "bolt12",
|
||||||
|
offer_encode(tmpctx, offinfo->offer));
|
||||||
|
if (offinfo->label)
|
||||||
|
json_add_string(req->js, "label", offinfo->label);
|
||||||
|
json_add_bool(req->js, "single_use", *offinfo->single_use);
|
||||||
|
|
||||||
|
return send_outreq(cmd->plugin, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct command_result *currency_done(struct command *cmd,
|
||||||
|
const char *buf,
|
||||||
|
const jsmntok_t *result,
|
||||||
|
struct offer_info *offinfo)
|
||||||
|
{
|
||||||
|
/* Fail in this case, by forwarding warnings. */
|
||||||
|
if (!json_get_member(buf, result, "msat"))
|
||||||
|
return forward_error(cmd, buf, result, offinfo);
|
||||||
|
|
||||||
|
return create_offer(cmd, offinfo);
|
||||||
|
}
|
||||||
|
|
||||||
struct command_result *json_offer(struct command *cmd,
|
struct command_result *json_offer(struct command *cmd,
|
||||||
const char *buffer,
|
const char *buffer,
|
||||||
const jsmntok_t *params)
|
const jsmntok_t *params)
|
||||||
{
|
{
|
||||||
const char *desc, *vendor, *label;
|
const char *desc, *vendor;
|
||||||
struct tlv_offer *offer;
|
struct tlv_offer *offer;
|
||||||
struct out_req *req;
|
struct offer_info *offinfo = tal(cmd, struct offer_info);
|
||||||
bool *single_use;
|
|
||||||
|
|
||||||
offer = tlv_offer_new(cmd);
|
offinfo->offer = offer = tlv_offer_new(offinfo);
|
||||||
|
|
||||||
if (!param(cmd, buffer, params,
|
if (!param(cmd, buffer, params,
|
||||||
p_req("amount", param_amount, offer),
|
p_req("amount", param_amount, offer),
|
||||||
p_req("description", param_escaped_string, &desc),
|
p_req("description", param_escaped_string, &desc),
|
||||||
p_opt("vendor", param_escaped_string, &vendor),
|
p_opt("vendor", param_escaped_string, &vendor),
|
||||||
p_opt("label", param_escaped_string, &label),
|
p_opt("label", param_escaped_string, &offinfo->label),
|
||||||
p_opt("quantity_min", param_u64, &offer->quantity_min),
|
p_opt("quantity_min", param_u64, &offer->quantity_min),
|
||||||
p_opt("quantity_max", param_u64, &offer->quantity_max),
|
p_opt("quantity_max", param_u64, &offer->quantity_max),
|
||||||
p_opt("absolute_expiry", param_u64, &offer->absolute_expiry),
|
p_opt("absolute_expiry", param_u64, &offer->absolute_expiry),
|
||||||
@ -278,7 +313,8 @@ struct command_result *json_offer(struct command *cmd,
|
|||||||
p_opt("recurrence_limit",
|
p_opt("recurrence_limit",
|
||||||
param_number,
|
param_number,
|
||||||
&offer->recurrence_limit),
|
&offer->recurrence_limit),
|
||||||
p_opt_def("single_use", param_bool, &single_use, false),
|
p_opt_def("single_use", param_bool,
|
||||||
|
&offinfo->single_use, false),
|
||||||
/* FIXME: hints support! */
|
/* FIXME: hints support! */
|
||||||
NULL))
|
NULL))
|
||||||
return command_param_failed();
|
return command_param_failed();
|
||||||
@ -318,16 +354,22 @@ struct command_result *json_offer(struct command *cmd,
|
|||||||
|
|
||||||
offer->node_id = tal_dup(offer, struct pubkey32, &id);
|
offer->node_id = tal_dup(offer, struct pubkey32, &id);
|
||||||
|
|
||||||
/* We simply pass this through. */
|
/* If they specify a different currency, warn if we can't
|
||||||
req = jsonrpc_request_start(cmd->plugin, cmd, "createoffer",
|
* convert it! */
|
||||||
forward_result, forward_error,
|
if (offer->currency) {
|
||||||
offer);
|
struct out_req *req;
|
||||||
json_add_string(req->js, "bolt12", offer_encode(tmpctx, offer));
|
|
||||||
if (label)
|
|
||||||
json_add_string(req->js, "label", label);
|
|
||||||
json_add_bool(req->js, "single_use", *single_use);
|
|
||||||
|
|
||||||
return send_outreq(cmd->plugin, req);
|
req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert",
|
||||||
|
currency_done, forward_error,
|
||||||
|
offinfo);
|
||||||
|
json_add_u32(req->js, "amount", 1);
|
||||||
|
json_add_stringn(req->js, "currency",
|
||||||
|
(const char *)offer->currency,
|
||||||
|
tal_bytelen(offer->currency));
|
||||||
|
return send_outreq(cmd->plugin, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_offer(cmd, offinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct command_result *json_offerout(struct command *cmd,
|
struct command_result *json_offerout(struct command *cmd,
|
||||||
|
18
tests/plugins/currencyUSDAUD5000.py
Executable file
18
tests/plugins/currencyUSDAUD5000.py
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
This plugin is used to test the currency command
|
||||||
|
"""
|
||||||
|
from pyln.client import Plugin, Millisatoshi
|
||||||
|
|
||||||
|
plugin = Plugin()
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.method("currencyconvert")
|
||||||
|
def currencyconvert(plugin, amount, currency):
|
||||||
|
"""Converts currency using given APIs."""
|
||||||
|
if currency in ('USD', 'AUD'):
|
||||||
|
return {"msat": Millisatoshi(round(amount * 5000))}
|
||||||
|
raise Exception("No values available for currency {}".format(currency.upper()))
|
||||||
|
|
||||||
|
|
||||||
|
plugin.run()
|
@ -3691,7 +3691,8 @@ def test_mpp_overload_payee(node_factory, bitcoind):
|
|||||||
|
|
||||||
@unittest.skipIf(not EXPERIMENTAL_FEATURES, "offers are experimental")
|
@unittest.skipIf(not EXPERIMENTAL_FEATURES, "offers are experimental")
|
||||||
def test_offer(node_factory, bitcoind):
|
def test_offer(node_factory, bitcoind):
|
||||||
l1 = node_factory.get_node()
|
plugin = os.path.join(os.path.dirname(__file__), 'plugins/currencyUSDAUD5000.py')
|
||||||
|
l1 = node_factory.get_node(options={'plugin': plugin})
|
||||||
|
|
||||||
bolt12tool = os.path.join(os.path.dirname(__file__), "..", "devtools", "bolt12-cli")
|
bolt12tool = os.path.join(os.path.dirname(__file__), "..", "devtools", "bolt12-cli")
|
||||||
# Try different amount strings
|
# Try different amount strings
|
||||||
@ -3719,6 +3720,11 @@ def test_offer(node_factory, bitcoind):
|
|||||||
l1.rpc.call('offer', {'amount': '1.1AUD',
|
l1.rpc.call('offer', {'amount': '1.1AUD',
|
||||||
'description': 'test for invalid amount'})
|
'description': 'test for invalid amount'})
|
||||||
|
|
||||||
|
# Make sure it fails on unknown currencies.
|
||||||
|
with pytest.raises(RpcError, match='No values available for currency EUR'):
|
||||||
|
l1.rpc.call('offer', {'amount': '1.00EUR',
|
||||||
|
'description': 'test for unknown currency'})
|
||||||
|
|
||||||
# Test label and description
|
# Test label and description
|
||||||
weird_label = 'label \\ " \t \n'
|
weird_label = 'label \\ " \t \n'
|
||||||
weird_desc = 'description \\ " \t \n ナンセンス 1杯'
|
weird_desc = 'description \\ " \t \n ナンセンス 1杯'
|
||||||
|
Loading…
Reference in New Issue
Block a user