tx_indexer/
from_oura.rs

1use ::oura::model::{MintRecord, OutputAssetRecord};
2use anyhow::Context;
3use data_encoding::HEXLOWER;
4use num_bigint::BigInt;
5use plutus_ledger_api::csl::{csl_to_pla::TryToPLA, lib as csl};
6use plutus_ledger_api::v3::{
7    address::Address,
8    crypto::LedgerBytes,
9    datum::{Datum, DatumHash},
10    redeemer::Redeemer,
11    script::{MintingPolicyHash, ScriptHash},
12    transaction::TransactionHash,
13    value::{CurrencySymbol, TokenName, Value},
14};
15use std::fmt::Debug;
16
17#[derive(thiserror::Error, Debug)]
18pub enum OuraParseError {
19    #[error(transparent)]
20    ParseError(#[from] anyhow::Error),
21
22    #[error("Unable to convert current time: {0}")]
23    TimeConversionError(tx_bakery::error::Error),
24}
25
26/// Convert an Oura transaction record type to its plutus-ledger-api counterpart
27pub trait FromOura<T> {
28    fn from_oura(value: T) -> Result<Self, OuraParseError>
29    where
30        Self: Sized;
31}
32
33impl FromOura<String> for LedgerBytes {
34    fn from_oura(value: String) -> Result<Self, OuraParseError> {
35        Ok(LedgerBytes(
36            HEXLOWER
37                .decode(&value.clone().into_bytes()[..])
38                .with_context(|| "Parsing LedgerBytes from Oura")?,
39        ))
40    }
41}
42
43impl FromOura<String> for TransactionHash {
44    fn from_oura(value: String) -> Result<Self, OuraParseError> {
45        Ok(TransactionHash(
46            LedgerBytes::from_oura(value).with_context(|| "Parsing TransactionHash from Oura")?,
47        ))
48    }
49}
50
51impl FromOura<String> for DatumHash {
52    fn from_oura(value: String) -> Result<Self, OuraParseError> {
53        Ok(DatumHash(LedgerBytes::from_oura(value)?))
54    }
55}
56
57impl FromOura<String> for ScriptHash {
58    fn from_oura(value: String) -> Result<Self, OuraParseError> {
59        Ok(ScriptHash(LedgerBytes::from_oura(value)?))
60    }
61}
62
63impl FromOura<String> for CurrencySymbol {
64    fn from_oura(value: String) -> Result<Self, OuraParseError> {
65        Ok(if value.is_empty() {
66            CurrencySymbol::Ada
67        } else {
68            CurrencySymbol::NativeToken(MintingPolicyHash(ScriptHash(LedgerBytes::from_oura(
69                value,
70            )?)))
71        })
72    }
73}
74
75impl FromOura<String> for TokenName {
76    fn from_oura(value: String) -> Result<Self, OuraParseError> {
77        Ok(if value.is_empty() {
78            TokenName::ada()
79        } else {
80            TokenName(LedgerBytes::from_oura(value)?)
81        })
82    }
83}
84
85impl FromOura<serde_json::Value> for Datum {
86    fn from_oura(value: serde_json::Value) -> Result<Self, OuraParseError> {
87        let csl_plutus_data =
88            csl::encode_json_value_to_plutus_datum(value, csl::PlutusDatumSchema::DetailedSchema)
89                .with_context(|| "Parsing Datum from Oura")?;
90
91        Ok(Datum(
92            csl_plutus_data
93                .try_to_pla()
94                .with_context(|| "Parsing Datum from Oura")?,
95        ))
96    }
97}
98
99impl FromOura<serde_json::Value> for Redeemer {
100    fn from_oura(value: serde_json::Value) -> Result<Self, OuraParseError> {
101        let csl_plutus_data =
102            csl::encode_json_value_to_plutus_datum(value, csl::PlutusDatumSchema::DetailedSchema)
103                .with_context(|| "Parsing Redeemer from Oura")?;
104
105        Ok(Redeemer(
106            csl_plutus_data
107                .try_to_pla()
108                .with_context(|| "Parsing Redeemer from Oura")?,
109        ))
110    }
111}
112
113impl FromOura<String> for Address {
114    fn from_oura(value: String) -> Result<Self, OuraParseError> {
115        let csl_addr = csl::Address::from_bech32(&value)
116            .or_else(|_| {
117                csl::ByronAddress::from_base58(&value).map(|byron_addr| byron_addr.to_address())
118            })
119            .with_context(|| "Parsing Address from Oura")?;
120
121        Ok(csl_addr
122            .try_to_pla()
123            .with_context(|| "Parsing Address from Oura")?)
124    }
125}
126
127impl FromOura<Vec<OutputAssetRecord>> for Value {
128    fn from_oura(value: Vec<OutputAssetRecord>) -> Result<Self, OuraParseError> {
129        value.iter().try_fold(Value::new(), |acc, x| {
130            let amt = BigInt::from(x.amount);
131            Ok(acc.insert_token(
132                &CurrencySymbol::from_oura(x.policy.clone())?,
133                &TokenName::from_oura(x.asset.clone())?,
134                &amt,
135            ))
136        })
137    }
138}
139
140impl FromOura<Vec<MintRecord>> for Value {
141    fn from_oura(value: Vec<MintRecord>) -> Result<Self, OuraParseError> {
142        value.iter().try_fold(Value::new(), |acc, x| {
143            let amt = BigInt::from(x.quantity);
144            Ok(acc.insert_token(
145                &CurrencySymbol::from_oura(x.policy.clone())?,
146                &TokenName::from_oura(x.asset.clone())?,
147                &amt,
148            ))
149        })
150    }
151}