rgbinvoice/
invoice.rs

1// RGB wallet library for smart contracts on Bitcoin & Lightning network
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::ops::Deref;
23use std::str::FromStr;
24
25use indexmap::IndexMap;
26use rgb::bitcoin::hashes::{hash160, sha256};
27use rgb::bitcoin::key::{TweakedPublicKey, UntweakedPublicKey};
28use rgb::bitcoin::{KnownHrp, Network, PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
29use rgb::{ChainNet, ContractId, Layer1, SchemaId, SecretSeal, StateType};
30use strict_types::FieldName;
31
32use crate::parse::AddressPayload;
33use crate::{Amount, NonFungible};
34
35#[derive(Clone, Eq, PartialEq, Hash, Debug)]
36#[non_exhaustive]
37pub enum RgbTransport {
38    JsonRpc { tls: bool, host: String },
39    RestHttp { tls: bool, host: String },
40    WebSockets { tls: bool, host: String },
41    Storm {/* todo */},
42    UnspecifiedMeans,
43}
44
45#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
46#[display(inner)]
47pub enum InvoiceStateError {
48    #[display(doc_comments)]
49    /// could not parse as amount, data, or attach: {0}.
50    ParseError(String),
51}
52
53#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
54pub enum InvoiceState {
55    #[display("")]
56    Void,
57    #[display("{0}")]
58    Amount(Amount),
59    #[display(inner)]
60    Data(NonFungible),
61}
62
63impl FromStr for InvoiceState {
64    type Err = InvoiceStateError;
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        if s.is_empty() {
67            Ok(InvoiceState::Void)
68        } else if let Ok(amount) = Amount::from_str(s) {
69            Ok(InvoiceState::Amount(amount))
70        } else if let Ok(data) = NonFungible::from_str(s) {
71            Ok(InvoiceState::Data(data))
72        } else {
73            Err(InvoiceStateError::ParseError(s.to_owned()))
74        }
75    }
76}
77
78impl From<InvoiceState> for StateType {
79    fn from(val: InvoiceState) -> Self {
80        match val {
81            InvoiceState::Void => StateType::Void,
82            InvoiceState::Amount(_) => StateType::Fungible,
83            InvoiceState::Data(_) => StateType::Structured,
84        }
85    }
86}
87
88#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
89#[non_exhaustive]
90pub enum XChainNet<T> {
91    BitcoinMainnet(T),
92    BitcoinTestnet3(T),
93    BitcoinTestnet4(T),
94    BitcoinSignet(T),
95    BitcoinRegtest(T),
96    LiquidMainnet(T),
97    LiquidTestnet(T),
98}
99
100impl<T> XChainNet<T> {
101    pub fn with(cn: ChainNet, data: T) -> Self {
102        match cn {
103            ChainNet::BitcoinMainnet => XChainNet::BitcoinMainnet(data),
104            ChainNet::BitcoinTestnet3 => XChainNet::BitcoinTestnet3(data),
105            ChainNet::BitcoinTestnet4 => XChainNet::BitcoinTestnet4(data),
106            ChainNet::BitcoinSignet => XChainNet::BitcoinSignet(data),
107            ChainNet::BitcoinRegtest => XChainNet::BitcoinRegtest(data),
108            ChainNet::LiquidMainnet => XChainNet::LiquidMainnet(data),
109            ChainNet::LiquidTestnet => XChainNet::LiquidTestnet(data),
110        }
111    }
112
113    pub fn bitcoin(network: Network, data: T) -> Self {
114        match network {
115            Network::Bitcoin => Self::BitcoinMainnet(data),
116            Network::Testnet => Self::BitcoinTestnet3(data),
117            Network::Testnet4 => Self::BitcoinTestnet4(data),
118            Network::Signet => Self::BitcoinSignet(data),
119            Network::Regtest => Self::BitcoinRegtest(data),
120        }
121    }
122
123    pub fn chain_network(&self) -> ChainNet {
124        match self {
125            XChainNet::BitcoinMainnet(_) => ChainNet::BitcoinMainnet,
126            XChainNet::BitcoinTestnet3(_) => ChainNet::BitcoinTestnet3,
127            XChainNet::BitcoinTestnet4(_) => ChainNet::BitcoinTestnet4,
128            XChainNet::BitcoinSignet(_) => ChainNet::BitcoinSignet,
129            XChainNet::BitcoinRegtest(_) => ChainNet::BitcoinRegtest,
130            XChainNet::LiquidMainnet(_) => ChainNet::LiquidMainnet,
131            XChainNet::LiquidTestnet(_) => ChainNet::LiquidTestnet,
132        }
133    }
134
135    pub fn into_inner(self) -> T {
136        match self {
137            XChainNet::BitcoinMainnet(inner)
138            | XChainNet::BitcoinTestnet3(inner)
139            | XChainNet::BitcoinTestnet4(inner)
140            | XChainNet::BitcoinSignet(inner)
141            | XChainNet::BitcoinRegtest(inner)
142            | XChainNet::LiquidMainnet(inner)
143            | XChainNet::LiquidTestnet(inner) => inner,
144        }
145    }
146
147    pub fn layer1(&self) -> Layer1 { self.chain_network().layer1() }
148
149    pub fn address_network(&self) -> KnownHrp {
150        match self.chain_network() {
151            ChainNet::BitcoinMainnet => KnownHrp::Mainnet,
152            ChainNet::BitcoinTestnet3 | ChainNet::BitcoinTestnet4 | ChainNet::BitcoinSignet => {
153                KnownHrp::Testnets
154            }
155            ChainNet::BitcoinRegtest => KnownHrp::Regtest,
156            ChainNet::LiquidMainnet => KnownHrp::Mainnet,
157            ChainNet::LiquidTestnet => KnownHrp::Testnets,
158        }
159    }
160}
161
162#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
163#[display(doc_comments)]
164pub enum Pay2VoutError {
165    /// unexpected address type byte {0:#04x}.
166    InvalidAddressType(u8),
167    /// invalid taproot output key
168    InvalidTapkey,
169}
170
171#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
172pub struct Pay2Vout(AddressPayload);
173
174impl Pay2Vout {
175    pub fn new(address_payload: AddressPayload) -> Self { Pay2Vout(address_payload) }
176}
177
178impl Deref for Pay2Vout {
179    type Target = AddressPayload;
180
181    fn deref(&self) -> &'_ Self::Target { &self.0 }
182}
183
184impl Pay2Vout {
185    pub(crate) const P2PKH: u8 = 1;
186    pub(crate) const P2SH: u8 = 2;
187    pub(crate) const P2WPKH: u8 = 3;
188    pub(crate) const P2WSH: u8 = 4;
189    pub(crate) const P2TR: u8 = 5;
190}
191
192impl TryFrom<[u8; 33]> for Pay2Vout {
193    type Error = Pay2VoutError;
194
195    fn try_from(data: [u8; 33]) -> Result<Self, Self::Error> {
196        let addr_bytes: [u8; 20] = data[1..21].try_into().unwrap();
197        let address = match data[0] {
198            Self::P2PKH => AddressPayload::Pkh(PubkeyHash::from_raw_hash(
199                *hash160::Hash::from_bytes_ref(&addr_bytes),
200            )),
201            Self::P2SH => AddressPayload::Sh(ScriptHash::from_raw_hash(
202                *hash160::Hash::from_bytes_ref(&addr_bytes),
203            )),
204            Self::P2WPKH => AddressPayload::Wpkh(WPubkeyHash::from_raw_hash(
205                *hash160::Hash::from_bytes_ref(&addr_bytes),
206            )),
207            Self::P2WSH => AddressPayload::Wsh(WScriptHash::from_raw_hash(
208                *sha256::Hash::from_bytes_ref(&data[1..].try_into().unwrap()),
209            )),
210            Self::P2TR => AddressPayload::Tr(TweakedPublicKey::dangerous_assume_tweaked(
211                UntweakedPublicKey::from_slice(&data[1..33])
212                    .map_err(|_| Pay2VoutError::InvalidTapkey)?,
213            )),
214            wrong => return Err(Pay2VoutError::InvalidAddressType(wrong)),
215        };
216        Ok(Pay2Vout(address))
217    }
218}
219
220#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
221pub enum Beneficiary {
222    #[from]
223    BlindedSeal(SecretSeal),
224    WitnessVout(Pay2Vout, Option<UntweakedPublicKey>),
225}
226
227#[derive(Clone, Eq, PartialEq, Debug)]
228#[non_exhaustive]
229pub struct RgbInvoice {
230    pub transports: Vec<RgbTransport>,
231    pub contract: Option<ContractId>,
232    pub schema: Option<SchemaId>,
233    pub assignment_name: Option<FieldName>,
234    pub assignment_state: Option<InvoiceState>,
235    pub beneficiary: XChainNet<Beneficiary>,
236    /// UTC unix timestamp
237    pub expiry: Option<i64>,
238    pub unknown_query: IndexMap<String, String>,
239}
240
241impl RgbInvoice {
242    pub fn chain_network(&self) -> ChainNet { self.beneficiary.chain_network() }
243    pub fn address_network(&self) -> KnownHrp { self.beneficiary.address_network() }
244    pub fn layer1(&self) -> Layer1 { self.beneficiary.layer1() }
245}