1use alloy_primitives::{Address, address};
4use serde::{Deserialize, Serialize};
5
6use crate::chain::TokenAmount;
7
8pub const PERMIT2_ADDRESS: Address = address!("0x000000000022D473030F116dDEE9F6B43aC78BA3");
10
11#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
15pub struct Permit2TokenPermissions {
16 pub token: Address,
18 pub amount: TokenAmount,
20}
21
22#[cfg(feature = "client")]
23use std::future::Future;
24#[cfg(feature = "client")]
25use std::pin::Pin;
26
27#[cfg(feature = "client")]
28use alloy_primitives::{Bytes, U256};
29#[cfg(feature = "client")]
30use alloy_sol_types::{SolCall, sol};
31#[cfg(feature = "client")]
32use r402_core::error::ClientError;
33
34#[cfg(feature = "client")]
45pub trait Permit2Approver: Send + Sync {
46 fn check_permit2_allowance(
49 &self,
50 token: Address,
51 owner: Address,
52 ) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send + '_>>;
53
54 fn approve_permit2(
57 &self,
58 token: Address,
59 owner: Address,
60 ) -> Pin<Box<dyn Future<Output = Result<(), ClientError>> + Send + '_>>;
61}
62
63#[cfg(all(feature = "client", feature = "client-provider"))]
65pub(crate) struct BuiltinPermit2Approver<P> {
66 pub(crate) provider: P,
67}
68
69#[cfg(all(feature = "client", feature = "client-provider"))]
70impl<P> Permit2Approver for BuiltinPermit2Approver<P>
71where
72 P: alloy_provider::Provider + Send + Sync,
73{
74 fn check_permit2_allowance(
75 &self,
76 token: Address,
77 owner: Address,
78 ) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send + '_>> {
79 Box::pin(async move {
80 let (_addr, calldata) = permit2_allowance_calldata(token, owner);
81 let tx = alloy_rpc_types_eth::TransactionRequest::default()
82 .to(token)
83 .input(calldata.into());
84 let result = self.provider.call(tx).await.map_err(|e| {
85 ClientError::PreConditionFailed(format!("Permit2 allowance check failed: {e}"))
86 })?;
87 Ok(U256::from_be_slice(&result))
88 })
89 }
90
91 fn approve_permit2(
92 &self,
93 token: Address,
94 _owner: Address,
95 ) -> Pin<Box<dyn Future<Output = Result<(), ClientError>> + Send + '_>> {
96 Box::pin(async move {
97 let calldata = IPermit2Approval::approveCall {
98 spender: PERMIT2_ADDRESS,
99 amount: U256::MAX,
100 }
101 .abi_encode();
102 let tx = alloy_rpc_types_eth::TransactionRequest::default()
103 .to(token)
104 .input(calldata.into());
105 let pending = self.provider.send_transaction(tx).await.map_err(|e| {
106 ClientError::PreConditionFailed(format!("Permit2 approve tx failed: {e}"))
107 })?;
108 let receipt = pending.get_receipt().await.map_err(|e| {
109 ClientError::PreConditionFailed(format!("Permit2 approve receipt failed: {e}"))
110 })?;
111 if !receipt.status() {
112 return Err(ClientError::PreConditionFailed(
113 "Permit2 approve transaction reverted".into(),
114 ));
115 }
116 Ok(())
117 })
118 }
119}
120
121#[cfg(feature = "client")]
122sol! {
123 #[allow(missing_docs, reason = "sol! generated interface")]
125 interface IPermit2Approval {
126 function allowance(address owner, address spender) external view returns (uint256);
127 function approve(address spender, uint256 amount) external returns (bool);
128 }
129}
130
131#[cfg(feature = "client")]
133#[must_use]
134pub fn permit2_allowance_calldata(token: Address, owner: Address) -> (Address, Bytes) {
135 let call = IPermit2Approval::allowanceCall {
136 owner,
137 spender: PERMIT2_ADDRESS,
138 };
139 (token, call.abi_encode().into())
140}
141
142#[cfg(feature = "client")]
145#[must_use]
146pub fn permit2_approval_calldata(token: Address) -> (Address, Bytes) {
147 let call = IPermit2Approval::approveCall {
148 spender: PERMIT2_ADDRESS,
149 amount: U256::MAX,
150 };
151 (token, call.abi_encode().into())
152}