Skip to content

Commit d8769ca

Browse files
authored
feat: add web3 signer support for op-succinct-lite (#514)
* refac: make ProposerSigner util * feat: add web3 signer support to op-succinct-lite * chore: clean up + missing changes * fix + ci + docs + chore * refac: add Signer::from_env() * chore: add . * apply comments * refac: rename send_transaction_request_inner
1 parent b9dedeb commit d8769ca

File tree

23 files changed

+450
-420
lines changed

23 files changed

+450
-420
lines changed

.github/workflows/pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
with:
3636
profile: minimal
3737
toolchain: nightly
38-
components: rustfmt
38+
components: rustfmt, clippy
3939

4040
- name: Run cargo check
4141
uses: actions-rs/cargo@v1

Cargo.lock

Lines changed: 17 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"utils/celestia/client",
77
"utils/celestia/host",
88
"utils/proof",
9+
"utils/signer",
910
"utils/ethereum/client",
1011
"utils/ethereum/host",
1112
"programs/range/*",
@@ -97,6 +98,7 @@ op-succinct-ethereum-host-utils = { path = "utils/ethereum/host" }
9798
op-succinct-celestia-client-utils = { path = "utils/celestia/client" }
9899
op-succinct-celestia-host-utils = { path = "utils/celestia/host" }
99100
op-succinct-proof-utils = { path = "utils/proof" }
101+
op-succinct-signer-utils = { path = "utils/signer" }
100102
op-succinct-range-utils = { path = "programs/range/utils" }
101103

102104
# Alloy (Network)

book/fault_proofs/challenger.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ The challenger is configured through environment variables. Create a `.env.chall
3535
| `L2_RPC` | L2 RPC endpoint URL |
3636
| `FACTORY_ADDRESS` | Address of the DisputeGameFactory contract |
3737
| `GAME_TYPE` | Type identifier for the dispute game |
38-
| `PRIVATE_KEY` | Private key for transaction signing |
38+
39+
Either `PRIVATE_KEY` or both `SIGNER_URL` and `SIGNER_ADDRESS` must be set for transaction signing:
40+
41+
| Variable | Description |
42+
|----------|-------------|
43+
| `PRIVATE_KEY` | Private key for transaction signing (if using private key signer) |
44+
| `SIGNER_URL` | URL of the web3 signer service (if using web3 signer) |
45+
| `SIGNER_ADDRESS` | Address of the account managed by the web3 signer (if using web3 signer) |
3946

4047
### Optional Environment Variables
4148

book/fault_proofs/proposer.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ The proposer is configured through various environment variables. Create a `.env
3535
| `L2_RPC` | L2 RPC endpoint URL |
3636
| `FACTORY_ADDRESS` | Address of the DisputeGameFactory contract |
3737
| `GAME_TYPE` | Type identifier for the dispute game |
38-
| `PRIVATE_KEY` | Private key for transaction signing |
3938
| `NETWORK_PRIVATE_KEY` | Private key for the succinct prover network (Set to `0x0000000000000000000000000000000000000000000000000000000000000001` if not using fast finality mode) |
4039

40+
Either `PRIVATE_KEY` or both `SIGNER_URL` and `SIGNER_ADDRESS` must be set for transaction signing:
41+
42+
| Variable | Description |
43+
|----------|-------------|
44+
| `PRIVATE_KEY` | Private key for transaction signing (if using private key signer) |
45+
| `SIGNER_URL` | URL of the web3 signer service (if using web3 signer) |
46+
| `SIGNER_ADDRESS` | Address of the account managed by the web3 signer (if using web3 signer) |
47+
4148
To get a whitelisted key on the Succinct Prover Network for OP Succinct, fill out this [form](https://docs.google.com/forms/d/e/1FAIpQLSd2Yil8TrU54cIuohH1WvDvbxTusyqh5rsDmMAtGC85-Arshg/viewform?ref=https://succinctlabs.github.io/op-succinct/). The Succinct team will reach out to you with an RPC endpoint you can use.
4249

4350
### Optional Environment Variables
@@ -64,7 +71,13 @@ L1_RPC= # L1 RPC endpoint URL
6471
L2_RPC= # L2 RPC endpoint URL
6572
FACTORY_ADDRESS= # Address of the DisputeGameFactory contract (obtained from deployment)
6673
GAME_TYPE= # Type identifier for the dispute game (must match factory configuration)
74+
75+
# Transaction Signing Configuration (Choose one)
76+
# Option 1: Private Key Signer
6777
PRIVATE_KEY= # Private key for transaction signing
78+
# Option 2: Web3 Signer
79+
SIGNER_URL= # URL of the web3 signer service
80+
SIGNER_ADDRESS= # Address of the account managed by the web3 signer
6881
6982
# Optional Configuration
7083
MOCK_MODE=false # Whether to use mock mode

book/fault_proofs/quick_start.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@ This guide provides the fastest path to try out OP Succinct fault dispute games
3131
DISPUTE_GAME_FINALITY_DELAY_SECONDS=604800
3232
MAX_CHALLENGE_DURATION=604800
3333
MAX_PROVE_DURATION=86400
34-
STARTING_ROOT=0xbd4ab027af3c4db0c6b1329dad6d9f8e2505accabec75bfaa2b6b8033c1e60c5...
34+
STARTING_ROOT=0xbd4ab027af3c4db0c6b1329dad6d9f8e2505accabec75bfaa2b6b8033c1e60c5
3535
STARTING_L2_BLOCK_NUMBER=791000
3636
37+
# Optional
38+
# Warning: Setting PERMISSIONLESS_MODE=true allows anyone to propose and challenge games. Ensure this behavior is intended for your deployment.
39+
# For a permissioned setup, set this to false and configure PROPOSER_ADDRESSES and CHALLENGER_ADDRESSES.
40+
PERMISSIONLESS_MODE=true
41+
3742
# For testing, use mock verifier
3843
USE_SP1_MOCK_VERIFIER=true
3944
```
@@ -54,7 +59,9 @@ Save the output addresses, particularly the `FACTORY_ADDRESS` output as "Factory
5459
```env
5560
# Required Configuration
5661
L1_RPC=<YOUR_L1_RPC_URL>
62+
L1_BEACON_RPC=<L1_BEACON_RPC_URL>
5763
L2_RPC=<YOUR_L2_RPC_URL>
64+
L2_NODE_RPC=<L2_NODE_RPC_URL>
5865
FACTORY_ADDRESS=<FACTORY_ADDRESS_FROM_DEPLOYMENT>
5966
GAME_TYPE=42
6067
PRIVATE_KEY=<YOUR_PRIVATE_KEY>
@@ -72,8 +79,6 @@ Save the output addresses, particularly the `FACTORY_ADDRESS` output as "Factory
7279
```env
7380
FAST_FINALITY_MODE=true
7481
NETWORK_PRIVATE_KEY=0x...
75-
L1_BEACON_RPC=<L1_BEACON_RPC_URL>
76-
L2_NODE_RPC=<L2_NODE_RPC_URL>
7782
```
7883

7984
To get a whitelisted key on the Succinct Prover Network for OP Succinct, fill out this [form](https://docs.google.com/forms/d/e/1FAIpQLSd-X9uH7G0bvXH_kjptnQtNil8L4dumrVPpFE4t8Ci1XT1GaQ/viewform). The Succinct team will reach out to you with an RPC endpoint you can use.

fault-proof/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,17 @@ op-succinct-client-utils.workspace = true
2525
op-succinct-elfs.workspace = true
2626
op-succinct-host-utils.workspace = true
2727
op-succinct-proof-utils.workspace = true
28+
op-succinct-signer-utils.workspace = true
2829

2930
# sp1
3031
sp1-sdk.workspace = true
3132

3233
# alloy
3334
alloy-contract.workspace = true
3435
alloy-eips.workspace = true
35-
alloy-network.workspace = true
3636
alloy-primitives.workspace = true
3737
alloy-provider = { workspace = true, features = ["reqwest"] }
3838
alloy-rpc-types-eth.workspace = true
39-
alloy-signer-local.workspace = true
4039
alloy-sol-macro.workspace = true
4140
alloy-sol-types.workspace = true
4241
alloy-transport-http = { workspace = true, features = ["reqwest", "reqwest-native-tls"] }
@@ -59,6 +58,9 @@ hex.workspace = true
5958
strum = { workspace = true, features = ["derive"] }
6059
strum_macros.workspace = true
6160

61+
[dev-dependencies]
62+
alloy-signer-local.workspace = true
63+
6264
[features]
6365
default = ["ethereum"]
6466
celestia = ["op-succinct-proof-utils/celestia"]

fault-proof/bin/challenger.rs

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
use std::{env, time::Duration};
22

3-
use alloy_network::Ethereum;
43
use alloy_primitives::{Address, U256};
5-
use alloy_provider::{fillers::TxFiller, Provider, ProviderBuilder};
6-
use alloy_signer_local::PrivateKeySigner;
4+
use alloy_provider::{Provider, ProviderBuilder};
75
use alloy_transport_http::reqwest::Url;
8-
use anyhow::{Context, Result};
6+
use anyhow::Result;
97
use clap::Parser;
10-
use op_alloy_network::EthereumWallet;
11-
use tokio::time;
12-
138
use fault_proof::{
149
config::ChallengerConfig,
1510
contract::{
@@ -18,70 +13,67 @@ use fault_proof::{
1813
},
1914
prometheus::ChallengerGauge,
2015
utils::setup_logging,
21-
Action, FactoryTrait, L1ProviderWithWallet, L2Provider, Mode, NUM_CONFIRMATIONS,
22-
TIMEOUT_SECONDS,
16+
Action, FactoryTrait, L1Provider, L2Provider, Mode,
2317
};
2418
use op_succinct_host_utils::metrics::{init_metrics, MetricsGauge};
19+
use op_succinct_signer_utils::Signer;
20+
use tokio::time;
2521

2622
#[derive(Parser)]
2723
struct Args {
2824
#[arg(long, default_value = ".env.challenger")]
2925
env_file: String,
3026
}
3127

32-
struct OPSuccinctChallenger<F, P>
28+
struct OPSuccinctChallenger<P>
3329
where
34-
F: TxFiller<Ethereum>,
35-
P: Provider<Ethereum> + Clone,
30+
P: Provider + Clone,
3631
{
3732
config: ChallengerConfig,
3833
challenger_address: Address,
34+
signer: Signer,
35+
l1_provider: L1Provider,
3936
l2_provider: L2Provider,
40-
l1_provider_with_wallet: L1ProviderWithWallet<F, P>,
41-
factory: DisputeGameFactoryInstance<L1ProviderWithWallet<F, P>>,
37+
factory: DisputeGameFactoryInstance<P>,
4238
challenger_bond: U256,
4339
}
4440

45-
impl<F, P> OPSuccinctChallenger<F, P>
41+
impl<P> OPSuccinctChallenger<P>
4642
where
47-
F: TxFiller<Ethereum>,
48-
P: Provider<Ethereum> + Clone,
43+
P: Provider + Clone,
4944
{
5045
/// Creates a new challenger instance with the provided L1 provider with wallet and factory
5146
/// contract instance.
5247
pub async fn new(
5348
challenger_address: Address,
54-
l1_provider_with_wallet: L1ProviderWithWallet<F, P>,
55-
factory: DisputeGameFactoryInstance<L1ProviderWithWallet<F, P>>,
49+
signer: Signer,
50+
l1_provider: L1Provider,
51+
factory: DisputeGameFactoryInstance<P>,
5652
) -> Result<Self> {
5753
let config = ChallengerConfig::from_env()?;
5854

5955
Ok(Self {
6056
config: config.clone(),
6157
challenger_address,
58+
signer,
59+
l1_provider: l1_provider.clone(),
6260
l2_provider: ProviderBuilder::default().connect_http(config.l2_rpc.clone()),
63-
l1_provider_with_wallet: l1_provider_with_wallet.clone(),
6461
factory: factory.clone(),
6562
challenger_bond: factory.fetch_challenger_bond(config.game_type).await?,
6663
})
6764
}
6865

6966
/// Challenges a specific game at the given address.
7067
async fn challenge_game(&self, game_address: Address) -> Result<()> {
71-
let game =
72-
OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider_with_wallet.clone());
68+
let game = OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider.clone());
7369

74-
let receipt = game
75-
.challenge()
76-
.value(self.challenger_bond)
77-
.send()
78-
.await
79-
.context("Failed to send challenge transaction")?
80-
.with_required_confirmations(NUM_CONFIRMATIONS)
81-
.with_timeout(Some(Duration::from_secs(TIMEOUT_SECONDS)))
82-
.get_receipt()
83-
.await
84-
.context("Failed to get transaction receipt for challenge")?;
70+
let transaction_request =
71+
game.challenge().value(self.challenger_bond).into_transaction_request();
72+
73+
let receipt = self
74+
.signer
75+
.send_transaction_request(self.config.l1_rpc.clone(), transaction_request)
76+
.await?;
8577

8678
tracing::info!(
8779
"Successfully challenged game {:?} with tx {:?}",
@@ -120,7 +112,9 @@ where
120112
.resolve_games(
121113
Mode::Challenger,
122114
self.config.max_games_to_check_for_resolution,
123-
self.l1_provider_with_wallet.clone(),
115+
self.signer.clone(),
116+
self.config.l1_rpc.clone(),
117+
self.l1_provider.clone(),
124118
self.l2_provider.clone(),
125119
)
126120
.await
@@ -142,21 +136,18 @@ where
142136
tracing::info!("Attempting to claim bond from game {:?}", game_address);
143137

144138
// Create a contract instance for the game
145-
let game =
146-
OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider_with_wallet.clone());
139+
let game = OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider.clone());
147140

148141
// Create a transaction to claim credit
149-
let tx = game.claimCredit(self.challenger_address);
150-
151-
// Send the transaction
152-
match tx.send().await {
153-
Ok(pending_tx) => {
154-
let receipt = pending_tx
155-
.with_required_confirmations(NUM_CONFIRMATIONS)
156-
.with_timeout(Some(Duration::from_secs(TIMEOUT_SECONDS)))
157-
.get_receipt()
158-
.await?;
159-
142+
let transaction_request =
143+
game.claimCredit(self.challenger_address).into_transaction_request();
144+
145+
match self
146+
.signer
147+
.send_transaction_request(self.config.l1_rpc.clone(), transaction_request)
148+
.await
149+
{
150+
Ok(receipt) => {
160151
tracing::info!(
161152
"\x1b[1mSuccessfully claimed bond from game {:?} with tx {:?}\x1b[0m",
162153
game_address,
@@ -232,28 +223,23 @@ async fn main() -> Result<()> {
232223
let args = Args::parse();
233224
dotenv::from_filename(args.env_file).ok();
234225

235-
let wallet = EthereumWallet::from(
236-
env::var("PRIVATE_KEY")
237-
.expect("PRIVATE_KEY must be set")
238-
.parse::<PrivateKeySigner>()
239-
.unwrap(),
240-
);
226+
let challenger_signer = Signer::from_env()?;
241227

242-
let l1_provider_with_wallet = ProviderBuilder::new()
243-
.wallet(wallet.clone())
228+
let l1_provider = ProviderBuilder::default()
244229
.connect_http(env::var("L1_RPC").unwrap().parse::<Url>().unwrap());
245230

246231
let factory = DisputeGameFactory::new(
247232
env::var("FACTORY_ADDRESS")
248233
.expect("FACTORY_ADDRESS must be set")
249234
.parse::<Address>()
250235
.unwrap(),
251-
l1_provider_with_wallet.clone(),
236+
l1_provider.clone(),
252237
);
253238

254239
let mut challenger = OPSuccinctChallenger::new(
255-
wallet.default_signer().address(),
256-
l1_provider_with_wallet,
240+
challenger_signer.address(),
241+
challenger_signer,
242+
l1_provider,
257243
factory,
258244
)
259245
.await

0 commit comments

Comments
 (0)