Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from ApeWorX/feat/starknet
Browse files Browse the repository at this point in the history
feat: starknet ecosystem support
  • Loading branch information
antazoey authored Apr 12, 2022
2 parents a476b28 + 37783ff commit 09abf0d
Show file tree
Hide file tree
Showing 27 changed files with 2,206 additions and 22 deletions.
7 changes: 1 addition & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ repos:
hooks:
- id: check-yaml

- repo: https://github.com/asottile/seed-isort-config
rev: v2.2.0
hooks:
- id: seed-isort-config

- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.9.3
hooks:
- id: isort

- repo: https://github.com/psf/black
rev: 21.10b0
rev: 21.12b0
hooks:
- id: black
name: black
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ To get started with working on the codebase, use the following steps prepare you

```bash
# clone the github repo and navigate into the folder
git clone https://github.com/ApeWorX/<PROJECT_NAME>.git
cd <PROJECT_NAME>
git clone https://github.com/ApeWorX/ape-starknet.git
cd ape-starknet

# create and load a virtual environment
python3 -m venv venv
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,38 @@ python3 setup.py install

## Quick Usage

TODO: Describe library overview in code
Deploy a new account:

```bash
ape starknet accounts create <ALIAS> --network starknet:testnet
```

You can deploy the same account to multiple networks.

```bash
ape starknet accounts create <ALIAS> --network starknet:mainnet
```

Now, when you look at this account, you will see it has multiple contract addresses:

```bash
ape starknet accounts list
```

shows:

```bash
Alias - <ALIAS>
Public key - 0x123444444d716666dd88882bE2e99991555DE1c7
Contract address (testnet) - 0x6b7111AA4111e5B2229c3332B66696888164440A773333143333B383333a183
Contract address (mainnet) - 0x7873113A4111e5B2229c3332B66696388163440A373333143333B3833332122
```

Import an existing account:

```bash
ape starknet accounts import <ALIAS> --address 0x6b7111AA4111e5B2229c3332B66696888164440A773333143333B383333a183 --network testnet
```

## Development

Expand Down
50 changes: 49 additions & 1 deletion ape_starknet/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# Add module top-level imports here
from ape import plugins
from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI, create_network_type
from ape.types import AddressType

from ape_starknet._utils import NETWORKS, PLUGIN_NAME
from ape_starknet.accounts import StarknetAccountContracts, StarknetKeyfileAccount
from ape_starknet.config import StarknetConfig
from ape_starknet.conversion import StarknetAddressConverter
from ape_starknet.ecosystems import Starknet
from ape_starknet.provider import StarknetProvider
from ape_starknet.tokens import TokenManager

tokens = TokenManager()


@plugins.register(plugins.ConversionPlugin)
def converters():
yield AddressType, StarknetAddressConverter


@plugins.register(plugins.Config)
def config_class():
return StarknetConfig


@plugins.register(plugins.EcosystemPlugin)
def ecosystems():
yield Starknet


@plugins.register(plugins.NetworkPlugin)
def networks():
for network_name, network_params in NETWORKS.items():
yield PLUGIN_NAME, network_name, create_network_type(*network_params)

# NOTE: This works for development providers, as they get chain_id from themselves
yield PLUGIN_NAME, LOCAL_NETWORK_NAME, NetworkAPI


@plugins.register(plugins.ProviderPlugin)
def providers():
network_names = [LOCAL_NETWORK_NAME] + [k for k in NETWORKS.keys()]
for network_name in network_names:
yield PLUGIN_NAME, network_name, StarknetProvider


@plugins.register(plugins.AccountPlugin)
def account_types():
return StarknetAccountContracts, StarknetKeyfileAccount
11 changes: 11 additions & 0 deletions ape_starknet/_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import click

from ape_starknet.accounts._cli import accounts


@click.group()
def cli():
"""Starknet ecosystem commands"""


cli.add_command(accounts) # type: ignore
94 changes: 94 additions & 0 deletions ape_starknet/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import re
from typing import Any, Union

from ape.api.networks import LOCAL_NETWORK_NAME
from ape.exceptions import AddressError, ApeException, ProviderError, VirtualMachineError
from ape.types import AddressType, RawAddress
from eth_typing import HexAddress, HexStr
from eth_utils import (
add_0x_prefix,
encode_hex,
hexstr_if_str,
is_text,
keccak,
remove_0x_prefix,
to_hex,
)
from starknet_py.net.client import BadRequest # type: ignore
from starkware.starknet.definitions.general_config import StarknetChainId # type: ignore

PLUGIN_NAME = "starknet"
NETWORKS = {
# chain_id, network_id
"mainnet": (StarknetChainId.MAINNET.value, StarknetChainId.MAINNET.value),
"testnet": (StarknetChainId.TESTNET.value, StarknetChainId.TESTNET.value),
}
_HEX_ADDRESS_REG_EXP = re.compile("(0x)?[0-9a-f]*", re.IGNORECASE | re.ASCII)
"""Same as from eth-utils except not limited length."""


def get_chain_id(network_id: Union[str, int]) -> StarknetChainId:
if isinstance(network_id, int):
return StarknetChainId(network_id)

if network_id == LOCAL_NETWORK_NAME:
return StarknetChainId.TESTNET # Use TESTNET chain ID for local network

if network_id not in NETWORKS:
raise ValueError(f"Unknown network '{network_id}'.")

return StarknetChainId(NETWORKS[network_id][0])


def to_checksum_address(address: RawAddress) -> AddressType:
try:
hex_address = hexstr_if_str(to_hex, address).lower()
except AttributeError:
raise AddressError(f"Value must be any string, instead got type {type(address)}")

cleaned_address = remove_0x_prefix(HexStr(hex_address))
address_hash = encode_hex(keccak(text=cleaned_address))

checksum_address = add_0x_prefix(
HexStr(
"".join(
(hex_address[i].upper() if int(address_hash[i], 16) > 7 else hex_address[i])
for i in range(2, len(hex_address))
)
)
)

hex_address = HexAddress(checksum_address)
return AddressType(hex_address)


def is_hex_address(value: Any) -> bool:
if not is_text(value):
return False

return _HEX_ADDRESS_REG_EXP.fullmatch(value) is not None


def handle_client_errors(f):
def func(*args, **kwargs):
try:
result = f(*args, **kwargs)
if isinstance(result, dict) and result.get("error"):
message = result["error"].get("message") or "Transaction failed"
raise ProviderError(message)

return result

except BadRequest as err:
msg = err.text if hasattr(err, "text") else str(err)
raise ProviderError(msg) from err
except ApeException:
# Don't catch ApeExceptions, let them raise as they would.
raise
except Exception as err:
if "rejected" in str(err):
raise VirtualMachineError(base_err=err) from err

raise # Original exception

return func
Loading

0 comments on commit 09abf0d

Please sign in to comment.