1#![deny(clippy::pedantic)]
2#![allow(clippy::doc_markdown)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5#![allow(clippy::module_name_repetitions)]
6#![allow(clippy::must_use_candidate)]
7
8#[cfg(feature = "uniffi")]
18uniffi::setup_scaffolding!();
19
20pub mod client;
21pub mod config;
22pub mod contracts;
23pub mod federation_endpoint_constants;
24pub mod gateway_endpoint_constants;
25
26use std::collections::BTreeMap;
27use std::io::{Error, Read, Write};
28use std::time::{Duration, SystemTime};
29
30use anyhow::Context as AnyhowContext;
31use bitcoin::hashes::{Hash, sha256};
32use config::LightningClientConfig;
33use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
34use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
35use fedimint_core::module::registry::ModuleDecoderRegistry;
36use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
37use fedimint_core::secp256k1::Message;
38use fedimint_core::util::SafeUrl;
39use fedimint_core::{
40 Amount, PeerId, encode_bolt11_invoice_features_without_length,
41 extensible_associated_module_type, plugin_types_trait_impl_common, secp256k1,
42};
43use lightning_invoice::{Bolt11Invoice, RoutingFees};
44pub use reqwest::Method;
45use secp256k1::schnorr::Signature;
46use serde::{Deserialize, Serialize};
47use thiserror::Error;
48use threshold_crypto::PublicKey;
49pub use {bitcoin, lightning_invoice};
50
51use crate::contracts::incoming::OfferId;
52use crate::contracts::{Contract, ContractId, ContractOutcome, Preimage, PreimageDecryptionShare};
53use crate::route_hints::RouteHint;
54
55pub const KIND: ModuleKind = ModuleKind::from_static_str("ln");
56pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(2, 0);
57
58extensible_associated_module_type!(
59 LightningInput,
60 LightningInputV0,
61 UnknownLightningInputVariantError
62);
63
64impl LightningInput {
65 pub fn new_v0(
66 contract_id: ContractId,
67 amount: Amount,
68 witness: Option<Preimage>,
69 ) -> LightningInput {
70 LightningInput::V0(LightningInputV0 {
71 contract_id,
72 amount,
73 witness,
74 })
75 }
76}
77
78#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
79pub struct LightningInputV0 {
80 pub contract_id: contracts::ContractId,
81 pub amount: Amount,
84 pub witness: Option<Preimage>,
88}
89
90impl std::fmt::Display for LightningInputV0 {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(
93 f,
94 "Lightning Contract {} with amount {}",
95 self.contract_id, self.amount
96 )
97 }
98}
99
100extensible_associated_module_type!(
101 LightningOutput,
102 LightningOutputV0,
103 UnknownLightningOutputVariantError
104);
105
106impl LightningOutput {
107 pub fn new_v0_contract(contract: ContractOutput) -> LightningOutput {
108 LightningOutput::V0(LightningOutputV0::Contract(contract))
109 }
110
111 pub fn new_v0_offer(offer: contracts::incoming::IncomingContractOffer) -> LightningOutput {
112 LightningOutput::V0(LightningOutputV0::Offer(offer))
113 }
114
115 pub fn new_v0_cancel_outgoing(
116 contract: ContractId,
117 gateway_signature: secp256k1::schnorr::Signature,
118 ) -> LightningOutput {
119 LightningOutput::V0(LightningOutputV0::CancelOutgoing {
120 contract,
121 gateway_signature,
122 })
123 }
124}
125
126#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
139pub enum LightningOutputV0 {
140 Contract(ContractOutput),
142 Offer(contracts::incoming::IncomingContractOffer),
144 CancelOutgoing {
146 contract: ContractId,
148 gateway_signature: fedimint_core::secp256k1::schnorr::Signature,
150 },
151}
152
153impl std::fmt::Display for LightningOutputV0 {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 LightningOutputV0::Contract(ContractOutput { amount, contract }) => match contract {
157 Contract::Incoming(incoming) => {
158 write!(
159 f,
160 "LN Incoming Contract for {} hash {}",
161 amount, incoming.hash
162 )
163 }
164 Contract::Outgoing(outgoing) => {
165 write!(
166 f,
167 "LN Outgoing Contract for {} hash {}",
168 amount, outgoing.hash
169 )
170 }
171 },
172 LightningOutputV0::Offer(offer) => {
173 write!(f, "LN offer for {} with hash {}", offer.amount, offer.hash)
174 }
175 LightningOutputV0::CancelOutgoing { contract, .. } => {
176 write!(f, "LN outgoing contract cancellation {contract}")
177 }
178 }
179 }
180}
181
182#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
183pub struct ContractOutput {
184 pub amount: fedimint_core::Amount,
185 pub contract: contracts::Contract,
186}
187
188#[derive(Debug, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize, Clone)]
189pub struct ContractAccount {
190 pub amount: fedimint_core::Amount,
191 pub contract: contracts::FundedContract,
192}
193
194extensible_associated_module_type!(
195 LightningOutputOutcome,
196 LightningOutputOutcomeV0,
197 UnknownLightningOutputOutcomeVariantError
198);
199
200impl LightningOutputOutcome {
201 pub fn new_v0_contract(id: ContractId, outcome: ContractOutcome) -> LightningOutputOutcome {
202 LightningOutputOutcome::V0(LightningOutputOutcomeV0::Contract { id, outcome })
203 }
204
205 pub fn new_v0_offer(id: OfferId) -> LightningOutputOutcome {
206 LightningOutputOutcome::V0(LightningOutputOutcomeV0::Offer { id })
207 }
208
209 pub fn new_v0_cancel_outgoing(id: ContractId) -> LightningOutputOutcome {
210 LightningOutputOutcome::V0(LightningOutputOutcomeV0::CancelOutgoingContract { id })
211 }
212}
213
214#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
215pub enum LightningOutputOutcomeV0 {
216 Contract {
217 id: ContractId,
218 outcome: ContractOutcome,
219 },
220 Offer {
221 id: OfferId,
222 },
223 CancelOutgoingContract {
224 id: ContractId,
225 },
226}
227
228impl LightningOutputOutcomeV0 {
229 pub fn is_permanent(&self) -> bool {
230 match self {
231 LightningOutputOutcomeV0::Contract { id: _, outcome } => outcome.is_permanent(),
232 LightningOutputOutcomeV0::Offer { .. }
233 | LightningOutputOutcomeV0::CancelOutgoingContract { .. } => true,
234 }
235 }
236}
237
238impl std::fmt::Display for LightningOutputOutcomeV0 {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 match self {
241 LightningOutputOutcomeV0::Contract { id, .. } => {
242 write!(f, "LN Contract {id}")
243 }
244 LightningOutputOutcomeV0::Offer { id } => {
245 write!(f, "LN Offer {id}")
246 }
247 LightningOutputOutcomeV0::CancelOutgoingContract { id: contract_id } => {
248 write!(f, "LN Outgoing Contract Cancellation {contract_id}")
249 }
250 }
251 }
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
260pub struct LightningGatewayRegistration {
261 pub info: LightningGateway,
262 pub vetted: bool,
264 pub valid_until: SystemTime,
267}
268
269impl Encodable for LightningGatewayRegistration {
270 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
271 let json_repr = serde_json::to_string(self).map_err(|e| {
272 Error::other(format!(
273 "Failed to serialize LightningGatewayRegistration: {e}"
274 ))
275 })?;
276
277 json_repr.consensus_encode(writer)
278 }
279}
280
281impl Decodable for LightningGatewayRegistration {
282 fn consensus_decode_partial<R: Read>(
283 r: &mut R,
284 modules: &ModuleDecoderRegistry,
285 ) -> Result<Self, DecodeError> {
286 let json_repr = String::consensus_decode_partial(r, modules)?;
287 serde_json::from_str(&json_repr).map_err(|e| {
288 DecodeError::new_custom(
289 anyhow::Error::new(e).context("Failed to deserialize LightningGatewayRegistration"),
290 )
291 })
292 }
293}
294
295impl LightningGatewayRegistration {
296 pub fn unanchor(self) -> LightningGatewayAnnouncement {
301 LightningGatewayAnnouncement {
302 info: self.info,
303 ttl: self
304 .valid_until
305 .duration_since(fedimint_core::time::now())
306 .unwrap_or_default(),
307 vetted: self.vetted,
308 }
309 }
310
311 pub fn is_expired(&self) -> bool {
312 self.valid_until < fedimint_core::time::now()
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
324#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
325pub struct LightningGatewayAnnouncement {
326 pub info: LightningGateway,
327 pub vetted: bool,
329 pub ttl: Duration,
333}
334
335impl LightningGatewayAnnouncement {
336 pub fn anchor(self) -> LightningGatewayRegistration {
339 LightningGatewayRegistration {
340 info: self.info,
341 vetted: self.vetted,
342 valid_until: fedimint_core::time::now() + self.ttl,
343 }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq, Hash)]
349#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
350pub struct LightningGateway {
351 #[serde(rename = "mint_channel_id")]
356 pub federation_index: u64,
357 pub gateway_redeem_key: fedimint_core::secp256k1::PublicKey,
359 pub node_pub_key: fedimint_core::secp256k1::PublicKey,
360 pub lightning_alias: String,
361 pub api: SafeUrl,
364 pub route_hints: Vec<route_hints::RouteHint>,
369 #[serde(with = "serde_routing_fees")]
371 pub fees: RoutingFees,
372 pub gateway_id: secp256k1::PublicKey,
373 pub supports_private_payments: bool,
375}
376
377#[cfg(feature = "uniffi")]
378#[derive(uniffi::Record)]
379pub struct RoutingFeesFfi {
380 pub base_msat: u32,
382 pub proportional_millionths: u32,
385}
386
387#[cfg(feature = "uniffi")]
388uniffi::custom_type!(RoutingFees, RoutingFeesFfi, {
389 remote,
390 lower: |fees| RoutingFeesFfi {
391 base_msat: fees.base_msat,
392 proportional_millionths: fees.proportional_millionths,
393 },
394 try_lift: |fees_ffi| Ok(RoutingFees {
395 base_msat: fees_ffi.base_msat,
396 proportional_millionths: fees_ffi.proportional_millionths,
397 }),
398});
399
400#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Serialize, Deserialize)]
401pub enum LightningConsensusItem {
402 DecryptPreimage(ContractId, PreimageDecryptionShare),
403 BlockCount(u64),
404 #[encodable_default]
405 Default {
406 variant: u64,
407 bytes: Vec<u8>,
408 },
409}
410
411impl std::fmt::Display for LightningConsensusItem {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 match self {
414 LightningConsensusItem::DecryptPreimage(contract_id, _) => {
415 write!(f, "LN Decryption Share - contract_id: {contract_id}")
416 }
417 LightningConsensusItem::BlockCount(count) => write!(f, "LN Block Count {count}"),
418 LightningConsensusItem::Default { variant, .. } => {
419 write!(f, "LN Unknown - variant={variant}")
420 }
421 }
422 }
423}
424
425#[derive(Debug)]
426pub struct LightningCommonInit;
427
428impl CommonModuleInit for LightningCommonInit {
429 const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
430 const KIND: ModuleKind = KIND;
431
432 type ClientConfig = LightningClientConfig;
433
434 fn decoder() -> Decoder {
435 LightningModuleTypes::decoder()
436 }
437}
438
439pub struct LightningModuleTypes;
440
441plugin_types_trait_impl_common!(
442 KIND,
443 LightningModuleTypes,
444 LightningClientConfig,
445 LightningInput,
446 LightningOutput,
447 LightningOutputOutcome,
448 LightningConsensusItem,
449 LightningInputError,
450 LightningOutputError
451);
452
453pub mod route_hints {
456 #[cfg(feature = "uniffi")]
457 use std::str::FromStr;
458
459 use fedimint_core::encoding::{Decodable, Encodable};
460 use fedimint_core::secp256k1::PublicKey;
461 use lightning_invoice::RoutingFees;
462 use serde::{Deserialize, Serialize};
463
464 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
465 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
466 pub struct RouteHintHop {
467 pub src_node_id: PublicKey,
469 pub short_channel_id: u64,
471 pub base_msat: u32,
473 pub proportional_millionths: u32,
476 pub cltv_expiry_delta: u16,
478 pub htlc_minimum_msat: Option<u64>,
480 pub htlc_maximum_msat: Option<u64>,
482 }
483
484 #[cfg(feature = "uniffi")]
485 uniffi::custom_type!(PublicKey, String, {
486 remote,
487 lower: |pk| pk.to_string(),
488 try_lift: |s| PublicKey::from_str(&s).map_err(|e| anyhow::anyhow!(e)),
489 });
490
491 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
494 pub struct RouteHint(pub Vec<RouteHintHop>);
495
496 #[cfg(feature = "uniffi")]
497 uniffi::custom_newtype!(RouteHint, Vec<RouteHintHop>);
498
499 impl RouteHint {
500 pub fn to_ldk_route_hint(&self) -> lightning_invoice::RouteHint {
501 lightning_invoice::RouteHint(
502 self.0
503 .iter()
504 .map(|hop| lightning_invoice::RouteHintHop {
505 src_node_id: hop.src_node_id,
506 short_channel_id: hop.short_channel_id,
507 fees: RoutingFees {
508 base_msat: hop.base_msat,
509 proportional_millionths: hop.proportional_millionths,
510 },
511 cltv_expiry_delta: hop.cltv_expiry_delta,
512 htlc_minimum_msat: hop.htlc_minimum_msat,
513 htlc_maximum_msat: hop.htlc_maximum_msat,
514 })
515 .collect(),
516 )
517 }
518 }
519
520 impl From<lightning_invoice::RouteHint> for RouteHint {
521 fn from(rh: lightning_invoice::RouteHint) -> Self {
522 RouteHint(rh.0.into_iter().map(Into::into).collect())
523 }
524 }
525
526 impl From<lightning_invoice::RouteHintHop> for RouteHintHop {
527 fn from(rhh: lightning_invoice::RouteHintHop) -> Self {
528 RouteHintHop {
529 src_node_id: rhh.src_node_id,
530 short_channel_id: rhh.short_channel_id,
531 base_msat: rhh.fees.base_msat,
532 proportional_millionths: rhh.fees.proportional_millionths,
533 cltv_expiry_delta: rhh.cltv_expiry_delta,
534 htlc_minimum_msat: rhh.htlc_minimum_msat,
535 htlc_maximum_msat: rhh.htlc_maximum_msat,
536 }
537 }
538 }
539}
540
541pub mod serde_routing_fees {
545 use lightning_invoice::RoutingFees;
546 use serde::ser::SerializeStruct;
547 use serde::{Deserialize, Deserializer, Serializer};
548
549 #[allow(missing_docs)]
550 pub fn serialize<S>(fees: &RoutingFees, serializer: S) -> Result<S::Ok, S::Error>
551 where
552 S: Serializer,
553 {
554 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
555 state.serialize_field("base_msat", &fees.base_msat)?;
556 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
557 state.end()
558 }
559
560 #[allow(missing_docs)]
561 pub fn deserialize<'de, D>(deserializer: D) -> Result<RoutingFees, D::Error>
562 where
563 D: Deserializer<'de>,
564 {
565 let fees = serde_json::Value::deserialize(deserializer)?;
566 let base_msat = fees["base_msat"]
568 .as_u64()
569 .ok_or_else(|| serde::de::Error::custom("base_msat is not a u64"))?;
570 let proportional_millionths = fees["proportional_millionths"]
571 .as_u64()
572 .ok_or_else(|| serde::de::Error::custom("proportional_millionths is not a u64"))?;
573
574 Ok(RoutingFees {
575 base_msat: base_msat
576 .try_into()
577 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?,
578 proportional_millionths: proportional_millionths.try_into().map_err(|_| {
579 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
580 })?,
581 })
582 }
583}
584
585pub mod serde_option_routing_fees {
586 use lightning_invoice::RoutingFees;
587 use serde::ser::SerializeStruct;
588 use serde::{Deserialize, Deserializer, Serializer};
589
590 #[allow(missing_docs)]
591 pub fn serialize<S>(fees: &Option<RoutingFees>, serializer: S) -> Result<S::Ok, S::Error>
592 where
593 S: Serializer,
594 {
595 if let Some(fees) = fees {
596 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
597 state.serialize_field("base_msat", &fees.base_msat)?;
598 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
599 state.end()
600 } else {
601 let state = serializer.serialize_struct("RoutingFees", 0)?;
602 state.end()
603 }
604 }
605
606 #[allow(missing_docs)]
607 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<RoutingFees>, D::Error>
608 where
609 D: Deserializer<'de>,
610 {
611 let fees = serde_json::Value::deserialize(deserializer)?;
612 let base_msat = fees["base_msat"].as_u64();
614
615 if let Some(base_msat) = base_msat
616 && let Some(proportional_millionths) = fees["proportional_millionths"].as_u64()
617 {
618 let base_msat: u32 = base_msat
619 .try_into()
620 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?;
621 let proportional_millionths: u32 =
622 proportional_millionths.try_into().map_err(|_| {
623 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
624 })?;
625 return Ok(Some(RoutingFees {
626 base_msat,
627 proportional_millionths,
628 }));
629 }
630
631 Ok(None)
632 }
633}
634
635#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
636pub enum LightningInputError {
637 #[error("The input contract {0} does not exist")]
638 UnknownContract(ContractId),
639 #[error("The input contract has too little funds, got {0}, input spends {1}")]
640 InsufficientFunds(Amount, Amount),
641 #[error("An outgoing LN contract spend did not provide a preimage")]
642 MissingPreimage,
643 #[error("An outgoing LN contract spend provided a wrong preimage")]
644 InvalidPreimage,
645 #[error("Incoming contract not ready to be spent yet, decryption in progress")]
646 ContractNotReady,
647 #[error("The lightning input version is not supported by this federation")]
648 UnknownInputVariant(#[from] UnknownLightningInputVariantError),
649}
650
651#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
652pub enum LightningOutputError {
653 #[error("The input contract {0} does not exist")]
654 UnknownContract(ContractId),
655 #[error("Output contract value may not be zero unless it's an offer output")]
656 ZeroOutput,
657 #[error("Offer contains invalid threshold-encrypted data")]
658 InvalidEncryptedPreimage,
659 #[error("Offer contains a ciphertext that has already been used")]
660 DuplicateEncryptedPreimage,
661 #[error("The incoming LN account requires more funding (need {0} got {1})")]
662 InsufficientIncomingFunding(Amount, Amount),
663 #[error("No offer found for payment hash {0}")]
664 NoOffer(bitcoin::secp256k1::hashes::sha256::Hash),
665 #[error("Only outgoing contracts support cancellation")]
666 NotOutgoingContract,
667 #[error("Cancellation request wasn't properly signed")]
668 InvalidCancellationSignature,
669 #[error("The lightning output version is not supported by this federation")]
670 UnknownOutputVariant(#[from] UnknownLightningOutputVariantError),
671}
672
673#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Decodable, Encodable)]
678pub struct PrunedInvoice {
679 pub amount: Amount,
680 pub destination: secp256k1::PublicKey,
681 #[serde(with = "fedimint_core::hex::serde", default)]
683 pub destination_features: Vec<u8>,
684 pub payment_hash: sha256::Hash,
685 pub payment_secret: [u8; 32],
686 pub route_hints: Vec<RouteHint>,
687 pub min_final_cltv_delta: u64,
688 pub expiry_timestamp: u64,
690}
691
692impl PrunedInvoice {
693 pub fn new(invoice: &Bolt11Invoice, amount: Amount) -> Self {
694 let expiry_timestamp = invoice.expires_at().map_or(u64::MAX, |t| t.as_secs());
697
698 let destination_features = if let Some(features) = invoice.features() {
699 encode_bolt11_invoice_features_without_length(features)
700 } else {
701 vec![]
702 };
703
704 PrunedInvoice {
705 amount,
706 destination: invoice
707 .payee_pub_key()
708 .copied()
709 .unwrap_or_else(|| invoice.recover_payee_pub_key()),
710 destination_features,
711 payment_hash: *invoice.payment_hash(),
712 payment_secret: invoice.payment_secret().0,
713 route_hints: invoice.route_hints().into_iter().map(Into::into).collect(),
714 min_final_cltv_delta: invoice.min_final_cltv_expiry_delta(),
715 expiry_timestamp,
716 }
717 }
718}
719
720impl TryFrom<Bolt11Invoice> for PrunedInvoice {
721 type Error = anyhow::Error;
722
723 fn try_from(invoice: Bolt11Invoice) -> Result<Self, Self::Error> {
724 Ok(PrunedInvoice::new(
725 &invoice,
726 Amount::from_msats(
727 invoice
728 .amount_milli_satoshis()
729 .context("Invoice amount is missing")?,
730 ),
731 ))
732 }
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct RemoveGatewayRequest {
741 pub gateway_id: secp256k1::PublicKey,
742 pub signatures: BTreeMap<PeerId, Signature>,
743}
744
745pub fn create_gateway_remove_message(
753 federation_public_key: PublicKey,
754 peer_id: PeerId,
755 challenge: sha256::Hash,
756) -> Message {
757 let mut message_preimage = "remove-gateway".as_bytes().to_vec();
758 message_preimage.append(&mut federation_public_key.consensus_encode_to_vec());
759 let guardian_id: u16 = peer_id.into();
760 message_preimage.append(&mut guardian_id.consensus_encode_to_vec());
761 message_preimage.append(&mut challenge.consensus_encode_to_vec());
762 Message::from_digest(*sha256::Hash::hash(message_preimage.as_slice()).as_ref())
763}