@@ -236,6 +236,15 @@ pub struct Note {
236236 pub data : Bytes ,
237237}
238238
239+ /// A fact log from the kimap, converted to a 'resolved' format using
240+ /// namespace data saved in the kns_indexer
241+ #[ derive( Clone , Debug , Deserialize , Serialize ) ]
242+ pub struct Fact {
243+ pub fact : String ,
244+ pub parent_path : String ,
245+ pub data : Bytes ,
246+ }
247+
239248/// Errors that can occur when decoding a log from the kimap using
240249/// [`decode_mint_log`] or [`decode_note_log`].
241250#[ derive( Clone , Debug , Deserialize , Serialize ) ]
@@ -257,24 +266,47 @@ pub enum DecodeLogError {
257266///
258267/// This checks a **single name**, not the full path-name. A full path-name
259268/// is comprised of valid names separated by `.`
260- pub fn valid_name ( name : & str , note : bool ) -> bool {
269+ pub fn valid_entry ( entry : & str , note : bool , fact : bool ) -> bool {
270+ if note && fact {
271+ return false ;
272+ }
261273 if note {
262- name. is_ascii ( )
263- && name. len ( ) >= 2
264- && name. chars ( ) . next ( ) == Some ( '~' )
265- && name
266- . chars ( )
267- . skip ( 1 )
268- . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
274+ valid_note ( entry)
275+ } else if fact {
276+ valid_fact ( entry)
269277 } else {
270- name. is_ascii ( )
271- && name. len ( ) >= 1
272- && name
273- . chars ( )
274- . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
278+ valid_name ( entry)
275279 }
276280}
277281
282+ pub fn valid_name ( name : & str ) -> bool {
283+ name. is_ascii ( )
284+ && name. len ( ) >= 1
285+ && name
286+ . chars ( )
287+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
288+ }
289+
290+ pub fn valid_note ( note : & str ) -> bool {
291+ note. is_ascii ( )
292+ && note. len ( ) >= 2
293+ && note. chars ( ) . next ( ) == Some ( '~' )
294+ && note
295+ . chars ( )
296+ . skip ( 1 )
297+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
298+ }
299+
300+ pub fn valid_fact ( fact : & str ) -> bool {
301+ fact. is_ascii ( )
302+ && fact. len ( ) >= 2
303+ && fact. chars ( ) . next ( ) == Some ( '!' )
304+ && fact
305+ . chars ( )
306+ . skip ( 1 )
307+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
308+ }
309+
278310/// Produce a namehash from a kimap name.
279311pub fn namehash ( name : & str ) -> String {
280312 let mut node = B256 :: default ( ) ;
@@ -299,7 +331,7 @@ pub fn decode_mint_log(log: &crate::eth::Log) -> Result<Mint, DecodeLogError> {
299331 let decoded = contract:: Mint :: decode_log_data ( log. data ( ) , true )
300332 . map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
301333 let name = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
302- if !valid_name ( & name, false ) {
334+ if !valid_name ( & name) {
303335 return Err ( DecodeLogError :: InvalidName ( name) ) ;
304336 }
305337 match resolve_parent ( log, None ) {
@@ -318,7 +350,7 @@ pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
318350 let decoded = contract:: Note :: decode_log_data ( log. data ( ) , true )
319351 . map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
320352 let note = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
321- if !valid_name ( & note, true ) {
353+ if !valid_note ( & note) {
322354 return Err ( DecodeLogError :: InvalidName ( note) ) ;
323355 }
324356 match resolve_parent ( log, None ) {
@@ -331,6 +363,26 @@ pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
331363 }
332364}
333365
366+ pub fn decode_fact_log ( log : & crate :: eth:: Log ) -> Result < Fact , DecodeLogError > {
367+ let contract:: Fact :: SIGNATURE_HASH = log. topics ( ) [ 0 ] else {
368+ return Err ( DecodeLogError :: UnexpectedTopic ( log. topics ( ) [ 0 ] ) ) ;
369+ } ;
370+ let decoded = contract:: Fact :: decode_log_data ( log. data ( ) , true )
371+ . map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
372+ let fact = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
373+ if !valid_fact ( & fact) {
374+ return Err ( DecodeLogError :: InvalidName ( fact) ) ;
375+ }
376+ match resolve_parent ( log, None ) {
377+ Some ( parent_path) => Ok ( Fact {
378+ fact,
379+ parent_path,
380+ data : decoded. data ,
381+ } ) ,
382+ None => Err ( DecodeLogError :: UnresolvedParent ( fact) ) ,
383+ }
384+ }
385+
334386/// Given a [`crate::eth::Log`] (which must be a log from kimap), resolve the parent name
335387/// of the new entry or note.
336388pub fn resolve_parent ( log : & crate :: eth:: Log , timeout : Option < u64 > ) -> Option < String > {
@@ -354,10 +406,18 @@ pub fn resolve_full_name(log: &crate::eth::Log, timeout: Option<u64>) -> Option<
354406 let decoded = contract:: Note :: decode_log_data ( log. data ( ) , true ) . unwrap ( ) ;
355407 decoded. label
356408 }
409+ contract:: Fact :: SIGNATURE_HASH => {
410+ let decoded = contract:: Fact :: decode_log_data ( log. data ( ) , true ) . unwrap ( ) ;
411+ decoded. label
412+ }
357413 _ => return None ,
358414 } ;
359415 let name = String :: from_utf8_lossy ( & log_name) ;
360- if !valid_name ( & name, log. topics ( ) [ 0 ] == contract:: Note :: SIGNATURE_HASH ) {
416+ if !valid_entry (
417+ & name,
418+ log. topics ( ) [ 0 ] == contract:: Note :: SIGNATURE_HASH ,
419+ log. topics ( ) [ 0 ] == contract:: Fact :: SIGNATURE_HASH ,
420+ ) {
361421 return None ;
362422 }
363423 Some ( format ! ( "{name}.{parent_name}" ) )
@@ -468,6 +528,13 @@ impl Kimap {
468528 . event ( contract:: Note :: SIGNATURE )
469529 }
470530
531+ /// Create a filter for all fact events.
532+ pub fn fact_filter ( & self ) -> crate :: eth:: Filter {
533+ crate :: eth:: Filter :: new ( )
534+ . address ( self . address )
535+ . event ( contract:: Fact :: SIGNATURE )
536+ }
537+
471538 /// Create a filter for a given set of specific notes. This function will
472539 /// hash the note labels and use them as the topic3 filter.
473540 ///
@@ -483,4 +550,20 @@ impl Kimap {
483550 . collect :: < Vec < _ > > ( ) ,
484551 )
485552 }
553+
554+ /// Create a filter for a given set of specific facts. This function will
555+ /// hash the fact labels and use them as the topic3 filter.
556+ ///
557+ /// Example:
558+ /// ```rust
559+ /// let filter = kimap.facts_filter(&["!fact1", "!fact2"]);
560+ /// ```
561+ pub fn facts_filter ( & self , facts : & [ & str ] ) -> crate :: eth:: Filter {
562+ self . fact_filter ( ) . topic3 (
563+ facts
564+ . into_iter ( )
565+ . map ( |fact| keccak256 ( fact) )
566+ . collect :: < Vec < _ > > ( ) ,
567+ )
568+ }
486569}
0 commit comments