Skip to content
Merged
Show file tree
Hide file tree
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
33 changes: 17 additions & 16 deletions brownie/data/network-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,32 @@ live:
chainid: 1
id: mainnet
host: https://lb.drpc.org/ethereum/$DRPC_KEY
explorer: https://api.etherscan.io/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696"
provider: drpc
- name: Sepolia (DRPC)
chainid: 11155111
id: sepolia
host: https://lb.drpc.org/sepolia/$DRPC_KEY
explorer: https://api-sepolia.etherscan.io/
explorer: https://api-sepolia.etherscan.io/v2/api
provider: drpc
- name: Arbitrum
networks:
- name: Mainnet
chainid: 42161
id: arbitrum-main
host: https://lb.drpc.org/arbitrum/$DRPC_KEY
explorer: https://api.arbiscan.io/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858"
- name: Avalanche
networks:
- chainid: 43114
explorer: https://api.snowscan.xyz/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/avalanche/$DRPC_KEY
id: avax-main
name: Mainnet
- chainid: 43113
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/avalanche-fuji/$DRPC_KEY
id: avax-test
name: Testnet
Expand All @@ -39,12 +40,12 @@ live:
chainid: 97
id: bsc-test
host: https://lb.drpc.org/bsc-testnet/$DRPC_KEY
explorer: https://api-testnet.bscscan.com/api
explorer: https://api.etherscan.io/v2/api
- name: Mainnet
chainid: 56
id: bsc-main
host: https://lb.drpc.org/bsc/$DRPC_KEY
explorer: https://api.bscscan.com/api
explorer: https://api.etherscan.io/v2/api
- name: Fantom Opera
networks:
- name: Testnet
Expand All @@ -63,27 +64,27 @@ live:
chainid: 10
id: optimism-main
host: https://lb.drpc.org/optimism/$DRPC_KEY
explorer: https://api-optimistic.etherscan.io/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0x2DC0E2aa608532Da689e89e237dF582B783E552C"
- name: Sepolia
chainid: 11155420
id: optimism-test
host: https://lb.drpc.org/optimism-sepolia/$DRPC_KEY
explorer: https://api-sepolia-optimistic.etherscan.io/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0x2DC0E2aa608532Da689e89e237dF582B783E552C"
- name: Polygon
networks:
- name: Mainnet (DRPC)
chainid: 137
id: polygon-main
host: https://lb.drpc.org/polygon/$DRPC_KEY
explorer: https://api.polygonscan.com/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0xc8E51042792d7405184DfCa245F2d27B94D013b6"
- name: Amoy Testnet (DRPC)
chainid: 80002
id: polygon-test
host: https://lb.drpc.org/polygon-amoy/$DRPC_KEY
explorer: https://api-testnet.polygonscan.com/api
explorer: https://api.etherscan.io/v2/api
multicall2: "0x6842E0412AC1c00464dc48961330156a07268d14"
- name: Gnosis Chain
networks:
Expand All @@ -100,21 +101,21 @@ live:
- name: Polygon zkEVM
networks:
- chainid: 1101
explorer: https://api-zkevm.polygonscan.com/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/polygon-zkevm/$DRPC_KEY
id: zkevm-main
name: Polygon zkEVM mainnet
- name: Base
networks:
- chainid: 8453
explorer: https://api.basescan.org/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/base/$DRPC_KEY
id: base-main
name: Base mainnet
- name: Linea
networks:
- chainid: 59144
explorer: https://api.lineascan.build/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/linea/$DRPC_KEY
id: linea-main
name: Linea mainnet
Expand All @@ -128,21 +129,21 @@ live:
- name: Fraxtal
networks:
- chainid: 252
explorer: https://api.fraxscan.com/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/fraxtal/$DRPC_KEY
id: fraxtal-main
name: Fraxtal mainnet
- name: Sonic
networks:
- chainid: 146
explorer: https://api.sonicscan.org/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/sonic/$DRPC_KEY
id: sonic-main
name: Sonic mainnet
- name: HyperEVM
networks:
- chainid: 999
explorer: https://api.hyperevmscan.io/api
explorer: https://api.etherscan.io/v2/api
host: https://lb.drpc.org/hyperliquid/$DRPC_KEY
id: hyperevm-main
name: HyperEVM mainnet
Expand Down
136 changes: 115 additions & 21 deletions brownie/network/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@
"blast": "BLASTSCAN_TOKEN",
}

# Mapping of chainids to legacy token names for backward compatibility
_chainid_to_legacy_token = {
1: "ETHERSCAN_TOKEN",
11155111: "ETHERSCAN_TOKEN", # Sepolia
42161: "ARBISCAN_TOKEN",
10: "OPTIMISMSCAN_TOKEN",
11155420: "OPTIMISMSCAN_TOKEN", # Optimism Sepolia
137: "POLYGONSCAN_TOKEN",
80002: "POLYGONSCAN_TOKEN", # Polygon Amoy
1101: "POLYGONSCAN_TOKEN", # Polygon zkEVM
56: "BSCSCAN_TOKEN",
97: "BSCSCAN_TOKEN", # BSC Testnet
8453: "BASESCAN_TOKEN",
43114: "SNOWSCAN_TOKEN", # Avalanche
43113: "SNOWSCAN_TOKEN", # Avalanche Fuji
59144: "LINEASCAN_TOKEN", # Linea
252: "FRAXSCAN_TOKEN", # Fraxtal
146: "SONICSCAN_TOKEN", # Sonic
999: "HYPEREVMSCAN_TOKEN", # HyperEVM
}


class _ContractBase:
_dir_color = "bright magenta"
Expand Down Expand Up @@ -348,22 +369,48 @@ def publish_source(self, contract: Any, silent: bool = False) -> bool:
url = CONFIG.active_network.get("explorer")
if url is None:
raise ValueError("Explorer API not set for this network")
env_token = next((v for k, v in _explorer_tokens.items() if k in url), None)
if env_token is None:
raise ValueError(
f"Publishing source is only supported on {', '.join(_explorer_tokens)},"
"change the Explorer API"
)

if os.getenv(env_token):
api_key = os.getenv(env_token)
# Determine which API key to use
is_etherscan_v2 = "/v2/api" in url
api_key = None
env_token = None

if is_etherscan_v2:
# Try unified ETHERSCAN_TOKEN first
api_key = os.getenv("ETHERSCAN_TOKEN")
env_token = "ETHERSCAN_TOKEN"

# Fall back to chain-specific token for backward compatibility
if not api_key:
chainid = CONFIG.active_network.get("chainid")
legacy_token_name = _chainid_to_legacy_token.get(chainid)
if legacy_token_name:
api_key = os.getenv(legacy_token_name)
env_token = legacy_token_name

if not api_key:
raise ValueError(
f"An API token is required to verify contract source code. Visit https://etherscan.io/myapikey "
f"to obtain a token, and then store it as the environment variable $ETHERSCAN_TOKEN"
)
else:
host = urlparse(url).netloc
host = host[host.index(".") + 1 :]
raise ValueError(
f"An API token is required to verify contract source code. Visit https://{host}/ "
f"to obtain a token, and then store it as the environment variable ${env_token}"
)
# For non-v2 explorers, use the original URL-based detection
env_token = next((v for k, v in _explorer_tokens.items() if k in url), None)
if env_token is None:
raise ValueError(
f"Publishing source is only supported on {', '.join(_explorer_tokens)},"
"change the Explorer API"
)

if os.getenv(env_token):
api_key = os.getenv(env_token)
else:
host = urlparse(url).netloc
host = host[host.index(".") + 1 :]
raise ValueError(
f"An API token is required to verify contract source code. Visit https://{host}/ "
f"to obtain a token, and then store it as the environment variable ${env_token}"
)

address = _resolve_address(contract.address)

Expand Down Expand Up @@ -410,6 +457,11 @@ def publish_source(self, contract: Any, silent: bool = False) -> bool:
"sort": "asc",
"offset": 1,
}
# Add chainid for Etherscan v2 API
if "/v2/api" in url:
chainid = CONFIG.active_network.get("chainid")
if chainid:
params_tx["chainid"] = chainid
i = 0
while True:
response = requests.get(url, params=params_tx, headers=REQUEST_HEADERS)
Expand Down Expand Up @@ -452,6 +504,11 @@ def publish_source(self, contract: Any, silent: bool = False) -> bool:
"constructorArguements": constructor_arguments,
"licenseType": license_code,
}
# Add chainid for Etherscan v2 API
if "/v2/api" in url:
chainid = CONFIG.active_network.get("chainid")
if chainid:
payload_verification["chainid"] = chainid
response = requests.post(url, data=payload_verification, headers=REQUEST_HEADERS)
if response.status_code != 200:
raise ConnectionError(
Expand All @@ -472,6 +529,11 @@ def publish_source(self, contract: Any, silent: bool = False) -> bool:
"action": "checkverifystatus",
"guid": guid,
}
# Add chainid for Etherscan v2 API
if "/v2/api" in url:
chainid = CONFIG.active_network.get("chainid")
if chainid:
params_status["chainid"] = chainid
while True:
response = requests.get(url, params=params_status, headers=REQUEST_HEADERS)
if response.status_code != 200:
Expand Down Expand Up @@ -2005,16 +2067,48 @@ def _fetch_from_explorer(address: str, action: str, silent: bool) -> Dict:
address = _resolve_address(code[120:160])

params: Dict = {"module": "contract", "action": action, "address": address}
explorer, env_key = next(
((k, v) for k, v in _explorer_tokens.items() if k in url), (None, None)
)

# Check if this is an Etherscan v2 API endpoint
is_etherscan_v2 = "/v2/api" in url
if is_etherscan_v2:
# Add chainid parameter for Etherscan v2 API
chainid = CONFIG.active_network.get("chainid")
if chainid:
params["chainid"] = chainid

# Determine which API key to use
# For Etherscan v2, prioritize ETHERSCAN_TOKEN, then fall back to chain-specific tokens
env_key = None
api_key = None

if is_etherscan_v2:
# Try unified ETHERSCAN_TOKEN first
api_key = os.getenv("ETHERSCAN_TOKEN")
env_key = "ETHERSCAN_TOKEN"

# Fall back to chain-specific token for backward compatibility
if not api_key:
chainid = CONFIG.active_network.get("chainid")
legacy_token_name = _chainid_to_legacy_token.get(chainid)
if legacy_token_name:
api_key = os.getenv(legacy_token_name)
env_key = legacy_token_name
else:
# For non-v2 explorers, use the original URL-based detection
explorer, env_key = next(
((k, v) for k, v in _explorer_tokens.items() if k in url), (None, None)
)
if env_key:
api_key = os.getenv(env_key)

if env_key is not None:
if os.getenv(env_key):
params["apiKey"] = os.getenv(env_key)
if api_key:
params["apiKey"] = api_key
elif not silent:
explorer_name = "Etherscan" if is_etherscan_v2 else urlparse(url).netloc
warnings.warn(
f"No {explorer} API token set. You may experience issues with rate limiting. "
f"Visit https://{explorer}.io/register to obtain a token, and then store it "
f"No API token set. You may experience issues with rate limiting. "
f"Visit https://{explorer_name}/myapikey to obtain a token, and then store it "
f"as the environment variable ${env_key}",
BrownieEnvironmentWarning,
)
Expand Down
Loading