use std::future::Future;
use std::pin::Pin;
use alloy_primitives::{Address, Bytes, U256};
use alloy_sol_types::{SolCall, sol};
use r402::scheme::ClientError;
use crate::exact::PERMIT2_ADDRESS;
pub trait Permit2Approver: Send + Sync {
fn check_permit2_allowance(
&self,
token: Address,
owner: Address,
) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send + '_>>;
fn approve_permit2(
&self,
token: Address,
owner: Address,
) -> Pin<Box<dyn Future<Output = Result<(), ClientError>> + Send + '_>>;
}
#[cfg(feature = "client-provider")]
pub(super) struct BuiltinPermit2Approver<P> {
pub(super) provider: P,
}
#[cfg(feature = "client-provider")]
impl<P> Permit2Approver for BuiltinPermit2Approver<P>
where
P: alloy_provider::Provider + Send + Sync,
{
fn check_permit2_allowance(
&self,
token: Address,
owner: Address,
) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send + '_>> {
Box::pin(async move {
let (_addr, calldata) = permit2_allowance_calldata(token, owner);
let tx = alloy_rpc_types_eth::TransactionRequest::default()
.to(token)
.input(calldata.into());
let result = self.provider.call(tx).await.map_err(|e| {
ClientError::PreConditionFailed(format!("Permit2 allowance check failed: {e}"))
})?;
Ok(U256::from_be_slice(&result))
})
}
fn approve_permit2(
&self,
token: Address,
_owner: Address,
) -> Pin<Box<dyn Future<Output = Result<(), ClientError>> + Send + '_>> {
Box::pin(async move {
let calldata = IPermit2Approval::approveCall {
spender: PERMIT2_ADDRESS,
amount: U256::MAX,
}
.abi_encode();
let tx = alloy_rpc_types_eth::TransactionRequest::default()
.to(token)
.input(calldata.into());
let pending = self.provider.send_transaction(tx).await.map_err(|e| {
ClientError::PreConditionFailed(format!("Permit2 approve tx failed: {e}"))
})?;
let receipt = pending.get_receipt().await.map_err(|e| {
ClientError::PreConditionFailed(format!("Permit2 approve receipt failed: {e}"))
})?;
if !receipt.status() {
return Err(ClientError::PreConditionFailed(
"Permit2 approve transaction reverted".into(),
));
}
Ok(())
})
}
}
sol! {
#[allow(missing_docs)]
interface IPermit2Approval {
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
}
#[must_use]
pub fn permit2_allowance_calldata(token: Address, owner: Address) -> (Address, Bytes) {
let call = IPermit2Approval::allowanceCall {
owner,
spender: PERMIT2_ADDRESS,
};
(token, call.abi_encode().into())
}
#[must_use]
pub fn permit2_approval_calldata(token: Address) -> (Address, Bytes) {
let call = IPermit2Approval::approveCall {
spender: PERMIT2_ADDRESS,
amount: U256::MAX,
};
(token, call.abi_encode().into())
}