Skip to main content

trident_sdk/
spend_attestation.rs

1use alloy_primitives::{Address, U256};
2use alloy_provider::network::AnyNetwork;
3use alloy_provider::{DynProvider, PendingTransactionBuilder};
4use alloy_sol_types::SolCall;
5use std::marker::PhantomData;
6use trident_evm_aa::{MulticallBuilder, MulticallError};
7
8use crate::evm::abi::TridentDelivery;
9use crate::evm::abi::TridentDelivery::tridentSpendCall;
10use crate::proto::v1;
11
12/// Raw pieces needed to execute the `tridentSpend` call against a Trident
13/// delivery contract.
14///
15/// Returned by [`SpendAttestation::into_call`] for callers that want to route
16/// the spend through their own transaction strategy — for example, composing
17/// it into a [`MulticallBuilder`] with custom ordering.
18#[derive(Debug)]
19pub struct EvmCallContext {
20    /// Trident delivery contract the `tridentSpend` call targets.
21    pub delivery_contract: Address,
22    /// Prepared `SolCall` carrying the ABI-encoded attestation and signature.
23    pub trident_spend_call: tridentSpendCall,
24    /// Live provider attached to the attestation when it was materialized.
25    pub provider: DynProvider<AnyNetwork>,
26    /// Address of the 7702-delegated EOA signer for this chain. Use as the
27    /// `tx_signer` argument to [`MulticallBuilder::send`].
28    pub signer_address: Address,
29}
30
31/// Chain-specific attestation data and execution context.
32#[derive(Debug)]
33pub(crate) enum SpendAttestationChainContext {
34    Evm {
35        provider: DynProvider<AnyNetwork>,
36        delivery_contract: Address,
37        /// Address of the signer registered for this chain. Carried so the
38        /// 7702 self-call in [`SpendMulticall::send`] knows the EOA to target
39        /// without needing a `WalletProvider`-bound provider.
40        signer_address: Address,
41    },
42}
43
44/// A spend attestation ready to be executed on-chain.
45///
46/// Obtained from [`TridentClient::spend`](crate::TridentClient::spend). Call
47/// [`send`](Self::send) to submit the transaction; await `.get_receipt()` on the
48/// returned builder to block until inclusion.
49#[derive(Debug)]
50pub struct SpendAttestation<T> {
51    inner: SpendAttestationChainContext,
52    attestation: v1::SpendAttestation,
53    spend_spec_hash: [u8; 32],
54    _chain_type: PhantomData<T>,
55}
56
57impl<T> SpendAttestation<T> {
58    pub(crate) fn new(
59        ctx: SpendAttestationChainContext,
60        attestation: v1::SpendAttestation,
61        spend_spec_hash: [u8; 32],
62    ) -> Self {
63        Self {
64            inner: ctx,
65            attestation,
66            spend_spec_hash,
67            _chain_type: PhantomData,
68        }
69    }
70    /// Returns the canonical spend spec hash that identifies this attestation.
71    pub fn spend_spec_hash(&self) -> [u8; 32] {
72        self.spend_spec_hash
73    }
74}
75
76impl SpendAttestation<Address> {
77    /// Submits the attestation on-chain as a standalone `tridentSpend`
78    /// transaction.
79    ///
80    /// Use [`chain`](Self::chain) or [`into_call`](Self::into_call)
81    /// to batch the spend with other calls in a single 7702-delegated
82    /// transaction instead.
83    pub async fn send(
84        self,
85    ) -> Result<PendingTransactionBuilder<AnyNetwork>, alloy_contract::Error> {
86        let SpendAttestationChainContext::Evm {
87            provider,
88            delivery_contract,
89            ..
90        } = self.inner;
91        let delivery = TridentDelivery::new(delivery_contract, &provider);
92        delivery
93            .tridentSpend(
94                self.attestation.bytes.into(),
95                self.attestation.signature.into(),
96            )
97            .send()
98            .await
99    }
100
101    /// Consumes the attestation and returns the raw pieces needed to execute
102    /// `tridentSpend`: the delivery-contract address, the prepared `SolCall`,
103    /// and the live provider.
104    ///
105    /// Use this when you want custom ordering against [`MulticallBuilder`] —
106    /// e.g. pre-spend calls, interleaving, or calling `aggregate3_calldata()`
107    /// yourself.
108    pub fn into_call(self) -> EvmCallContext {
109        let SpendAttestationChainContext::Evm {
110            provider,
111            delivery_contract,
112            signer_address,
113        } = self.inner;
114        EvmCallContext {
115            delivery_contract,
116            trident_spend_call: tridentSpendCall {
117                attestation: self.attestation.bytes.into(),
118                signature: self.attestation.signature.into(),
119            },
120            provider,
121            signer_address,
122        }
123    }
124
125    /// Returns a [`SpendMulticall`] whose first entry is this attestation's
126    /// `tridentSpend` call and whose second entry is `call` against
127    /// `contract` (zero value, run after the spend).
128    ///
129    /// The returned wrapper carries this attestation's provider so the caller
130    /// can just `.send().await` without rethreading one.
131    ///
132    /// Convenience for "spend + one follow-up" flows. For pre-spend calls or
133    /// any other ordering, use [`into_call`](Self::into_call) and compose a
134    /// [`MulticallBuilder`] yourself.
135    pub fn chain<C: SolCall>(self, contract: Address, call: &C) -> SpendMulticall {
136        let ctx = self.into_call();
137        let builder = MulticallBuilder::new()
138            .call(ctx.delivery_contract, &ctx.trident_spend_call)
139            .call(contract, call);
140        SpendMulticall {
141            builder,
142            provider: ctx.provider,
143            signer_address: ctx.signer_address,
144        }
145    }
146
147    /// Like [`chain`](Self::chain) but forwards `value` wei to `contract` on
148    /// the follow-up call.
149    pub fn chain_value<C: SolCall>(
150        self,
151        contract: Address,
152        call: &C,
153        value: U256,
154    ) -> SpendMulticall {
155        let ctx = self.into_call();
156        let builder = MulticallBuilder::new()
157            .call(ctx.delivery_contract, &ctx.trident_spend_call)
158            .call_with_value(contract, call, value);
159        SpendMulticall {
160            builder,
161            provider: ctx.provider,
162            signer_address: ctx.signer_address,
163        }
164    }
165}
166
167/// A [`MulticallBuilder`] seeded with a Trident spend, carrying the
168/// attestation's provider so `.send()` doesn't need one passed in.
169///
170/// Built by [`SpendAttestation::chain`] and
171/// [`SpendAttestation::chain_value`]. Forwards the common
172/// `MulticallBuilder` push methods so additional calls can be appended with
173/// normal builder ergonomics.
174pub struct SpendMulticall {
175    builder: MulticallBuilder,
176    provider: DynProvider<AnyNetwork>,
177    signer_address: Address,
178}
179
180impl SpendMulticall {
181    /// Appends a zero-value contract call to the batch.
182    #[must_use]
183    pub fn call<C: SolCall>(self, contract: Address, call: &C) -> Self {
184        Self {
185            builder: self.builder.call(contract, call),
186            ..self
187        }
188    }
189
190    /// Appends a value-bearing contract call to the batch.
191    #[must_use]
192    pub fn call_with_value<C: SolCall>(self, contract: Address, call: &C, value: U256) -> Self {
193        Self {
194            builder: self.builder.call_with_value(contract, call, value),
195            ..self
196        }
197    }
198
199    /// Appends a pure ETH transfer to the batch.
200    #[must_use]
201    pub fn send_value(self, to: Address, value: U256) -> Self {
202        Self {
203            builder: self.builder.send_value(to, value),
204            ..self
205        }
206    }
207
208    /// Submits the batch using the provider and 7702-delegated signer address
209    /// carried since construction.
210    pub async fn send(self) -> Result<PendingTransactionBuilder<AnyNetwork>, MulticallError> {
211        self.builder.send(self.provider, self.signer_address).await
212    }
213
214    /// Drops the wrapper and returns the underlying [`MulticallBuilder`], the
215    /// provider, and the 7702-delegated signer address — for callers that
216    /// need the raw pieces.
217    pub fn into_parts(self) -> (MulticallBuilder, DynProvider<AnyNetwork>, Address) {
218        (self.builder, self.provider, self.signer_address)
219    }
220}