Skip to content

Support Option types #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 17, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions async_substrate_interface/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,11 @@ def _load_registry_type_map(self, registry):
type_id = type_entry["id"]
type_def = type_type["def"]
type_path = type_type.get("path")
if type_path and type_path[-1] == "Option":
self._handle_option_type(
type_entry, type_id, registry_type_map, type_id_to_name
)
continue
if type_entry.get("params") or type_def.get("variant"):
continue # has generics or is Enum
if type_path:
Expand Down Expand Up @@ -686,6 +691,23 @@ def _load_registry_type_map(self, registry):
self.registry_type_map = registry_type_map
self.type_id_to_name = type_id_to_name

def _handle_option_type(
self, type_entry, type_id, registry_type_map, type_id_to_name
):
params = type_entry["type"].get("params", [])
if params:
inner_names = []
for param in params:
inner_id = param["type"]
inner_name = type_id_to_name.get(inner_id, f"Type{inner_id}")
inner_names.append(inner_name)
type_name = f"Option<{', '.join(inner_names)}>"
else:
type_name = "Option"

registry_type_map[type_name] = type_id
type_id_to_name[type_id] = type_name

def reload_type_registry(
self, use_remote_preset: bool = True, auto_discover: bool = True
):
Expand Down Expand Up @@ -815,10 +837,28 @@ def _encode_scale(self, type_string, value: Any) -> bytes:
except KeyError:
vec_acct_id = "scale_info::152"

try:
optional_acct_u16 = f"scale_info::{self.registry_type_map['Option<(AccountId32, u16)>']}"
except KeyError:
optional_acct_u16 = "scale_info::573"

if type_string == "scale_info::0": # Is an AccountId
# encode string into AccountId
## AccountId is a composite type with one, unnamed field
return bytes.fromhex(ss58_decode(value, SS58_FORMAT))
return self._encode_account_id(value)

elif type_string == optional_acct_u16:
if value is None:
return b"\x00" # None

if not isinstance(value, (list, tuple)) or len(value) != 2:
raise ValueError("Expected tuple of (account_id, u16)")
account_id, u16_value = value

result = b"\x01"
result += self._encode_account_id(account_id)
result += u16_value.to_bytes(2, "little")
return result

elif type_string == vec_acct_id: # Vec<AccountId>
if not isinstance(value, (list, tuple)):
Expand All @@ -833,12 +873,7 @@ def _encode_scale(self, type_string, value: Any) -> bytes:

# Encode each AccountId
for account in value:
if isinstance(account, bytes):
result += account # Already encoded
else:
result += bytes.fromhex(
ss58_decode(account, SS58_FORMAT)
) # SS58 string
result += self._encode_account_id(account)
return result

if isinstance(value, ScaleType):
Expand All @@ -852,3 +887,16 @@ def _encode_scale(self, type_string, value: Any) -> bytes:
encode_by_type_string(type_string, self.runtime.registry, value)
)
return result

def _encode_account_id(self, account) -> bytes:
"""Encode an account ID into bytes.

Args:
account: Either bytes (already encoded) or SS58 string

Returns:
bytes: The encoded account ID
"""
if isinstance(account, bytes):
return account # Already encoded
return bytes.fromhex(ss58_decode(account, SS58_FORMAT)) # SS58 string