1use hopr_bindings::{
2 exports::alloy::{
3 consensus::Transaction,
4 eips::Decodable2718,
5 sol_types::{SolCall, SolType},
6 },
7 hopr_channels::HoprChannels::{
8 closeIncomingChannelCall, closeIncomingChannelSafeCall, finalizeOutgoingChannelClosureCall,
9 finalizeOutgoingChannelClosureSafeCall, fundChannelCall, fundChannelSafeCall,
10 initiateOutgoingChannelClosureCall, initiateOutgoingChannelClosureSafeCall, redeemTicketCall,
11 redeemTicketSafeCall,
12 },
13 hopr_node_management_module::HoprNodeManagementModule::execTransactionFromModuleCall,
14 hopr_node_safe_registry::HoprNodeSafeRegistry::registerSafeByNodeCall,
15 hopr_token::HoprToken::{sendCall, transferCall},
16};
17use hopr_crypto_types::prelude::OffchainPublicKey;
18use hopr_internal_types::prelude::{ChannelId, generate_channel_id};
19use hopr_primitive_types::prelude::*;
20use multiaddr::Multiaddr;
21
22use crate::{ContractAddresses, a2al, errors::ChainTypesError, payload::KeyBindAndAnnouncePayload};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ParsedHoprChainAction {
29 RegisterSafeAddress(Address),
31 Announce {
33 packet_key: OffchainPublicKey,
35 multiaddress: Option<Multiaddr>,
37 },
38 WithdrawNative(Address, XDaiBalance),
40 WithdrawToken(Address, HoprBalance),
42 FundChannel(Address, HoprBalance),
44 InitializeChannelClosure(ChannelId),
46 FinalizeChannelClosure(ChannelId),
48 IncomingChannelClosure(ChannelId),
50 RedeemTicket {
52 channel_id: ChannelId,
54 ticket_index: u64,
56 ticket_amount: HoprBalance,
58 },
59}
60
61impl ParsedHoprChainAction {
62 pub fn parse_from_eip2718(
65 signed_tx: &[u8],
66 module: &Address,
67 contract_addresses: &ContractAddresses,
68 ) -> Result<(Self, Address), ChainTypesError> {
69 let tx = hopr_bindings::exports::alloy::consensus::TxEnvelope::decode_2718_exact(signed_tx)
70 .map_err(|e| ChainTypesError::ParseError(e.into()))?
71 .into_signed();
72
73 let signer = Address::from(
74 tx.recover_signer()
75 .map_err(|e| ChainTypesError::ParseError(e.into()))?
76 .0
77 .0,
78 );
79
80 let tx_target = tx
81 .to()
82 .map(|to| Address::from(to.0.0))
83 .ok_or(ChainTypesError::ParseError(anyhow::anyhow!(
84 "transaction has no recipient"
85 )))?;
86
87 let (target_contract, input, module_call) = if &tx_target == module {
88 let module_call = execTransactionFromModuleCall::abi_decode(tx.input().as_ref())
89 .map_err(|e| ChainTypesError::ParseError(e.into()))?;
90 (module_call.to.0.0.into(), module_call.data, true)
91 } else if contract_addresses.into_iter().any(|addr| addr == a2al(tx_target)) {
92 (tx_target, tx.input().clone(), false)
93 } else if tx.value() > 0 {
94 return Ok((
95 Self::WithdrawNative(tx_target, XDaiBalance::from_be_bytes(tx.value().to_be_bytes::<32>())),
96 signer,
97 ));
98 } else {
99 return Err(ChainTypesError::ParseError(anyhow::anyhow!(
100 "failed to determine type of transaction"
101 )));
102 };
103
104 let target_contract = a2al(target_contract);
105
106 if target_contract == contract_addresses.node_safe_registry {
107 let register_call = registerSafeByNodeCall::abi_decode(input.as_ref())
108 .map_err(|e| ChainTypesError::ParseError(e.into()))?;
109
110 Ok((Self::RegisterSafeAddress(register_call.safeAddr.0.0.into()), signer))
111 } else if target_contract == contract_addresses.channels && module_call {
112 if let Ok(fund) = fundChannelSafeCall::abi_decode(input.as_ref()) {
113 return Ok((
114 Self::FundChannel(
115 fund.account.0.0.into(),
116 HoprBalance::from_be_bytes(fund.amount.to_be_bytes::<12>()),
117 ),
118 signer,
119 ));
120 }
121
122 if let Ok(initiate) = initiateOutgoingChannelClosureSafeCall::abi_decode(input.as_ref()) {
123 return Ok((
124 Self::InitializeChannelClosure(generate_channel_id(&signer, &initiate.destination.0.0.into())),
125 signer,
126 ));
127 }
128
129 if let Ok(finalize) = finalizeOutgoingChannelClosureSafeCall::abi_decode(input.as_ref()) {
130 return Ok((
131 Self::FinalizeChannelClosure(generate_channel_id(&signer, &finalize.destination.0.0.into())),
132 signer,
133 ));
134 }
135
136 if let Ok(close_incoming) = closeIncomingChannelSafeCall::abi_decode(input.as_ref()) {
137 return Ok((
138 Self::IncomingChannelClosure(generate_channel_id(&close_incoming.source.0.0.into(), &signer)),
139 signer,
140 ));
141 }
142
143 if let Ok(redeem) = redeemTicketSafeCall::abi_decode(input.as_ref()) {
144 let ticket_data = redeem.redeemable.data;
145 return Ok((
146 Self::RedeemTicket {
147 channel_id: ticket_data.channelId.0.into(),
148 ticket_index: U256::from_be_bytes(ticket_data.ticketIndex.to_be_bytes::<6>()).as_u64(),
149 ticket_amount: HoprBalance::from_be_bytes(ticket_data.amount.to_be_bytes::<12>()),
150 },
151 signer,
152 ));
153 }
154
155 Err(ChainTypesError::ParseError(anyhow::anyhow!(
156 "channel transaction has invalid type"
157 )))?
158 } else if target_contract == contract_addresses.channels && !module_call {
159 if let Ok(fund) = fundChannelCall::abi_decode(input.as_ref()) {
160 return Ok((
161 Self::FundChannel(
162 fund.account.0.0.into(),
163 HoprBalance::from_be_bytes(fund.amount.to_be_bytes::<12>()),
164 ),
165 signer,
166 ));
167 }
168
169 if let Ok(initiate) = initiateOutgoingChannelClosureCall::abi_decode(input.as_ref()) {
170 return Ok((
171 Self::InitializeChannelClosure(generate_channel_id(&signer, &initiate.destination.0.0.into())),
172 signer,
173 ));
174 }
175
176 if let Ok(finalize) = finalizeOutgoingChannelClosureCall::abi_decode(input.as_ref()) {
177 return Ok((
178 Self::FinalizeChannelClosure(generate_channel_id(&signer, &finalize.destination.0.0.into())),
179 signer,
180 ));
181 }
182
183 if let Ok(close_incoming) = closeIncomingChannelCall::abi_decode(input.as_ref()) {
184 return Ok((
185 Self::IncomingChannelClosure(generate_channel_id(&close_incoming.source.0.0.into(), &signer)),
186 signer,
187 ));
188 }
189
190 if let Ok(redeem) = redeemTicketCall::abi_decode(input.as_ref()) {
191 let ticket_data = redeem.redeemable.data;
192 return Ok((
193 Self::RedeemTicket {
194 channel_id: ticket_data.channelId.0.into(),
195 ticket_index: U256::from_be_bytes(ticket_data.ticketIndex.to_be_bytes::<6>()).as_u64(),
196 ticket_amount: HoprBalance::from_be_bytes(ticket_data.amount.to_be_bytes::<12>()),
197 },
198 signer,
199 ));
200 }
201 Err(ChainTypesError::ParseError(anyhow::anyhow!(
202 "channel transaction has invalid type"
203 )))?
204 } else if target_contract == contract_addresses.token {
205 if let Ok(send) = sendCall::abi_decode(input.as_ref()) {
206 if send.recipient == contract_addresses.announcements {
207 let mut data = vec![0u8; 32 + send.data.len()];
208 data[31] = 32;
209 data[32..].copy_from_slice(&send.data);
210
211 let kb = KeyBindAndAnnouncePayload::abi_decode(&data)
212 .map_err(|e| ChainTypesError::ParseError(e.into()))?;
213
214 return Ok((
215 Self::Announce {
216 packet_key: kb.ed25519_pub_key.0.try_into().map_err(|_| {
217 ChainTypesError::ParseError(anyhow::anyhow!("failed to parse packet key"))
218 })?,
219 multiaddress: if kb.multiaddress.is_empty() {
220 None
221 } else {
222 Some(kb.multiaddress.parse().map_err(|_| {
223 ChainTypesError::ParseError(anyhow::anyhow!("failed to parse multiaddress"))
224 })?)
225 },
226 },
227 signer,
228 ));
229 } else {
230 Err(ChainTypesError::ParseError(anyhow::anyhow!(
231 "token send transaction transaction has invalid type"
232 )))?
233 }
234 }
235
236 let transfer =
237 transferCall::abi_decode(input.as_ref()).map_err(|e| ChainTypesError::ParseError(e.into()))?;
238
239 Ok((
240 Self::WithdrawToken(
241 transfer.recipient.0.0.into(),
242 HoprBalance::from_be_bytes(transfer.amount.to_be_bytes::<32>()),
243 ),
244 signer,
245 ))
246 } else {
247 Err(ChainTypesError::ParseError(anyhow::anyhow!(
248 "transaction has invalid contract address"
249 )))?
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use hex_literal::hex;
257 use hopr_crypto_types::{
258 crypto_traits::Randomizable,
259 prelude::{ChainKeypair, HalfKey, Hash, Keypair, OffchainKeypair, Response},
260 };
261 use hopr_internal_types::prelude::{AnnouncementData, KeyBinding, TicketBuilder};
262
263 use super::*;
264 use crate::payload::{
265 BasicPayloadGenerator, PayloadGenerator, SafePayloadGenerator, SignableTransaction, tests::CONTRACT_ADDRS,
266 };
267
268 const PRIVATE_KEY_1: [u8; 32] = hex!("c14b8faa0a9b8a5fa4453664996f23a7e7de606d42297d723fc4a794f375e260");
269 const PRIVATE_KEY_2: [u8; 32] = hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775");
270
271 #[tokio::test]
272 async fn announce_safe_action_should_decode() -> anyhow::Result<()> {
273 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
274 let ocp = OffchainKeypair::random();
275
276 let ad = AnnouncementData::new(
277 KeyBinding::new(cp.public().to_address(), &ocp),
278 Some("/ip4/127.0.0.1/tcp/10000".parse()?),
279 )?;
280
281 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
282 let signed_tx = safe_gen
283 .announce(ad, 10_u32.into())?
284 .sign_and_encode_to_eip2718(1, 1, None, &cp)
285 .await?;
286
287 let (action, signer) =
288 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
289 assert_eq!(
290 action,
291 ParsedHoprChainAction::Announce {
292 packet_key: *ocp.public(),
293 multiaddress: Some("/ip4/127.0.0.1/tcp/10000".parse()?),
294 }
295 );
296 assert_eq!(signer, cp.public().to_address());
297
298 Ok(())
299 }
300
301 #[tokio::test]
302 async fn fund_channel_action_should_decode() -> anyhow::Result<()> {
303 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
304
305 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
306 let signed_tx = basic_gen
307 .fund_channel([2u8; Address::SIZE].into(), 123_u32.into())?
308 .sign_and_encode_to_eip2718(1, 1, None, &cp)
309 .await?;
310
311 let (action, signer) =
312 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
313 assert_eq!(
314 action,
315 ParsedHoprChainAction::FundChannel([2u8; Address::SIZE].into(), 123_u32.into())
316 );
317 assert_eq!(signer, cp.public().to_address());
318
319 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
320 let signed_tx = safe_gen
321 .fund_channel([2u8; Address::SIZE].into(), 123_u32.into())?
322 .sign_and_encode_to_eip2718(1, 1, None, &cp)
323 .await?;
324
325 let (action, signer) =
326 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
327 assert_eq!(
328 action,
329 ParsedHoprChainAction::FundChannel([2u8; Address::SIZE].into(), 123_u32.into())
330 );
331 assert_eq!(signer, cp.public().to_address());
332
333 Ok(())
334 }
335
336 #[tokio::test]
337 async fn initiate_channel_closure_action_should_decode() -> anyhow::Result<()> {
338 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
339
340 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
341 let signed_tx = basic_gen
342 .initiate_outgoing_channel_closure([2u8; Address::SIZE].into())?
343 .sign_and_encode_to_eip2718(1, 1, None, &cp)
344 .await?;
345
346 let channel_id = generate_channel_id(&cp.public().to_address(), &[2u8; Address::SIZE].into());
347
348 let (action, signer) =
349 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
350 assert_eq!(action, ParsedHoprChainAction::InitializeChannelClosure(channel_id));
351 assert_eq!(signer, cp.public().to_address());
352
353 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
354 let signed_tx = safe_gen
355 .initiate_outgoing_channel_closure([2u8; Address::SIZE].into())?
356 .sign_and_encode_to_eip2718(1, 1, None, &cp)
357 .await?;
358
359 let (action, signer) =
360 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
361 assert_eq!(action, ParsedHoprChainAction::InitializeChannelClosure(channel_id));
362 assert_eq!(signer, cp.public().to_address());
363
364 Ok(())
365 }
366
367 #[tokio::test]
368 async fn finalize_channel_closure_action_should_decode() -> anyhow::Result<()> {
369 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
370
371 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
372 let signed_tx = basic_gen
373 .finalize_outgoing_channel_closure([2u8; Address::SIZE].into())?
374 .sign_and_encode_to_eip2718(1, 1, None, &cp)
375 .await?;
376
377 let channel_id = generate_channel_id(&cp.public().to_address(), &[2u8; Address::SIZE].into());
378
379 let (action, signer) =
380 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
381 assert_eq!(action, ParsedHoprChainAction::FinalizeChannelClosure(channel_id));
382 assert_eq!(signer, cp.public().to_address());
383
384 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
385 let signed_tx = safe_gen
386 .finalize_outgoing_channel_closure([2u8; Address::SIZE].into())?
387 .sign_and_encode_to_eip2718(1, 1, None, &cp)
388 .await?;
389
390 let (action, signer) =
391 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
392 assert_eq!(action, ParsedHoprChainAction::FinalizeChannelClosure(channel_id));
393 assert_eq!(signer, cp.public().to_address());
394
395 Ok(())
396 }
397
398 #[tokio::test]
399 async fn incoming_channel_closure_action_should_decode() -> anyhow::Result<()> {
400 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
401
402 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
403 let signed_tx = basic_gen
404 .close_incoming_channel([2u8; Address::SIZE].into())?
405 .sign_and_encode_to_eip2718(1, 1, None, &cp)
406 .await?;
407
408 let channel_id = generate_channel_id(&[2u8; Address::SIZE].into(), &cp.public().to_address());
409
410 let (action, signer) =
411 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
412 assert_eq!(action, ParsedHoprChainAction::IncomingChannelClosure(channel_id));
413 assert_eq!(signer, cp.public().to_address());
414
415 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
416 let signed_tx = safe_gen
417 .close_incoming_channel([2u8; Address::SIZE].into())?
418 .sign_and_encode_to_eip2718(1, 1, None, &cp)
419 .await?;
420
421 let (action, signer) =
422 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
423 assert_eq!(action, ParsedHoprChainAction::IncomingChannelClosure(channel_id));
424 assert_eq!(signer, cp.public().to_address());
425
426 Ok(())
427 }
428
429 #[tokio::test]
430 async fn register_safe_action_should_decode() -> anyhow::Result<()> {
431 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
432
433 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
434 let signed_tx = basic_gen
435 .register_safe_by_node([2u8; Address::SIZE].into())?
436 .sign_and_encode_to_eip2718(1, 1, None, &cp)
437 .await?;
438
439 let (action, signer) =
440 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
441 assert_eq!(
442 action,
443 ParsedHoprChainAction::RegisterSafeAddress([2u8; Address::SIZE].into())
444 );
445 assert_eq!(signer, cp.public().to_address());
446
447 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
448 let signed_tx = safe_gen
449 .register_safe_by_node([2u8; Address::SIZE].into())?
450 .sign_and_encode_to_eip2718(1, 1, None, &cp)
451 .await?;
452
453 let (action, signer) =
454 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
455 assert_eq!(
456 action,
457 ParsedHoprChainAction::RegisterSafeAddress([2u8; Address::SIZE].into())
458 );
459 assert_eq!(signer, cp.public().to_address());
460
461 Ok(())
462 }
463
464 #[tokio::test]
465 async fn redeem_ticket_safe_action_should_decode() -> anyhow::Result<()> {
466 let cp_1 = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
467 let cp_2 = ChainKeypair::from_secret(&PRIVATE_KEY_2)?;
468 let hk1 = HalfKey::random();
469 let hk2 = HalfKey::random();
470 let resp = Response::from_half_keys(&hk1, &hk2)?;
471
472 let ticket = TicketBuilder::default()
473 .counterparty(&cp_2)
474 .amount(123_u32)
475 .index(7)
476 .challenge(resp.to_challenge()?)
477 .build_signed(&cp_1, &Hash::default())?
478 .into_acknowledged(resp)
479 .into_redeemable(&cp_2, &Hash::default())?;
480
481 let basic_gen = BasicPayloadGenerator::new(cp_2.public().to_address(), *CONTRACT_ADDRS);
482 let signed_tx = basic_gen
483 .redeem_ticket(ticket.clone())?
484 .sign_and_encode_to_eip2718(1, 1, None, &cp_2)
485 .await?;
486
487 let (action, signer) =
488 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
489 assert_eq!(
490 action,
491 ParsedHoprChainAction::RedeemTicket {
492 channel_id: generate_channel_id(&cp_1.public().to_address(), &cp_2.public().to_address()),
493 ticket_index: 7,
494 ticket_amount: 123_u32.into()
495 }
496 );
497 assert_eq!(signer, cp_2.public().to_address());
498
499 let safe_gen = SafePayloadGenerator::new(&cp_2, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
500 let signed_tx = safe_gen
501 .redeem_ticket(ticket.clone())?
502 .sign_and_encode_to_eip2718(1, 1, None, &cp_2)
503 .await?;
504
505 let (action, signer) =
506 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
507 assert_eq!(
508 action,
509 ParsedHoprChainAction::RedeemTicket {
510 channel_id: generate_channel_id(&cp_1.public().to_address(), &cp_2.public().to_address()),
511 ticket_index: 7,
512 ticket_amount: 123_u32.into()
513 }
514 );
515 assert_eq!(signer, cp_2.public().to_address());
516
517 Ok(())
518 }
519
520 #[tokio::test]
521 async fn withdraw_native_action_should_decode() -> anyhow::Result<()> {
522 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
523
524 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
525 let signed_tx = basic_gen
526 .transfer::<XDai>([2u8; Address::SIZE].into(), 123_u32.into())?
527 .sign_and_encode_to_eip2718(1, 1, None, &cp)
528 .await?;
529
530 let (action, signer) =
531 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
532 assert_eq!(
533 action,
534 ParsedHoprChainAction::WithdrawNative([2u8; Address::SIZE].into(), 123_u32.into())
535 );
536 assert_eq!(signer, cp.public().to_address());
537
538 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
539 let signed_tx = safe_gen
540 .transfer::<XDai>([2u8; Address::SIZE].into(), 123_u32.into())?
541 .sign_and_encode_to_eip2718(1, 1, None, &cp)
542 .await?;
543
544 let (action, signer) =
545 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
546 assert_eq!(
547 action,
548 ParsedHoprChainAction::WithdrawNative([2u8; Address::SIZE].into(), 123_u32.into())
549 );
550 assert_eq!(signer, cp.public().to_address());
551
552 Ok(())
553 }
554
555 #[tokio::test]
556 async fn withdraw_token_safe_action_should_decode() -> anyhow::Result<()> {
557 let cp = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
558
559 let basic_gen = BasicPayloadGenerator::new(cp.public().to_address(), *CONTRACT_ADDRS);
560 let signed_tx = basic_gen
561 .transfer::<WxHOPR>([2u8; Address::SIZE].into(), 123_u32.into())?
562 .sign_and_encode_to_eip2718(1, 1, None, &cp)
563 .await?;
564
565 let (action, signer) =
566 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
567 assert_eq!(
568 action,
569 ParsedHoprChainAction::WithdrawToken([2u8; Address::SIZE].into(), 123_u32.into())
570 );
571 assert_eq!(signer, cp.public().to_address());
572
573 let safe_gen = SafePayloadGenerator::new(&cp, *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
574 let signed_tx = safe_gen
575 .transfer::<WxHOPR>([2u8; Address::SIZE].into(), 123_u32.into())?
576 .sign_and_encode_to_eip2718(1, 1, None, &cp)
577 .await?;
578
579 let (action, signer) =
580 ParsedHoprChainAction::parse_from_eip2718(&signed_tx, &[1u8; Address::SIZE].into(), &CONTRACT_ADDRS)?;
581 assert_eq!(
582 action,
583 ParsedHoprChainAction::WithdrawToken([2u8; Address::SIZE].into(), 123_u32.into())
584 );
585 assert_eq!(signer, cp.public().to_address());
586
587 Ok(())
588 }
589}