use alloy_primitives::{Bytes, TxHash, U256};
use alloy_provider::{MulticallItem, Provider};
use serde::{Deserialize, Serialize};
use x402_types::chain::ChainProviderOps;
use x402_types::proto::PaymentVerificationError;
use x402_types::timestamp::UnixTimestamp;
#[cfg(feature = "telemetry")]
use tracing::Instrument;
#[cfg(feature = "telemetry")]
use tracing::instrument;
use crate::chain::permit2::EXACT_PERMIT2_PROXY_ADDRESS;
use crate::chain::permit2::PERMIT2_ADDRESS;
use crate::chain::{ChecksummedAddress, EOASignature, EOASignatureExt};
use crate::chain::{Eip155MetaTransactionProvider, MetaTransaction};
use crate::v1_eip155_exact::Eip155ExactError;
use crate::v1_eip155_exact::StructuredSignature;
use crate::v2_eip155_exact::facilitator::permit2::execute_permit2_settlement;
use crate::v2_eip155_exact::permit2::PreparedExactPermit2;
use crate::v2_eip155_exact::types::Permit2PaymentPayload;
use crate::v2_eip155_exact::types::{X402ExactPermit2Proxy, x402ExactPermit2Proxy};
pub trait Permit2PaymentPayloadExt {
fn eip2612_gas_sponsoring(&self) -> Option<Eip2612GasSponsoringInfo>;
}
impl Permit2PaymentPayloadExt for Permit2PaymentPayload {
fn eip2612_gas_sponsoring(&self) -> Option<Eip2612GasSponsoringInfo> {
let extensions = self.extensions.as_ref()?;
let ext_obj = extensions.as_object()?;
let raw = ext_obj.get(EXTENSION_KEY)?;
let sponsoring: Eip2612GasSponsoring = serde_json::from_value(raw.clone()).ok()?;
Some(sponsoring.info)
}
}
pub const EXTENSION_KEY: &str = "eip2612GasSponsoring";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Eip2612GasSponsoringInfo {
pub from: ChecksummedAddress,
pub asset: ChecksummedAddress,
pub spender: ChecksummedAddress,
#[serde(with = "crate::decimal_u256")]
pub amount: U256,
#[serde(with = "crate::decimal_u256")]
pub nonce: U256,
pub deadline: UnixTimestamp,
pub signature: EOASignature,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Eip2612GasSponsoring {
pub info: Eip2612GasSponsoringInfo,
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub fn assert_eip2612_offchain_valid(
info: &Eip2612GasSponsoringInfo,
payment_payload: &Permit2PaymentPayload,
) -> Result<(), PaymentVerificationError> {
if info.spender.0 != PERMIT2_ADDRESS {
return Err(PaymentVerificationError::InvalidSignature(
"eip2612GasSponsoring spender must be the canonical Permit2 address".to_string(),
));
}
if info.asset != payment_payload.accepted.asset {
return Err(PaymentVerificationError::AssetMismatch);
}
let authorization = &payment_payload.payload.permit_2_authorization;
if info.from != authorization.from {
return Err(PaymentVerificationError::InvalidSignature(
"eip2612GasSponsoring 'from' does not match permit2 authorization 'from'".to_string(),
));
}
if info.deadline < authorization.deadline {
return Err(PaymentVerificationError::Expired);
}
Ok(())
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub async fn assert_onchain_exact_permit2_with_eip2612<P: Provider>(
provider: &P,
chain_reference: &crate::chain::Eip155ChainReference,
payment_payload: &Permit2PaymentPayload,
info: &Eip2612GasSponsoringInfo,
) -> Result<(), Eip155ExactError> {
#[cfg(feature = "telemetry")]
let authorization = &payment_payload.payload.permit_2_authorization;
let PreparedExactPermit2 {
payer,
eip712_hash: _,
structured_signature,
permit_transfer_from,
witness,
} = PreparedExactPermit2::try_new(chain_reference, payment_payload)?;
let permit2612 = x402ExactPermit2Proxy::EIP2612Permit {
value: info.amount,
deadline: U256::from(info.deadline.as_secs()),
r: info.signature.r_bytes(),
s: info.signature.s_bytes(),
v: info.signature.v_legacy(),
};
let exact_permit2_proxy = X402ExactPermit2Proxy::new(EXACT_PERMIT2_PROXY_ADDRESS, provider);
let sig_bytes: Bytes = match &structured_signature {
StructuredSignature::EIP6492 { inner, .. } => inner.clone(),
StructuredSignature::EOA(sig) => sig.as_ref().as_bytes().into(),
StructuredSignature::EIP1271(sig) => sig.clone(),
};
let settle_call = exact_permit2_proxy.settleWithPermit(
permit2612,
permit_transfer_from,
payer,
witness,
sig_bytes,
);
let settle_call_fut = settle_call.call().into_future();
#[cfg(feature = "telemetry")]
settle_call_fut
.instrument(
tracing::info_span!("call_settle_with_permit_exact_permit2_simulate",
from = %payer,
to = %authorization.witness.to,
value = %authorization.permitted.amount,
valid_after = %authorization.witness.valid_after,
valid_before = %authorization.deadline,
nonce = %authorization.nonce,
token_contract = %authorization.permitted.token,
otel.kind = "client",
),
)
.await?;
#[cfg(not(feature = "telemetry"))]
settle_call_fut.await?;
Ok(())
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub async fn settle_exact_permit2_with_eip2612<P, E>(
provider: &P,
payment_payload: &Permit2PaymentPayload,
info: &Eip2612GasSponsoringInfo,
) -> Result<TxHash, Eip155ExactError>
where
P: Eip155MetaTransactionProvider<Error = E> + ChainProviderOps,
Eip155ExactError: From<E>,
{
let PreparedExactPermit2 {
payer,
eip712_hash: _,
structured_signature,
permit_transfer_from,
witness,
} = PreparedExactPermit2::try_new(provider.chain(), payment_payload)?;
let permit2612 = x402ExactPermit2Proxy::EIP2612Permit {
value: info.amount,
deadline: U256::from(info.deadline.as_secs()),
r: info.signature.r_bytes(),
s: info.signature.s_bytes(),
v: info.signature.v_legacy(),
};
let build_call = move |sig_bytes: Bytes| {
let inner = provider.inner();
let exact_permit2_proxy = X402ExactPermit2Proxy::new(EXACT_PERMIT2_PROXY_ADDRESS, inner);
let call = exact_permit2_proxy.settleWithPermit(
permit2612,
permit_transfer_from,
payer,
witness,
sig_bytes,
);
MetaTransaction::new(call.target(), call.calldata().clone())
};
execute_permit2_settlement(provider, payer, structured_signature, build_call).await
}