1use crate::cli_opts::{CliCommand, OracleCommand, WalletCommand};
2use 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::{Amount, Transaction};
12use chrono::TimeDelta;
13use ddk::json::*;
14use ddk::oracle::kormir::KormirOracleClient;
15use ddk::util;
16use ddk::wallet::LocalOutput;
17use ddk_dlc::{EnumerationPayout, Payout};
18use ddk_manager::contract::contract_input::{ContractInput, ContractInputInfo, OracleInput};
19use ddk_manager::contract::enum_descriptor::EnumDescriptor;
20use ddk_manager::contract::offered_contract::OfferedContract;
21use ddk_manager::contract::{Contract, ContractDescriptor};
22use ddk_manager::Oracle;
23use ddk_messages::oracle_msgs::{EventDescriptor, OracleAnnouncement};
24use ddk_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: Amount::from_sat(21_000_000),
219 accept: Amount::ZERO,
220 },
221 },
222 EnumerationPayout {
223 outcome: "CAT".to_string(),
224 payout: Payout {
225 offer: Amount::ZERO,
226 accept: Amount::from_sat(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: Amount::from_sat(10_500_000),
251 accept_collateral: Amount::from_sat(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 Amount::from_sat(offer_collateral),
294 Amount::from_sat(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 {
319 offer: Amount::from_sat(offer),
320 accept: Amount::from_sat(accept),
321 },
322 };
323 outcome_payouts.push(outcome_payout);
324 }
325 let fee_rate: u64 = Text::new("Fee rate (sats/vbyte):").prompt()?.parse()?;
326 ddk_payouts::enumeration::create_contract_input(
328 outcome_payouts,
329 Amount::from_sat(offer_collateral),
330 Amount::from_sat(accept_collateral),
331 fee_rate,
332 selected_announcement.oracle_public_key.to_string(),
333 selected_announcement.oracle_event.event_id.clone(),
334 )
335 }
336 _ => return Err(anyhow!("Invalid contract type.")),
337 };
338
339 Ok(contract_input)
340}