Skip to content

Commit 72aeecb

Browse files
committed
Add alloy script for EOAMultisend
1 parent 5a01765 commit 72aeecb

File tree

3 files changed

+90
-22
lines changed

3 files changed

+90
-22
lines changed

rs/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
alloy = { version = "0.15.11", features = ["full", "node-bindings"] }
7+
alloy = { version = "0.15.11", features = ["full", "node-bindings", "json"] }
8+
bytes = "1.10.1"
89
eyre = "0.6.12"
9-
tokio = "1.45.0"
10+
tokio = { version = "1.45.0", features = ["rt-multi-thread", "macros"] }

rs/src/main.rs

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,122 @@
11
use alloy::{
22
eips::eip7702::Authorization,
3-
network::{TransactionBuilder, TransactionBuilder7702, TxSigner},
4-
node_bindings::Anvil,
5-
primitives::{Address, U256, address},
3+
network::{TransactionBuilder, TransactionBuilder7702},
4+
primitives::{Address, Bytes, TxKind, U256, address},
65
providers::{Provider, ProviderBuilder},
76
rpc::types::TransactionRequest,
8-
signers::{Signer, SignerSync, local::PrivateKeySigner},
7+
signers::{SignerSync, local::PrivateKeySigner},
98
sol,
109
};
11-
use eyre::{ErrReport, Result};
10+
use bytes::{BufMut, BytesMut};
11+
use eyre::Result;
1212
use std::env;
1313
use std::str::FromStr;
1414

1515
sol!(
16+
#[allow(missing_docs)]
1617
#[sol(rpc)]
17-
"../src/EOAMultisend.sol"
18+
EOAMultisend,
19+
"../out/EOAMultisend.sol/EOAMultisend.json"
1820
);
1921

22+
const EOA_MULTISEND_ADDRESS: Address = address!("0xDa51eBfBb740D2183e91FAf762666B169A1A9a62");
23+
2024
async fn eoa_multisend(pk: PrivateKeySigner, calls: Vec<TransactionRequest>) -> Result<()> {
21-
let sepolia = "https://sepolia.etherscan.io".parse()?;
25+
let rpc_url = "https://sepolia.drpc.org".parse()?;
2226
let provider = ProviderBuilder::new()
2327
.wallet(pk.clone())
24-
.connect_http(sepolia);
28+
.connect_http(rpc_url);
29+
30+
let contract = EOAMultisend::new(EOA_MULTISEND_ADDRESS, provider.clone());
31+
32+
let authorization = Authorization {
33+
chain_id: U256::from(11155111),
34+
address: *contract.address(),
35+
nonce: provider.get_transaction_count(pk.address()).await?,
36+
};
37+
38+
let signature = pk.sign_hash_sync(&authorization.signature_hash())?;
39+
let signed_authorization = authorization.into_signed(signature);
40+
41+
let batched = pack_multisend(&calls)?;
42+
43+
let call = contract.execute_0(batched);
44+
let execute_calldata = call.calldata().to_owned();
45+
46+
let tx = TransactionRequest::default()
47+
.with_to(pk.address())
48+
.with_authorization_list(vec![signed_authorization])
49+
.with_input(execute_calldata);
50+
51+
// Send the transaction and wait for the broadcast.
52+
let pending_tx = provider.send_transaction(tx).await?;
53+
54+
println!("Pending transaction... {}", pending_tx.tx_hash());
2555

26-
// let contract = EOAMultisend::deploy(&provider).await?;
56+
let receipt = pending_tx.get_receipt().await?;
57+
58+
let etherscan_url = String::from("https://sepolia.etherscan.io/tx/");
59+
println!(
60+
" Tx Receipt: {}",
61+
etherscan_url + &receipt.transaction_hash.to_string()
62+
);
2763

28-
// let authorization = Authorization {
29-
// chain_id: U256::from(11155111),
30-
// // Reference to the contract that will be set as code for the authority.
31-
// address: *contract.address(),
32-
// nonce: provider.get_transaction_count(pk.address()).await?,
33-
// };
3464
Ok(())
3565
}
3666

67+
/// Encode a list of EOA-style txs for MultiSend/MultiSendCallOnly.
68+
fn pack_multisend(calls: &[TransactionRequest]) -> eyre::Result<Bytes> {
69+
let mut out = BytesMut::new();
70+
71+
for tx in calls {
72+
// operation ─ normal CALL
73+
out.put_u8(0);
74+
75+
// to ─ 20 bytes (error if missing)
76+
let to = match tx.to.as_ref() {
77+
Some(TxKind::Call(addr)) => addr,
78+
_ => return Err(eyre::eyre!("Tx is missing `to` address")),
79+
};
80+
out.extend_from_slice(to.as_slice());
81+
82+
// value ─ 32 bytes big-endian
83+
let value: U256 = tx.value.unwrap_or_default();
84+
out.extend_from_slice(&value.to_be_bytes::<32>());
85+
86+
// data length & data
87+
let data: Bytes = tx
88+
.input
89+
.input // first try the “input” field
90+
.clone()
91+
.or_else(|| tx.input.data.clone()) // else fall back to “data”
92+
.unwrap_or_default(); // or empty
93+
out.extend_from_slice(&U256::from(data.len()).to_be_bytes::<32>());
94+
out.extend_from_slice(&data);
95+
}
96+
97+
Ok(Bytes::from(out.freeze()))
98+
}
99+
37100
#[tokio::main]
38-
fn main() -> Result<()> {
39-
let anvil = Anvil::new().arg("--hardfork").arg("prague").try_spawn()?;
101+
async fn main() -> Result<()> {
40102
let private_key: PrivateKeySigner = match env::var("PK") {
41103
Ok(pk) => PrivateKeySigner::from_str(pk.as_str())?,
42-
Err(_) => anvil.keys()[0].clone().into(),
104+
Err(_) => PrivateKeySigner::from_str(
105+
"0xe4dc8cbe94cbc139084c9c7adc5c2a829d3246f76282679e0c067147a47eb3f8",
106+
)?,
43107
};
44108

45109
let tx1 = TransactionRequest::default()
46110
.with_to(Address::ZERO)
47-
.with_value(U256::from(10000000000000));
111+
.with_value(U256::from(10000000000000_i64));
48112

49113
let tx2 = TransactionRequest::default()
50114
.with_to(address!("0x1111111111111111111111111111111111111111"))
51-
.with_value(U256::from(20000000000000));
115+
.with_value(U256::from(20000000000000_i64));
52116

53117
let calls = vec![tx1, tx2];
54118

119+
eoa_multisend(private_key, calls).await?;
120+
55121
Ok(())
56122
}

0 commit comments

Comments
 (0)