use alloy_primitives::{Address, B256, Bytes, address};
use r402_core::scheme::BatchSettlementScheme;
use serde::{Deserialize, Serialize};
use crate::asset::AssetTransferMethod;
use crate::chain::{ChecksummedAddress, TokenAmount};
pub const BATCH_SETTLEMENT_ADDRESS: Address =
address!("0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003");
pub const ERC3009_DEPOSIT_COLLECTOR_ADDRESS: Address =
address!("0x4020806089470a89826cB9fB1f4059150b550004");
pub const PERMIT2_DEPOSIT_COLLECTOR_ADDRESS: Address =
address!("0x4020425FAf3B746C082C2f942b4E5159887B0005");
pub const MIN_WITHDRAW_DELAY: u64 = 900;
pub const MAX_WITHDRAW_DELAY: u64 = 2_592_000;
pub const BATCH_SETTLEMENT_DOMAIN_NAME: &str = "x402 Batch Settlement";
pub const BATCH_SETTLEMENT_DOMAIN_VERSION: &str = "1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelConfig {
pub payer: Address,
pub payer_authorizer: Address,
pub receiver: Address,
pub receiver_authorizer: Address,
pub token: Address,
pub withdraw_delay: u64,
pub salt: B256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoucherFields {
pub channel_id: B256,
pub max_claimable_amount: TokenAmount,
pub signature: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchSettlementExtra {
pub receiver_authorizer: ChecksummedAddress,
pub withdraw_delay: u64,
pub name: String,
pub version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub asset_transfer_method: Option<AssetTransferMethod>,
}
impl BatchSettlementExtra {
#[must_use]
pub const fn transfer_method(&self) -> AssetTransferMethod {
match self.asset_transfer_method {
Some(m) => m,
None => AssetTransferMethod::Eip3009,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChannelState {
pub balance: TokenAmount,
pub total_claimed: TokenAmount,
pub charged_cumulative: TokenAmount,
pub refund_nonce: TokenAmount,
}
impl Default for ChannelState {
fn default() -> Self {
use alloy_primitives::U256;
Self {
balance: TokenAmount::from(U256::ZERO),
total_claimed: TokenAmount::from(U256::ZERO),
charged_cumulative: TokenAmount::from(U256::ZERO),
refund_nonce: TokenAmount::from(U256::ZERO),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DepositErc3009Authorization {
pub valid_after: TokenAmount,
pub valid_before: TokenAmount,
pub salt: B256,
pub signature: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DepositPermit2Authorization {
pub from: Address,
pub permitted: DepositTokenPermissions,
pub spender: Address,
pub nonce: TokenAmount,
pub deadline: TokenAmount,
pub witness: DepositWitness,
pub signature: Bytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct DepositTokenPermissions {
pub token: Address,
pub amount: TokenAmount,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DepositWitness {
pub channel_id: B256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DepositAuthorization {
Permit2 {
#[serde(rename = "permit2Authorization")]
permit2_authorization: DepositPermit2Authorization,
},
Erc3009 {
#[serde(rename = "erc3009Authorization")]
erc3009_authorization: DepositErc3009Authorization,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum BatchSettlementPayload {
Deposit {
channel_config: ChannelConfig,
voucher: VoucherFields,
deposit: Box<DepositBody>,
},
Voucher {
channel_config: ChannelConfig,
voucher: VoucherFields,
},
Refund {
channel_config: ChannelConfig,
voucher: VoucherFields,
#[serde(default, skip_serializing_if = "Option::is_none")]
amount: Option<TokenAmount>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DepositBody {
pub amount: TokenAmount,
pub authorization: DepositAuthorization,
}
impl BatchSettlementPayload {
#[must_use]
pub const fn channel_config(&self) -> &ChannelConfig {
match self {
Self::Deposit { channel_config, .. }
| Self::Voucher { channel_config, .. }
| Self::Refund { channel_config, .. } => channel_config,
}
}
#[must_use]
pub const fn voucher(&self) -> &VoucherFields {
match self {
Self::Deposit { voucher, .. }
| Self::Voucher { voucher, .. }
| Self::Refund { voucher, .. } => voucher,
}
}
}
pub mod v2 {
use r402_core::wire as proto_v2;
use super::{BatchSettlementExtra, BatchSettlementPayload, BatchSettlementScheme};
use crate::chain::{ChecksummedAddress, TokenAmount};
pub type VerifyRequest = proto_v2::TypedVerifyRequest<2, PaymentPayload, PaymentRequirements>;
pub type SettleRequest = VerifyRequest;
pub type PaymentPayload = proto_v2::PaymentPayload<PaymentRequirements, BatchSettlementPayload>;
pub type PaymentRequirements = proto_v2::PaymentRequirements<
BatchSettlementScheme,
TokenAmount,
ChecksummedAddress,
BatchSettlementExtra,
>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addresses() {
assert_eq!(
BATCH_SETTLEMENT_ADDRESS.to_checksum(None),
"0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003"
);
}
}