1#[cfg(feature = "rust-bitcoin")]
12use bitcoin::hashes::sha256;
13#[cfg(feature = "rust-bitcoin")]
14use bitcoin::hashes::{Hash, HashEngine};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::time::{SystemTime, UNIX_EPOCH};
18use uuid;
19#[cfg(feature = "rust-bitcoin")]
22use bitcoin::secp256k1::{Message, Secp256k1, SecretKey};
23use thiserror::Error;
24use uuid::Uuid;
25
26pub type DlcResult<T> = Result<T, DlcError>;
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct DlcContract {
33 pub id: String,
34 pub collateral: u64,
35 pub oracle_event_id: String,
36 pub outcomes: Vec<String>,
37 pub payouts: Vec<u64>,
38 pub status: DlcContractStatus,
39 pub created_at: u64,
40 pub updated_at: Option<u64>,
41 pub signatures: Vec<DlcSignature>,
42 pub metadata: HashMap<String, String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct DlcSignature {
48 pub id: String,
49 pub contract_id: String,
50 pub signer: String,
51 pub signature: Vec<u8>,
52 pub message: Vec<u8>,
53 pub created_at: u64,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DlcExecution {
59 pub id: String,
60 pub contract_id: String,
61 pub outcome: String,
62 pub payout: u64,
63 pub transaction_id: String,
64 pub executed_at: u64,
65 pub oracle_attestation: Vec<u8>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum ExecutionStatus {
71 Pending,
72 Confirmed,
73 Failed,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct OracleEvent {
79 pub id: String,
80 pub event_type: OracleEventType,
81 pub outcome_domain: Vec<String>,
82 pub start_time: u64,
83 pub end_time: u64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub enum OracleEventType {
89 PriceFeed,
90 BinaryOutcome,
91 MultipleChoice,
92 NumericOutcome,
93 Sports,
94 Election,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct OracleAttestation {
100 pub event_id: String,
101 pub outcome: String,
102 pub signature: String,
103 pub timestamp: u64,
104}
105
106pub struct ContractManager {
109 #[cfg(feature = "rust-bitcoin")]
110 secp: Secp256k1<bitcoin::secp256k1::All>,
111 #[cfg(not(feature = "rust-bitcoin"))]
112 _placeholder: (),
113}
114
115impl Default for ContractManager {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl ContractManager {
122 #[cfg(feature = "rust-bitcoin")]
125 pub fn new() -> Self {
126 Self {
127 secp: Secp256k1::new(),
128 }
129 }
130
131 #[cfg(not(feature = "rust-bitcoin"))]
134 pub fn new() -> Self {
135 Self { _placeholder: () }
136 }
137
138 pub async fn create_contract(
141 &self,
142 _settlement_address: &str,
143 collateral: u64,
144 oracle_info: &OracleEvent,
145 payout_curve: &PayoutCurve,
146 ) -> Result<DlcContract, DlcError> {
147 let outcomes = oracle_info.outcome_domain.clone();
150
151 let mut payouts = Vec::new();
153 for (i, _) in outcomes.iter().enumerate() {
154 let x = i as f64;
156 let payout =
157 ((payout_curve.slope * x + payout_curve.intercept) * collateral as f64) as u64;
158 payouts.push(payout);
159 }
160
161 let now = SystemTime::now()
163 .duration_since(UNIX_EPOCH)
164 .unwrap()
165 .as_secs();
166 Ok(DlcContract {
167 id: format!("dlc-{now}"),
168 collateral,
169 oracle_event_id: oracle_info.id.clone(),
170 outcomes,
171 payouts,
172 status: DlcContractStatus::Created,
173 created_at: now,
174 updated_at: None,
175 signatures: Vec::new(),
176 metadata: HashMap::new(),
177 })
178 }
179
180 #[cfg(feature = "rust-bitcoin")]
183 pub fn sign_contract(
184 &self,
185 contract: &DlcContract,
186 private_key: &SecretKey,
187 ) -> Result<DlcSignature, DlcError> {
188 let contract_hash = self.hash_contract(contract)?;
192 let message = Message::from_digest_slice(&contract_hash)
193 .map_err(|_| DlcError::ContractError("Invalid message format".to_string()))?;
194
195 let signature = self.secp.sign_ecdsa(&message, private_key);
196
197 Ok(DlcSignature {
199 id: format!("sig_{}", Uuid::new_v4()),
200 contract_id: contract.id.clone(),
201 signer: "self".to_string(), signature: signature.serialize_der().to_vec(),
203 message: contract_hash.to_vec(),
204 created_at: chrono::Utc::now().timestamp() as u64,
205 })
206 }
207
208 pub fn execute_contract(
211 &self,
212 contract: &DlcContract,
213 attestation: &OracleAttestation,
214 ) -> Result<DlcExecution, DlcError> {
215 let outcome_index = contract
217 .outcomes
218 .iter()
219 .position(|o| o == &attestation.outcome)
220 .ok_or_else(|| DlcError::ContractError("Invalid outcome".to_string()))?;
221
222 let payout = contract.payouts[outcome_index];
224
225 Ok(DlcExecution {
227 id: format!("exec_{}", Uuid::new_v4()),
228 contract_id: contract.id.clone(),
229 outcome: attestation.outcome.clone(),
230 payout,
231 transaction_id: format!("tx_{}", Uuid::new_v4()), executed_at: chrono::Utc::now().timestamp() as u64,
233 oracle_attestation: attestation.signature.clone().into_bytes(),
236 })
237 }
238
239 #[cfg(feature = "rust-bitcoin")]
242 fn hash_contract(&self, contract: &DlcContract) -> Result<[u8; 32], DlcError> {
243 let mut engine = sha256::HashEngine::default();
244
245 engine.input(contract.id.as_bytes());
247 engine.input(&contract.collateral.to_le_bytes());
248 engine.input(contract.oracle_event_id.as_bytes());
249
250 for outcome in &contract.outcomes {
251 engine.input(outcome.as_bytes());
252 }
253
254 for payout in &contract.payouts {
255 engine.input(&payout.to_le_bytes());
256 }
257
258 let hash = sha256::Hash::from_engine(engine);
260
261 let mut result = [0u8; 32];
263 result.copy_from_slice(hash.as_ref());
264 Ok(result)
265 }
266
267 #[cfg(feature = "rust-bitcoin")]
270 pub fn into_inner(hash_bytes: &[u8; 32]) -> sha256::Hash {
271 sha256::Hash::from_slice(hash_bytes).unwrap()
272 }
273
274 pub fn broadcast_contract(&self, contract: &DlcContract) -> Result<String, DlcError> {
277 let tx_id = format!("tx-{}", contract.id);
280
281 println!(
283 "[AIR-3][AIS-3][BPC-3][RES-3] Broadcasting DLC contract: {}",
284 contract.id
285 );
286
287 Ok(tx_id)
288 }
289
290 pub fn settle_contract(
293 &self,
294 contract: &DlcContract,
295 attestation: &OracleAttestation,
296 ) -> Result<DlcExecution, DlcError> {
297 let execution = self.execute_contract(contract, attestation)?;
299
300 let tx_id = format!("settlement-{}", contract.id);
302
303 let settlement_execution = DlcExecution {
305 id: format!("exec_{}", Uuid::new_v4()),
306 contract_id: execution.contract_id,
307 outcome: execution.outcome,
308 payout: execution.payout,
309 executed_at: execution.executed_at,
310 transaction_id: tx_id,
311 oracle_attestation: attestation.signature.clone().into_bytes(),
314 };
315
316 Ok(settlement_execution)
317 }
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
323pub enum DlcContractStatus {
324 Created,
325 Signed,
326 Funded,
327 Broadcast,
328 Executed,
329 Settled,
330 Refunded,
331 Expired,
332}
333
334#[derive(Debug, Error)]
337pub enum DlcError {
338 #[error("Invalid parameters: {0}")]
339 InvalidParameters(String),
340
341 #[error("Invalid signature: {0}")]
342 InvalidSignature(String),
343
344 #[error("Contract error: {0}")]
345 ContractError(String),
346
347 #[error("Oracle error: {0}")]
348 OracleError(String),
349
350 #[error("Serialization error: {0}")]
351 SerializationError(String),
352
353 #[error("Bitcoin error: {0}")]
354 BitcoinError(String),
355
356 #[error("Internal error: {0}")]
357 InternalError(String),
358}
359
360impl From<&DlcError> for String {
361 fn from(error: &DlcError) -> Self {
362 match error {
363 DlcError::InvalidParameters(e) => format!("Invalid parameters: {e}"),
364 DlcError::InvalidSignature(e) => format!("Invalid signature: {e}"),
365 DlcError::BitcoinError(e) => format!("Bitcoin error: {e}"),
366 DlcError::ContractError(e) => format!("Contract error: {e}"),
367 DlcError::OracleError(e) => format!("Oracle error: {e}"),
368 DlcError::SerializationError(e) => format!("Serialization error: {e}"),
369 DlcError::InternalError(e) => format!("Internal error: {e}"),
370 }
371 }
372}
373
374#[derive(Serialize, Deserialize, Debug, Clone)]
377pub struct DlcConfig {
378 pub oracle_pubkey: String, pub contract_type: DlcContractType,
380 pub settlement_address: String,
381 pub collateral: u64,
382 pub event_descriptor: EventDescriptor,
383 pub payout_curve: PayoutCurve,
384 pub oracle_event_id: String,
385 pub private_key: String,
388 pub oracle_event_type: OracleEventType,
389 pub outcome_domain: Vec<String>,
390 pub base_point: (f64, f64),
391 pub slope: f64,
392 pub intercept: f64,
393}
394
395#[derive(Serialize, Deserialize, Debug, Clone)]
396pub enum DlcContractType {
397 Binary,
398 Continuous,
399 Discrete,
400}
401
402#[derive(Serialize, Deserialize, Debug, Clone)]
403pub struct EventDescriptor {
404 pub event_id: String,
405 pub event_type: EventType,
406 pub outcome_domain: Vec<String>,
407}
408
409#[derive(Serialize, Deserialize, Debug, Clone)]
410pub enum EventType {
411 Binary,
412 PriceFeed,
413 Sports,
414 Election,
415}
416
417#[derive(Serialize, Deserialize, Debug, Clone)]
418pub struct PayoutCurve {
419 pub base_point: (f64, f64),
420 pub slope: f64,
421 pub intercept: f64,
422}
423
424impl Default for DlcConfig {
430 fn default() -> Self {
431 Self {
432 oracle_pubkey: "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
434 .to_string(), contract_type: DlcContractType::Continuous,
436 settlement_address: "bc1q...".to_string(),
437 collateral: 1000000, private_key: "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tQTiKDH".to_string(), event_descriptor: EventDescriptor {
440 event_id: "event_123".to_string(),
441 event_type: EventType::PriceFeed,
442 outcome_domain: vec!["0-100".to_string()],
443 },
444 payout_curve: PayoutCurve {
445 base_point: (50.0, 0.5),
446 slope: 0.01,
447 intercept: 0.0,
448 },
449 oracle_event_id: "oracle_event_123".to_string(),
450 oracle_event_type: OracleEventType::PriceFeed,
451 outcome_domain: vec!["0-100".to_string()],
452 base_point: (50.0, 0.5),
453 slope: 0.01,
454 intercept: 0.0,
455 }
456 }
457}
458
459#[derive(Debug, Clone)]
462pub struct OracleClient {
463 pub oracle_pubkey: String,
465 pub attestations: HashMap<String, NonInteractiveOracleAttestation>,
467}
468
469impl OracleClient {
470 pub fn new(oracle_pubkey: &str) -> Self {
473 Self {
474 oracle_pubkey: oracle_pubkey.to_string(),
475 attestations: HashMap::new(),
476 }
477 }
478
479 pub async fn get_event_info(&self, event_id: &str) -> DlcResult<OracleEvent> {
482 let now = SystemTime::now()
485 .duration_since(UNIX_EPOCH)
486 .unwrap()
487 .as_secs();
488
489 Ok(OracleEvent {
490 id: event_id.to_string(),
491 event_type: OracleEventType::PriceFeed,
492 outcome_domain: vec![
493 "0".to_string(),
494 "1".to_string(),
495 "2".to_string(),
496 "3".to_string(),
497 "4".to_string(),
498 ],
499 start_time: now,
500 end_time: now + 86400, })
502 }
503
504 pub fn verify_attestation(&self, _attestation: &OracleAttestation) -> DlcResult<bool> {
507 Ok(true)
510 }
511
512 pub async fn connect(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
514 Ok(())
516 }
517
518 pub async fn create_contract(
520 &mut self,
521 _contract_id: &str,
522 _contract_info: DlcContractInfo,
523 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
524 Ok(format!("contract_{_contract_id}"))
526 }
527
528 pub async fn close_contract(
530 &mut self,
531 _contract_id: &str,
532 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
533 Ok(())
535 }
536
537 pub async fn get_signature(
539 &self,
540 event_id: &str,
541 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
542 Ok(format!("sig_{event_id}").into_bytes())
544 }
545
546 pub fn is_connected(&self) -> bool {
548 true
550 }
551
552 pub async fn create_dlc(
554 &mut self,
555 params: DlcParameters,
556 ) -> Result<DlcContract, Box<dyn std::error::Error + Send + Sync>> {
557 Ok(DlcContract {
559 id: uuid::Uuid::new_v4().to_string(),
560 collateral: params.funding_amount,
561 oracle_event_id: params.oracle_info.event_id,
562 outcomes: vec!["outcome1".to_string(), "outcome2".to_string()],
563 payouts: vec![params.funding_amount / 2, params.funding_amount / 2],
564 status: DlcContractStatus::Created,
565 created_at: SystemTime::now()
566 .duration_since(UNIX_EPOCH)
567 .unwrap()
568 .as_secs(),
569 updated_at: None,
570 signatures: Vec::new(),
571 metadata: HashMap::new(),
572 })
573 }
574}
575
576pub struct DlcManager {
579 config: DlcConfig,
580 oracle_client: OracleClient,
581 contract_manager: ContractManager,
582}
583
584impl DlcManager {
585 pub fn new(config: DlcConfig) -> Self {
588 let oracle_client = OracleClient::new(&config.oracle_pubkey);
590 let contract_manager = ContractManager::new();
591 Self {
592 config,
593 oracle_client,
594 contract_manager,
595 }
596 }
597
598 pub async fn create_contract(&self) -> DlcResult<DlcContract> {
601 let oracle_info = self
602 .oracle_client
603 .get_event_info(&self.config.oracle_event_id)
604 .await?;
605 let contract = self
606 .contract_manager
607 .create_contract(
608 &self.config.settlement_address,
609 self.config.collateral,
610 &oracle_info, &self.config.payout_curve,
612 )
613 .await?;
614 Ok(contract)
615 }
616
617 #[cfg(feature = "rust-bitcoin")]
620 pub async fn sign_contract(&self, contract: &DlcContract) -> DlcResult<DlcSignature> {
621 let private_key =
624 match SecretKey::from_slice(&hex::decode(&self.config.private_key).map_err(|_| {
625 DlcError::SerializationError("Failed to decode private key hex".to_string())
626 })?) {
627 Ok(key) => key,
628 Err(_) => {
629 return Err(DlcError::InvalidSignature(
630 "Invalid private key format".to_string(),
631 ))
632 }
633 };
634
635 self.contract_manager.sign_contract(contract, &private_key)
637 }
638
639 pub async fn broadcast_contract(&self, contract: DlcContract) -> DlcResult<DlcContract> {
643 let signature = DlcSignature {
645 id: format!("sig_{}", uuid::Uuid::new_v4()),
646 contract_id: contract.id.clone(),
647 signer: "self".to_string(),
648 signature: vec![0, 1, 2, 3], message: vec![4, 5, 6, 7], created_at: chrono::Utc::now().timestamp() as u64,
651 };
652
653 let mut updated_contract = contract;
655 updated_contract.status = DlcContractStatus::Broadcast;
656 updated_contract.signatures.push(signature);
657 updated_contract.updated_at = Some(chrono::Utc::now().timestamp() as u64);
658
659 Ok(updated_contract)
662 }
663
664 pub async fn settle_contract(
668 &self,
669 contract: DlcContract,
670 outcome: String,
671 ) -> DlcResult<DlcContract> {
672 if !contract.outcomes.contains(&outcome) {
674 return Err(DlcError::ContractError(format!(
675 "Invalid outcome: {outcome}"
676 )));
677 }
678
679 let outcome_index = contract
681 .outcomes
682 .iter()
683 .position(|o| o == &outcome)
684 .ok_or_else(|| DlcError::ContractError("Outcome not found".to_string()))?;
685
686 let payout = contract
687 .payouts
688 .get(outcome_index)
689 .ok_or_else(|| DlcError::ContractError("Payout not found for outcome".to_string()))?;
690
691 let _execution = DlcExecution {
693 id: format!("exec_{}", uuid::Uuid::new_v4()),
694 contract_id: contract.id.clone(),
695 outcome: outcome.clone(),
696 payout: *payout,
697 transaction_id: format!("tx_{}", uuid::Uuid::new_v4()), executed_at: chrono::Utc::now().timestamp() as u64,
699 oracle_attestation: vec![8, 9, 10, 11], };
701
702 let mut updated_contract = contract;
704 updated_contract.status = DlcContractStatus::Settled;
705 updated_contract.updated_at = Some(chrono::Utc::now().timestamp() as u64);
706
707 Ok(updated_contract)
710 }
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize)]
715pub struct DlcContractInfo {
716 pub oracle_public_key: String,
717 pub event_id: String,
718 pub collateral_amount: u64,
719 pub contract_maturity: u64,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct DlcParameters {
725 pub oracle_info: DlcContractInfo,
726 pub fee_rate: u64,
727 pub funding_amount: u64,
728}
729
730#[derive(Clone, Debug)]
735pub struct NonInteractiveOracleAttestation {
736 pub event_id: String,
737 pub outcome: String,
738 pub signature: Vec<u8>,
739 #[cfg(feature = "rust-bitcoin")]
740 pub r_point: bitcoin::secp256k1::PublicKey,
741 #[cfg(not(feature = "rust-bitcoin"))]
742 pub r_point: Vec<u8>, }
744
745impl OracleClient {
747 pub async fn get_attestation(
750 &self,
751 event_id: &str,
752 ) -> Result<NonInteractiveOracleAttestation, DlcError> {
753 if let Some(attestation) = self.attestations.get(event_id) {
755 return Ok(attestation.clone());
756 }
757
758 Err(DlcError::OracleError(format!(
759 "Attestation for event {event_id} not found"
760 )))
761 }
762}
763
764use crate::layer2::{
766 create_protocol_state, create_validation_result, create_verification_result, AssetParams,
767 AssetTransfer, Layer2Protocol, Proof, ProtocolState, TransactionStatus, TransferResult,
768 ValidationResult, VerificationResult,
769};
770use async_trait::async_trait;
771
772#[derive(Debug, Clone)]
775pub struct DlcProtocol {
776 oracle_client: OracleClient,
777}
778
779impl DlcProtocol {
780 pub fn new() -> Self {
781 Self {
782 oracle_client: OracleClient::new("oracle_pubkey_placeholder"),
783 }
784 }
785
786 pub fn get_oracle_client(&self) -> &OracleClient {
788 &self.oracle_client
789 }
790
791 pub fn get_oracle_client_mut(&mut self) -> &mut OracleClient {
793 &mut self.oracle_client
794 }
795
796 pub async fn connect_oracle(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
798 self.oracle_client.connect().await
799 }
800
801 pub async fn create_dlc_contract(
803 &mut self,
804 contract_info: DlcContractInfo,
805 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
806 let contract_id = format!("dlc_{}", uuid::Uuid::new_v4());
807 self.oracle_client
808 .create_contract(&contract_id, contract_info)
809 .await?;
810 Ok(contract_id)
811 }
812
813 pub async fn close_dlc_contract(
815 &mut self,
816 contract_id: &str,
817 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
818 self.oracle_client.close_contract(contract_id).await
819 }
820
821 pub async fn get_oracle_signature(
823 &self,
824 event_id: &str,
825 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
826 self.oracle_client.get_signature(event_id).await
827 }
828
829 pub fn oracle_status(&self) -> bool {
831 self.oracle_client.is_connected()
832 }
833
834 pub async fn create_dlc(
836 &mut self,
837 params: DlcParameters,
838 ) -> Result<DlcContract, Box<dyn std::error::Error + Send + Sync>> {
839 self.oracle_client.create_dlc(params).await
840 }
841}
842
843impl Default for DlcProtocol {
844 fn default() -> Self {
845 Self::new()
846 }
847}
848
849#[async_trait]
850impl Layer2Protocol for DlcProtocol {
851 async fn initialize(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
852 Ok(())
854 }
855
856 async fn connect(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
857 Ok(())
859 }
860
861 async fn get_state(&self) -> Result<ProtocolState, Box<dyn std::error::Error + Send + Sync>> {
862 Ok(create_protocol_state("1.0", 0, None, true))
863 }
864
865 async fn submit_transaction(
866 &self,
867 _tx_data: &[u8],
868 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
869 let tx_id = format!("dlc_tx_{}", Uuid::new_v4());
870 Ok(tx_id)
871 }
872
873 async fn check_transaction_status(
874 &self,
875 _tx_id: &str,
876 ) -> Result<TransactionStatus, Box<dyn std::error::Error + Send + Sync>> {
877 Ok(TransactionStatus::Confirmed)
878 }
879
880 async fn sync_state(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
881 Ok(())
883 }
884
885 async fn issue_asset(
886 &self,
887 _params: AssetParams,
888 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
889 let asset_id = format!("dlc_asset_{}", Uuid::new_v4());
890 Ok(asset_id)
891 }
892
893 async fn transfer_asset(
894 &self,
895 _transfer: AssetTransfer,
896 ) -> Result<TransferResult, Box<dyn std::error::Error + Send + Sync>> {
897 use crate::layer2::TransferResult;
898 Ok(TransferResult {
899 tx_id: format!("dlc_transfer_{}", Uuid::new_v4()),
900 status: TransactionStatus::Pending,
901 fee: Some(1000),
902 timestamp: std::time::SystemTime::now()
903 .duration_since(std::time::UNIX_EPOCH)
904 .unwrap_or_default()
905 .as_secs(),
906 })
907 }
908
909 async fn verify_proof(
910 &self,
911 _proof: Proof,
912 ) -> Result<VerificationResult, Box<dyn std::error::Error + Send + Sync>> {
913 Ok(create_verification_result(true, None))
915 }
916
917 async fn validate_state(
918 &self,
919 _state_data: &[u8],
920 ) -> Result<ValidationResult, Box<dyn std::error::Error + Send + Sync>> {
921 Ok(create_validation_result(true, vec![]))
923 }
924}