Skip to content

Commit

Permalink
Merge branch 'main' into feat/upgrade-web3
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Dec 5, 2024
2 parents eaffbcf + b148078 commit 2d9ce22
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 28 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,39 @@
Use the [Alchemy](https://alchemy.com/?r=jk3NDM0MTIwODIzM) provider plugin to interact with blockchains via APIs.
The `ape-alchemy` plugin supports the following ecosystems:

- Ethereum
- Abstract
- ApeChain
- Arbitrum
- Astar
- Avalanche
- Base
- Berachain
- Blast
- BSC (including opBNB)
- CrossFi
- Ethereum
- Fantom
- Flow EVM
- Geist
- Gnosis
- Lens
- Linea
- Lumia
- Mantle
- Metis
- Optimism
- Polygon
- Polygon-ZkEVM
- Rootstock
- Scroll
- Shape
- Soneium
- Unichain
- XMTP
- World-Chain
- zetachain
- ZkSync
- Zora

## Dependencies

Expand Down
96 changes: 94 additions & 2 deletions ape_alchemy/_utils.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,75 @@
NETWORKS = {
"ethereum": [
"abstract": [
"testnet",
],
"apechain": [
"mainnet",
"sepolia",
"curtis",
],
"arbitrum": [
"mainnet",
"nova",
"sepolia",
],
"astar": [
"mainnet",
],
"avalanche": [
"fuji",
"mainnet",
],
"base": [
"mainnet",
"sepolia",
],
"berachain": ["bartio"],
"blast": [
"mainnet",
"sepolia",
],
"bsc": ["mainnet", "testnet", "opbnb", "opbnb-testnet"],
"crossfi": [
"mainnet",
"testnet",
],
"ethereum": [
"mainnet",
"sepolia",
],
"fantom": [
"opera",
"testnet",
],
"flow-evm": [
"mainnet",
"testnet",
],
"geist": [
"mainnet",
"polter",
],
"gnosis": [
"mainnet",
"chiado",
],
"lens": [
"sepolia",
],
"linea": [
"mainnet",
"sepolia",
],
"lumia": [
"prism",
"testnet",
],
"mantle": [
"mainnet",
"sepolia",
],
"metis": [
"mainnet",
],
"optimism": [
"mainnet",
"sepolia",
Expand All @@ -27,4 +82,41 @@
"mainnet",
"cardona",
],
"rootstock": [
"mainnet",
"testnet",
],
"scroll": [
"mainnet",
"sepolia",
],
"shape": [
"mainnet",
"sepolia",
],
"soneium": [
"minato",
],
"unichain": [
"sepolia",
],
"xmtp": [
"sepolia",
],
"world-chain": [
"mainnet",
"sepolia",
],
"zetachain": [
"mainnet",
"testnet",
],
"zksync": [
"mainnet",
"sepolia",
],
"zora": [
"mainnet",
"sepolia",
],
}
84 changes: 62 additions & 22 deletions ape_alchemy/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
from typing import TYPE_CHECKING, Any, Optional

from ape.api import ReceiptAPI, TraceAPI, TransactionAPI, UpstreamProvider
from ape.exceptions import (
APINotImplementedError,
ContractLogicError,
ProviderError,
VirtualMachineError,
)
from ape.exceptions import APINotImplementedError, ContractLogicError, VirtualMachineError
from ape.logging import logger
from ape_ethereum.provider import Web3Provider
from eth_pydantic_types import HexBytes
Expand Down Expand Up @@ -42,7 +37,29 @@
# Alchemy will try to publish private transactions for 25 blocks.
PRIVATE_TX_BLOCK_WAIT = 25

NETWORKS_SUPPORTING_WEBSOCKETS = ("ethereum", "arbitrum", "base", "optimism", "polygon", "fantom")
# NOTE: "*" means "all networks".
NETWORKS_SUPPORTING_WEBSOCKETS = {
"arbitrum": "*",
"avalanche": "*",
"base": "*",
"bsc": ("mainnet", "testnet"),
"berachain": "*",
"blast": "*",
"ethereum": "*",
"fantom": "*",
"geist": ("polter",),
"gnosis": "*",
"lens": "*",
"linea": "*",
"optimism": "*",
"polygon": "*",
"scroll": "*",
"shape": "*",
"soneium": "*",
"unichain": "*",
"world-chain": "*",
"zksync": "*",
}


class Alchemy(Web3Provider, UpstreamProvider):
Expand Down Expand Up @@ -84,24 +101,41 @@ def uri(self):
if not key:
raise MissingProjectKeyError(options)

network_formats_by_ecosystem = {
"ethereum": "https://eth-{0}.g.alchemy.com/v2/{1}",
"arbitrum": "https://arb-{0}.g.alchemy.com/v2/{1}",
"base": "https://base-{0}.g.alchemy.com/v2/{1}",
"optimism": "https://opt-{0}.g.alchemy.com/v2/{1}",
"polygon": "https://polygon-{0}.g.alchemy.com/v2/{1}",
"polygon-zkevm": "https://polygonzkevm-{0}.g.alchemy.com/v2/{1}",
"fantom": "https://fantom-{0}.g.alchemy.com/v2/{1}",
}

network_format = network_formats_by_ecosystem[ecosystem_name]
network_name = self.network.name

# NOTE: Fantom's mainnet is named "opera", but the Alchemy URI expects "mainnet".
if self.network.ecosystem.name == "fantom" and self.network.name == "opera":
network_name = "mainnet"

uri = network_format.format(network_name, key)
default_format = "{0}-{1}.g.alchemy.com/v2/{2}"
network_formats_by_ecosystem = {
"arbitrum": "arb-{0}.g.alchemy.com/v2/{1}",
"avalanche": "avax-{0}.g.alchemy.com/v2/{1}",
"bsc": "bnb-{0}.g.alchemy.com/v2/{1}",
"ethereum": "eth-{0}.g.alchemy.com/v2/{1}",
"flow-evm": "flow-{0}.g.alchemy.com/v2/{1}",
"fraxtal": "frax-{0}.g.alchemy.com/v2/{1}",
"optimism": "opt-{0}.g.alchemy.com/v2/{1}",
"polygon-zkevm": "polygonzkevm-{0}.g.alchemy.com/v2/{1}",
"world-chain": "worldchain-{0}.g.alchemy.com/v2/{1}",
}

if ecosystem_name in network_formats_by_ecosystem:
# Special cases.
if network_name == "nova":
uri = "arbnova-mainnet.g.alchemy.com/v2/{}".format(key)
elif network_name.startswith("opbnb"):
sub_network = "mainnet" if network_name == "opbnb" else "testnet"
uri = "opbnb-{0}.g.alchemy.com/v2/{1}".format(sub_network, key)
else:
network_format = network_formats_by_ecosystem[ecosystem_name]
uri = network_format.format(network_name, key)
elif ecosystem_name == "xmtp" and network_name == "sepolia":
uri = "xmtp-testnet.g.alchemy.com/v2/{0}".format(key)
else:
uri = default_format.format(ecosystem_name, network_name, key)

uri = f"https://{uri}"
self.network_uris[(ecosystem_name, network_name)] = uri
return uri

Expand All @@ -111,7 +145,13 @@ def http_uri(self) -> str:
return self.uri

@property
def ws_uri(self) -> str:
def ws_uri(self) -> Optional[str]:
ecosystem_name = self.network.ecosystem.name
network_name = self.network.name
supported_networks = NETWORKS_SUPPORTING_WEBSOCKETS.get(ecosystem_name, [])
if supported_networks != "*" and network_name not in supported_networks:
return None

# NOTE: Overriding `Web3Provider.ws_uri` implementation
return "ws" + self.uri[4:] # Remove `http` in default URI w/ `ws`

Expand Down Expand Up @@ -142,8 +182,8 @@ def connect(self):
is_poa = True

self._web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)
except Exception as err:
raise ProviderError(f"Failed to connect to Alchemy.\n{repr(err)}") from err
except Exception:
is_poa = None

if is_poa is None:
# Check if is PoA but just wasn't as such yet.
Expand Down
9 changes: 6 additions & 3 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ape.utils import ZERO_ADDRESS

from ape_alchemy._utils import NETWORKS
from ape_alchemy.provider import NETWORKS_SUPPORTING_WEBSOCKETS, Alchemy
from ape_alchemy.provider import Alchemy


@pytest.fixture(params=[(name, net) for name, values in NETWORKS.items() for net in values])
Expand All @@ -19,13 +19,16 @@ def provider(request):
def test_http(provider):
assert isinstance(provider, Alchemy)
assert provider.http_uri.startswith("https")
assert provider.get_balance(ZERO_ADDRESS) > 0

# NOTE: Sometimes the balance is 0, for some chains.
assert provider.get_balance(ZERO_ADDRESS) is not None
assert provider.get_block(0)
assert provider.get_block("latest")


def test_ws(provider):
if provider.network.ecosystem.name not in NETWORKS_SUPPORTING_WEBSOCKETS:
ws_uri = provider.ws_uri
if ws_uri is None:
# Test will fail. Network does not support ws clients.
return

Expand Down

0 comments on commit 2d9ce22

Please sign in to comment.