use alloy_primitives::{Address, Bytes, TxHash, U256};
use alloy_provider::{MulticallItem, Provider};
use alloy_sol_types::{SolStruct, eip712_domain};
use x402_types::chain::ChainProviderOps;
use x402_types::proto::{PaymentVerificationError, v2};
use x402_types::scheme::X402SchemeFacilitatorError;
#[cfg(feature = "telemetry")]
use tracing::Instrument;
#[cfg(feature = "telemetry")]
use tracing::instrument;
use crate::chain::erc20::IERC20;
use crate::chain::permit2::{PERMIT2_ADDRESS, UPTO_PERMIT2_PROXY_ADDRESS};
use crate::chain::{
Eip155ChainReference, Eip155MetaTransactionProvider, Eip155SignerAddresses, MetaTransaction,
};
use crate::v1_eip155_exact::{
Eip155ExactError, StructuredSignature, VALIDATOR_ADDRESS, Validator6492, assert_time,
};
use crate::v2_eip155_exact::eip2612::assert_eip2612_offchain_valid;
use crate::v2_eip155_exact::facilitator::permit2::{
PreparedPermit2, assert_onchain_allowance, assert_onchain_balance, execute_permit2_settlement,
};
use crate::v2_eip155_upto::eip2612::Permit2PaymentPayloadExt;
use crate::v2_eip155_upto::types::{
ISignatureTransfer, Permit2PaymentPayload, Permit2PaymentRequirements,
PermitWitnessTransferFrom, UptoSettleResponse, X402UptoPermit2Proxy, x402UptoPermit2Proxy,
};
use crate::v2_eip155_upto::{eip2612, types};
pub type PreparedUptoPermit2 =
PreparedPermit2<ISignatureTransfer::PermitTransferFrom, x402UptoPermit2Proxy::Witness>;
impl PreparedUptoPermit2 {
pub fn try_new(
chain_reference: &Eip155ChainReference,
payment_payload: &Permit2PaymentPayload,
) -> Result<Self, Eip155ExactError> {
let authorization = &payment_payload.payload.permit_2_authorization;
let payer = authorization.from.0;
let domain = eip712_domain! {
name: "Permit2",
chain_id: chain_reference.inner(),
verifying_contract: PERMIT2_ADDRESS,
};
let permit_witness_transfer_from = PermitWitnessTransferFrom {
permitted: ISignatureTransfer::TokenPermissions {
token: authorization.permitted.token.into(),
amount: authorization.permitted.amount,
},
spender: UPTO_PERMIT2_PROXY_ADDRESS,
nonce: authorization.nonce,
deadline: U256::from(authorization.deadline.as_secs()),
witness: x402UptoPermit2Proxy::Witness {
to: authorization.witness.to.into(),
facilitator: authorization.witness.facilitator.into(),
validAfter: U256::from(authorization.witness.valid_after.as_secs()),
},
};
let eip712_hash = permit_witness_transfer_from.eip712_signing_hash(&domain);
let structured_signature = StructuredSignature::try_from_bytes(
payment_payload.payload.signature.clone(),
payer,
&eip712_hash,
)?;
let permit_transfer_from = ISignatureTransfer::PermitTransferFrom {
permitted: permit_witness_transfer_from.permitted,
nonce: permit_witness_transfer_from.nonce,
deadline: permit_witness_transfer_from.deadline,
};
let witness = permit_witness_transfer_from.witness;
Ok(Self {
payer,
eip712_hash,
structured_signature,
permit_transfer_from,
witness,
})
}
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub async fn verify_permit2_payment<P: Eip155MetaTransactionProvider + Eip155SignerAddresses>(
provider: &P,
eip2612_gas_sponsoring: bool,
payment_payload: &Permit2PaymentPayload,
payment_requirements: &Permit2PaymentRequirements,
) -> Result<v2::VerifyResponse, Eip155ExactError> {
let required_amount = assert_offchain_valid_verify(payment_payload, payment_requirements)?;
let authorization = &payment_payload.payload.permit_2_authorization;
let witness_facilitator = &authorization.witness.facilitator;
assert_own_signer(provider, witness_facilitator.as_ref())?;
let payer = authorization.from;
let eip2612_payload = payment_payload.eip2612_gas_sponsoring();
if let Some(eip2612_gas_sponsoring_payload) = &eip2612_payload {
if !eip2612_gas_sponsoring {
return Err(PaymentVerificationError::eip2612_gas_sponsoring_not_enabled().into());
}
assert_eip2612_offchain_valid(eip2612_gas_sponsoring_payload, payment_payload)?;
eip2612::assert_onchain_upto_permit2_with_eip2612(
provider.inner(),
provider.chain(),
payment_payload,
eip2612_gas_sponsoring_payload,
)
.await?;
} else {
assert_onchain_upto_permit2(
provider.inner(),
provider.chain(),
payment_payload,
required_amount,
)
.await?;
}
Ok(v2::VerifyResponse::valid(payer.to_string()))
}
pub fn assert_own_signer<P>(
provider: &P,
witness_facilitator: &Address,
) -> Result<(), PaymentVerificationError>
where
P: Eip155SignerAddresses,
{
let signer_addresses = provider.signer_addresses();
let is_ours = signer_addresses
.iter()
.any(|addr| addr == witness_facilitator);
if is_ours {
Ok(())
} else {
Err(PaymentVerificationError::InvalidSignature(format!(
"witness facilitator address {witness_facilitator} does not match any of this facilitator's signer addresses"
)))
}
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub async fn settle_permit2_payment<P, E>(
provider: &P,
eip2612_gas_sponsoring: bool,
payment_payload: &Permit2PaymentPayload,
payment_requirements: &Permit2PaymentRequirements,
) -> Result<UptoSettleResponse, X402SchemeFacilitatorError>
where
P: Eip155MetaTransactionProvider<Error = E> + ChainProviderOps,
Eip155ExactError: From<E>,
{
let required_amount = assert_offchain_valid_settle(payment_payload, payment_requirements)?;
let authorization = &payment_payload.payload.permit_2_authorization;
let payer = authorization.from;
if required_amount.is_zero() {
let network = &payment_payload.accepted.network;
return Ok(UptoSettleResponse::success(
payer.to_string(),
String::new(), network.to_string(),
"0".to_string(),
));
}
let eip2612_payload = payment_payload.eip2612_gas_sponsoring();
let tx_hash = if let Some(ref info) = eip2612_payload {
if !eip2612_gas_sponsoring {
return Err(PaymentVerificationError::eip2612_gas_sponsoring_not_enabled().into());
}
assert_eip2612_offchain_valid(info, payment_payload)?;
eip2612::settle_upto_permit2_with_eip2612(provider, payment_payload, info, required_amount)
.await?
} else {
settle_upto_permit2(provider, payment_payload, required_amount).await?
};
let network = &payment_payload.accepted.network;
Ok(UptoSettleResponse::success(
payer.to_string(),
tx_hash.to_string(),
network.to_string(),
required_amount.to_string(),
))
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub fn assert_offchain_valid_verify(
payment_payload: &Permit2PaymentPayload,
payment_requirements: &Permit2PaymentRequirements,
) -> Result<U256, PaymentVerificationError> {
assert_offchain_valid(payment_payload, payment_requirements)?;
let authorization = &payment_payload.payload.permit_2_authorization;
let accepted_amount = payment_payload.accepted.amount;
if authorization.permitted.amount != accepted_amount {
return Err(PaymentVerificationError::InvalidPaymentAmount);
}
Ok(accepted_amount)
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub fn assert_offchain_valid_settle(
payment_payload: &Permit2PaymentPayload,
payment_requirements: &Permit2PaymentRequirements,
) -> Result<U256, PaymentVerificationError> {
assert_offchain_valid(payment_payload, payment_requirements)?;
let authorization = &payment_payload.payload.permit_2_authorization;
let permitted_amount = authorization.permitted.amount;
let accepted = &payment_payload.accepted;
if permitted_amount != accepted.amount {
return Err(PaymentVerificationError::InvalidPaymentAmount);
}
let amount_to_settle = payment_requirements.amount;
if permitted_amount < amount_to_settle {
return Err(PaymentVerificationError::InvalidPaymentAmount);
}
Ok(amount_to_settle)
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub fn assert_offchain_valid(
payment_payload: &Permit2PaymentPayload,
payment_requirements: &Permit2PaymentRequirements,
) -> Result<(), PaymentVerificationError> {
let payload = &payment_payload.payload;
let accepted = &payment_payload.accepted;
if accepted.scheme != types::UptoScheme {
return Err(PaymentVerificationError::UnsupportedScheme);
}
if accepted.network != payment_requirements.network {
return Err(PaymentVerificationError::ChainIdMismatch);
}
if accepted.asset != payment_requirements.asset {
return Err(PaymentVerificationError::AssetMismatch);
}
let authorization = &payload.permit_2_authorization;
if authorization.spender.0 != UPTO_PERMIT2_PROXY_ADDRESS {
return Err(PaymentVerificationError::RecipientMismatch);
}
let witness = &authorization.witness;
if witness.to != accepted.pay_to {
return Err(PaymentVerificationError::RecipientMismatch);
}
let valid_after = witness.valid_after;
let valid_before = authorization.deadline;
assert_time(valid_after, valid_before)?;
if authorization.permitted.token != accepted.asset {
return Err(PaymentVerificationError::AssetMismatch);
}
Ok(())
}
#[cfg_attr(feature = "telemetry", instrument(skip_all, err))]
pub async fn assert_onchain_upto_permit2<P: Provider>(
provider: &P,
chain_reference: &Eip155ChainReference,
payment_payload: &Permit2PaymentPayload,
required_amount: U256,
) -> Result<(), Eip155ExactError> {
let authorization = &payment_payload.payload.permit_2_authorization;
let asset_address = payment_payload.accepted.asset.0;
let token_contract = IERC20::new(asset_address, provider);
let onchain_allowance_fut =
assert_onchain_allowance(&token_contract, authorization.from.0, required_amount);
let onchain_balance_fut =
assert_onchain_balance(&token_contract, authorization.from.0, required_amount);
tokio::try_join!(onchain_allowance_fut, onchain_balance_fut)?;
let PreparedUptoPermit2 {
payer,
eip712_hash,
structured_signature,
permit_transfer_from,
witness,
} = PreparedUptoPermit2::try_new(chain_reference, payment_payload)?;
let facilitator_address = witness.facilitator;
let upto_permit2_proxy = X402UptoPermit2Proxy::new(UPTO_PERMIT2_PROXY_ADDRESS, provider);
match structured_signature {
StructuredSignature::EIP6492 {
factory: _,
factory_calldata: _,
inner,
original,
} => {
let validator6492 = Validator6492::new(VALIDATOR_ADDRESS, provider);
let is_valid_signature_call =
validator6492.isValidSigWithSideEffects(payer, eip712_hash, original);
let settle_call = upto_permit2_proxy
.settle(
permit_transfer_from,
authorization.permitted.amount,
payer,
witness,
inner,
)
.from(facilitator_address);
let aggregate3 = provider
.multicall()
.add(is_valid_signature_call)
.add(settle_call);
let aggregate3_call = aggregate3.aggregate3();
#[cfg(feature = "telemetry")]
let (is_valid_signature_result, transfer_result) = aggregate3_call
.instrument(tracing::info_span!("multi_call_settle_upto_permit2",
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"))]
let (is_valid_signature_result, transfer_result) = aggregate3_call.await?;
let is_valid_signature_result = is_valid_signature_result
.map_err(|e| PaymentVerificationError::InvalidSignature(e.to_string()))?;
if !is_valid_signature_result {
return Err(PaymentVerificationError::InvalidSignature(
"Chain reported signature to be invalid".to_string(),
)
.into());
}
transfer_result
.map_err(|e| PaymentVerificationError::TransactionSimulation(e.to_string()))?;
Ok(())
}
StructuredSignature::EOA(signature) => {
let settle_call = upto_permit2_proxy
.settle(
permit_transfer_from,
authorization.permitted.amount,
payer,
witness,
signature.as_ref().as_bytes().into(),
)
.from(facilitator_address);
let settle_call_fut = settle_call.call().into_future();
#[cfg(feature = "telemetry")]
settle_call_fut
.instrument(tracing::info_span!("call_settle_upto_permit2",
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(())
}
StructuredSignature::EIP1271(signature) => {
let settle_call = upto_permit2_proxy
.settle(
permit_transfer_from,
authorization.permitted.amount,
payer,
witness,
signature,
)
.from(facilitator_address);
let settle_call_fut = settle_call.call().into_future();
#[cfg(feature = "telemetry")]
settle_call_fut
.instrument(tracing::info_span!("call_settle_upto_permit2",
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(())
}
}
}
pub async fn settle_upto_permit2<P, E>(
provider: &P,
payment_payload: &Permit2PaymentPayload,
actual_amount: U256,
) -> Result<TxHash, Eip155ExactError>
where
P: Eip155MetaTransactionProvider<Error = E> + ChainProviderOps,
Eip155ExactError: From<E>,
{
let PreparedUptoPermit2 {
payer,
eip712_hash: _,
structured_signature,
permit_transfer_from,
witness,
} = PreparedUptoPermit2::try_new(provider.chain(), payment_payload)?;
let build_call = move |sig_bytes: Bytes| {
let inner = provider.inner();
let upto_permit2_proxy = X402UptoPermit2Proxy::new(UPTO_PERMIT2_PROXY_ADDRESS, inner);
let facilitator_address = witness.facilitator;
let call = upto_permit2_proxy.settle(
permit_transfer_from,
actual_amount,
payer,
witness,
sig_bytes,
);
let to = call.target();
let calldata = call.calldata().clone();
MetaTransaction::new(to, calldata).with_from(facilitator_address)
};
execute_permit2_settlement(provider, payer, structured_signature, build_call).await
}