@@ -796,6 +796,100 @@ def create_storage_key(
796
796
metadata = self .runtime .metadata ,
797
797
)
798
798
799
+ def subscribe_storage (
800
+ self ,
801
+ storage_keys : list [StorageKey ],
802
+ subscription_handler : Callable [[StorageKey , Any , str ], Any ],
803
+ ):
804
+ """
805
+
806
+ Subscribe to provided storage_keys and keep tracking until `subscription_handler` returns a value
807
+
808
+ Example of a StorageKey:
809
+ ```
810
+ StorageKey.create_from_storage_function(
811
+ "System", "Account", ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"]
812
+ )
813
+ ```
814
+
815
+ Example of a subscription handler:
816
+ ```
817
+ def subscription_handler(storage_key, obj, subscription_id):
818
+ if obj is not None:
819
+ # the subscription will run until your subscription_handler returns something other than `None`
820
+ return obj
821
+ ```
822
+
823
+ Args:
824
+ storage_keys: StorageKey list of storage keys to subscribe to
825
+ subscription_handler: coroutine function to handle value changes of subscription
826
+
827
+ """
828
+ self .init_runtime ()
829
+
830
+ storage_key_map = {s .to_hex (): s for s in storage_keys }
831
+
832
+ def result_handler (
833
+ message : dict , subscription_id : str
834
+ ) -> tuple [bool , Optional [Any ]]:
835
+ result_found = False
836
+ subscription_result = None
837
+ if "params" in message :
838
+ # Process changes
839
+ for change_storage_key , change_data in message ["params" ]["result" ][
840
+ "changes"
841
+ ]:
842
+ # Check for target storage key
843
+ storage_key = storage_key_map [change_storage_key ]
844
+
845
+ if change_data is not None :
846
+ change_scale_type = storage_key .value_scale_type
847
+ result_found = True
848
+ elif (
849
+ storage_key .metadata_storage_function .value ["modifier" ]
850
+ == "Default"
851
+ ):
852
+ # Fallback to default value of storage function if no result
853
+ change_scale_type = storage_key .value_scale_type
854
+ change_data = (
855
+ storage_key .metadata_storage_function .value_object [
856
+ "default"
857
+ ].value_object
858
+ )
859
+ else :
860
+ # No result is interpreted as an Option<...> result
861
+ change_scale_type = f"Option<{ storage_key .value_scale_type } >"
862
+ change_data = (
863
+ storage_key .metadata_storage_function .value_object [
864
+ "default"
865
+ ].value_object
866
+ )
867
+
868
+ # Decode SCALE result data
869
+ updated_obj = self .decode_scale (
870
+ type_string = change_scale_type ,
871
+ scale_bytes = hex_to_bytes (change_data ),
872
+ )
873
+
874
+ subscription_result = subscription_handler (
875
+ storage_key , updated_obj , subscription_id
876
+ )
877
+
878
+ if subscription_result is not None :
879
+ # Handler returned end result: unsubscribe from further updates
880
+ self .rpc_request ("state_unsubscribeStorage" , [subscription_id ])
881
+
882
+ return result_found , subscription_result
883
+
884
+ if not callable (subscription_handler ):
885
+ raise ValueError ("Provided `subscription_handler` is not callable" )
886
+
887
+ return self .rpc_request (
888
+ "state_subscribeStorage" ,
889
+ [[s .to_hex () for s in storage_keys ]],
890
+ result_handler = result_handler ,
891
+ )
892
+
799
893
def get_metadata_storage_functions (self , block_hash = None ) -> list :
800
894
"""
801
895
Retrieves a list of all storage functions in metadata active at given block_hash (or chaintip if block_hash is
0 commit comments