1use std::sync::Arc;
2
3use crate::cli_opts::{CliCommand, OracleCommand, WalletCommand};
4use 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 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}