use std::str::FromStr;
use alloy_primitives::{Address, B256, Bytes, U256, address};
use r402_protocol::error::VerificationError;
use r402_protocol::payment::UnixTimestamp;
pub use r402_protocol::scheme::AuthCaptureScheme;
use serde::{Deserialize, Serialize};
use crate::asset::AssetTransferMethod;
use crate::chain::{ChecksummedAddress, TokenAmount};
pub const INVALID_AUTH_CAPTURE_EVM_EXTRA: &str = "invalid_auth_capture_evm_extra";
pub const INVALID_AUTH_CAPTURE_EVM_PAYLOAD_FORMAT: &str = "invalid_auth_capture_evm_payload_format";
pub const AUTH_CAPTURE_ESCROW_V1_0_ADDRESS: Address =
address!("0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff");
pub const EIP3009_TOKEN_COLLECTOR_V1_0_ADDRESS: Address =
address!("0x0E3dF9510de65469C4518D7843919c0b8C7A7757");
pub const PERMIT2_TOKEN_COLLECTOR_V1_0_ADDRESS: Address =
address!("0x992476B9Ee81d52a5BdA0622C333938D0Af0aB26");
pub const OPERATOR_REFUND_COLLECTOR_V1_0_ADDRESS: Address =
address!("0x934907bffd0901b6A21e398B9C53A4A38F02fa5d");
pub const AUTH_CAPTURE_ESCROW_V1_1_ADDRESS: Address =
address!("0x13AC3b34322D12FE27D5e192D0c2b2266d4F29CB");
pub const EIP3009_TOKEN_COLLECTOR_V1_1_ADDRESS: Address =
address!("0xEA902B37036bcb4944577ec2101ABdEDF56EbD28");
pub const PERMIT2_TOKEN_COLLECTOR_V1_1_ADDRESS: Address =
address!("0x1aacb38b16a1a8709e80746825E53A0C9Cae9b70");
pub const OPERATOR_REFUND_COLLECTOR_V1_1_ADDRESS: Address =
address!("0x6a1ADdEEb4bD9c5811a613e20c172b6CE61A4aaB");
pub const AUTH_CAPTURE_ESCROW_ADDRESS: Address = AUTH_CAPTURE_ESCROW_V1_1_ADDRESS;
pub const EIP3009_TOKEN_COLLECTOR_ADDRESS: Address = EIP3009_TOKEN_COLLECTOR_V1_1_ADDRESS;
pub const PERMIT2_TOKEN_COLLECTOR_ADDRESS: Address = PERMIT2_TOKEN_COLLECTOR_V1_1_ADDRESS;
pub const OPERATOR_REFUND_COLLECTOR_ADDRESS: Address = OPERATOR_REFUND_COLLECTOR_V1_1_ADDRESS;
pub const AUTH_CAPTURE_CLOCK_SKEW_SECS: u64 = 6;
pub const OPERATOR_EIP712_NAME: &str = "x402 Auth Capture Operator";
pub const OPERATOR_EIP712_VERSION: &str = "1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthCaptureDeploymentVersion {
V1_0,
V1_1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AuthCaptureDeployment {
pub version: AuthCaptureDeploymentVersion,
pub escrow: Address,
pub eip3009_collector: Address,
pub permit2_collector: Address,
pub operator_refund_collector: Address,
}
pub const AUTH_CAPTURE_DEPLOYMENT_V1_0: AuthCaptureDeployment = AuthCaptureDeployment {
version: AuthCaptureDeploymentVersion::V1_0,
escrow: AUTH_CAPTURE_ESCROW_V1_0_ADDRESS,
eip3009_collector: EIP3009_TOKEN_COLLECTOR_V1_0_ADDRESS,
permit2_collector: PERMIT2_TOKEN_COLLECTOR_V1_0_ADDRESS,
operator_refund_collector: OPERATOR_REFUND_COLLECTOR_V1_0_ADDRESS,
};
pub const AUTH_CAPTURE_DEPLOYMENT_V1_1: AuthCaptureDeployment = AuthCaptureDeployment {
version: AuthCaptureDeploymentVersion::V1_1,
escrow: AUTH_CAPTURE_ESCROW_V1_1_ADDRESS,
eip3009_collector: EIP3009_TOKEN_COLLECTOR_V1_1_ADDRESS,
permit2_collector: PERMIT2_TOKEN_COLLECTOR_V1_1_ADDRESS,
operator_refund_collector: OPERATOR_REFUND_COLLECTOR_V1_1_ADDRESS,
};
impl AuthCaptureDeployment {
#[must_use]
pub const fn collector(self, method: AssetTransferMethod) -> Address {
match method {
AssetTransferMethod::Eip3009 => self.eip3009_collector,
AssetTransferMethod::Permit2 => self.permit2_collector,
}
}
}
#[must_use]
pub fn resolve_auth_capture_deployment(escrow: Option<&str>) -> Option<AuthCaptureDeployment> {
let Some(escrow) = escrow.filter(|s| !s.is_empty()) else {
return Some(AUTH_CAPTURE_DEPLOYMENT_V1_1);
};
let Ok(addr) = Address::from_str(escrow) else {
return None;
};
if addr == AUTH_CAPTURE_ESCROW_V1_1_ADDRESS || addr == AUTH_CAPTURE_ESCROW_ADDRESS {
return Some(AUTH_CAPTURE_DEPLOYMENT_V1_1);
}
if addr == AUTH_CAPTURE_ESCROW_V1_0_ADDRESS {
return Some(AUTH_CAPTURE_DEPLOYMENT_V1_0);
}
None
}
#[must_use]
pub fn fee_amount_from_bps(amount: U256, fee_bps: u16) -> U256 {
(amount * U256::from(fee_bps)) / U256::from(10_000u16)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
variant_size_differences,
reason = "official wire is feeBps (u16) XOR feeAmount (U256)"
)]
pub enum SubmittedFee {
Bps(u16),
Amount(TokenAmount),
}
#[must_use]
pub fn default_submitted_fee(
version: AuthCaptureDeploymentVersion,
amount: U256,
min_fee_bps: u16,
) -> SubmittedFee {
match version {
AuthCaptureDeploymentVersion::V1_1 => {
SubmittedFee::Amount(TokenAmount(fee_amount_from_bps(amount, min_fee_bps)))
}
AuthCaptureDeploymentVersion::V1_0 => SubmittedFee::Bps(min_fee_bps),
}
}
pub fn submitted_fee_from_wire(
version: AuthCaptureDeploymentVersion,
fee_bps: Option<u16>,
fee_amount: Option<TokenAmount>,
) -> Result<Option<SubmittedFee>, VerificationError> {
match (version, fee_bps, fee_amount) {
(_, None, None) => Ok(None),
(AuthCaptureDeploymentVersion::V1_1, None, Some(amount)) => {
Ok(Some(SubmittedFee::Amount(amount)))
}
(AuthCaptureDeploymentVersion::V1_0, Some(bps), None) => Ok(Some(SubmittedFee::Bps(bps))),
(AuthCaptureDeploymentVersion::V1_1, Some(_), None)
| (AuthCaptureDeploymentVersion::V1_0, None, Some(_))
| (_, Some(_), Some(_)) => Err(VerificationError::from_wire(
INVALID_AUTH_CAPTURE_EVM_PAYLOAD_FORMAT,
)),
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthCaptureExtra {
pub name: String,
pub version: String,
pub capture_authorizer: ChecksummedAddress,
pub capture_deadline: u64,
pub refund_deadline: u64,
pub fee_recipient: ChecksummedAddress,
pub min_fee_bps: u16,
pub max_fee_bps: u16,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_capture: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub asset_transfer_method: Option<AssetTransferMethod>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth_capture_escrow: Option<String>,
}
impl AuthCaptureExtra {
#[must_use]
pub const fn transfer_method(&self) -> AssetTransferMethod {
match self.asset_transfer_method {
Some(m) => m,
None => AssetTransferMethod::Eip3009,
}
}
#[must_use]
pub const fn auto_capture(&self) -> bool {
matches!(self.auto_capture, Some(true))
}
#[must_use]
pub fn deployment(&self) -> Option<AuthCaptureDeployment> {
resolve_auth_capture_deployment(self.auth_capture_escrow.as_deref())
}
pub fn require_deployment(&self) -> Result<AuthCaptureDeployment, VerificationError> {
self.deployment()
.ok_or_else(|| VerificationError::from_wire(INVALID_AUTH_CAPTURE_EVM_EXTRA))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthCaptureEip3009Authorization {
pub from: Address,
pub to: Address,
pub value: TokenAmount,
pub valid_after: UnixTimestamp,
pub valid_before: UnixTimestamp,
pub nonce: B256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthCaptureEip3009Payload {
pub authorization: AuthCaptureEip3009Authorization,
pub signature: Bytes,
pub salt: B256,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fee_bps: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fee_amount: Option<TokenAmount>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthCaptureTokenPermissions {
pub token: Address,
pub amount: TokenAmount,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthCapturePermit2Authorization {
pub from: Address,
pub permitted: AuthCaptureTokenPermissions,
pub spender: Address,
pub nonce: TokenAmount,
pub deadline: TokenAmount,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthCapturePermit2Payload {
pub permit2_authorization: AuthCapturePermit2Authorization,
pub signature: Bytes,
pub salt: B256,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fee_bps: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fee_amount: Option<TokenAmount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AuthCapturePayload {
Permit2(AuthCapturePermit2Payload),
Eip3009(AuthCaptureEip3009Payload),
}
impl AuthCapturePayload {
#[must_use]
pub const fn payer(&self) -> Address {
match self {
Self::Eip3009(p) => p.authorization.from,
Self::Permit2(p) => p.permit2_authorization.from,
}
}
#[must_use]
pub const fn salt(&self) -> B256 {
match self {
Self::Eip3009(p) => p.salt,
Self::Permit2(p) => p.salt,
}
}
#[must_use]
pub const fn signature(&self) -> &Bytes {
match self {
Self::Eip3009(p) => &p.signature,
Self::Permit2(p) => &p.signature,
}
}
#[must_use]
pub const fn transfer_method(&self) -> AssetTransferMethod {
match self {
Self::Eip3009(_) => AssetTransferMethod::Eip3009,
Self::Permit2(_) => AssetTransferMethod::Permit2,
}
}
#[must_use]
pub const fn fee_bps(&self) -> Option<u16> {
match self {
Self::Eip3009(p) => p.fee_bps,
Self::Permit2(p) => p.fee_bps,
}
}
#[must_use]
pub const fn fee_amount(&self) -> Option<TokenAmount> {
match self {
Self::Eip3009(p) => p.fee_amount,
Self::Permit2(p) => p.fee_amount,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PaymentInfo {
pub operator: Address,
pub payer: Address,
pub receiver: Address,
pub token: Address,
pub max_amount: U256,
pub pre_approval_expiry: u64,
pub authorization_expiry: u64,
pub refund_expiry: u64,
pub min_fee_bps: u16,
pub max_fee_bps: u16,
pub fee_receiver: Address,
pub salt: U256,
}
#[cfg(any(feature = "client", feature = "facilitator"))]
use alloy_sol_types::sol;
#[cfg(any(feature = "client", feature = "facilitator"))]
sol! {
struct ReceiveWithAuthorization {
address from;
address to;
uint256 value;
uint256 validAfter;
uint256 validBefore;
bytes32 nonce;
}
struct TokenPermissions {
address token;
uint256 amount;
}
struct PermitTransferFrom {
TokenPermissions permitted;
address spender;
uint256 nonce;
uint256 deadline;
}
}
#[cfg(any(feature = "client", feature = "facilitator"))]
pub mod v1_0 {
use alloy_sol_types::sol;
sol! {
struct Charge {
bytes32 paymentInfoHash;
uint256 amount;
address tokenCollector;
bytes32 collectorDataHash;
uint16 feeBps;
address feeReceiver;
}
struct Capture {
bytes32 paymentInfoHash;
uint256 amount;
uint16 feeBps;
address feeReceiver;
uint256 expectedCapturableAmount;
uint256 expectedRefundableAmount;
}
}
}
#[cfg(any(feature = "client", feature = "facilitator"))]
pub mod v1_1 {
use alloy_sol_types::sol;
sol! {
struct Charge {
bytes32 paymentInfoHash;
uint256 amount;
address tokenCollector;
bytes32 collectorDataHash;
uint256 feeAmount;
address feeReceiver;
}
struct Capture {
bytes32 paymentInfoHash;
uint256 amount;
uint256 feeAmount;
address feeReceiver;
uint256 expectedCapturableAmount;
uint256 expectedRefundableAmount;
}
}
}
#[cfg(any(feature = "client", feature = "facilitator"))]
pub use v1_1::Capture;
#[cfg(any(feature = "client", feature = "facilitator"))]
pub use v1_1::Charge;
pub mod v2 {
use r402_protocol::payment::{
PaymentPayload as WirePayload, PaymentRequirements as WireReqs, TypedVerifyRequest,
};
use super::{AuthCaptureExtra, AuthCapturePayload, AuthCaptureScheme};
use crate::chain::{ChecksummedAddress, TokenAmount};
pub type VerifyRequest = TypedVerifyRequest<2, PaymentPayload, PaymentRequirements>;
pub type SettleRequest = VerifyRequest;
pub type PaymentPayload = WirePayload<PaymentRequirements, AuthCapturePayload>;
pub type PaymentRequirements =
WireReqs<AuthCaptureScheme, TokenAmount, ChecksummedAddress, AuthCaptureExtra>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_addresses_checksum() {
assert_eq!(
AUTH_CAPTURE_ESCROW_ADDRESS.to_checksum(None),
"0x13AC3b34322D12FE27D5e192D0c2b2266d4F29CB"
);
assert_eq!(
EIP3009_TOKEN_COLLECTOR_ADDRESS.to_checksum(None),
"0xEA902B37036bcb4944577ec2101ABdEDF56EbD28"
);
assert_eq!(
PERMIT2_TOKEN_COLLECTOR_ADDRESS.to_checksum(None),
"0x1aacb38b16a1a8709e80746825E53A0C9Cae9b70"
);
assert_eq!(
OPERATOR_REFUND_COLLECTOR_ADDRESS.to_checksum(None),
"0x6a1ADdEEb4bD9c5811a613e20c172b6CE61A4aaB"
);
}
#[test]
fn v1_0_named_constants_checksum() {
assert_eq!(
AUTH_CAPTURE_ESCROW_V1_0_ADDRESS.to_checksum(None),
"0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff"
);
assert_eq!(
EIP3009_TOKEN_COLLECTOR_V1_0_ADDRESS.to_checksum(None),
"0x0E3dF9510de65469C4518D7843919c0b8C7A7757"
);
assert_eq!(
PERMIT2_TOKEN_COLLECTOR_V1_0_ADDRESS.to_checksum(None),
"0x992476B9Ee81d52a5BdA0622C333938D0Af0aB26"
);
assert_eq!(
OPERATOR_REFUND_COLLECTOR_V1_0_ADDRESS.to_checksum(None),
"0x934907bffd0901b6A21e398B9C53A4A38F02fa5d"
);
}
#[test]
fn aliases_are_v1_1_not_v1_0() {
assert_eq!(
AUTH_CAPTURE_ESCROW_ADDRESS,
AUTH_CAPTURE_ESCROW_V1_1_ADDRESS
);
assert_ne!(
AUTH_CAPTURE_ESCROW_ADDRESS,
AUTH_CAPTURE_ESCROW_V1_0_ADDRESS
);
assert_eq!(
EIP3009_TOKEN_COLLECTOR_ADDRESS,
EIP3009_TOKEN_COLLECTOR_V1_1_ADDRESS
);
assert_eq!(
PERMIT2_TOKEN_COLLECTOR_ADDRESS,
PERMIT2_TOKEN_COLLECTOR_V1_1_ADDRESS
);
}
#[test]
fn resolve_absent_is_v1_1() {
let d = resolve_auth_capture_deployment(None).expect("default");
assert_eq!(d, AUTH_CAPTURE_DEPLOYMENT_V1_1);
assert_eq!(d.version, AuthCaptureDeploymentVersion::V1_1);
}
#[test]
fn resolve_v1_1_escrow_is_v1_1() {
let s = AUTH_CAPTURE_ESCROW_V1_1_ADDRESS.to_checksum(None);
let d = resolve_auth_capture_deployment(Some(s.as_str())).expect("v1.1 escrow");
assert_eq!(d, AUTH_CAPTURE_DEPLOYMENT_V1_1);
}
#[test]
fn resolve_v1_0_escrow_is_v1_0() {
let s = AUTH_CAPTURE_ESCROW_V1_0_ADDRESS.to_checksum(None);
let d = resolve_auth_capture_deployment(Some(s.as_str())).expect("v1.0 escrow");
assert_eq!(d, AUTH_CAPTURE_DEPLOYMENT_V1_0);
assert_eq!(d.eip3009_collector, EIP3009_TOKEN_COLLECTOR_V1_0_ADDRESS);
assert_eq!(d.permit2_collector, PERMIT2_TOKEN_COLLECTOR_V1_0_ADDRESS);
}
#[test]
fn resolve_empty_string_is_v1_1() {
assert_eq!(
resolve_auth_capture_deployment(Some("")),
Some(AUTH_CAPTURE_DEPLOYMENT_V1_1)
);
}
#[test]
fn resolve_not_an_address_is_none() {
assert_eq!(
resolve_auth_capture_deployment(Some("not-an-address")),
None,
"not-an-address must not fall back to v1.0 or v1.1"
);
}
#[test]
fn resolve_unknown_escrow_is_none() {
let s = Address::repeat_byte(0x99).to_checksum(None);
assert_eq!(
resolve_auth_capture_deployment(Some(s.as_str())),
None,
"unknown escrow must not fall back to v1.0 or v1.1"
);
}
#[test]
fn extra_empty_escrow_deserializes_as_v1_1() {
let extra: AuthCaptureExtra = serde_json::from_value(serde_json::json!({
"name": "USD Coin",
"version": "2",
"captureAuthorizer": "0x1111111111111111111111111111111111111111",
"captureDeadline": 1,
"refundDeadline": 2,
"feeRecipient": "0x2222222222222222222222222222222222222222",
"minFeeBps": 0,
"maxFeeBps": 0,
"authCaptureEscrow": ""
}))
.expect("empty escrow is valid extra");
assert_eq!(extra.auth_capture_escrow.as_deref(), Some(""));
assert_eq!(
extra.deployment(),
Some(AUTH_CAPTURE_DEPLOYMENT_V1_1),
"empty string selects v1.1"
);
}
#[test]
fn extra_not_an_address_deserializes_and_rejects_at_resolve() {
let extra: AuthCaptureExtra = serde_json::from_value(serde_json::json!({
"name": "USD Coin",
"version": "2",
"captureAuthorizer": "0x1111111111111111111111111111111111111111",
"captureDeadline": 1,
"refundDeadline": 2,
"feeRecipient": "0x2222222222222222222222222222222222222222",
"minFeeBps": 0,
"maxFeeBps": 0,
"authCaptureEscrow": "not-an-address"
}))
.expect("not-an-address must not fail extra deserialize");
assert_eq!(
extra.require_deployment().expect_err("reject").to_string(),
"invalid_auth_capture_evm_extra"
);
}
#[test]
fn fee_amount_from_bps_matches_escrow_integer_div() {
assert_eq!(
fee_amount_from_bps(U256::from(1_000_000_u64), 100),
U256::from(10_000_u64)
);
assert_eq!(
fee_amount_from_bps(U256::from(1_u64), 1),
U256::ZERO,
"escrow integer division truncates"
);
assert_eq!(
default_submitted_fee(
AuthCaptureDeploymentVersion::V1_1,
U256::from(1_000_000_u64),
100
),
SubmittedFee::Amount(TokenAmount(U256::from(10_000_u64)))
);
assert_eq!(
default_submitted_fee(
AuthCaptureDeploymentVersion::V1_0,
U256::from(1_000_000_u64),
100
),
SubmittedFee::Bps(100)
);
}
#[test]
fn submitted_fee_rejects_mixed_and_wrong_deployment_field() {
let amount = TokenAmount(U256::from(10_u64));
assert!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_1, Some(1), Some(amount))
.is_err()
);
assert!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_1, Some(1), None).is_err(),
"v1.1 rejects feeBps"
);
assert!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_0, None, Some(amount))
.is_err(),
"v1.0 pin rejects feeAmount"
);
assert_eq!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_1, None, Some(amount))
.expect("v1.1 feeAmount"),
Some(SubmittedFee::Amount(amount))
);
assert_eq!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_0, Some(50), None)
.expect("v1.0 feeBps"),
Some(SubmittedFee::Bps(50))
);
assert_eq!(
submitted_fee_from_wire(AuthCaptureDeploymentVersion::V1_1, None, None)
.expect("authorize"),
None
);
}
#[test]
fn collectors_are_not_read_from_extra() {
let extra: AuthCaptureExtra = serde_json::from_value(serde_json::json!({
"name": "USD Coin",
"version": "2",
"captureAuthorizer": "0x1111111111111111111111111111111111111111",
"captureDeadline": 1,
"refundDeadline": 2,
"feeRecipient": "0x2222222222222222222222222222222222222222",
"minFeeBps": 0,
"maxFeeBps": 0,
"eip3009Collector": "0x0E3dF9510de65469C4518D7843919c0b8C7A7757",
"permit2Collector": "0x992476B9Ee81d52a5BdA0622C333938D0Af0aB26"
}))
.expect("unknown collector keys ignored");
let d = extra.deployment().expect("omitted escrow is v1.1");
assert_eq!(d.eip3009_collector, EIP3009_TOKEN_COLLECTOR_V1_1_ADDRESS);
assert_eq!(d.permit2_collector, PERMIT2_TOKEN_COLLECTOR_V1_1_ADDRESS);
assert_eq!(d.escrow, AUTH_CAPTURE_ESCROW_V1_1_ADDRESS);
}
#[cfg(any(feature = "client", feature = "facilitator"))]
#[test]
fn charge_capture_eip712_encode_type_is_official_primary_type() {
use alloy_sol_types::SolStruct;
assert_eq!(
Charge::eip712_encode_type(),
"Charge(bytes32 paymentInfoHash,uint256 amount,address tokenCollector,bytes32 collectorDataHash,uint256 feeAmount,address feeReceiver)"
);
assert_eq!(
Capture::eip712_encode_type(),
"Capture(bytes32 paymentInfoHash,uint256 amount,uint256 feeAmount,address feeReceiver,uint256 expectedCapturableAmount,uint256 expectedRefundableAmount)"
);
assert_eq!(
v1_0::Charge::eip712_encode_type(),
"Charge(bytes32 paymentInfoHash,uint256 amount,address tokenCollector,bytes32 collectorDataHash,uint16 feeBps,address feeReceiver)"
);
assert_eq!(
v1_0::Capture::eip712_encode_type(),
"Capture(bytes32 paymentInfoHash,uint256 amount,uint16 feeBps,address feeReceiver,uint256 expectedCapturableAmount,uint256 expectedRefundableAmount)"
);
assert_eq!(Charge::NAME, "Charge");
assert_eq!(v1_0::Charge::NAME, "Charge");
assert_eq!(Capture::NAME, "Capture");
assert_eq!(v1_0::Capture::NAME, "Capture");
}
}