|
1 | 1 | //! Complete lifecycle on Monad Mainnet: `Open → Funded → Submitted → Completed`. |
2 | 2 | //! |
| 3 | +//! A single private key plays all three roles (client, provider, evaluator). |
| 4 | +//! This is valid because the contract checks `msg.sender == role_address`, |
| 5 | +//! not that the roles are distinct addresses. |
| 6 | +//! |
3 | 7 | //! ```sh |
4 | | -//! export RPC_URL=https://rpc.monad.xyz |
5 | | -//! export CLIENT_KEY=0xac0974... |
6 | | -//! export PROVIDER_KEY=0x59c699... |
7 | | -//! export EVALUATOR_KEY=0x5de411... |
| 8 | +//! export PRIVATE_KEY=0xac0974... |
8 | 9 | //! cargo run --example lifecycle |
9 | 10 | //! ``` |
10 | 11 |
|
11 | | -use std::env; |
| 12 | +use std::{env, time::SystemTime}; |
12 | 13 |
|
13 | 14 | use alloy::{ |
14 | 15 | network::EthereumWallet, |
15 | | - primitives::{Address, FixedBytes, U256}, |
16 | | - providers::{Provider, ProviderBuilder}, |
17 | | - signers::local::PrivateKeySigner, |
| 16 | + primitives::{FixedBytes, U256}, |
| 17 | + providers::ProviderBuilder, |
| 18 | + sol, |
18 | 19 | }; |
19 | | -use erc8183::{Erc8183, Network, job::JobHandle, types::CreateJobParams}; |
20 | | - |
21 | | -fn connect( |
22 | | - rpc: &str, |
23 | | - key_env: &str, |
24 | | -) -> Result<(Erc8183<impl Provider>, Address), Box<dyn std::error::Error>> { |
25 | | - let signer: PrivateKeySigner = env::var(key_env)?.parse()?; |
26 | | - let addr = signer.address(); |
27 | | - let sdk = Erc8183::new( |
28 | | - ProviderBuilder::new() |
29 | | - .wallet(EthereumWallet::from(signer)) |
30 | | - .connect_http(rpc.parse()?), |
31 | | - ) |
32 | | - .with_network(Network::MonadMainnet); |
33 | | - Ok((sdk, addr)) |
34 | | -} |
35 | | - |
36 | | -/// Client: create job → set budget → fund escrow. |
37 | | -async fn client_create_and_fund<P: Provider>( |
38 | | - job: &JobHandle<P>, |
39 | | - provider: Address, |
40 | | - evaluator: Address, |
41 | | -) -> erc8183::Result<U256> { |
42 | | - let params = CreateJobParams::new( |
43 | | - provider, |
44 | | - evaluator, |
45 | | - U256::from(u64::MAX), |
46 | | - "Analyze market data", |
47 | | - ); |
48 | | - let id = job.create_job(¶ms).await?; |
49 | | - let budget = U256::from(1_000_000u64); |
50 | | - job.set_budget(id, budget, None).await?; |
51 | | - job.fund(id, budget, None).await?; |
52 | | - Ok(id) |
53 | | -} |
| 20 | +use erc8183::{Erc8183, Network, types::CreateJobParams}; |
54 | 21 |
|
55 | | -/// Provider: submit work deliverable. |
56 | | -async fn provider_submit<P: Provider>(job: &JobHandle<P>, id: U256) -> erc8183::Result<()> { |
57 | | - job.submit(id, FixedBytes::from([0xAB; 32]), None).await |
| 22 | +sol! { |
| 23 | + #[sol(rpc)] |
| 24 | + interface IERC20 { |
| 25 | + function approve(address spender, uint256 amount) external returns (bool); |
| 26 | + } |
58 | 27 | } |
59 | 28 |
|
60 | | -/// Evaluator: attest completion → release escrow. |
61 | | -async fn evaluator_complete<P: Provider>(job: &JobHandle<P>, id: U256) -> erc8183::Result<()> { |
62 | | - job.complete(id, FixedBytes::ZERO, None).await |
| 29 | +fn now_secs() -> u64 { |
| 30 | + SystemTime::now() |
| 31 | + .duration_since(SystemTime::UNIX_EPOCH) |
| 32 | + .expect("system clock before epoch") |
| 33 | + .as_secs() |
63 | 34 | } |
64 | 35 |
|
65 | 36 | #[allow(clippy::print_stdout)] |
66 | 37 | #[tokio::main] |
67 | 38 | async fn main() -> Result<(), Box<dyn std::error::Error>> { |
68 | | - let rpc = env::var("RPC_URL")?; |
| 39 | + let network = Network::MonadMainnet; |
| 40 | + let signer: alloy::signers::local::PrivateKeySigner = env::var("PRIVATE_KEY")?.parse()?; |
| 41 | + let addr = signer.address(); |
| 42 | + |
| 43 | + let provider = ProviderBuilder::new() |
| 44 | + .wallet(EthereumWallet::from(signer)) |
| 45 | + .connect_http(network.rpc_url().parse()?); |
69 | 46 |
|
70 | | - let (client_sdk, _) = connect(&rpc, "CLIENT_KEY")?; |
71 | | - let (provider_sdk, provider_addr) = connect(&rpc, "PROVIDER_KEY")?; |
72 | | - let (evaluator_sdk, evaluator_addr) = connect(&rpc, "EVALUATOR_KEY")?; |
| 47 | + let sdk = Erc8183::new(&provider).with_network(network); |
| 48 | + let job = sdk.job()?; |
73 | 49 |
|
74 | | - let client = client_sdk.job()?; |
75 | | - let provider = provider_sdk.job()?; |
76 | | - let evaluator = evaluator_sdk.job()?; |
| 50 | + println!("Network: {network}"); |
| 51 | + println!("Contract: {}", job.contract_address()); |
| 52 | + println!("Wallet: {addr}\n"); |
| 53 | + |
| 54 | + // 1. Create job — one wallet is client, provider, and evaluator |
| 55 | + let expires_at = U256::from(now_secs() + 3600); |
| 56 | + let params = CreateJobParams::new(addr, addr, expires_at, "Analyze market data"); |
| 57 | + let id = job.create_job(¶ms).await?; |
| 58 | + println!("[1/5] Created job #{id}"); |
77 | 59 |
|
78 | | - // Open → Funded → Submitted → Completed |
79 | | - let id = client_create_and_fund(&client, provider_addr, evaluator_addr).await?; |
80 | | - println!("[Client] Created & funded job #{id}"); |
| 60 | + // 2. Set budget (1 USDC = 1_000_000 units with 6 decimals) |
| 61 | + let budget = U256::from(1_000_000u64); |
| 62 | + job.set_budget(id, budget, None).await?; |
| 63 | + println!("[2/5] Budget set: {budget} (1 USDC)"); |
| 64 | + |
| 65 | + // 3. Approve USDC spend, then fund escrow |
| 66 | + let usdc = job.payment_token().await?; |
| 67 | + IERC20::new(usdc, &provider) |
| 68 | + .approve(job.contract_address(), budget) |
| 69 | + .send() |
| 70 | + .await? |
| 71 | + .get_receipt() |
| 72 | + .await?; |
| 73 | + job.fund(id, budget, None).await?; |
| 74 | + println!("[3/5] Funded escrow"); |
81 | 75 |
|
82 | | - provider_submit(&provider, id).await?; |
83 | | - println!("[Provider] Submitted deliverable"); |
| 76 | + // 4. Submit work deliverable |
| 77 | + job.submit(id, FixedBytes::from([0xAB; 32]), None).await?; |
| 78 | + println!("[4/5] Submitted deliverable"); |
84 | 79 |
|
85 | | - evaluator_complete(&evaluator, id).await?; |
86 | | - println!("[Evaluator] Completed — payment released"); |
| 80 | + // 5. Evaluator completes — releases payment |
| 81 | + job.complete(id, FixedBytes::ZERO, None).await?; |
| 82 | + println!("[5/5] Completed — payment released"); |
87 | 83 |
|
88 | | - let job = client.get_job(id).await?; |
89 | | - println!( |
90 | | - "\nJob #{}: {} (terminal: {})", |
91 | | - job.id, |
92 | | - job.status, |
93 | | - job.status.is_terminal() |
94 | | - ); |
| 84 | + // Query final state |
| 85 | + let data = job.get_job(id).await?; |
| 86 | + println!("\n{data}"); |
| 87 | + println!("Terminal: {}", data.status.is_terminal()); |
95 | 88 |
|
96 | 89 | Ok(()) |
97 | 90 | } |
0 commit comments