Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v2 verify routes #73

Merged
merged 14 commits into from
Mar 15, 2025
64 changes: 60 additions & 4 deletions crates/block-explorers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,27 @@ pub mod verify;

pub(crate) type Result<T, E = EtherscanError> = std::result::Result<T, E>;

/// The URL for the etherscan V2 API without the chainid param set.
pub const ETHERSCAN_V2_API_BASE_URL: &str = "https://api.etherscan.io/v2/api?chainid=";

/// The Etherscan.io API version 1 - classic verifier, one API per chain, 2 - new multichain
/// verifier
#[derive(Clone, Default, Debug, PartialEq, Copy)]
pub enum EtherscanApiVersion {
#[default]
V1,
V2,
}

/// The Etherscan.io API client.
#[derive(Clone, Debug)]
pub struct Client {
/// Client that executes HTTP requests
client: reqwest::Client,
/// Etherscan API key
api_key: Option<String>,
/// Etherscan API version
etherscan_api_version: EtherscanApiVersion,
/// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
etherscan_api_url: Url,
/// Etherscan base endpoint like <https://etherscan.io>
Expand Down Expand Up @@ -99,6 +113,16 @@ impl Client {
Client::builder().with_api_key(api_key).chain(chain)?.build()
}

/// Create a new client with the correct endpoint with the chain and chosen API v2 version
pub fn new_v2_from_env(chain: Chain) -> Result<Self> {
let api_key = std::env::var("ETHERSCAN_API_KEY")?;
Client::builder()
.with_api_version(EtherscanApiVersion::V2)
.with_api_key(api_key)
.chain(chain)?
.build()
Comment on lines +118 to +123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then this can just call new ad with_version(v2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little confused as to where this would be called -- within the builder or skip the builder entirely and use with_version() rather than new_v2_from_env?

}

/// Create a new client with the correct endpoints based on the chain and API key
/// from the default environment variable defined in [`Chain`].
pub fn new_from_env(chain: Chain) -> Result<Self> {
Expand Down Expand Up @@ -155,6 +179,11 @@ impl Client {
self
}

/// Returns the configured etherscan api version.
pub fn etherscan_api_version(&self) -> &EtherscanApiVersion {
&self.etherscan_api_version
}

pub fn etherscan_api_url(&self) -> &Url {
&self.etherscan_api_url
}
Expand Down Expand Up @@ -279,6 +308,8 @@ pub struct ClientBuilder {
api_key: Option<String>,
/// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
etherscan_api_url: Option<Url>,
/// Etherscan API version (v2 is new verifier version, v1 is the default)
etherscan_api_version: EtherscanApiVersion,
/// Etherscan base endpoint like <https://etherscan.io>
etherscan_url: Option<Url>,
/// Path to where ABI files should be cached
Expand All @@ -303,14 +334,29 @@ impl ClientBuilder {
) -> (reqwest::Result<Url>, reqwest::Result<Url>) {
(api.into_url(), url.into_url())
}
let (etherscan_api_url, etherscan_url) = chain
let (default_etherscan_api_url, etherscan_url) = chain
.named()
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?
.etherscan_urls()
.map(urls)
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?;

self.with_chain_id(chain).with_api_url(etherscan_api_url?)?.with_url(etherscan_url?)
// V2 etherscan default API urls are different – this handles that case.
let etherscan_api_url = if self.etherscan_api_version == EtherscanApiVersion::V2 {
let chain_id = chain.id();
Url::parse(&format!("{ETHERSCAN_V2_API_BASE_URL}{chain_id}"))
.map_err(|_| EtherscanError::Builder("Bad URL Parse".into()))?
} else {
default_etherscan_api_url?
};

self.with_chain_id(chain).with_api_url(etherscan_api_url)?.with_url(etherscan_url?)
}

/// Configures the etherscan api version
pub fn with_api_version(mut self, api_version: EtherscanApiVersion) -> Self {
self.etherscan_api_version = api_version;
self
}

/// Configures the etherscan url
Expand Down Expand Up @@ -370,14 +416,24 @@ impl ClientBuilder {
/// - `etherscan_api_url`
/// - `etherscan_url`
pub fn build(self) -> Result<Client> {
let ClientBuilder { client, api_key, etherscan_api_url, etherscan_url, cache, chain_id } =
self;
let ClientBuilder {
client,
api_key,
etherscan_api_version,
etherscan_api_url,
etherscan_url,
cache,
chain_id,
} = self;

let client = Client {
client: client.unwrap_or_default(),
api_key,
etherscan_api_url: etherscan_api_url
.clone()
.ok_or_else(|| EtherscanError::Builder("etherscan api url".to_string()))?,
// Set default API version to V1 if missing
etherscan_api_version,
etherscan_url: etherscan_url
.ok_or_else(|| EtherscanError::Builder("etherscan url".to_string()))?,
cache,
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"expiry":1741879991,"data":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"pubkey","type":"bytes","internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","internalType":"bytes"},{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"deposit_data_root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"get_deposit_count","inputs":[],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"get_deposit_root","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"event","name":"DepositEvent","inputs":[{"name":"pubkey","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"amount","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"signature","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"index","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}]}
{"expiry":1742116310,"data":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"pubkey","type":"bytes","internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","internalType":"bytes"},{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"deposit_data_root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"get_deposit_count","inputs":[],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"get_deposit_root","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"event","name":"DepositEvent","inputs":[{"name":"pubkey","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"amount","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"signature","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"index","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}]}

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions crates/block-explorers/test-data/rewards/IProtocolRewards.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/// @title IProtocolRewards
/// @notice The interface for deposits & withdrawals for Protocol Rewards
interface IProtocolRewards {
/// @notice Rewards Deposit Event
/// @param creator Creator for NFT rewards
/// @param createReferral Creator referral
/// @param mintReferral Mint referral user
/// @param firstMinter First minter reward recipient
/// @param zora ZORA recipient
/// @param from The caller of the deposit
/// @param creatorReward Creator reward amount
/// @param createReferralReward Creator referral reward
/// @param mintReferralReward Mint referral amount
/// @param firstMinterReward First minter reward amount
/// @param zoraReward ZORA amount
event RewardsDeposit(
address indexed creator,
address indexed createReferral,
address indexed mintReferral,
address firstMinter,
address zora,
address from,
uint256 creatorReward,
uint256 createReferralReward,
uint256 mintReferralReward,
uint256 firstMinterReward,
uint256 zoraReward
);

/// @notice Deposit Event
/// @param from From user
/// @param to To user (within contract)
/// @param reason Optional bytes4 reason for indexing
/// @param amount Amount of deposit
/// @param comment Optional user comment
event Deposit(address indexed from, address indexed to, bytes4 indexed reason, uint256 amount, string comment);

/// @notice Withdraw Event
/// @param from From user
/// @param to To user (within contract)
/// @param amount Amount of deposit
event Withdraw(address indexed from, address indexed to, uint256 amount);

/// @notice Cannot send to address zero
error ADDRESS_ZERO();

/// @notice Function argument array length mismatch
error ARRAY_LENGTH_MISMATCH();

/// @notice Invalid deposit
error INVALID_DEPOSIT();

/// @notice Invalid signature for deposit
error INVALID_SIGNATURE();

/// @notice Invalid withdraw
error INVALID_WITHDRAW();

/// @notice Signature for withdraw is too old and has expired
error SIGNATURE_DEADLINE_EXPIRED();

/// @notice Low-level ETH transfer has failed
error TRANSFER_FAILED();

/// @notice Generic function to deposit ETH for a recipient, with an optional comment
/// @param to Address to deposit to
/// @param to Reason system reason for deposit (used for indexing)
/// @param comment Optional comment as reason for deposit
function deposit(address to, bytes4 why, string calldata comment) external payable;

/// @notice Generic function to deposit ETH for multiple recipients, with an optional comment
/// @param recipients recipients to send the amount to, array aligns with amounts
/// @param amounts amounts to send to each recipient, array aligns with recipients
/// @param reasons optional bytes4 hash for indexing
/// @param comment Optional comment to include with mint
function depositBatch(address[] calldata recipients, uint256[] calldata amounts, bytes4[] calldata reasons, string calldata comment) external payable;

/// @notice Used by Zora ERC-721 & ERC-1155 contracts to deposit protocol rewards
/// @param creator Creator for NFT rewards
/// @param creatorReward Creator reward amount
/// @param createReferral Creator referral
/// @param createReferralReward Creator referral reward
/// @param mintReferral Mint referral user
/// @param mintReferralReward Mint referral amount
/// @param firstMinter First minter reward
/// @param firstMinterReward First minter reward amount
/// @param zora ZORA recipient
/// @param zoraReward ZORA amount
function depositRewards(
address creator,
uint256 creatorReward,
address createReferral,
uint256 createReferralReward,
address mintReferral,
uint256 mintReferralReward,
address firstMinter,
uint256 firstMinterReward,
address zora,
uint256 zoraReward
) external payable;

/// @notice Withdraw protocol rewards
/// @param to Withdraws from msg.sender to this address
/// @param amount amount to withdraw
function withdraw(address to, uint256 amount) external;
}
Loading