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 , 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}