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