aztec_fee/
sponsored.rs

1use async_trait::async_trait;
2use aztec_core::abi::{FunctionSelector, FunctionType};
3use aztec_core::tx::{ExecutionPayload, FunctionCall};
4use aztec_core::types::AztecAddress;
5use aztec_core::Error;
6
7use crate::fee_payment_method::FeePaymentMethod;
8
9/// A fee payment method where a sponsor contract pays the fee unconditionally.
10///
11/// This is the simplest strategy — useful for testing, development, and
12/// gasless transaction experiences. The sponsor contract must be pre-funded
13/// with Fee Juice and expose a `sponsor_unconditionally()` private function.
14pub struct SponsoredFeePaymentMethod {
15    /// Address of the sponsor contract that will pay fees.
16    payment_contract: AztecAddress,
17}
18
19impl SponsoredFeePaymentMethod {
20    /// Create a new sponsored fee payment method.
21    ///
22    /// `payment_contract` is the address of the sponsor contract that will pay fees.
23    pub fn new(payment_contract: AztecAddress) -> Self {
24        Self { payment_contract }
25    }
26}
27
28#[async_trait]
29impl FeePaymentMethod for SponsoredFeePaymentMethod {
30    async fn get_asset(&self) -> Result<AztecAddress, Error> {
31        Err(Error::InvalidData(
32            "SponsoredFeePaymentMethod does not have an associated asset".into(),
33        ))
34    }
35
36    async fn get_fee_payer(&self) -> Result<AztecAddress, Error> {
37        Ok(self.payment_contract)
38    }
39
40    async fn get_fee_execution_payload(&self) -> Result<ExecutionPayload, Error> {
41        let call = FunctionCall {
42            to: self.payment_contract,
43            selector: FunctionSelector::from_signature("sponsor_unconditionally()"),
44            args: vec![],
45            function_type: FunctionType::Private,
46            is_static: false,
47            hide_msg_sender: false,
48        };
49
50        Ok(ExecutionPayload {
51            calls: vec![call],
52            auth_witnesses: vec![],
53            capsules: vec![],
54            extra_hashed_args: vec![],
55            fee_payer: Some(self.payment_contract),
56        })
57    }
58}
59
60#[cfg(test)]
61#[allow(clippy::expect_used)]
62mod tests {
63    use super::*;
64    use aztec_core::types::Fr;
65
66    #[tokio::test]
67    async fn payload_has_single_call_to_sponsor_unconditionally() {
68        let contract = AztecAddress(Fr::from(42u64));
69        let method = SponsoredFeePaymentMethod::new(contract);
70        let payload = method.get_fee_execution_payload().await.expect("payload");
71
72        assert_eq!(payload.calls.len(), 1);
73        let call = &payload.calls[0];
74        assert_eq!(call.to, contract);
75        assert_eq!(
76            call.selector,
77            FunctionSelector::from_signature("sponsor_unconditionally()")
78        );
79        assert!(call.args.is_empty());
80        assert_eq!(call.function_type, FunctionType::Private);
81        assert!(!call.is_static);
82    }
83
84    #[tokio::test]
85    async fn fee_payer_is_payment_contract() {
86        let contract = AztecAddress(Fr::from(42u64));
87        let method = SponsoredFeePaymentMethod::new(contract);
88
89        assert_eq!(method.get_fee_payer().await.expect("fee payer"), contract);
90
91        let payload = method.get_fee_execution_payload().await.expect("payload");
92        assert_eq!(payload.fee_payer, Some(contract));
93    }
94
95    #[tokio::test]
96    async fn get_asset_returns_error() {
97        let contract = AztecAddress(Fr::from(42u64));
98        let method = SponsoredFeePaymentMethod::new(contract);
99        assert!(method.get_asset().await.is_err());
100    }
101}