Skip to content

Commit d139a17

Browse files
committed
Converts all AccountIds from bt-decode to SS58
1 parent 50e70b2 commit d139a17

File tree

4 files changed

+95
-7
lines changed

4 files changed

+95
-7
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
_determine_if_old_runtime_call,
6565
_bt_decode_to_dict_or_list,
6666
legacy_scale_decode,
67+
convert_account_ids,
6768
)
6869
from async_substrate_interface.utils.storage import StorageKey
6970
from async_substrate_interface.type_registry import _TYPE_REGISTRY
@@ -816,6 +817,7 @@ async def initialize(self):
816817

817818
if ss58_prefix_constant:
818819
self.ss58_format = ss58_prefix_constant.value
820+
runtime.ss58_format = ss58_prefix_constant.value
819821
self.initialized = True
820822
self._initializing = False
821823

@@ -994,6 +996,12 @@ async def decode_scale(
994996
runtime = await self.init_runtime(block_hash=block_hash)
995997
if runtime.metadata_v15 is not None or force_legacy is True:
996998
obj = decode_by_type_string(type_string, runtime.registry, scale_bytes)
999+
try:
1000+
type_str_int = int(type_string.split("::")[1])
1001+
decoded_type_str = runtime.type_id_to_name[type_str_int]
1002+
obj = convert_account_ids(obj, decoded_type_str)
1003+
except (ValueError, KeyError):
1004+
pass
9971005
else:
9981006
obj = legacy_scale_decode(type_string, scale_bytes, runtime)
9991007
if return_scale_obj:
@@ -1105,6 +1113,7 @@ async def _get_runtime_for_version(
11051113
metadata_v15=metadata_v15,
11061114
runtime_info=runtime_info,
11071115
registry=registry,
1116+
ss58_format=self.ss58_format,
11081117
)
11091118
self.runtime_cache.add_item(
11101119
block=block_number,

async_substrate_interface/sync_substrate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ def get_runtime_for_version(
834834
metadata_v15=metadata_v15,
835835
runtime_info=runtime_info,
836836
registry=registry,
837+
ss58_format=self.ss58_format,
837838
)
838839
self.runtime_cache.add_item(
839840
block=block_number,

async_substrate_interface/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def __init__(
116116
metadata_v15=None,
117117
runtime_info=None,
118118
registry=None,
119+
ss58_format=SS58_FORMAT,
119120
):
121+
self.ss58_format = ss58_format
120122
self.config = {}
121123
self.chain = chain
122124
self.type_registry = type_registry

async_substrate_interface/utils/decoding.py

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Union, TYPE_CHECKING
1+
from typing import Union, TYPE_CHECKING, Any
22

33
from bt_decode import AxonInfo, PrometheusInfo, decode_list
4-
from scalecodec import ScaleBytes
4+
from scalecodec import ScaleBytes, ss58_encode
55

66
from async_substrate_interface.utils import hex_to_bytes
77
from async_substrate_interface.types import ScaleObj
@@ -120,24 +120,35 @@ def concat_hash_len(key_hasher: str) -> int:
120120
)
121121
middl_index = len(all_decoded) // 2
122122
decoded_keys = all_decoded[:middl_index]
123-
decoded_values = [ScaleObj(x) for x in all_decoded[middl_index:]]
124-
for dk, dv in zip(decoded_keys, decoded_values):
123+
decoded_values = all_decoded[middl_index:]
124+
for (kts, vts), (dk, dv) in zip(
125+
zip(pre_decoded_key_types, pre_decoded_value_types),
126+
zip(decoded_keys, decoded_values),
127+
):
125128
try:
126129
# strip key_hashers to use as item key
127130
if len(param_types) - len(params) == 1:
128131
item_key = dk[1]
132+
if kts[kts.index(", ") + 2 : kts.index(")")] == "scale_info::0":
133+
item_key = ss58_encode(bytes(item_key[0]), runtime.ss58_format)
134+
129135
else:
130136
item_key = tuple(
131137
dk[key + 1] for key in range(len(params), len(param_types) + 1, 2)
132138
)
139+
# TODO handle decoding here, but first figure out what triggers this
133140

134141
except Exception as _:
135142
if not ignore_decoding_errors:
136143
raise
137144
item_key = None
138-
139-
item_value = dv
140-
result.append([item_key, item_value])
145+
try:
146+
value_type_str_int = int(vts.split("::")[1])
147+
decoded_type_str = runtime.type_id_to_name[value_type_str_int]
148+
item_value = convert_account_ids(dv, decoded_type_str, runtime.ss58_format)
149+
except (ValueError, KeyError) as e:
150+
item_value = dv
151+
result.append([item_key, ScaleObj(item_value)])
141152
return result
142153

143154

@@ -154,3 +165,68 @@ def legacy_scale_decode(
154165
obj.decode(check_remaining=runtime.config.get("strict_scale_decode"))
155166

156167
return obj.value
168+
169+
170+
def is_accountid32(value: Any) -> bool:
171+
return (
172+
isinstance(value, tuple)
173+
and len(value) == 32
174+
and all(isinstance(b, int) and 0 <= b <= 255 for b in value)
175+
)
176+
177+
178+
def convert_account_ids(value: Any, type_str: str, ss58_format=42) -> Any:
179+
if "AccountId32" not in type_str:
180+
return value
181+
182+
# Option<T>
183+
if type_str.startswith("Option<") and value is not None:
184+
inner_type = type_str[7:-1]
185+
return convert_account_ids(value, inner_type)
186+
# Vec<T>
187+
if type_str.startswith("Vec<") and isinstance(value, (list, tuple)):
188+
inner_type = type_str[4:-1]
189+
return tuple(convert_account_ids(v, inner_type) for v in value)
190+
191+
# Vec<Vec<T>>
192+
if type_str.startswith("Vec<Vec<") and isinstance(value, (list, tuple)):
193+
inner_type = type_str[8:-2]
194+
return tuple(
195+
tuple(convert_account_ids(v2, inner_type) for v2 in v1) for v1 in value
196+
)
197+
198+
# Tuple
199+
if type_str.startswith("(") and isinstance(value, (list, tuple)):
200+
inner_parts = split_tuple_type(type_str)
201+
return tuple(convert_account_ids(v, t) for v, t in zip(value, inner_parts))
202+
203+
# AccountId32
204+
if type_str == "AccountId32" and is_accountid32(value[0]):
205+
return ss58_encode(bytes(value[0]), ss58_format=ss58_format)
206+
207+
# Fallback
208+
return value
209+
210+
211+
def split_tuple_type(type_str: str) -> list[str]:
212+
"""
213+
Splits a type string like '(AccountId32, Vec<StakeInfo>)' into ['AccountId32', 'Vec<StakeInfo>']
214+
Handles nested generics.
215+
"""
216+
s = type_str[1:-1]
217+
parts = []
218+
depth = 0
219+
current = ""
220+
for char in s:
221+
if char == "," and depth == 0:
222+
parts.append(current.strip())
223+
current = ""
224+
else:
225+
if char == "<":
226+
depth += 1
227+
elif char == ">":
228+
depth -= 1
229+
current += char
230+
if current:
231+
parts.append(current.strip())
232+
return parts

0 commit comments

Comments
 (0)