trident_sdk/
spend_request.rs1use alloy_primitives::{Address, U256};
2
3use crate::Domain;
4use crate::conversions::ProtoConversionError;
5use crate::proto::v1;
6
7pub trait ChainAddress:
20 Clone
21 + Default
22 + Into<v1::MultiChainAddress>
23 + TryFrom<v1::MultiChainAddress, Error = ProtoConversionError>
24{
25}
26
27impl ChainAddress for Address {}
28
29pub struct SpendRequestBuilder<SrcAddressType, DstAddressType> {
35 pub(crate) src_domain: Domain,
36 pub(crate) src_token: SrcAddressType,
37 pub(crate) dst_domain: Domain,
38 pub(crate) dst_token: DstAddressType,
39 pub(crate) dst_recipient: DstAddressType,
40 pub(crate) amount: U256,
41 pub(crate) expiry_height: Option<u64>,
42 pub(crate) max_fee: Option<U256>,
43 pub(crate) caller: Option<DstAddressType>,
44}
45
46impl<SrcAddressType, DstAddressType> SpendRequestBuilder<SrcAddressType, DstAddressType> {
47 pub const fn src_domain(&self) -> Domain {
49 self.src_domain
50 }
51
52 pub const fn dst_domain(&self) -> Domain {
54 self.dst_domain
55 }
56
57 pub const fn max_fee(&self) -> Option<U256> {
59 self.max_fee
60 }
61
62 pub const fn expiry_height(&self) -> Option<u64> {
64 self.expiry_height
65 }
66}
67
68impl<SrcAddressType, DstAddressType> SpendRequestBuilder<SrcAddressType, DstAddressType>
69where
70 SrcAddressType: ChainAddress,
71 DstAddressType: ChainAddress,
72{
73 pub fn new(
80 src_domain: Domain,
81 src_token: SrcAddressType,
82 dst_domain: Domain,
83 dst_token: DstAddressType,
84 dst_recipient: DstAddressType,
85 amount: U256,
86 ) -> Self {
87 Self {
88 src_domain,
89 src_token,
90 dst_domain,
91 dst_token,
92 dst_recipient,
93 amount,
94 expiry_height: None,
95 max_fee: None,
96 caller: None,
97 }
98 }
99
100 pub fn with_max_fee(mut self, max_fee: U256) -> Self {
102 self.max_fee = Some(max_fee);
103 self
104 }
105
106 pub fn with_expiry_height(mut self, expiry_height: u64) -> Self {
114 self.expiry_height = Some(expiry_height);
115 self
116 }
117
118 pub fn with_caller(mut self, caller: DstAddressType) -> Self {
121 self.caller = Some(caller);
122 self
123 }
124
125 pub(crate) fn build_fee_intent(&self) -> v1::SpendIntent
127 where
128 SrcAddressType: Clone,
129 DstAddressType: Clone,
130 {
131 let dst_caller = self
132 .caller
133 .clone()
134 .map_or_else(|| DstAddressType::default().into(), Into::into);
135
136 v1::SpendIntent {
137 spec: Some(v1::SpendSpec {
138 src_spender: Some(SrcAddressType::default().into()),
139 src_domain: self.src_domain,
140 src_token: Some(self.src_token.clone().into()),
141 src_deposit_contract: Some(SrcAddressType::default().into()),
142 dst_domain: self.dst_domain,
143 dst_token: Some(self.dst_token.clone().into()),
144 dst_delivery_contract: Some(DstAddressType::default().into()),
145 dst_recipient: Some(self.dst_recipient.clone().into()),
146 dst_caller: Some(dst_caller),
147 amount: Some(self.amount.into()),
148 salt: Some([0u8; 32].into()),
149 }),
150 max_fee: Some(U256::ZERO.into()),
151 expiry_height: self.expiry_height.unwrap_or(0),
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use alloy_primitives::Address;
160
161 #[derive(Clone, Debug, PartialEq, Eq)]
162 struct SrcAddress(Address);
163
164 impl Default for SrcAddress {
165 fn default() -> Self {
166 Self(Address::from([0x11; 20]))
167 }
168 }
169
170 impl From<SrcAddress> for v1::MultiChainAddress {
171 fn from(value: SrcAddress) -> Self {
172 value.0.into()
173 }
174 }
175
176 impl TryFrom<v1::MultiChainAddress> for SrcAddress {
177 type Error = ProtoConversionError;
178
179 fn try_from(value: v1::MultiChainAddress) -> Result<Self, Self::Error> {
180 Address::try_from(value).map(Self)
181 }
182 }
183
184 impl ChainAddress for SrcAddress {}
185
186 #[derive(Clone, Debug, PartialEq, Eq)]
187 struct DstAddress(Address);
188
189 impl Default for DstAddress {
190 fn default() -> Self {
191 Self(Address::from([0x22; 20]))
192 }
193 }
194
195 impl From<DstAddress> for v1::MultiChainAddress {
196 fn from(value: DstAddress) -> Self {
197 value.0.into()
198 }
199 }
200
201 impl TryFrom<v1::MultiChainAddress> for DstAddress {
202 type Error = ProtoConversionError;
203
204 fn try_from(value: v1::MultiChainAddress) -> Result<Self, Self::Error> {
205 Address::try_from(value).map(Self)
206 }
207 }
208
209 impl ChainAddress for DstAddress {}
210
211 #[test]
212 fn chain_address_roundtrip_success() {
213 let address = Address::from([0x44; 20]);
214 let proto: v1::MultiChainAddress = address.into();
215 let decoded = Address::try_from(proto).unwrap();
216
217 assert_eq!(decoded, address);
218 }
219
220 #[test]
221 fn build_fee_intent_success_uses_source_default_for_src_spender() {
222 let request = SpendRequestBuilder::new(
223 1,
224 SrcAddress(Address::from([0x33; 20])),
225 2,
226 DstAddress(Address::from([0x44; 20])),
227 DstAddress(Address::from([0x55; 20])),
228 U256::from(100u64),
229 );
230
231 let intent = request.build_fee_intent();
232 let spec = intent.spec.expect("fee intent spec");
233 let src_spender = spec.src_spender.expect("src_spender");
234
235 assert_eq!(src_spender, SrcAddress::default().into());
236 }
237}