1use alloy_network::{Ethereum, Network};
46use alloy_primitives::{Address, U256};
47use alloy_provider::Provider;
48use alloy_rpc_types::TransactionRequest;
49use alloy_sol_types::{sol, SolCall};
50
51pub async fn check_balance<P>(
65 provider: &P,
66 token: Address,
67 owner: Address,
68) -> Result<U256, alloy_transport::TransportError>
69where
70 P: Provider<Ethereum>,
71{
72 let calldata = balanceOfCall { owner }.abi_encode();
73 let tx = TransactionRequest::default()
74 .to(token)
75 .input(calldata.into());
76
77 let result = provider.call(tx).await?;
78
79 if result.len() >= 32 {
80 Ok(U256::from_be_slice(&result[..32]))
81 } else {
82 Ok(U256::ZERO)
83 }
84}
85
86pub async fn check_allowance<P>(
96 provider: &P,
97 token: Address,
98 owner: Address,
99 spender: Address,
100) -> Result<U256, alloy_transport::TransportError>
101where
102 P: Provider<Ethereum>,
103{
104 let calldata = allowanceCall { owner, spender }.abi_encode();
105 let tx = TransactionRequest::default()
106 .to(token)
107 .input(calldata.into());
108
109 let result = provider.call(tx).await?;
110
111 if result.len() >= 32 {
112 Ok(U256::from_be_slice(&result[..32]))
113 } else {
114 Ok(U256::ZERO)
115 }
116}
117
118pub async fn check_balance_and_allowance<P>(
134 provider: &P,
135 token: Address,
136 owner: Address,
137 spender: Address,
138) -> Result<(U256, U256), alloy_transport::TransportError>
139where
140 P: Provider<Ethereum>,
141{
142 let balance_fut = check_balance(provider, token, owner);
143 let allowance_fut = check_allowance(provider, token, owner, spender);
144
145 let (balance, allowance) = tokio::join!(balance_fut, allowance_fut);
146
147 Ok((balance?, allowance?))
148}
149
150pub const MULTICALL3_ADDRESS: Address =
156 alloy_primitives::address!("cA11bde05977b3631167028862bE2a173976CA11");
157
158sol! {
160 #[allow(missing_docs)]
161 function balanceOf(address owner) external view returns (uint256);
162
163 #[allow(missing_docs)]
164 function allowance(address owner, address spender) external view returns (uint256);
165}
166
167sol! {
168 #[allow(missing_docs)]
169 #[sol(rpc)]
170 interface IMulticall3 {
171 struct Call3 {
172 address target;
173 bool allowFailure;
174 bytes callData;
175 }
176
177 struct Result {
178 bool success;
179 bytes returnData;
180 }
181
182 function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);
183 }
184}
185
186use IMulticall3::{Call3, IMulticall3Instance, Result as MulticallResult};
187
188#[derive(Debug, Clone)]
190pub struct SwapPreflightCheck {
191 pub token: Address,
193 pub owner: Address,
195 pub spender: Address,
197 pub required_amount: U256,
199}
200
201#[derive(Debug, Clone)]
203pub struct PreflightResult {
204 pub token: Address,
206 pub balance: U256,
208 pub allowance: U256,
210 pub sufficient_balance: bool,
212 pub sufficient_allowance: bool,
214}
215
216impl PreflightResult {
217 pub fn is_ready(&self) -> bool {
219 self.sufficient_balance && self.sufficient_allowance
220 }
221
222 pub fn approval_needed(&self, required: U256) -> U256 {
224 if self.allowance >= required {
225 U256::ZERO
226 } else {
227 required.saturating_sub(self.allowance)
228 }
229 }
230}
231
232pub async fn multicall_check_balances<N, P>(
254 provider: &P,
255 owner: Address,
256 tokens: &[Address],
257) -> Result<Vec<U256>, alloy_contract::Error>
258where
259 N: Network,
260 P: Provider<N>,
261{
262 if tokens.is_empty() {
263 return Ok(vec![]);
264 }
265
266 let multicall = IMulticall3Instance::new(MULTICALL3_ADDRESS, provider);
267
268 let calls: Vec<Call3> = tokens
269 .iter()
270 .map(|&token| {
271 let calldata = balanceOfCall { owner }.abi_encode();
272 Call3 {
273 target: token,
274 allowFailure: true,
275 callData: calldata.into(),
276 }
277 })
278 .collect();
279
280 let results: Vec<MulticallResult> = multicall.aggregate3(calls).call().await?;
281
282 Ok(results
283 .into_iter()
284 .map(|result| {
285 if result.success && result.returnData.len() >= 32 {
286 U256::from_be_slice(&result.returnData[..32])
287 } else {
288 U256::ZERO
289 }
290 })
291 .collect())
292}
293
294pub async fn multicall_check_allowances<N, P>(
310 provider: &P,
311 owner: Address,
312 spender: Address,
313 tokens: &[Address],
314) -> Result<Vec<U256>, alloy_contract::Error>
315where
316 N: Network,
317 P: Provider<N>,
318{
319 if tokens.is_empty() {
320 return Ok(vec![]);
321 }
322
323 let multicall = IMulticall3Instance::new(MULTICALL3_ADDRESS, provider);
324
325 let calls: Vec<Call3> = tokens
326 .iter()
327 .map(|&token| {
328 let calldata = allowanceCall { owner, spender }.abi_encode();
329 Call3 {
330 target: token,
331 allowFailure: true,
332 callData: calldata.into(),
333 }
334 })
335 .collect();
336
337 let results: Vec<MulticallResult> = multicall.aggregate3(calls).call().await?;
338
339 Ok(results
340 .into_iter()
341 .map(|result| {
342 if result.success && result.returnData.len() >= 32 {
343 U256::from_be_slice(&result.returnData[..32])
344 } else {
345 U256::ZERO
346 }
347 })
348 .collect())
349}
350
351pub async fn multicall_preflight_checks<N, P>(
365 provider: &P,
366 checks: &[SwapPreflightCheck],
367) -> Result<Vec<PreflightResult>, alloy_contract::Error>
368where
369 N: Network,
370 P: Provider<N>,
371{
372 if checks.is_empty() {
373 return Ok(vec![]);
374 }
375
376 let multicall = IMulticall3Instance::new(MULTICALL3_ADDRESS, provider);
377
378 let mut calls: Vec<Call3> = Vec::with_capacity(checks.len() * 2);
380
381 for check in checks {
382 let balance_calldata = balanceOfCall { owner: check.owner }.abi_encode();
384 calls.push(Call3 {
385 target: check.token,
386 allowFailure: true,
387 callData: balance_calldata.into(),
388 });
389
390 let allowance_calldata = allowanceCall {
392 owner: check.owner,
393 spender: check.spender,
394 }
395 .abi_encode();
396 calls.push(Call3 {
397 target: check.token,
398 allowFailure: true,
399 callData: allowance_calldata.into(),
400 });
401 }
402
403 let results: Vec<MulticallResult> = multicall.aggregate3(calls).call().await?;
404
405 Ok(checks
407 .iter()
408 .enumerate()
409 .map(|(i, check)| {
410 let balance_result = &results[i * 2];
411 let allowance_result = &results[i * 2 + 1];
412
413 let balance = if balance_result.success && balance_result.returnData.len() >= 32 {
414 U256::from_be_slice(&balance_result.returnData[..32])
415 } else {
416 U256::ZERO
417 };
418
419 let allowance = if allowance_result.success && allowance_result.returnData.len() >= 32 {
420 U256::from_be_slice(&allowance_result.returnData[..32])
421 } else {
422 U256::ZERO
423 };
424
425 PreflightResult {
426 token: check.token,
427 balance,
428 allowance,
429 sufficient_balance: balance >= check.required_amount,
430 sufficient_allowance: allowance >= check.required_amount,
431 }
432 })
433 .collect())
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use alloy_primitives::address;
440
441 #[test]
442 fn test_multicall3_address() {
443 assert_eq!(
445 MULTICALL3_ADDRESS,
446 address!("cA11bde05977b3631167028862bE2a173976CA11")
447 );
448 }
449
450 #[test]
451 fn test_preflight_result_is_ready() {
452 let result = PreflightResult {
453 token: Address::ZERO,
454 balance: U256::from(1000),
455 allowance: U256::from(1000),
456 sufficient_balance: true,
457 sufficient_allowance: true,
458 };
459 assert!(result.is_ready());
460
461 let result_insufficient = PreflightResult {
462 token: Address::ZERO,
463 balance: U256::from(1000),
464 allowance: U256::from(100),
465 sufficient_balance: true,
466 sufficient_allowance: false,
467 };
468 assert!(!result_insufficient.is_ready());
469 }
470
471 #[test]
472 fn test_approval_needed() {
473 let result = PreflightResult {
474 token: Address::ZERO,
475 balance: U256::from(1000),
476 allowance: U256::from(500),
477 sufficient_balance: true,
478 sufficient_allowance: false,
479 };
480
481 assert_eq!(result.approval_needed(U256::from(800)), U256::from(300));
482 assert_eq!(result.approval_needed(U256::from(500)), U256::ZERO);
483 assert_eq!(result.approval_needed(U256::from(300)), U256::ZERO);
484 }
485
486 #[test]
487 fn test_balance_of_encoding() {
488 let owner = address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0");
489 let calldata = balanceOfCall { owner }.abi_encode();
490
491 assert_eq!(&calldata[0..4], &[0x70, 0xa0, 0x82, 0x31]);
493 }
494
495 #[test]
496 fn test_allowance_encoding() {
497 let owner = address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0");
498 let spender = address!("cf5540fffcdc3d510b18bfca6d2b9987b0772559");
499 let calldata = allowanceCall { owner, spender }.abi_encode();
500
501 assert_eq!(&calldata[0..4], &[0xdd, 0x62, 0xed, 0x3e]);
503 }
504}