from typing import Optional, List, Dict, Any, Union
from pydantic import Field, model_serializer, model_validator, BaseModel
from pypergraph.core import BIP_44_PATHS, KeyringWalletType, NetworkId
from .shared import sid_manager
from ..accounts.dag_account import DagAccount
from ..accounts.eth_account import EthAccount
from ..bip_helpers.bip39_helper import Bip39Helper
from ..keyrings.hd_keyring import HdKeyring
[docs]
class MultiChainWallet(BaseModel):
type: str = Field(default=KeyringWalletType.MultiChainWallet.value)
id: str = Field(default=None)
supported_assets: List[str] = Field(default=[])
label: Optional[str] = Field(default=None, max_length=12)
keyrings: List[HdKeyring] = Field(default=[])
mnemonic: Optional[str] = Field(default=None)
[docs]
@model_validator(mode="after")
def compute_id(self):
"""Automatically computes the id based on injected SID value."""
self.id = sid_manager.next_sid(self.type)
return self
[docs]
@model_serializer
def model_serialize(self) -> Dict[str, Any]:
return {
"type": self.type,
"label": self.label,
"secret": self.mnemonic,
"rings": [ring.model_dump() for ring in self.keyrings],
}
[docs]
def create(
self, label: str, mnemonic: Optional[str] = None, rings: Optional[list] = None
):
"""
If mnemonic is set, restore the wallet. Else, generate mnemonic and create new wallet.
:param label: Name of the wallet.
:param mnemonic: Seed phrase.
:param rings: Keyrings.
"""
bip39 = Bip39Helper()
self.label = label
self.mnemonic = mnemonic or bip39.generate_mnemonic()
if not bip39.is_valid(self.mnemonic):
raise ValueError("MultiAccountWallet :: Not a valid mnemonic phrase.")
self.deserialize(secret=self.mnemonic, label=label, rings=rings)
[docs]
def set_label(self, label: str):
"""
Set the name of the wallet.
:param label: Sets the name of the wallet.
"""
if not label:
raise ValueError("MultiChainWallet :: No label set.")
self.label = label
[docs]
def get_label(self) -> str:
"""
Get the name on the wallet.
:return: The name of the wallet.
"""
return self.label
[docs]
@staticmethod
def get_network():
raise ValueError("MultiChainWallet :: Does not support this method")
[docs]
def get_state(self) -> Dict[str, Any]:
return {
"id": self.id,
"type": self.type,
"label": self.label,
"supported_assets": self.supported_assets,
"accounts": [
{
"address": a.get_address(),
"network": a.get_network_id(),
"tokens": a.get_tokens(),
}
for a in self.get_accounts()
],
}
[docs]
@staticmethod
def import_account():
"""Importing MCW account is not supported."""
raise ValueError(
"MultiChainWallet :: Multi chain wallet does not support importing account."
)
[docs]
def get_accounts(self) -> List[Union[DagAccount, EthAccount]]:
"""
Get all MCW accounts.
:return: List of accounts with signing key.
"""
return [
account for keyring in self.keyrings for account in keyring.get_accounts()
]
[docs]
def get_account_by_address(
self, address: str
) -> Union[DagAccount, EthAccount]: # IKeyringAccount
account = None
for keyring in self.keyrings:
account = keyring.get_account_by_address(address)
if account:
break
return account
[docs]
@staticmethod
def remove_account():
"""Remove MCW not supported."""
raise ValueError("MultiChainWallet :: Does not allow removing accounts.")
[docs]
def export_secret_key(self) -> str:
"""
Export mnemonic seed phrase.
:return: Mnemonic seed phrase.
"""
return self.mnemonic
[docs]
def deserialize(self, label: str, secret: str, rings: Optional[list] = None):
self.set_label(label)
self.mnemonic = secret
self.keyrings = [
HdKeyring().create(
mnemonic=self.mnemonic,
hd_path=BIP_44_PATHS.CONSTELLATION_PATH.value,
network=NetworkId.Constellation.value,
number_of_accounts=1,
),
HdKeyring().create(
mnemonic=self.mnemonic,
hd_path=BIP_44_PATHS.ETH_WALLET_PATH.value,
network=NetworkId.Ethereum.value,
number_of_accounts=1,
),
]
if rings:
for i, r in enumerate(rings):
self.keyrings[i].deserialize(r)
[docs]
@staticmethod
def reset_sid():
sid_manager.reset_sid()