Skip to content

Commit

Permalink
Implement new EVM chains (#182)
Browse files Browse the repository at this point in the history
* Feature: Implemented new EVM chains.

* Fix: Added chain argument on initialization.

* Fix: Remove venv folder to keep on track.

* Fix: Added chain auto-loading if it's not defined.

* Fix: Added chain auto-loading by default configuration if the user don't request it explicitly.

* Fix: Solve issue with str chain value

* Fix: Solve typing issue passing the chain argument.

* Fix: Disable temporarily the chain field change to test it deeply.

* Fix: Update to already released aleph_message dependency.

* Fix: Removed build action for macos-12 as it's deprecated on GitHub actions.

---------

Co-authored-by: Andres D. Molins <[email protected]>
  • Loading branch information
nesitor and Andres D. Molins authored Nov 4, 2024
1 parent d54e9ac commit c24a3cf
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-12, macos-13, macos-14, ubuntu-22.04, ubuntu-24.04]
os: [macos-13, macos-14, ubuntu-22.04, ubuntu-24.04]
runs-on: ${{ matrix.os }}

steps:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ MANIFEST

# Per-project virtualenvs
.venv*/
venv/*
**/device.key

# environment variables
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dynamic = [ "version" ]
dependencies = [
"aiohttp>=3.8.3",
"aioresponses>=0.7.6",
"aleph-message>=0.4.9",
"aleph-message>=0.5",
"aleph-superfluid>=0.2.1",
"base58==2.1.1", # Needed now as default with _load_account changement
"coincurve; python_version<'3.11'",
Expand Down
51 changes: 34 additions & 17 deletions src/aleph/sdk/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

from aleph.sdk.chains.common import get_fallback_private_key
from aleph.sdk.chains.ethereum import ETHAccount
from aleph.sdk.chains.evm import EVMAccount
from aleph.sdk.chains.remote import RemoteAccount
from aleph.sdk.chains.solana import SOLAccount
from aleph.sdk.chains.substrate import DOTAccount
from aleph.sdk.conf import load_main_configuration, settings
from aleph.sdk.evm_utils import get_chains_with_super_token
from aleph.sdk.types import AccountFromPrivateKey
Expand All @@ -18,10 +20,24 @@
T = TypeVar("T", bound=AccountFromPrivateKey)

chain_account_map: Dict[Chain, Type[T]] = { # type: ignore
Chain.ETH: ETHAccount,
Chain.ARBITRUM: EVMAccount,
Chain.AVAX: ETHAccount,
Chain.BASE: ETHAccount,
Chain.BLAST: EVMAccount,
Chain.BOB: EVMAccount,
Chain.CYBER: EVMAccount,
Chain.DOT: DOTAccount,
Chain.ETH: ETHAccount,
Chain.FRAXTAL: EVMAccount,
Chain.LINEA: EVMAccount,
Chain.LISK: EVMAccount,
Chain.METIS: EVMAccount,
Chain.MODE: EVMAccount,
Chain.OPTIMISM: EVMAccount,
Chain.POL: EVMAccount,
Chain.SOL: SOLAccount,
Chain.WORLDCHAIN: EVMAccount,
Chain.ZORA: EVMAccount,
}


Expand All @@ -43,7 +59,7 @@ def account_from_hex_string(
return account_type(bytes.fromhex(private_key_str)) # type: ignore

account_type = load_chain_account_type(chain)
account = account_type(bytes.fromhex(private_key_str))
account = account_type(bytes.fromhex(private_key_str), chain)
if chain in get_chains_with_super_token():
account.switch_chain(chain)
return account # type: ignore
Expand All @@ -62,7 +78,7 @@ def account_from_file(
return account_type(private_key) # type: ignore

account_type = load_chain_account_type(chain)
account = account_type(private_key)
account = account_type(private_key, chain)
if chain in get_chains_with_super_token():
account.switch_chain(chain)
return account
Expand All @@ -76,28 +92,29 @@ def _load_account(
) -> AccountFromPrivateKey:
"""Load an account from a private key string or file, or from the configuration file."""

# Loads configuration if no account_type is specified
if not account_type:
config = load_main_configuration(settings.CONFIG_FILE)
config = load_main_configuration(settings.CONFIG_FILE)
chain_to_use = settings.DEFAULT_CHAIN

if not chain:
if config and hasattr(config, "chain"):
account_type = load_chain_account_type(config.chain)
chain_to_use = config.chain
logger.debug(
f"Detected {config.chain} account for path {settings.CONFIG_FILE}"
)
else:
account_type = account_type = load_chain_account_type(
Chain.ETH
) # Defaults to ETHAccount
logger.warning(
f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type and account_type.__name__}"
)

# Loads configuration if no account_type is specified
if not account_type:
account_type = load_chain_account_type(chain_to_use)
logger.warning(
f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type and account_type.__name__}"
)

# Loads private key from a string
if private_key_str:
return account_from_hex_string(private_key_str, account_type, chain)
return account_from_hex_string(private_key_str, account_type, chain_to_use)
# Loads private key from a file
elif private_key_path and private_key_path.is_file():
return account_from_file(private_key_path, account_type, chain)
return account_from_file(private_key_path, account_type, chain_to_use)
# For ledger keys
elif settings.REMOTE_CRYPTO_HOST:
logger.debug("Using remote account")
Expand All @@ -112,7 +129,7 @@ def _load_account(
else:
new_private_key = get_fallback_private_key()
account = account_from_hex_string(
bytes.hex(new_private_key), account_type, chain
bytes.hex(new_private_key), account_type, chain_to_use
)
logger.info(
f"Generated fallback private key with address {account.get_address()}"
Expand Down
48 changes: 48 additions & 0 deletions src/aleph/sdk/chains/evm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from decimal import Decimal
from pathlib import Path
from typing import Awaitable, Optional

from aleph_message.models import Chain
from eth_account import Account # type: ignore

from .common import get_fallback_private_key
from .ethereum import ETHAccount


class EVMAccount(ETHAccount):
def __init__(self, private_key: bytes, chain: Optional[Chain] = None):
super().__init__(private_key, chain)
# Decide if we have to send also the specified chain value or always use ETH
# if chain:
# self.CHAIN = chain

@staticmethod
def from_mnemonic(mnemonic: str, chain: Optional[Chain] = None) -> "EVMAccount":
Account.enable_unaudited_hdwallet_features()
return EVMAccount(
private_key=Account.from_mnemonic(mnemonic=mnemonic).key, chain=chain
)

def get_token_balance(self) -> Decimal:
raise ValueError(f"Token not implemented for this chain {self.CHAIN}")

def get_super_token_balance(self) -> Decimal:
raise ValueError(f"Super token not implemented for this chain {self.CHAIN}")

def create_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
raise ValueError(f"Flow creation not implemented for this chain {self.CHAIN}")

def get_flow(self, receiver: str):
raise ValueError(f"Get flow not implemented for this chain {self.CHAIN}")

def update_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
raise ValueError(f"Flow update not implemented for this chain {self.CHAIN}")

def delete_flow(self, receiver: str) -> Awaitable[str]:
raise ValueError(f"Flow deletion not implemented for this chain {self.CHAIN}")


def get_fallback_account(
path: Optional[Path] = None, chain: Optional[Chain] = None
) -> ETHAccount:
return ETHAccount(private_key=get_fallback_private_key(path=path), chain=chain)
62 changes: 58 additions & 4 deletions src/aleph/sdk/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,9 @@ class Settings(BaseSettings):
active=False,
),
# MAINNETS
Chain.ETH: ChainInfo(
chain_id=1,
rpc="https://eth-mainnet.public.blastapi.io",
token="0x27702a26126e0B3702af63Ee09aC4d1A084EF628",
Chain.ARBITRUM: ChainInfo(
chain_id=42161,
rpc="https://arbitrum-one.publicnode.com",
),
Chain.AVAX: ChainInfo(
chain_id=43114,
Expand All @@ -116,12 +115,65 @@ class Settings(BaseSettings):
token="0xc0Fbc4967259786C743361a5885ef49380473dCF",
super_token="0xc0Fbc4967259786C743361a5885ef49380473dCF",
),
Chain.BLAST: ChainInfo(
chain_id=81457,
rpc="https://blastl2-mainnet.public.blastapi.io",
),
Chain.BOB: ChainInfo(
chain_id=60808,
rpc="https://bob-mainnet.public.blastapi.io",
),
Chain.BSC: ChainInfo(
chain_id=56,
rpc="https://binance.llamarpc.com",
token="0x82D2f8E02Afb160Dd5A480a617692e62de9038C4",
active=False,
),
Chain.CYBER: ChainInfo(
chain_id=7560,
rpc="https://rpc.cyber.co",
),
Chain.ETH: ChainInfo(
chain_id=1,
rpc="https://eth-mainnet.public.blastapi.io",
token="0x27702a26126e0B3702af63Ee09aC4d1A084EF628",
),
Chain.FRAXTAL: ChainInfo(
chain_id=252,
rpc="https://rpc.frax.com",
),
Chain.LINEA: ChainInfo(
chain_id=59144,
rpc="https://linea-rpc.publicnode.com",
),
Chain.LISK: ChainInfo(
chain_id=1135,
rpc="https://rpc.api.lisk.com",
),
Chain.METIS: ChainInfo(
chain_id=1088,
rpc="https://metis.drpc.org",
),
Chain.MODE: ChainInfo(
chain_id=34443,
rpc="https://mode.drpc.org",
),
Chain.OPTIMISM: ChainInfo(
chain_id=10,
rpc="https://optimism-rpc.publicnode.com",
),
Chain.POL: ChainInfo(
chain_id=137,
rpc="https://polygon.gateway.tenderly.co",
),
Chain.WORLDCHAIN: ChainInfo(
chain_id=480,
rpc="https://worldchain-mainnet.gateway.tenderly.co",
),
Chain.ZORA: ChainInfo(
chain_id=7777777,
rpc="https://rpc.zora.energy/",
),
}
# Add all placeholders to allow easy dynamic setup of CHAINS
CHAINS_SEPOLIA_ACTIVE: Optional[bool]
Expand All @@ -135,6 +187,8 @@ class Settings(BaseSettings):
CHAINS_BASE_RPC: Optional[str]
CHAINS_BSC_RPC: Optional[str]

DEFAULT_CHAIN: Chain = Chain.ETH

# Dns resolver
DNS_IPFS_DOMAIN = "ipfs.public.aleph.sh"
DNS_PROGRAM_DOMAIN = "program.public.aleph.sh"
Expand Down
8 changes: 7 additions & 1 deletion src/aleph/sdk/evm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,16 @@ def get_super_token_address(
return None


def get_chains_with_holding() -> List[Union[Chain, str]]:
def get_compatible_chains() -> List[Union[Chain, str]]:
return [chain for chain, info in settings.CHAINS.items() if info.active]


def get_chains_with_holding() -> List[Union[Chain, str]]:
return [
chain for chain, info in settings.CHAINS.items() if info.active and info.token
]


def get_chains_with_super_token() -> List[Union[Chain, str]]:
return [
chain
Expand Down
6 changes: 3 additions & 3 deletions src/aleph/sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

__all__ = ("StorageEnum", "Account", "AccountFromPrivateKey", "GenericMessage")

from aleph_message.models import AlephMessage
from aleph_message.models import AlephMessage, Chain


class StorageEnum(str, Enum):
Expand Down Expand Up @@ -35,7 +35,7 @@ def get_public_key(self) -> str: ...
class AccountFromPrivateKey(Account, Protocol):
"""Only accounts that are initialized from a private key string are supported."""

def __init__(self, private_key: bytes): ...
def __init__(self, private_key: bytes, chain: Chain): ...

async def sign_raw(self, buffer: bytes) -> bytes: ...

Expand Down Expand Up @@ -77,6 +77,6 @@ class ChainInfo(BaseModel):

chain_id: int
rpc: str
token: str
token: Optional[str] = None
super_token: Optional[str] = None
active: bool = True

0 comments on commit c24a3cf

Please sign in to comment.