use alloy_primitives::{Address, address};
use serde::{Deserialize, Serialize};
use crate::chain::TokenAmount;
pub const PERMIT2_ADDRESS: Address = address!("0x000000000022D473030F116dDEE9F6B43aC78BA3");
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Permit2TokenPermissions {
pub token: Address,
pub amount: TokenAmount,
}
#[cfg(feature = "client")]
use std::future::Future;
#[cfg(feature = "client")]
use std::pin::Pin;
#[cfg(feature = "client")]
use alloy_primitives::{Bytes, U256};
#[cfg(feature = "client")]
use alloy_sol_types::{SolCall, sol};
#[cfg(feature = "client")]
use r402_core::error::ClientError;
#[cfg(feature = "client")]
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(all(feature = "client", feature = "client-provider"))]
pub(crate) struct BuiltinPermit2Approver<P> {
pub(crate) provider: P,
}
#[cfg(all(feature = "client", 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(())
})
}
}
#[cfg(feature = "client")]
sol! {
#[allow(missing_docs, reason = "sol! generated interface")]
interface IPermit2Approval {
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
}
#[cfg(feature = "client")]
#[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())
}
#[cfg(feature = "client")]
#[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())
}