kormir_wasm/
lib.rs

1use std::str::FromStr;
2
3use gloo_utils::format::JsValueSerdeExt;
4use nostr::{EventId, JsonUtil, Keys};
5use nostr_sdk::Client;
6use wasm_bindgen::prelude::wasm_bindgen;
7use wasm_bindgen::JsValue;
8
9use kormir::bitcoin::secp256k1::SecretKey;
10use kormir::storage::Storage;
11use kormir::{Oracle, OracleAnnouncement, OracleAttestation, Readable, Writeable};
12
13use crate::error::JsError;
14use crate::models::{Announcement, Attestation, EventData};
15use crate::storage::{IndexedDb, NSEC_KEY};
16
17mod error;
18mod models;
19mod storage;
20mod utils;
21
22#[derive(Debug, Clone)]
23#[wasm_bindgen]
24pub struct Kormir {
25    oracle: Oracle<IndexedDb>,
26    storage: IndexedDb,
27    client: Client,
28}
29
30#[wasm_bindgen]
31impl Kormir {
32    pub async fn new(relays: Vec<String>) -> Result<Kormir, JsError> {
33        utils::set_panic_hook();
34        let storage = IndexedDb::new().await?;
35
36        let nsec: Option<String> = storage.get_from_indexed_db(NSEC_KEY).await?;
37        let nsec: SecretKey = match nsec {
38            Some(str) => SecretKey::from_str(&str)?,
39            None => {
40                let mut entropy: [u8; 32] = [0; 32];
41                getrandom::getrandom(&mut entropy).unwrap();
42
43                let nsec = SecretKey::from_slice(&entropy)?;
44                storage
45                    .save_to_indexed_db(NSEC_KEY, hex::encode(nsec.secret_bytes()))
46                    .await?;
47                nsec
48            }
49        };
50
51        let oracle = Oracle::from_signing_key(storage.clone(), nsec)?;
52
53        let client = Client::new(oracle.nostr_keys());
54        for relay in &relays {
55            client.add_relay(relay.as_str()).await?;
56        }
57        client.connect().await;
58
59        Ok(Kormir {
60            oracle,
61            storage,
62            client,
63        })
64    }
65
66    pub async fn restore(str: String) -> Result<(), JsError> {
67        let nsec = Keys::parse(&str)?;
68        IndexedDb::clear().await?;
69        let storage = IndexedDb::new().await?;
70
71        storage
72            .save_to_indexed_db(NSEC_KEY, hex::encode(nsec.secret_key().secret_bytes()))
73            .await?;
74
75        Ok(())
76    }
77
78    pub fn get_public_key(&self) -> String {
79        hex::encode(self.oracle.public_key().serialize())
80    }
81
82    pub async fn create_enum_event(
83        &self,
84        event_id: String,
85        outcomes: Vec<String>,
86        event_maturity_epoch: u32,
87    ) -> Result<String, JsError> {
88        let ann = self
89            .oracle
90            .create_enum_event(event_id.clone(), outcomes, event_maturity_epoch)
91            .await?;
92
93        let hex = hex::encode(ann.encode());
94
95        log::info!("Created enum event: {hex}");
96
97        let event =
98            kormir::nostr_events::create_announcement_event(&self.oracle.nostr_keys(), &ann)
99                .map_err(|_| JsError::Nostr)?;
100
101        log::debug!("Created nostr event: {}", event.as_json());
102
103        self.storage
104            .add_announcement_event_id(event_id, event.id.to_hex())
105            .await?;
106
107        log::debug!(
108            "Added announcement event id to storage: {}",
109            event.id.to_hex()
110        );
111
112        self.client.send_event(&event).await?;
113
114        log::trace!("Sent event to nostr");
115
116        Ok(hex)
117    }
118
119    pub async fn sign_enum_event(
120        &self,
121        event_id: String,
122        outcome: String,
123    ) -> Result<String, JsError> {
124        let attestation = self
125            .oracle
126            .sign_enum_event(event_id.clone(), outcome)
127            .await?;
128
129        let event = self
130            .storage
131            .get_event(event_id.clone())
132            .await?
133            .ok_or(JsError::NotFound)?;
134        let nostr_event_id = EventId::from_hex(&event.announcement_event_id.unwrap()).unwrap();
135
136        let event = kormir::nostr_events::create_attestation_event(
137            &self.oracle.nostr_keys(),
138            &attestation,
139            nostr_event_id,
140        )
141        .map_err(|_| JsError::Nostr)?;
142
143        self.storage
144            .add_attestation_event_id(event_id, event.id.to_hex())
145            .await?;
146
147        self.client.send_event(&event).await?;
148
149        Ok(hex::encode(attestation.encode()))
150    }
151
152    pub async fn create_numeric_event(
153        &self,
154        event_id: String,
155        num_digits: u16,
156        is_signed: bool,
157        precision: i32,
158        unit: String,
159        event_maturity_epoch: u32,
160    ) -> Result<String, JsError> {
161        let ann = self
162            .oracle
163            .create_numeric_event(
164                event_id.clone(),
165                num_digits,
166                is_signed,
167                precision,
168                unit,
169                event_maturity_epoch,
170            )
171            .await?;
172
173        let hex = hex::encode(ann.encode());
174
175        log::info!("Created numeric event: {hex}");
176
177        let event =
178            kormir::nostr_events::create_announcement_event(&self.oracle.nostr_keys(), &ann)
179                .map_err(|_| JsError::Nostr)?;
180
181        log::debug!("Created nostr event: {}", event.as_json());
182
183        self.storage
184            .add_announcement_event_id(event_id, event.id.to_hex())
185            .await?;
186
187        log::debug!(
188            "Added announcement event id to storage: {}",
189            event.id.to_hex()
190        );
191
192        self.client.send_event(&event).await?;
193
194        log::trace!("Sent event to nostr");
195
196        Ok(hex)
197    }
198
199    pub async fn sign_numeric_event(
200        &self,
201        event_id: String,
202        outcome: i64,
203    ) -> Result<String, JsError> {
204        let attestation = self
205            .oracle
206            .sign_numeric_event(event_id.clone(), outcome)
207            .await?;
208
209        let event = self
210            .storage
211            .get_event(event_id.clone())
212            .await?
213            .ok_or(JsError::NotFound)?;
214        let nostr_event_id = EventId::from_hex(&event.announcement_event_id.unwrap()).unwrap();
215
216        let event = kormir::nostr_events::create_attestation_event(
217            &self.oracle.nostr_keys(),
218            &attestation,
219            nostr_event_id,
220        )
221        .map_err(|_| JsError::Nostr)?;
222
223        self.storage
224            .add_attestation_event_id(event_id, event.id.to_hex())
225            .await?;
226
227        self.client.send_event(&event).await?;
228
229        Ok(hex::encode(attestation.encode()))
230    }
231
232    pub async fn list_events(&self) -> Result<JsValue /* Vec<EventData> */, JsError> {
233        let data = self.storage.list_events().await?;
234        let events = data.into_iter().map(EventData::from).collect::<Vec<_>>();
235
236        Ok(JsValue::from_serde(&events)?)
237    }
238
239    pub async fn decode_announcement(str: String) -> Result<Announcement, JsError> {
240        let bytes = hex::decode(str)?;
241        let mut cursor = kormir::lightning::io::Cursor::new(&bytes);
242        let ann = OracleAnnouncement::read(&mut cursor)?;
243        Ok(ann.into())
244    }
245
246    pub async fn decode_attestation(str: String) -> Result<Attestation, JsError> {
247        let bytes = hex::decode(str)?;
248        let mut cursor = kormir::lightning::io::Cursor::new(&bytes);
249        let attestation = OracleAttestation::read(&mut cursor)?;
250        Ok(attestation.into())
251    }
252}