use alloy_primitives::{Address, FixedBytes, Signature, U256};
use alloy_signer_local::PrivateKeySigner;
use alloy_sol_types::{SolStruct, eip712_domain};
use async_trait::async_trait;
use rand::{Rng, rng};
use std::sync::Arc;
use crate::chain::ChainId;
use crate::chain::eip155::Eip155ChainReference;
use crate::proto::PaymentRequired;
use crate::proto::v1::X402Version1;
use crate::scheme::client::{
PaymentCandidate, PaymentCandidateSigner, X402Error, X402SchemeClient,
};
use crate::scheme::v1_eip155_exact::{
ExactEvmPayload, ExactEvmPayloadAuthorization, ExactScheme, PaymentRequirementsExtra,
TransferWithAuthorization, types,
};
use crate::scheme::{V1Eip155Exact, X402SchemeId};
use crate::timestamp::UnixTimestamp;
use crate::util::Base64Bytes;
#[derive(Debug)]
#[allow(dead_code)] pub struct V1Eip155ExactClient<S> {
signer: S,
}
#[allow(dead_code)] impl<S> V1Eip155ExactClient<S> {
pub fn new(signer: S) -> Self {
Self { signer }
}
}
impl<S> X402SchemeId for V1Eip155ExactClient<S> {
fn namespace(&self) -> &str {
V1Eip155Exact.namespace()
}
fn scheme(&self) -> &str {
V1Eip155Exact.scheme()
}
}
impl<S> X402SchemeClient for V1Eip155ExactClient<S>
where
S: SignerLike + Clone + Send + Sync + 'static,
{
fn accept(&self, payment_required: &PaymentRequired) -> Vec<PaymentCandidate> {
let payment_required = match payment_required {
PaymentRequired::V1(payment_required) => payment_required,
PaymentRequired::V2(_) => {
return vec![];
}
};
payment_required
.accepts
.iter()
.filter_map(|v| {
let requirements: types::PaymentRequirements = v.as_concrete()?;
let chain_id = ChainId::from_network_name(&requirements.network)?;
let chain_reference = Eip155ChainReference::try_from(chain_id.clone()).ok()?;
let candidate = PaymentCandidate {
chain_id,
asset: requirements.asset.to_string(),
amount: requirements.max_amount_required,
scheme: self.scheme().to_string(),
x402_version: self.x402_version(),
pay_to: requirements.pay_to.to_string(),
signer: Box::new(PayloadSigner {
signer: self.signer.clone(),
chain_reference,
requirements,
}),
};
Some(candidate)
})
.collect::<Vec<_>>()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct Eip3009SigningParams {
pub chain_id: u64,
pub asset_address: Address,
pub pay_to: Address,
pub amount: U256,
pub max_timeout_seconds: u64,
pub extra: Option<PaymentRequirementsExtra>,
}
#[allow(dead_code)] pub async fn sign_erc3009_authorization<S: SignerLike + Sync>(
signer: &S,
params: &Eip3009SigningParams,
) -> Result<ExactEvmPayload, X402Error> {
let (name, version) = match ¶ms.extra {
None => ("".to_string(), "".to_string()),
Some(extra) => (extra.name.clone(), extra.version.clone()),
};
let domain = eip712_domain! {
name: name,
version: version,
chain_id: params.chain_id,
verifying_contract: params.asset_address,
};
let now = UnixTimestamp::now();
let valid_after_secs = now.as_secs().saturating_sub(10 * 60);
let valid_after = UnixTimestamp::from_secs(valid_after_secs);
let valid_before = now + params.max_timeout_seconds;
let nonce: [u8; 32] = rng().random();
let nonce = FixedBytes(nonce);
let authorization = ExactEvmPayloadAuthorization {
from: signer.address(),
to: params.pay_to,
value: params.amount,
valid_after,
valid_before,
nonce,
};
let transfer_with_authorization = TransferWithAuthorization {
from: authorization.from,
to: authorization.to,
value: authorization.value,
validAfter: U256::from(authorization.valid_after.as_secs()),
validBefore: U256::from(authorization.valid_before.as_secs()),
nonce: authorization.nonce,
};
let eip712_hash = transfer_with_authorization.eip712_signing_hash(&domain);
let signature = signer
.sign_hash(&eip712_hash)
.await
.map_err(|e| X402Error::SigningError(format!("{e:?}")))?;
Ok(ExactEvmPayload {
signature: signature.as_bytes().into(),
authorization,
})
}
#[allow(dead_code)] struct PayloadSigner<S> {
signer: S,
chain_reference: Eip155ChainReference,
requirements: types::PaymentRequirements,
}
#[async_trait]
impl<S> PaymentCandidateSigner for PayloadSigner<S>
where
S: SignerLike + Sync,
{
async fn sign_payment(&self) -> Result<String, X402Error> {
let params = Eip3009SigningParams {
chain_id: self.chain_reference.inner(),
asset_address: self.requirements.asset,
pay_to: self.requirements.pay_to,
amount: self.requirements.max_amount_required,
max_timeout_seconds: self.requirements.max_timeout_seconds,
extra: self.requirements.extra.clone(),
};
let evm_payload = sign_erc3009_authorization(&self.signer, ¶ms).await?;
let payload = types::PaymentPayload {
x402_version: X402Version1,
scheme: ExactScheme,
network: self.requirements.network.clone(),
payload: evm_payload,
};
let json = serde_json::to_vec(&payload)?;
let b64 = Base64Bytes::encode(&json);
Ok(b64.to_string())
}
}
#[async_trait]
pub trait SignerLike {
fn address(&self) -> Address;
async fn sign_hash(&self, hash: &FixedBytes<32>) -> Result<Signature, alloy_signer::Error>;
}
#[async_trait]
impl SignerLike for PrivateKeySigner {
fn address(&self) -> Address {
PrivateKeySigner::address(self)
}
async fn sign_hash(&self, hash: &FixedBytes<32>) -> Result<Signature, alloy_signer::Error> {
alloy_signer::Signer::sign_hash(self, hash).await
}
}
#[async_trait]
impl SignerLike for Arc<PrivateKeySigner> {
fn address(&self) -> Address {
PrivateKeySigner::address(self.as_ref())
}
async fn sign_hash(&self, hash: &FixedBytes<32>) -> Result<Signature, alloy_signer::Error> {
alloy_signer::Signer::sign_hash(self.as_ref(), hash).await
}
}