fedimint_ln_common/contracts/
outgoing.rs

1use bitcoin::hashes::Hash as BitcoinHash;
2use fedimint_core::Amount;
3use fedimint_core::encoding::{Decodable, Encodable};
4use fedimint_core::secp256k1::PublicKey;
5use serde::{Deserialize, Serialize};
6
7use super::Preimage;
8use crate::LightningInput;
9use crate::contracts::{ContractId, IdentifiableContract};
10
11const CANCELLATION_TAG: &str = "outgoing contract cancellation";
12
13/// Specialized smart contract for outgoing payments.
14///
15/// A user locks up funds that can be claimed by a lightning gateway if it pays
16/// the invoice and thus receives the preimage to the payment hash and can
17/// thereby prove the payment. If the gateway is not able to do so before the
18/// timelock expires the user can claim back the funds.
19#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
20pub struct OutgoingContract {
21    /// Hash that can be used to spend the output before the timelock expires
22    pub hash: bitcoin::hashes::sha256::Hash,
23    /// Public key of the LN gateway allowed to claim the HTLC before the
24    /// timelock expires
25    pub gateway_key: PublicKey,
26    /// Block height at which the money will be spendable by the pubkey
27    pub timelock: u32,
28    /// Public key of the user that can claim the money back after the timelock
29    /// expires
30    pub user_key: PublicKey,
31    /// Flag that can be set by the gateway and allows the client to claim an
32    /// early refund
33    pub cancelled: bool,
34}
35
36impl IdentifiableContract for OutgoingContract {
37    fn contract_id(&self) -> ContractId {
38        let mut engine = ContractId::engine();
39        Encodable::consensus_encode(&self.hash, &mut engine).expect("Hashing never fails");
40        Encodable::consensus_encode(&self.gateway_key, &mut engine).expect("Hashing never fails");
41        Encodable::consensus_encode(&self.timelock, &mut engine).expect("Hashing never fails");
42        Encodable::consensus_encode(&self.user_key, &mut engine).expect("Hashing never fails");
43        ContractId::from_engine(engine)
44    }
45}
46
47impl OutgoingContract {
48    pub fn cancellation_message(&self) -> bitcoin::hashes::sha256::Hash {
49        let mut engine = bitcoin::hashes::sha256::Hash::engine();
50        Encodable::consensus_encode(&CANCELLATION_TAG.as_bytes(), &mut engine)
51            .expect("Hashing never fails");
52        Encodable::consensus_encode(&self.contract_id(), &mut engine).expect("Hashing never fails");
53        bitcoin::hashes::sha256::Hash::from_engine(engine)
54    }
55}
56
57#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)]
58pub struct OutgoingContractData {
59    pub recovery_key: bitcoin::key::Keypair,
60    pub contract_account: OutgoingContractAccount,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Serialize, Deserialize)]
64pub struct OutgoingContractAccount {
65    pub amount: Amount,
66    pub contract: OutgoingContract,
67}
68
69impl OutgoingContractAccount {
70    pub fn claim(&self, preimage: Preimage) -> LightningInput {
71        LightningInput::new_v0(self.contract.contract_id(), self.amount, Some(preimage))
72    }
73
74    pub fn refund(&self) -> LightningInput {
75        LightningInput::new_v0(self.contract.contract_id(), self.amount, None)
76    }
77}