ddk_node/
command.rs

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