From 8af4cac1f9b32afb06da63dcfd392f132b178bf6 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 10:37:48 +0200 Subject: [PATCH 1/7] feat(core): Add tip method to allow tipping in the lib Signed-off-by: Vincenzo Palazzo --- coffee_core/src/coffee.rs | 4 ++++ coffee_lib/src/plugin_manager.rs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index a548cf9..64d7eaa 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -547,6 +547,10 @@ impl PluginManager for CoffeeManager { } Ok(nurse_actions) } + + async fn tip(&mut self, plugins: &[&str], amount_msat: u64) -> Result<(), CoffeeError> { + unimplemented!() + } } // FIXME: we need to move on but this is not safe and with the coffee diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index ba40e47..d49f2e4 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -61,4 +61,12 @@ pub trait PluginManager { &mut self, repos: Vec, ) -> Result, CoffeeError>; + + /// tip a specific plugins of the following amount + /// + /// The tip command required that the receiver of the + /// donation is runing the coffee core lightning plugin. + /// + /// P.S: only Bitcoin ofc + async fn tip(&mut self, plugins: &[&str], amount_msat: u64) -> Result<(), CoffeeError>; } From d684ccdbd092246c07cfb8746fad1905c647b4c1 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 10:44:05 +0200 Subject: [PATCH 2/7] feat(cmd): Support the tipping comand in the command line Signed-off-by: Vincenzo Palazzo --- coffee_cmd/src/cmd.rs | 7 +++++++ coffee_cmd/src/main.rs | 8 ++++++++ coffee_core/src/lib.rs | 2 ++ rust-toolchain | 1 + 4 files changed, 18 insertions(+) create mode 100644 rust-toolchain diff --git a/coffee_cmd/src/cmd.rs b/coffee_cmd/src/cmd.rs index 0807d15..6f60502 100644 --- a/coffee_cmd/src/cmd.rs +++ b/coffee_cmd/src/cmd.rs @@ -70,6 +70,9 @@ pub enum CoffeeCommand { #[arg(short, long, action = clap::ArgAction::SetTrue)] verify: bool, }, + /// tipping a plugins developer. + #[clap(arg_required_else_help = false)] + Tip { plugin: String, amount_msat: u64 }, } #[derive(Debug, Subcommand)] @@ -107,6 +110,10 @@ impl From<&CoffeeCommand> for coffee_core::CoffeeOperation { CoffeeCommand::Show { plugin } => Self::Show(plugin.to_owned()), CoffeeCommand::Search { plugin } => Self::Search(plugin.to_owned()), CoffeeCommand::Nurse { verify } => Self::Nurse(*verify), + CoffeeCommand::Tip { + plugin, + amount_msat, + } => Self::Tip(plugin.to_owned(), amount_msat.clone()), } } } diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index b90f09d..d3e7ffa 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -169,6 +169,14 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr coffee_term::show_nurse_result(nurse_result)?; } } + CoffeeCommand::Tip { + plugin, + amount_msat, + } => { + let _tip_result = coffee.tip(&[&plugin], amount_msat).await?; + term::info!("Tipping done with success"); + Ok(()) + } }; Ok(()) } diff --git a/coffee_core/src/lib.rs b/coffee_core/src/lib.rs index 4f0c529..9654dd0 100644 --- a/coffee_core/src/lib.rs +++ b/coffee_core/src/lib.rs @@ -22,6 +22,8 @@ pub enum CoffeeOperation { /// Search(plugin name) Search(String), Nurse(bool), + /// Tip operation + Tip(String, u64), } #[derive(Clone, Debug)] diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..07cde98 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.75 From 09bc6710e35cdd67df3478c47a9f567c2656575a Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 11:19:55 +0200 Subject: [PATCH 3/7] feat(core): implement the tip logic to pay a bolt 12 This commit is implementing the tip logic to pay and bolt12 specified inside the coffee conf and to use the command line to pay it. Signed-off-by: Vincenzo Palazzo --- coffee_cmd/src/coffee_term/command_show.rs | 35 ++++++++++++++-- coffee_cmd/src/main.rs | 5 +-- coffee_core/src/coffee.rs | 46 ++++++++++++++++++++-- coffee_lib/src/plugin.rs | 6 ++- coffee_lib/src/plugin_conf.rs | 10 ++--- coffee_lib/src/plugin_manager.rs | 6 ++- coffee_lib/src/types/mod.rs | 12 ++++++ 7 files changed, 103 insertions(+), 17 deletions(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 64267d2..b562658 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -2,12 +2,12 @@ //! the command result on the terminal! use radicle_term as term; -use radicle_term::table::TableOptions; +use term::table::TableOptions; +use term::Element; use coffee_lib::error; use coffee_lib::errors::CoffeeError; -use coffee_lib::types::response::{CoffeeList, CoffeeNurse, CoffeeRemote, NurseStatus}; -use term::Element; +use coffee_lib::types::response::{CoffeeList, CoffeeNurse, CoffeeRemote, CoffeeTip, NurseStatus}; pub fn show_list(coffee_list: Result) -> Result<(), CoffeeError> { let remotes = coffee_list?; @@ -119,6 +119,35 @@ pub fn show_nurse_result( } Err(err) => eprintln!("{}", err), } + Ok(()) +} + +pub fn show_tips(coffee_tips: &Vec) -> Result<(), CoffeeError> { + term::println( + term::format::tertiary_bold("●"), + term::format::tertiary("Plugin"), + ); + let mut table = radicle_term::Table::new(TableOptions::bordered()); + table.push([ + term::format::dim(String::from("●")), + term::format::bold(String::from("Plugin")), + term::format::bold(String::from("Receiver")), + term::format::bold(String::from("Amount Sent (msat)")), + ]); + table.divider(); + for tip in coffee_tips.iter() { + table.push([ + if tip.status == "completed" { + term::format::positive("●").into() + } else { + term::format::positive("●").into() + }, + term::format::highlight(tip.for_plugin.clone().unwrap_or_default()), + term::format::bold(tip.destination.clone().unwrap_or_default()), + term::format::highlight(tip.amount_msat.to_string()), + ]) + } + table.print(); Ok(()) } diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index d3e7ffa..3d12373 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -173,9 +173,8 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr plugin, amount_msat, } => { - let _tip_result = coffee.tip(&[&plugin], amount_msat).await?; - term::info!("Tipping done with success"); - Ok(()) + let tip_result = coffee.tip(&[&plugin], amount_msat).await?; + coffee_term::show_tips(&tip_result) } }; Ok(()) diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 64d7eaa..014d625 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -1,5 +1,4 @@ //! Coffee mod implementation - use std::collections::HashMap; use std::fmt::Debug; use std::vec::Vec; @@ -12,6 +11,7 @@ use clightningrpc_conf::{CLNConf, SyncCLNConf}; use log; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use serde_json::json; use tokio::process::Command; use coffee_github::repository::Github; @@ -548,8 +548,48 @@ impl PluginManager for CoffeeManager { Ok(nurse_actions) } - async fn tip(&mut self, plugins: &[&str], amount_msat: u64) -> Result<(), CoffeeError> { - unimplemented!() + async fn tip( + &mut self, + plugins: &[&str], + amount_msat: u64, + ) -> Result, CoffeeError> { + let plugins = self + .config + .plugins + .iter() + .filter(|plugin| plugins.contains(&plugin.name().as_str())) + .collect::>(); + let mut tips = Vec::new(); + for plugin in plugins { + let Some(tipping) = plugin.tipping_info() else { + continue; + }; + // FIXME write a tip_plugin method as method + #[derive(Debug, Deserialize)] + struct FetchResult { + invoice: String, + } + let invoice: FetchResult = self + .cln( + "fetchinvoice", + json!({ + "offer": tipping.bolt12, + "amount_msat": amount_msat, + }), + ) + .await?; + let tip: CoffeeTip = self + .cln( + "pay", + json!({ + "bolt11": invoice.invoice, + "amount_msat": amount_msat, + }), + ) + .await?; + tips.push(tip); + } + Ok(tips) } } diff --git a/coffee_lib/src/plugin.rs b/coffee_lib/src/plugin.rs index 0dba972..e32b052 100644 --- a/coffee_lib/src/plugin.rs +++ b/coffee_lib/src/plugin.rs @@ -8,7 +8,7 @@ use tokio::process::Command; use crate::errors::CoffeeError; use crate::macros::error; -use crate::plugin_conf::Conf; +use crate::plugin_conf::{Conf, Tipping}; use crate::sh; /// Plugin language definition @@ -156,6 +156,10 @@ impl Plugin { pub fn name(&self) -> String { self.name.clone() } + + pub fn tipping_info(&self) -> Option { + self.conf.as_ref().and_then(|conf| conf.tipping.clone()) + } } impl fmt::Display for Plugin { diff --git a/coffee_lib/src/plugin_conf.rs b/coffee_lib/src/plugin_conf.rs index 3cb1496..b241c6b 100644 --- a/coffee_lib/src/plugin_conf.rs +++ b/coffee_lib/src/plugin_conf.rs @@ -2,13 +2,12 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] - pub struct Conf { pub plugin: Plugin, + pub tipping: Option, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] - pub struct Plugin { pub name: String, pub version: String, @@ -24,8 +23,7 @@ pub struct Deprecaterd { pub reason: String, } -#[cfg(test)] -mod tests { - #[test] - fn test_remote() {} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Tipping { + pub bolt12: String, } diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index d49f2e4..773e026 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -68,5 +68,9 @@ pub trait PluginManager { /// donation is runing the coffee core lightning plugin. /// /// P.S: only Bitcoin ofc - async fn tip(&mut self, plugins: &[&str], amount_msat: u64) -> Result<(), CoffeeError>; + async fn tip( + &mut self, + plugins: &[&str], + amount_msat: u64, + ) -> Result, CoffeeError>; } diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index fe77e87..a894558 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -234,4 +234,16 @@ pub mod response { } } } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct CoffeeTip { + pub for_plugin: Option, + pub invoice: String, + pub status: String, + pub destination: Option, + pub amount_msat: u64, + // This includes the fee + pub amount_sent_msat: u64, + pub warning_partial_completion: Option, + } } From 4edd1d998e3e167cd9faebe82d3a82b88bd80133 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 11:39:36 +0200 Subject: [PATCH 4/7] feat(plugin): include the macro to write in a clear way the plugins Signed-off-by: Vincenzo Palazzo --- Cargo.lock | 27 ++++- coffee_plugin/Cargo.toml | 1 + coffee_plugin/src/plugin/plugin_mod.rs | 155 +++++++++---------------- 3 files changed, 81 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ee6d82..a39a0d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,6 +587,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "clightningrpc-plugin-macros" +version = "0.3.0-beta.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef81a4e59115a4ed470ea218793a76fa67d9974e6fdf9b93cb1c693130eb7755" +dependencies = [ + "clightningrpc-plugin", + "convert_case 0.5.0", + "kproc-parser", + "serde_json", +] + [[package]] name = "coffee" version = "0.0.1-alpha.1" @@ -677,6 +689,7 @@ version = "0.1.0" dependencies = [ "clightningrpc-common", "clightningrpc-plugin", + "clightningrpc-plugin-macros", "coffee_core", "coffee_lib", "log", @@ -736,6 +749,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + [[package]] name = "cookie" version = "0.16.2" @@ -896,7 +915,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -1411,6 +1430,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kproc-parser" +version = "0.0.1-beta.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a3c5d81e3af35bb269378a933889f7f45c884bdf820a9c268711035430f3f7" + [[package]] name = "language-tags" version = "0.3.2" diff --git a/coffee_plugin/Cargo.toml b/coffee_plugin/Cargo.toml index b950294..2fcbd5f 100644 --- a/coffee_plugin/Cargo.toml +++ b/coffee_plugin/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" tokio = { version = "1.22.0", features = ["rt"] } clightningrpc-common = "0.3.0-beta.3" clightningrpc-plugin = { version = "0.3.0-beta.8", features = ["log"] } +clightningrpc-plugin-macros = "0.3.0-beta.4" coffee_core = { path = "../coffee_core" } coffee_lib = { path = "../coffee_lib" } serde_json = "1" diff --git a/coffee_plugin/src/plugin/plugin_mod.rs b/coffee_plugin/src/plugin/plugin_mod.rs index 34383eb..63a3de7 100644 --- a/coffee_plugin/src/plugin/plugin_mod.rs +++ b/coffee_plugin/src/plugin/plugin_mod.rs @@ -7,9 +7,10 @@ use tokio::runtime::Runtime; use clightningrpc_common::json_utils; use clightningrpc_plugin::commands::RPCCommand; +use clightningrpc_plugin::error; use clightningrpc_plugin::plugin::{debug, info}; -use clightningrpc_plugin::{add_rpc, error}; use clightningrpc_plugin::{errors::PluginError, plugin::Plugin}; +use clightningrpc_plugin_macros::{plugin, rpc_method}; use coffee_core::coffee::CoffeeManager; use coffee_lib::errors::CoffeeError; @@ -21,10 +22,18 @@ use super::state::PluginArgs; use crate::plugin::State; pub fn build_plugin() -> Result, PluginError> { - let mut plugin = Plugin::::new(State::new(), /* dynamic */ true).on_init(on_init); - add_rpc!(plugin, CoffeeInstall); - add_rpc!(plugin, CoffeeList); - add_rpc!(plugin, CoffeeRemote); + let mut plugin = plugin! { + state: State::new(), + dynamic: true, + notification: [], + methods: [ + coffee_install, + coffee_list, + coffee_remote, + ], + hooks: [], + }; + plugin.on_init(on_init); Ok(plugin) } @@ -70,104 +79,48 @@ fn from(err: T) -> PluginError { error!("{err}") } -#[derive(Clone)] -struct CoffeeInstall { - name: String, - usage: String, - description: String, +#[rpc_method( + rpc_name = "coffee_install", + description = "install a plugin from one of the repository choosed" +)] +fn coffee_install(plugin: &mut Plugin, request: Value) -> Result { + let coffee = plugin.state.coffee(); + let mut coffee = coffee.lock().unwrap(); + let rt = Runtime::new().unwrap(); + + let request: InstallReq = serde_json::from_value(request)?; + rt.block_on(coffee.install(&request.name, false, true)) + .map_err(from)?; + Ok(json!({})) } -impl CoffeeInstall { - fn new() -> Self { - CoffeeInstall { - name: "coffee_install".to_string(), - usage: String::new(), - description: String::from("install a plugin from one of the repository choosed"), - } - } -} - -impl RPCCommand for CoffeeInstall { - fn call<'c>( - &self, - plugin: &mut Plugin, - request: serde_json::Value, - ) -> Result { - let coffee = plugin.state.coffee(); - let mut coffee = coffee.lock().unwrap(); - let rt = Runtime::new().unwrap(); - - let request: InstallReq = serde_json::from_value(request)?; - rt.block_on(coffee.install(&request.name, false, true)) - .map_err(from)?; - Ok(json!({})) - } -} - -#[derive(Clone)] -struct CoffeeList { - name: String, - usage: String, - description: String, -} - -impl CoffeeList { - fn new() -> Self { - CoffeeList { name: "coffee_list".to_owned(), usage: String::new(), description: "show all the plugin installed and if {remotes} is specified show also the one available".to_owned() } - } -} - -impl RPCCommand for CoffeeList { - fn call<'c>( - &self, - plugin: &mut Plugin, - _: serde_json::Value, - ) -> Result { - let runtime = Runtime::new().unwrap(); - let coffee = plugin.state.coffee(); - let mut coffee = coffee.lock().unwrap(); - let result = runtime.block_on(coffee.list()).map_err(from)?; - Ok(serde_json::to_value(result)?) - } -} - -#[derive(Clone)] -struct CoffeeRemote { - name: String, - usage: String, - description: String, -} - -impl CoffeeRemote { - fn new() -> Self { - CoffeeRemote { - name: "coffee_remote".to_owned(), - usage: String::new(), - description: "manage a remote".to_owned(), - } - } +#[rpc_method( + rpc_name = "coffee_list", + description = "show all the plugin installed and if {remotes} is specified show also the one available" +)] +fn coffee_list(plugin: &mut Plugin, _: Value) -> Result { + let runtime = Runtime::new().unwrap(); + let coffee = plugin.state.coffee(); + let mut coffee = coffee.lock().unwrap(); + let result = runtime.block_on(coffee.list()).map_err(from)?; + Ok(serde_json::to_value(result)?) } -impl RPCCommand for CoffeeRemote { - fn call<'c>( - &self, - plugin: &mut Plugin, - request: serde_json::Value, - ) -> Result { - let request: RemoteReq = serde_json::from_value(request)?; - let runtime = Runtime::new().unwrap(); - let coffee = plugin.state.coffee(); - - runtime - .block_on(async { - let mut coffee = coffee.lock().unwrap(); - let cmd = request.cmd().unwrap(); - match cmd { - RemoteCmd::Add => coffee.add_remote(&request.name, &request.url()).await, - RemoteCmd::Rm => coffee.rm_remote(&request.name).await, - } - }) - .map_err(from)?; - Ok(json!({})) - } +#[rpc_method(rpc_name = "coffee_remote", description = "manage a remote")] +fn coffee_remote(plugin: &mut Plugin, request: Value) -> Result { + let request: RemoteReq = serde_json::from_value(request)?; + let runtime = Runtime::new().unwrap(); + let coffee = plugin.state.coffee(); + + runtime + .block_on(async { + let mut coffee = coffee.lock().unwrap(); + let cmd = request.cmd().unwrap(); + match cmd { + RemoteCmd::Add => coffee.add_remote(&request.name, &request.url()).await, + RemoteCmd::Rm => coffee.rm_remote(&request.name).await, + } + }) + .map_err(from)?; + Ok(json!({})) } From 644ae755cdc01b24e029115cb74013de5715ab5d Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 11:58:21 +0200 Subject: [PATCH 5/7] feat(plugin): implementing the method to generate the invoice to put inside the coffee conf This commit assume that the developer of the plugins is running a lightning node and is allow to receive payments with lightning. So this is a helper method that help you to generate an BOLT 12 invoice without understand how to do it :) Signed-off-by: Vincenzo Palazzo --- coffee_cmd/src/coffee_term/command_show.rs | 29 ++++----- coffee_cmd/src/main.rs | 4 +- coffee_core/src/coffee.rs | 68 ++++++++++------------ coffee_core/src/lib.rs | 2 + coffee_lib/src/plugin_manager.rs | 6 +- coffee_lib/src/types/mod.rs | 2 +- coffee_plugin/src/plugin/plugin_mod.rs | 27 +++++++++ 7 files changed, 77 insertions(+), 61 deletions(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index b562658..819635c 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -122,11 +122,8 @@ pub fn show_nurse_result( Ok(()) } -pub fn show_tips(coffee_tips: &Vec) -> Result<(), CoffeeError> { - term::println( - term::format::tertiary_bold("●"), - term::format::tertiary("Plugin"), - ); +pub fn show_tips(coffee_tip: &CoffeeTip) -> Result<(), CoffeeError> { + term::println(term::format::bold("●"), term::format::tertiary("Plugin")); let mut table = radicle_term::Table::new(TableOptions::bordered()); table.push([ term::format::dim(String::from("●")), @@ -136,18 +133,16 @@ pub fn show_tips(coffee_tips: &Vec) -> Result<(), CoffeeError> { ]); table.divider(); - for tip in coffee_tips.iter() { - table.push([ - if tip.status == "completed" { - term::format::positive("●").into() - } else { - term::format::positive("●").into() - }, - term::format::highlight(tip.for_plugin.clone().unwrap_or_default()), - term::format::bold(tip.destination.clone().unwrap_or_default()), - term::format::highlight(tip.amount_msat.to_string()), - ]) - } + table.push([ + if coffee_tip.status == "completed" { + term::format::positive("●").into() + } else { + term::format::negative("●").into() + }, + term::format::highlight(coffee_tip.for_plugin.clone()), + term::format::bold(coffee_tip.destination.clone().unwrap_or_default()), + term::format::highlight(coffee_tip.amount_msat.to_string()), + ]); table.print(); Ok(()) } diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index 3d12373..623a6f2 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -173,8 +173,8 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr plugin, amount_msat, } => { - let tip_result = coffee.tip(&[&plugin], amount_msat).await?; - coffee_term::show_tips(&tip_result) + let tip_result = coffee.tip(&plugin, amount_msat).await?; + coffee_term::show_tips(&tip_result)?; } }; Ok(()) diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 014d625..a01e21a 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -548,48 +548,44 @@ impl PluginManager for CoffeeManager { Ok(nurse_actions) } - async fn tip( - &mut self, - plugins: &[&str], - amount_msat: u64, - ) -> Result, CoffeeError> { + async fn tip(&mut self, plugin: &str, amount_msat: u64) -> Result { let plugins = self .config .plugins .iter() - .filter(|plugin| plugins.contains(&plugin.name().as_str())) + .filter(|repo_plugin| plugin == repo_plugin.name()) .collect::>(); - let mut tips = Vec::new(); - for plugin in plugins { - let Some(tipping) = plugin.tipping_info() else { - continue; - }; - // FIXME write a tip_plugin method as method - #[derive(Debug, Deserialize)] - struct FetchResult { - invoice: String, - } - let invoice: FetchResult = self - .cln( - "fetchinvoice", - json!({ - "offer": tipping.bolt12, - "amount_msat": amount_msat, - }), - ) - .await?; - let tip: CoffeeTip = self - .cln( - "pay", - json!({ - "bolt11": invoice.invoice, - "amount_msat": amount_msat, - }), - ) - .await?; - tips.push(tip); + let plugin = plugins.first().ok_or(error!( + "No plugin with name `{plugin}` found in the plugins installed" + ))?; + + let Some(tipping) = plugin.tipping_info() else { + return Err(error!("Plugin `{plugin}` has no tipping information")); + }; + // FIXME write a tip_plugin method as method + #[derive(Debug, Deserialize)] + struct FetchResult { + invoice: String, } - Ok(tips) + let invoice: FetchResult = self + .cln( + "fetchinvoice", + json!({ + "offer": tipping.bolt12, + "amount_msat": amount_msat, + }), + ) + .await?; + let tip: CoffeeTip = self + .cln( + "pay", + json!({ + "bolt11": invoice.invoice, + "amount_msat": amount_msat, + }), + ) + .await?; + Ok(tip) } } diff --git a/coffee_core/src/lib.rs b/coffee_core/src/lib.rs index 9654dd0..fe695e1 100644 --- a/coffee_core/src/lib.rs +++ b/coffee_core/src/lib.rs @@ -23,6 +23,8 @@ pub enum CoffeeOperation { Search(String), Nurse(bool), /// Tip operation + /// + /// (plugin_name, amount_msat) Tip(String, u64), } diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index 773e026..65bca03 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -68,9 +68,5 @@ pub trait PluginManager { /// donation is runing the coffee core lightning plugin. /// /// P.S: only Bitcoin ofc - async fn tip( - &mut self, - plugins: &[&str], - amount_msat: u64, - ) -> Result, CoffeeError>; + async fn tip(&mut self, plugin: &str, amount_msat: u64) -> Result; } diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index a894558..794dc53 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -237,7 +237,7 @@ pub mod response { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CoffeeTip { - pub for_plugin: Option, + pub for_plugin: String, pub invoice: String, pub status: String, pub destination: Option, diff --git a/coffee_plugin/src/plugin/plugin_mod.rs b/coffee_plugin/src/plugin/plugin_mod.rs index 63a3de7..4f3fa40 100644 --- a/coffee_plugin/src/plugin/plugin_mod.rs +++ b/coffee_plugin/src/plugin/plugin_mod.rs @@ -2,6 +2,7 @@ //! Coffee as a core lightning plugin. use std::fmt::Display; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use tokio::runtime::Runtime; @@ -30,6 +31,7 @@ pub fn build_plugin() -> Result, PluginError> { coffee_install, coffee_list, coffee_remote, + coffee_generate_tip, ], hooks: [], }; @@ -124,3 +126,28 @@ fn coffee_remote(plugin: &mut Plugin, request: Value) -> Result, request: Value) -> Result { + let runtime = Runtime::new().unwrap(); + let coffee = plugin.state.coffee(); + + #[derive(Serialize, Deserialize, Debug)] + struct Offer { + pub bolt12: String, + } + + let offer = runtime + .block_on(async { + let mut coffee = coffee.lock().unwrap(); + coffee.cln::("offer", json!({ + "amount": "any", + "description": "Generating BOLT 12 for coffee tips regarding the plugin ...", + })).await + }) + .map_err(from)?; + Ok(serde_json::to_value(offer)?) +} From 3e5faa05726c7106bf10d4845ee0d0266609c56c Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sat, 29 Jul 2023 14:14:12 +0200 Subject: [PATCH 6/7] meta: add help message for make file Signed-off-by: Vincenzo Palazzo --- Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index a089eb4..43af85e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,6 @@ ARGS="" default: fmt $(CC) build - @make example doc-deps: $(CC) install mdbook @@ -13,26 +12,27 @@ doc-deps: fmt: $(CC) fmt --all -check: +check: ## Runs unit testing $(CC) test $(ARGS) -example: - @echo "No example for the moment" - -clean: +clean: ## Clean up everythings $(CC) clean -book: +book: ## Build the release version of documentation cd docs/docs-book; mdbook build -dev-book: +dev-book: ## Build the docs in dev mode cd docs/docs-book; mdbook serve --open -install: +install: ## Install coffee inside the local machine $(CC) install --locked --path ./coffee_cmd -integration: default +integration: default ## Runs integration testing $(CC) test -j 4 -p tests $(ARGS) setup: git config core.hooksPath .githooks + +help: ## Show Help + @grep --no-filename -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-15s\033[0m %s\n", $$1, $$2}' From e2e6fc060cd54b7f891e0e3e979f32e413b2cc3d Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sun, 30 Jul 2023 08:46:07 +0200 Subject: [PATCH 7/7] docs: add tip command inside the docs Signed-off-by: Vincenzo Palazzo --- docs/docs-book/src/support-coffee.md | 22 ++++++++++++++++++++++ docs/docs-book/src/using-coffee.md | 9 +++++++++ 2 files changed, 31 insertions(+) diff --git a/docs/docs-book/src/support-coffee.md b/docs/docs-book/src/support-coffee.md index 59f96a2..27b92f6 100644 --- a/docs/docs-book/src/support-coffee.md +++ b/docs/docs-book/src/support-coffee.md @@ -41,3 +41,25 @@ itself. With some craziness will be also possible to manage core lightning itsel Please if you feel that additional meta information needs to be specified open an issue https://github.com/coffee-tools/coffee/issues + +## Tipping + +While there are possibility to tipping anything on lightning, there is any solution to tipping a core lightning plugin +developer. So with coffee as developer you can define a BOLT 12 invoice that allow the developer of the plugin +to receive tips. So, the developer of the plugin should define a coffee manifest that specify the `tipping` info. + +An example can be: + +```yaml +--- +plugin: + name: btcli4j + version: 0.0.1 + lang: java + install: | + sh -C ./gradlew createRunnableScript + main: btcli4j-gen.sh +tipping: + bolt12: +``` + diff --git a/docs/docs-book/src/using-coffee.md b/docs/docs-book/src/using-coffee.md index 2e5558a..a23d367 100644 --- a/docs/docs-book/src/using-coffee.md +++ b/docs/docs-book/src/using-coffee.md @@ -159,6 +159,15 @@ Additionally, if you wish to perform a verification of coffee without making any coffee nurse --verify ``` _________ +### Tipping a plugin in Bitcoin + +> ✅ Implemented + +``` +coffee tip +``` + +------ ## Running coffee as a server To run Coffee as a server, you can use the `coffee_httpd` binary.