r402-evm 0.14.0

EIP-155 (EVM) chain support for the x402 payment protocol.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//! Type definitions for the EIP-155 "exact" payment scheme.
//!
//! This module defines shared wire format types for EVM exact payments,
//! supporting both EIP-3009 (`transferWithAuthorization`) and Permit2
//! transfer methods. Wire format type aliases live in the [`v2`] sub-module.

use alloy_primitives::{Address, B256, Bytes, address};
#[cfg(any(feature = "facilitator", feature = "client"))]
use alloy_sol_types::sol;
pub use r402_core::scheme::ExactScheme;
use r402_core::wire::UnixTimestamp;
use serde::{Deserialize, Serialize};

use crate::chain::TokenAmount;

/// Canonical Uniswap Permit2 contract address (same on all EVM chains via CREATE2).
pub const PERMIT2_ADDRESS: Address = address!("0x000000000022D473030F116dDEE9F6B43aC78BA3");

/// Canonical x402 exact payment Permit2 proxy address.
///
/// Vanity address `0x4020...0001`, deterministically deployed via CREATE2 with the
/// same address on every supported EVM chain. The proxy validates EIP-712 witness
/// data and atomically performs `permitTransferFromWithWitness` against the canonical
/// Permit2 contract.
///
/// Source of truth: [x402 spec — Exact EVM scheme](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)
/// and the Go reference SDK (`go/mechanisms/evm/constants.go`).
pub const X402_EXACT_PERMIT2_PROXY: Address =
    address!("0x402085c248EeA27D92E8b30b2C58ed07f9E20001");

/// Canonical x402 upto payment Permit2 proxy address.
///
/// Vanity address `0x4020...0002`, deterministically deployed via CREATE2 with the
/// same address on every supported EVM chain. Mirrors [`X402_EXACT_PERMIT2_PROXY`]
/// but accepts a `Witness` containing an additional `facilitator` field that binds
/// the signed permit to a specific facilitator (anti-reflection).
///
/// Source of truth: [x402 spec — Upto EVM scheme](https://github.com/x402-foundation/x402/blob/main/specs/schemes/upto/scheme_upto_evm.md)
/// and the Go reference SDK (`go/mechanisms/evm/constants.go`).
pub const X402_UPTO_PERMIT2_PROXY: Address = address!("0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002");

/// Universal signature verifier for EIP-6492, EIP-1271, and EOA signatures.
///
/// Deployed at the same address on every supported EVM chain. The exact and
/// upto facilitators delegate to this contract via
/// `Validator6492::isValidSigWithSideEffects`; if the validator is missing on
/// a particular chain, EIP-6492 / EIP-1271 verification will revert and only
/// EOA signatures will work. Use the `validator_deployed_on_configured_chains`
/// integration test to confirm presence on every chain you operate.
///
/// This address is **r402-specific** (not currently part of the cross-SDK
/// canonical-address set). Out-of-tree deployments must mirror the bytecode
/// to maintain interop.
pub const VALIDATOR_ADDRESS: Address = address!("0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B");

/// Determines which on-chain mechanism is used for token transfers.
///
/// - `Eip3009`: Uses `transferWithAuthorization` (USDC, etc.) — recommended for compatible tokens
/// - `Permit2`: Uses Permit2 + `x402Permit2Proxy` — universal fallback for any ERC-20
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AssetTransferMethod {
    /// EIP-3009 `transferWithAuthorization`.
    Eip3009,
    /// Uniswap Permit2 via `x402Permit2Proxy`.
    Permit2,
}

/// Unified exact payment payload — either EIP-3009 or Permit2.
///
/// Deserialization uses `#[serde(untagged)]`: the Permit2 variant is tried first
/// because it contains the unique `permit2Authorization` field.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ExactPayload {
    /// Permit2-based payment (tried first during deserialization).
    Permit2(Permit2Payload),
    /// EIP-3009-based payment.
    Eip3009(Eip3009Payload),
}

impl ExactPayload {
    /// Returns the transfer method used by this payload.
    #[must_use]
    pub const fn transfer_method(&self) -> AssetTransferMethod {
        match self {
            Self::Eip3009(_) => AssetTransferMethod::Eip3009,
            Self::Permit2(_) => AssetTransferMethod::Permit2,
        }
    }

    /// Returns the sender (`from`) address for this payment.
    #[must_use]
    pub const fn sender(&self) -> Address {
        match self {
            Self::Eip3009(p) => p.authorization.from,
            Self::Permit2(p) => p.permit2_authorization.from,
        }
    }

    /// Returns the raw signature bytes.
    pub const fn signature(&self) -> &Bytes {
        match self {
            Self::Eip3009(p) => &p.signature,
            Self::Permit2(p) => &p.signature,
        }
    }
}

/// EIP-3009 `transferWithAuthorization` payment payload.
///
/// Contains both the EIP-712 signature and the structured authorization
/// data. Together, they provide everything needed to execute a
/// `transferWithAuthorization` call on an ERC-3009 compliant token contract.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Eip3009Payload {
    /// The cryptographic signature authorizing the transfer.
    ///
    /// This can be:
    /// - An EOA signature (64-65 bytes, split into r, s, v components)
    /// - An EIP-1271 signature (arbitrary length, validated by contract)
    /// - An EIP-6492 signature (wrapped with deployment data and magic suffix)
    pub signature: Bytes,

    /// The structured authorization data that was signed.
    pub authorization: Eip3009Authorization,
}

/// Permit2 token permissions — which token and how much.
///
/// Part of the `PermitWitnessTransferFrom` message structure that gets signed.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Permit2TokenPermissions {
    /// Token contract address.
    pub token: Address,
    /// Amount in smallest unit as decimal string (e.g., `"1000000"` for 1 USDC).
    pub amount: TokenAmount,
}

/// Witness data verified on-chain by `x402ExactPermit2Proxy`.
///
/// Included in the EIP-712 signature and checked by the proxy contract.
/// The upper time bound is enforced by Permit2's `deadline` field on the outer
/// `PermitWitnessTransferFrom` struct, so only `validAfter` lives in the witness.
///
/// The on-chain typehash is hashed from `"Witness(address to,uint256 validAfter)"`,
/// matching the canonical [exact EVM scheme](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md).
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Permit2Witness {
    /// Destination address for funds.
    pub to: Address,
    /// Unix timestamp — payment invalid before this time.
    pub valid_after: TokenAmount,
}

/// Permit2 authorization parameters.
///
/// Maps to the `PermitWitnessTransferFrom` struct used by the Permit2 contract.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Permit2Authorization {
    /// Signer / owner address.
    pub from: Address,
    /// Token and amount permitted.
    pub permitted: Permit2TokenPermissions,
    /// Must be the `x402Permit2Proxy` address.
    pub spender: Address,
    /// Unique nonce (uint256 as decimal string).
    pub nonce: TokenAmount,
    /// Signature expires after this unix timestamp (uint256 as decimal string).
    pub deadline: TokenAmount,
    /// Witness data verified by `x402Permit2Proxy`.
    pub witness: Permit2Witness,
}

/// Permit2 payment payload sent by clients.
///
/// Contains the EIP-712 signature over a `PermitWitnessTransferFrom`
/// and the authorization parameters that were signed.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Permit2Payload {
    /// EIP-712 signature (hex, 65 bytes for EOA).
    pub signature: Bytes,
    /// Authorization parameters that were signed.
    pub permit2_authorization: Permit2Authorization,
}

/// EIP-712 structured data for ERC-3009 transfer authorization.
///
/// This struct defines the parameters of a `transferWithAuthorization` call:
/// who can transfer tokens, to whom, how much, and during what time window.
/// The struct is signed using EIP-712 typed data signing.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Eip3009Authorization {
    /// The address authorizing the transfer (token owner).
    pub from: Address,

    /// The recipient address for the transfer.
    pub to: Address,

    /// The amount of tokens to transfer (in token's smallest unit).
    pub value: TokenAmount,

    /// The authorization is not valid before this timestamp (inclusive).
    pub valid_after: UnixTimestamp,

    /// The authorization expires at this timestamp (exclusive).
    pub valid_before: UnixTimestamp,

    /// A unique 32-byte nonce to prevent replay attacks.
    pub nonce: B256,
}

/// Extra payment requirements data for the EVM exact scheme.
///
/// Contains optional EIP-712 domain parameters and the asset transfer method.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequirementsExtra {
    /// The token name as used in the EIP-712 domain (required for EIP-3009).
    pub name: String,

    /// The token version as used in the EIP-712 domain (required for EIP-3009).
    pub version: String,

    /// Which on-chain transfer mechanism to use.
    ///
    /// - `Some(Eip3009)` or `None` → EIP-3009 `transferWithAuthorization`
    /// - `Some(Permit2)` → Permit2 via `x402Permit2Proxy`
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub asset_transfer_method: Option<AssetTransferMethod>,
}

impl PaymentRequirementsExtra {
    /// Builds the serialized `extra` JSON from EIP-712 deployment data and
    /// an optional transfer method.
    ///
    /// Returns `None` when both `eip712` and `method` are absent (pure default).
    #[must_use]
    pub fn from_deployment(
        eip712: Option<crate::chain::TokenDeploymentEip712>,
        method: Option<AssetTransferMethod>,
    ) -> Option<serde_json::Value> {
        let extra = match (eip712, method) {
            (Some(eip712), method) => Self::from(eip712).with_transfer_method(method),
            (None, Some(m)) => Self {
                name: String::new(),
                version: String::new(),
                asset_transfer_method: Some(m),
            },
            (None, None) => return None,
        };
        serde_json::to_value(extra).ok()
    }

    /// Sets the asset transfer method, consuming and returning `self`.
    #[must_use]
    pub const fn with_transfer_method(mut self, method: Option<AssetTransferMethod>) -> Self {
        self.asset_transfer_method = method;
        self
    }
}

impl From<crate::chain::TokenDeploymentEip712> for PaymentRequirementsExtra {
    fn from(eip712: crate::chain::TokenDeploymentEip712) -> Self {
        Self {
            name: eip712.name,
            version: eip712.version,
            asset_transfer_method: None,
        }
    }
}

#[cfg(any(feature = "facilitator", feature = "client"))]
sol!(
    /// Solidity-compatible struct definition for ERC-3009 `transferWithAuthorization`.
    ///
    /// This matches the EIP-3009 format used in EIP-712 typed data:
    /// it defines the authorization to transfer tokens from `from` to `to`
    /// for a specific `value`, valid only between `validAfter` and `validBefore`
    /// and identified by a unique `nonce`.
    ///
    /// This struct is primarily used to reconstruct the typed data domain/message
    /// when verifying a client's signature.
    #[derive(Serialize, Deserialize)]
    struct TransferWithAuthorization {
        address from;
        address to;
        uint256 value;
        uint256 validAfter;
        uint256 validBefore;
        bytes32 nonce;
    }
);

#[cfg(any(feature = "facilitator", feature = "client"))]
sol!(
    /// EIP-712 struct for Permit2 token permissions.
    #[derive(Serialize, Deserialize)]
    struct TokenPermissions {
        address token;
        uint256 amount;
    }

    /// EIP-712 struct for x402 Permit2 witness data.
    ///
    /// Field order and typehash MUST match the deployed `x402ExactPermit2Proxy`
    /// contract definition (`Witness(address to,uint256 validAfter)`).
    #[derive(Serialize, Deserialize)]
    struct Witness {
        address to;
        uint256 validAfter;
    }

    /// EIP-712 struct for Permit2 `PermitWitnessTransferFrom`.
    ///
    /// This is the primary type signed by the payer when using Permit2.
    /// The domain uses `name = "Permit2"`, no version, and
    /// `verifyingContract = PERMIT2_ADDRESS`.
    #[derive(Serialize, Deserialize)]
    struct PermitWitnessTransferFrom {
        TokenPermissions permitted;
        address spender;
        uint256 nonce;
        uint256 deadline;
        Witness witness;
    }
);

/// Wire format type aliases for EIP-155 exact scheme.
///
/// Uses CAIP-2 chain IDs (e.g., `eip155:8453`) for chain identification
/// and embeds requirements directly in the payload.
pub mod v2 {
    use r402_core::wire as proto_v2;

    use super::{ExactPayload, ExactScheme, PaymentRequirementsExtra};
    use crate::chain::{ChecksummedAddress, TokenAmount};

    /// Type alias for verify requests using the exact EVM payment scheme.
    pub type VerifyRequest = proto_v2::TypedVerifyRequest<2, PaymentPayload, PaymentRequirements>;

    /// Type alias for settle requests (same structure as verify requests).
    pub type SettleRequest = VerifyRequest;

    /// Type alias for payment payloads with embedded requirements and EVM-specific data.
    pub type PaymentPayload = proto_v2::PaymentPayload<PaymentRequirements, ExactPayload>;

    /// Type alias for payment requirements with EVM-specific types.
    pub type PaymentRequirements = proto_v2::PaymentRequirements<
        ExactScheme,
        TokenAmount,
        ChecksummedAddress,
        PaymentRequirementsExtra,
    >;
}

#[cfg(test)]
#[cfg(any(feature = "facilitator", feature = "client"))]
mod tests {
    use alloy_primitives::{U256, keccak256};
    use alloy_sol_types::SolStruct;

    use super::*;

    /// Typehash for the x402 `Witness` struct MUST equal the on-chain value
    /// baked into the `x402ExactPermit2Proxy` contract. If the `sol!` macro
    /// ever emits a field in the wrong order or with the wrong type, a signed
    /// Permit2 request would produce a hash that the proxy's `ecrecover`
    /// cannot match, breaking every payment on every chain. The authoritative
    /// byte string comes from the x402 spec, not from the Rust struct — this
    /// test cross-checks the macro expansion against an independent source.
    #[test]
    fn witness_typehash_equals_spec_byte_string() {
        let witness = Witness {
            to: Address::ZERO,
            validAfter: U256::ZERO,
        };
        let expected = keccak256(b"Witness(address to,uint256 validAfter)");
        assert_eq!(witness.eip712_type_hash(), expected);
    }

    /// Same cross-check for the top-level `PermitWitnessTransferFrom` typehash.
    /// The canonical byte string is defined by the Uniswap Permit2 contract
    /// and reproduced verbatim in the x402 spec's EVM exact scheme section.
    #[test]
    fn permit_witness_transfer_from_typehash_equals_spec_byte_string() {
        let permit = PermitWitnessTransferFrom {
            permitted: TokenPermissions {
                token: Address::ZERO,
                amount: U256::ZERO,
            },
            spender: Address::ZERO,
            nonce: U256::ZERO,
            deadline: U256::ZERO,
            witness: Witness {
                to: Address::ZERO,
                validAfter: U256::ZERO,
            },
        };
        let expected = keccak256(
            b"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Witness witness)TokenPermissions(address token,uint256 amount)Witness(address to,uint256 validAfter)",
        );
        assert_eq!(permit.eip712_type_hash(), expected);
    }
}