ddk_node/
command.rs

1use std::sync::Arc;
2
3use crate::cli_opts::{CliCommand, OracleCommand, WalletCommand};
4// use crate::convert::*;
5use crate::ddkrpc::ddk_rpc_client::DdkRpcClient;
6use crate::ddkrpc::{
7    AcceptOfferRequest, ConnectRequest, CreateEnumRequest, GetWalletTransactionsRequest,
8    InfoRequest, ListContractsRequest, ListOffersRequest, ListPeersRequest, ListUtxosRequest,
9    NewAddressRequest, OracleAnnouncementsRequest, SendOfferRequest, SendRequest, SyncRequest,
10    WalletBalanceRequest, WalletSyncRequest,
11};
12use anyhow::anyhow;
13use bitcoin::{Amount, Transaction};
14use chrono::TimeDelta;
15use ddk::json::*;
16use ddk::logger::{LogLevel, Logger};
17use ddk::oracle::kormir::KormirOracleClient;
18use ddk::util;
19use ddk::wallet::LocalOutput;
20use ddk_dlc::{EnumerationPayout, Payout};
21use ddk_manager::contract::contract_input::{ContractInput, ContractInputInfo, OracleInput};
22use ddk_manager::contract::enum_descriptor::EnumDescriptor;
23use ddk_manager::contract::offered_contract::OfferedContract;
24use ddk_manager::contract::{Contract, ContractDescriptor};
25use ddk_manager::Oracle;
26use ddk_messages::oracle_msgs::{EventDescriptor, OracleAnnouncement};
27use ddk_messages::{AcceptDlc, OfferDlc};
28use inquire::{Select, Text};
29use serde_json::Value;
30use tonic::transport::Channel;
31
32pub async fn cli_command(
33    arg: CliCommand,
34    client: &mut DdkRpcClient<Channel>,
35) -> anyhow::Result<()> {
36    match arg {
37        CliCommand::Info => {
38            let info = client.info(InfoRequest::default()).await?.into_inner();
39            print!("{}", serde_json::to_string_pretty(&info)?);
40        }
41        CliCommand::OfferContract(arg) => {
42            let contract_input = if arg.generate {
43                generate_contract_input().await?
44            } else {
45                interactive_contract_input(client).await?
46            };
47
48            let contract_input = serde_json::to_vec(&contract_input)?;
49            let offer = client
50                .send_offer(SendOfferRequest {
51                    contract_input,
52                    counter_party: arg.counter_party,
53                })
54                .await?
55                .into_inner();
56            let offer_dlc: OfferDlc = serde_json::from_slice(&offer.offer_dlc)?;
57            let offer = serde_json::to_string_pretty(&offer_dlc)?;
58            print!("{offer}");
59        }
60        CliCommand::Offers => {
61            let offers_request = client.list_offers(ListOffersRequest {}).await?.into_inner();
62            let offers: Vec<OfferedContract> = offers_request
63                .offers
64                .iter()
65                .map(|offer| serde_json::from_slice(offer).unwrap())
66                .collect();
67            let pretty_offer = offers
68                .iter()
69                .map(|offer| offered_contract_to_value(offer, "offer"))
70                .collect::<Vec<Value>>();
71            print!("{}", serde_json::to_string_pretty(&pretty_offer).unwrap());
72        }
73        CliCommand::AcceptOffer(accept) => {
74            let accept = client
75                .accept_offer(AcceptOfferRequest {
76                    contract_id: accept.contract_id,
77                })
78                .await?
79                .into_inner();
80            let accept_dlc: AcceptDlc = serde_json::from_slice(&accept.accept_dlc)?;
81            let accept_dlc = serde_json::to_string_pretty(&accept_dlc)?;
82            print!("{accept_dlc}");
83        }
84        CliCommand::Contracts => {
85            let contracts = client
86                .list_contracts(ListContractsRequest {})
87                .await?
88                .into_inner()
89                .contracts;
90            let contract_values = contracts
91                .iter()
92                .map(|c| {
93                    let contract: Contract = util::ser::deserialize_contract(c).unwrap();
94                    contract_to_value(&contract)
95                })
96                .collect::<Vec<Value>>();
97            print!("{}", serde_json::to_string_pretty(&contract_values)?)
98        }
99        CliCommand::Balance => {
100            let balance = client
101                .wallet_balance(WalletBalanceRequest::default())
102                .await?
103                .into_inner();
104            let pretty_string = serde_json::to_string_pretty(&balance)?;
105            println!("{pretty_string}");
106        }
107        CliCommand::Wallet(wallet) => match wallet {
108            WalletCommand::NewAddress => {
109                let address = client
110                    .new_address(NewAddressRequest::default())
111                    .await?
112                    .into_inner();
113                let pretty_string = serde_json::to_string_pretty(&address)?;
114                print!("{pretty_string}");
115            }
116            WalletCommand::Transactions => {
117                let transactions = client
118                    .get_wallet_transactions(GetWalletTransactionsRequest::default())
119                    .await?
120                    .into_inner();
121                let txns = transactions
122                    .transactions
123                    .iter()
124                    .map(|txn| serde_json::from_slice(txn).unwrap())
125                    .collect::<Vec<Transaction>>();
126                let txns = serde_json::to_string_pretty(&txns)?;
127                print!("{txns}");
128            }
129            WalletCommand::Utxos => {
130                let utxos = client
131                    .list_utxos(ListUtxosRequest::default())
132                    .await?
133                    .into_inner();
134                let local_outputs = utxos
135                    .utxos
136                    .iter()
137                    .map(|utxo| serde_json::from_slice(utxo).unwrap())
138                    .collect::<Vec<LocalOutput>>();
139
140                print!("{}", serde_json::to_string_pretty(&local_outputs).unwrap())
141            }
142            WalletCommand::Send {
143                address,
144                amount,
145                fee_rate,
146            } => {
147                let txid = client
148                    .send(SendRequest {
149                        address,
150                        amount,
151                        fee_rate,
152                    })
153                    .await?
154                    .into_inner();
155                print!("{}", serde_json::to_string_pretty(&txid)?)
156            }
157            WalletCommand::Sync => {
158                let _ = client.wallet_sync(WalletSyncRequest {}).await?.into_inner();
159                println!("Wallet synced.")
160            }
161        },
162        CliCommand::Oracle(command) => match command {
163            OracleCommand::Announcements { event_id } => {
164                let announcements = client
165                    .oracle_announcements(OracleAnnouncementsRequest { event_id })
166                    .await?
167                    .into_inner();
168                let oracle_announcement: OracleAnnouncement =
169                    serde_json::from_slice(&announcements.announcement)?;
170                print!(
171                    "{}",
172                    serde_json::to_string_pretty(&oracle_announcement).unwrap()
173                )
174            }
175            OracleCommand::CreateEnum { maturity, outcomes } => {
176                let response = client
177                    .create_enum(CreateEnumRequest { maturity, outcomes })
178                    .await?
179                    .into_inner();
180                let oracle_announcement: OracleAnnouncement =
181                    serde_json::from_slice(&response.announcement)?;
182                print!(
183                    "{}",
184                    serde_json::to_string_pretty(&oracle_announcement).unwrap()
185                )
186            }
187        },
188        CliCommand::Peers => {
189            let peers_response = client
190                .list_peers(ListPeersRequest::default())
191                .await?
192                .into_inner();
193            let peers = serde_json::to_string_pretty(&peers_response.peers)?;
194            print!("{peers}");
195        }
196        CliCommand::Connect { connect_string } => {
197            let parts = connect_string.split("@").collect::<Vec<&str>>();
198            client
199                .connect_peer(ConnectRequest {
200                    pubkey: parts[0].to_string(),
201                    host: parts[1].to_string(),
202                })
203                .await?;
204            print!("Connected to {}", parts[0])
205        }
206        CliCommand::Sync => {
207            let _ = client.sync(SyncRequest {}).await?.into_inner();
208            println!("Synced.")
209        }
210    }
211
212    Ok(())
213}
214
215async fn generate_contract_input() -> anyhow::Result<ContractInput> {
216    let contract_descriptor = ContractDescriptor::Enum(EnumDescriptor {
217        outcome_payouts: vec![
218            EnumerationPayout {
219                outcome: "CTV".to_string(),
220                payout: Payout {
221                    offer: Amount::from_sat(21_000_000),
222                    accept: Amount::ZERO,
223                },
224            },
225            EnumerationPayout {
226                outcome: "CAT".to_string(),
227                payout: Payout {
228                    offer: Amount::ZERO,
229                    accept: Amount::from_sat(21_000_000),
230                },
231            },
232        ],
233    });
234    let logger = Arc::new(Logger::console(
235        "generate_contract_input".to_string(),
236        LogLevel::Info,
237    ));
238
239    let kormir = KormirOracleClient::new("https://kormir.dlcdevkit.com", None, logger).await?;
240
241    let expiry = (chrono::Utc::now()
242        .checked_add_signed(TimeDelta::minutes(15))
243        .unwrap()
244        .timestamp()) as u32;
245
246    let announcement = kormir
247        .create_enum_event(vec!["CTV".to_string(), "CAT".to_string()], expiry)
248        .await?;
249
250    let oracle_input = OracleInput {
251        public_keys: vec![kormir.get_public_key()],
252        event_id: announcement.oracle_event.event_id,
253        threshold: 1,
254    };
255
256    Ok(ContractInput {
257        offer_collateral: Amount::from_sat(10_500_000),
258        accept_collateral: Amount::from_sat(10_500_000),
259        fee_rate: 1,
260        contract_infos: vec![ContractInputInfo {
261            contract_descriptor,
262            oracles: oracle_input,
263        }],
264    })
265}
266
267async fn interactive_contract_input(
268    client: &mut DdkRpcClient<Channel>,
269) -> anyhow::Result<ContractInput> {
270    let contract_type =
271        Select::new("Select type of contract.", vec!["enum", "numerical"]).prompt()?;
272
273    let event_id = Text::new("Oracle announcement event id:").prompt()?;
274
275    let announcement = client
276        .oracle_announcements(OracleAnnouncementsRequest { event_id })
277        .await?
278        .into_inner();
279
280    let selected_announcement: OracleAnnouncement =
281        serde_json::from_slice(&announcement.announcement)?;
282
283    let contract_input = match contract_type {
284        "numerical" => {
285            let offer_collateral: u64 =
286                Text::new("Collateral from you (sats):").prompt()?.parse()?;
287            let accept_collateral: u64 = Text::new("Collateral from counterparty (sats):")
288                .prompt()?
289                .parse()?;
290            let fee_rate: u64 = Text::new("Fee rate (sats/vbyte):").prompt()?.parse()?;
291            let min_price: u64 = Text::new("Minimum Bitcoin price:").prompt()?.parse()?;
292            let max_price: u64 = Text::new("Maximum Bitcoin price:").prompt()?.parse()?;
293            let num_steps: u64 = Text::new("Number of rounding steps:").prompt()?.parse()?;
294            let oracle_pubkey = Text::new("Oracle public key:").prompt()?;
295            let event_id = Text::new("Oracle event id:").prompt()?;
296            ddk_payouts::create_contract_input(
297                min_price,
298                max_price,
299                num_steps,
300                Amount::from_sat(offer_collateral),
301                Amount::from_sat(accept_collateral),
302                fee_rate,
303                oracle_pubkey,
304                event_id,
305            )
306        }
307        "enum" => {
308            let offer_collateral: u64 =
309                Text::new("Collateral from you (sats):").prompt()?.parse()?;
310            let accept_collateral: u64 = Text::new("Collateral from your counterparty (sats):")
311                .prompt()?
312                .parse()?;
313            let outcomes = match &selected_announcement.oracle_event.event_descriptor {
314                EventDescriptor::EnumEvent(e) => e.outcomes.clone(),
315                _ => return Err(anyhow!("Not an enum event from announcement.")),
316            };
317            let mut outcome_payouts = Vec::with_capacity(outcomes.len());
318            println!("Specify the payouts for each outcome.");
319            for outcome in outcomes {
320                println!("> Event outcome: {outcome}");
321                let offer: u64 = Text::new("Your payout:").prompt()?.parse()?;
322                let accept: u64 = Text::new("Counterparty payout:").prompt()?.parse()?;
323                let outcome_payout = EnumerationPayout {
324                    outcome,
325                    payout: Payout {
326                        offer: Amount::from_sat(offer),
327                        accept: Amount::from_sat(accept),
328                    },
329                };
330                outcome_payouts.push(outcome_payout);
331            }
332            let fee_rate: u64 = Text::new("Fee rate (sats/vbyte):").prompt()?.parse()?;
333            // TODO: list possible events.
334            ddk_payouts::enumeration::create_contract_input(
335                outcome_payouts,
336                Amount::from_sat(offer_collateral),
337                Amount::from_sat(accept_collateral),
338                fee_rate,
339                selected_announcement.oracle_public_key.to_string(),
340                selected_announcement.oracle_event.event_id.clone(),
341            )
342        }
343        _ => return Err(anyhow!("Invalid contract type.")),
344    };
345
346    Ok(contract_input)
347}