1use crate::error::Error;
2use crate::lifecycle::adapters::{CorrelationOutcome, EventPayload, ProtocolAdapter};
3use crate::protocols::{AccountInfo, EventType, Protocol, ProtocolHelpers};
4use crate::types::{RawEvent, RawInstruction, ResolveContext};
5use strum::VariantNames;
6
7#[derive(serde::Deserialize, strum_macros::VariantNames)]
9pub enum LimitV2EventEnvelope {
10 CreateOrderEvent(OrderKeyHolder),
11 CancelOrderEvent(OrderKeyHolder),
12 TradeEvent(TradeEventFields),
13}
14
15#[derive(serde::Deserialize)]
17pub enum LimitV2InstructionKind {
18 InitializeOrder(serde_json::Value),
19 PreFlashFillOrder(serde_json::Value),
20 FlashFillOrder(serde_json::Value),
21 CancelOrder(serde_json::Value),
22 UpdateFee(serde_json::Value),
23 WithdrawFee(serde_json::Value),
24}
25
26pub const INSTRUCTION_EVENT_TYPES: &[(&str, EventType)] = &[
27 ("InitializeOrder", EventType::Created),
28 ("PreFlashFillOrder", EventType::FillInitiated),
29 ("FlashFillOrder", EventType::FillCompleted),
30 ("CancelOrder", EventType::Cancelled),
31];
32
33pub const EVENT_EVENT_TYPES: &[(&str, EventType)] = &[
34 ("CreateOrderEvent", EventType::Created),
35 ("CancelOrderEvent", EventType::Cancelled),
36 ("TradeEvent", EventType::FillCompleted),
37];
38
39pub const CLOSED_VARIANTS: &[&str] = &[];
40
41#[derive(Debug)]
43pub struct LimitV2Adapter;
44
45#[derive(serde::Deserialize)]
47pub struct OrderKeyHolder {
48 order_key: String,
49}
50
51#[derive(serde::Deserialize)]
53pub struct TradeEventFields {
54 order_key: String,
55 #[serde(default = "LimitV2Adapter::default_unknown")]
56 taker: String,
57 making_amount: u64,
58 taking_amount: u64,
59 remaining_making_amount: u64,
60 #[expect(dead_code, reason = "consumed by serde for completeness")]
61 remaining_taking_amount: u64,
62}
63
64pub struct LimitV2TradeEvent {
66 pub order_pda: String,
67 pub taker: String,
68 pub in_amount: i64,
69 pub out_amount: i64,
70 pub remaining_in_amount: i64,
71 pub remaining_out_amount: i64,
72}
73
74pub struct LimitV2CreateArgs {
78 pub unique_id: Option<i64>,
79 pub making_amount: i64,
80 pub taking_amount: i64,
81 pub expired_at: Option<i64>,
82 pub fee_bps: Option<i16>,
83}
84
85pub struct LimitV2CreateMints {
87 pub input_mint: String,
88 pub output_mint: String,
89}
90
91#[derive(serde::Deserialize)]
92struct InitializeOrderParamsFields {
93 #[serde(default)]
94 unique_id: Option<u64>,
95 making_amount: u64,
96 taking_amount: u64,
97 expired_at: Option<i64>,
98 #[serde(default)]
99 fee_bps: Option<u16>,
100}
101
102#[derive(serde::Deserialize)]
103struct InitializeOrderWrapper {
104 params: InitializeOrderParamsFields,
105}
106
107impl ProtocolAdapter for LimitV2Adapter {
108 fn protocol(&self) -> Protocol {
109 Protocol::LimitV2
110 }
111
112 fn classify_instruction(&self, ix: &RawInstruction) -> Option<EventType> {
113 ProtocolHelpers::lookup_event_type(&ix.instruction_name, INSTRUCTION_EVENT_TYPES)
114 }
115
116 fn classify_and_resolve_event(
117 &self,
118 ev: &RawEvent,
119 _ctx: &ResolveContext,
120 ) -> Option<Result<(EventType, CorrelationOutcome, EventPayload), Error>> {
121 let fields = ev.fields.as_ref()?;
122 let envelope: LimitV2EventEnvelope = match serde_json::from_value(fields.clone()) {
123 Ok(e) => e,
124 Err(err) => {
125 if !ProtocolHelpers::contains_known_variant(fields, LimitV2EventEnvelope::VARIANTS)
126 {
127 return None;
128 }
129 return Some(Err(Error::Protocol {
130 reason: format!("failed to parse Limit v2 event payload: {err}"),
131 }));
132 }
133 };
134
135 Some(Self::resolve_event(envelope))
136 }
137}
138
139impl LimitV2Adapter {
140 fn default_unknown() -> String {
141 "unknown".to_string()
142 }
143
144 fn resolve_event(
145 envelope: LimitV2EventEnvelope,
146 ) -> Result<(EventType, CorrelationOutcome, EventPayload), Error> {
147 match envelope {
148 LimitV2EventEnvelope::CreateOrderEvent(OrderKeyHolder { order_key }) => Ok((
149 EventType::Created,
150 CorrelationOutcome::Correlated(vec![order_key]),
151 EventPayload::None,
152 )),
153 LimitV2EventEnvelope::CancelOrderEvent(OrderKeyHolder { order_key }) => Ok((
154 EventType::Cancelled,
155 CorrelationOutcome::Correlated(vec![order_key]),
156 EventPayload::None,
157 )),
158 LimitV2EventEnvelope::TradeEvent(TradeEventFields {
159 order_key,
160 taker,
161 making_amount,
162 taking_amount,
163 remaining_making_amount,
164 ..
165 }) => Ok((
166 EventType::FillCompleted,
167 CorrelationOutcome::Correlated(vec![order_key]),
168 EventPayload::LimitFill {
169 in_amount: ProtocolHelpers::checked_u64_to_i64(making_amount, "making_amount")?,
170 out_amount: ProtocolHelpers::checked_u64_to_i64(
171 taking_amount,
172 "taking_amount",
173 )?,
174 remaining_in_amount: ProtocolHelpers::checked_u64_to_i64(
175 remaining_making_amount,
176 "remaining_making_amount",
177 )?,
178 counterparty: taker,
179 },
180 )),
181 }
182 }
183
184 pub fn extract_order_pda(
188 accounts: &[AccountInfo],
189 instruction_name: &str,
190 ) -> Result<String, Error> {
191 if let Some(acc) = ProtocolHelpers::find_account_by_name(accounts, "order") {
192 return Ok(acc.pubkey.clone());
193 }
194
195 let wrapper = serde_json::json!({ instruction_name: serde_json::Value::Null });
196 let kind: LimitV2InstructionKind =
197 serde_json::from_value(wrapper).map_err(|_| Error::Protocol {
198 reason: format!("unknown Limit v2 instruction: {instruction_name}"),
199 })?;
200
201 let idx = match kind {
202 LimitV2InstructionKind::InitializeOrder(_) => 2,
203 LimitV2InstructionKind::FlashFillOrder(_) | LimitV2InstructionKind::CancelOrder(_) => 2,
204 LimitV2InstructionKind::PreFlashFillOrder(_) => 1,
205 LimitV2InstructionKind::UpdateFee(_) | LimitV2InstructionKind::WithdrawFee(_) => {
206 return Err(Error::Protocol {
207 reason: format!("Limit v2 instruction {instruction_name} has no order PDA"),
208 });
209 }
210 };
211
212 accounts
213 .get(idx)
214 .map(|a| a.pubkey.clone())
215 .ok_or_else(|| Error::Protocol {
216 reason: format!(
217 "Limit v2 account index {idx} out of bounds for {instruction_name}"
218 ),
219 })
220 }
221
222 pub fn extract_create_mints(accounts: &[AccountInfo]) -> Result<LimitV2CreateMints, Error> {
226 let by_name_input =
227 ProtocolHelpers::find_account_by_name(accounts, "input_mint").map(|a| a.pubkey.clone());
228 let by_name_output = ProtocolHelpers::find_account_by_name(accounts, "output_mint")
229 .map(|a| a.pubkey.clone());
230
231 if let (Some(input_mint), Some(output_mint)) = (by_name_input, by_name_output) {
232 return Ok(LimitV2CreateMints {
233 input_mint,
234 output_mint,
235 });
236 }
237
238 let input_mint =
239 accounts
240 .get(7)
241 .map(|a| a.pubkey.clone())
242 .ok_or_else(|| Error::Protocol {
243 reason: "Limit v2 input_mint index 7 out of bounds".into(),
244 })?;
245 let output_mint =
246 accounts
247 .get(8)
248 .map(|a| a.pubkey.clone())
249 .ok_or_else(|| Error::Protocol {
250 reason: "Limit v2 output_mint index 8 out of bounds".into(),
251 })?;
252
253 Ok(LimitV2CreateMints {
254 input_mint,
255 output_mint,
256 })
257 }
258
259 pub fn parse_create_args(args: &serde_json::Value) -> Result<LimitV2CreateArgs, Error> {
263 let params =
264 if let Ok(wrapper) = serde_json::from_value::<InitializeOrderWrapper>(args.clone()) {
265 wrapper.params
266 } else {
267 serde_json::from_value::<InitializeOrderParamsFields>(args.clone()).map_err(
268 |e| Error::Protocol {
269 reason: format!("failed to parse Limit v2 create args: {e}"),
270 },
271 )?
272 };
273
274 let InitializeOrderParamsFields {
275 unique_id,
276 making_amount,
277 taking_amount,
278 expired_at,
279 fee_bps,
280 } = params;
281
282 Ok(LimitV2CreateArgs {
283 unique_id: unique_id.and_then(ProtocolHelpers::optional_u64_to_i64),
284 making_amount: ProtocolHelpers::checked_u64_to_i64(making_amount, "making_amount")?,
285 taking_amount: ProtocolHelpers::checked_u64_to_i64(taking_amount, "taking_amount")?,
286 expired_at,
287 fee_bps: fee_bps
288 .map(|v| ProtocolHelpers::checked_u16_to_i16(v, "fee_bps"))
289 .transpose()?,
290 })
291 }
292
293 #[cfg(all(test, feature = "native"))]
294 pub fn classify_decoded(
295 decoded: &carbon_jupiter_limit_order_2_decoder::instructions::JupiterLimitOrder2Instruction,
296 ) -> Option<EventType> {
297 use carbon_jupiter_limit_order_2_decoder::instructions::JupiterLimitOrder2Instruction;
298 match decoded {
299 JupiterLimitOrder2Instruction::InitializeOrder(_) => Some(EventType::Created),
300 JupiterLimitOrder2Instruction::PreFlashFillOrder(_) => Some(EventType::FillInitiated),
301 JupiterLimitOrder2Instruction::FlashFillOrder(_) => Some(EventType::FillCompleted),
302 JupiterLimitOrder2Instruction::CancelOrder(_) => Some(EventType::Cancelled),
303 JupiterLimitOrder2Instruction::CreateOrderEvent(_) => Some(EventType::Created),
304 JupiterLimitOrder2Instruction::CancelOrderEvent(_) => Some(EventType::Cancelled),
305 JupiterLimitOrder2Instruction::TradeEvent(_) => Some(EventType::FillCompleted),
306 JupiterLimitOrder2Instruction::UpdateFee(_)
307 | JupiterLimitOrder2Instruction::WithdrawFee(_) => None,
308 }
309 }
310}
311
312#[cfg(test)]
313#[expect(
314 clippy::unwrap_used,
315 clippy::expect_used,
316 clippy::panic,
317 reason = "test assertions"
318)]
319mod tests {
320 use super::*;
321
322 fn account(pubkey: &str, name: Option<&str>) -> AccountInfo {
323 AccountInfo {
324 pubkey: pubkey.to_string(),
325 is_signer: false,
326 is_writable: false,
327 name: name.map(str::to_string),
328 }
329 }
330
331 fn make_event(fields: serde_json::Value) -> RawEvent {
332 RawEvent {
333 id: 1,
334 signature: "sig".to_string(),
335 event_index: 0,
336 event_path: None,
337 program_id: "p".to_string(),
338 inner_program_id: "p".to_string(),
339 event_name: "test".to_string(),
340 fields: Some(fields),
341 slot: 1,
342 }
343 }
344
345 fn resolve(
346 fields: serde_json::Value,
347 ) -> Option<Result<(EventType, CorrelationOutcome, EventPayload), crate::error::Error>> {
348 let ev = make_event(fields);
349 let ctx = ResolveContext {
350 pre_fetched_order_pdas: None,
351 };
352 LimitV2Adapter.classify_and_resolve_event(&ev, &ctx)
353 }
354
355 #[test]
356 fn classify_known_instructions_via_envelope() {
357 let cases = [
358 ("InitializeOrder", Some(EventType::Created)),
359 ("PreFlashFillOrder", Some(EventType::FillInitiated)),
360 ("FlashFillOrder", Some(EventType::FillCompleted)),
361 ("CancelOrder", Some(EventType::Cancelled)),
362 ("UpdateFee", None),
363 ("WithdrawFee", None),
364 ("Unknown", None),
365 ];
366 for (name, expected) in cases {
367 let ix = RawInstruction {
368 id: 1,
369 signature: "sig".to_string(),
370 instruction_index: 0,
371 instruction_path: None,
372 program_id: "p".to_string(),
373 inner_program_id: "p".to_string(),
374 instruction_name: name.to_string(),
375 accounts: None,
376 args: None,
377 slot: 1,
378 };
379 assert_eq!(
380 LimitV2Adapter.classify_instruction(&ix),
381 expected,
382 "mismatch for {name}"
383 );
384 }
385 }
386
387 #[test]
388 fn resolve_trade_event_from_envelope() {
389 let fields = serde_json::json!({
390 "TradeEvent": {
391 "order_key": "HkLZgYy93cEi3Fn96SvdWeJk8DNeHeU5wiNV5SeRLiJC",
392 "taker": "j1oeQoPeuEDmjvyMwBmCWexzCQup77kbKKxV59CnYbd",
393 "making_amount": 724_773_829_u64,
394 "taking_amount": 51_821_329_u64,
395 "remaining_making_amount": 89_147_181_051_u64,
396 "remaining_taking_amount": 6_374_023_074_u64
397 }
398 });
399 let (event_type, correlation, payload) = resolve(fields).unwrap().unwrap();
400 assert_eq!(event_type, EventType::FillCompleted);
401 let CorrelationOutcome::Correlated(pdas) = correlation else {
402 panic!("expected Correlated");
403 };
404 assert_eq!(pdas, vec!["HkLZgYy93cEi3Fn96SvdWeJk8DNeHeU5wiNV5SeRLiJC"]);
405 let EventPayload::LimitFill {
406 in_amount,
407 out_amount,
408 remaining_in_amount,
409 counterparty,
410 } = payload
411 else {
412 panic!("expected LimitFill");
413 };
414 assert_eq!(in_amount, 724_773_829);
415 assert_eq!(out_amount, 51_821_329);
416 assert_eq!(remaining_in_amount, 89_147_181_051);
417 assert_eq!(counterparty, "j1oeQoPeuEDmjvyMwBmCWexzCQup77kbKKxV59CnYbd");
418 }
419
420 #[test]
421 fn malformed_known_event_returns_error() {
422 let fields = serde_json::json!({
423 "TradeEvent": {
424 "order_key": "order",
425 "making_amount": "bad",
426 "taking_amount": 1_u64,
427 "remaining_making_amount": 0_u64,
428 "remaining_taking_amount": 0_u64
429 }
430 });
431 let result = resolve(fields).unwrap();
432 assert!(result.is_err());
433 }
434
435 #[test]
436 fn resolve_trade_event_rejects_amount_overflow() {
437 let fields = serde_json::json!({
438 "TradeEvent": {
439 "order_key": "order",
440 "making_amount": (i64::MAX as u64) + 1,
441 "taking_amount": 1_u64,
442 "remaining_making_amount": 0_u64,
443 "remaining_taking_amount": 0_u64
444 }
445 });
446 let result = resolve(fields).unwrap();
447 assert!(result.is_err());
448 }
449
450 #[test]
451 fn resolve_trade_event_defaults_missing_taker() {
452 let fields = serde_json::json!({
453 "TradeEvent": {
454 "order_key": "order",
455 "making_amount": 10_u64,
456 "taking_amount": 5_u64,
457 "remaining_making_amount": 1_u64,
458 "remaining_taking_amount": 0_u64
459 }
460 });
461 let (_, _, payload) = resolve(fields).unwrap().unwrap();
462 let EventPayload::LimitFill { counterparty, .. } = payload else {
463 panic!("expected LimitFill");
464 };
465 assert_eq!(counterparty, "unknown");
466 }
467
468 #[test]
469 fn unknown_event_returns_none() {
470 let fields = serde_json::json!({"UnknownEvent": {"some_field": 1}});
471 assert!(resolve(fields).is_none());
472 }
473
474 #[test]
475 fn parse_create_args_with_params_wrapper() {
476 let args = serde_json::json!({
477 "params": {
478 "making_amount": 1000_u64,
479 "taking_amount": 500_u64,
480 "unique_id": 42_u64,
481 "expired_at": 1_700_000_000_i64,
482 "fee_bps": 25_u16
483 }
484 });
485 let parsed = LimitV2Adapter::parse_create_args(&args).unwrap();
486 assert_eq!(parsed.making_amount, 1000);
487 assert_eq!(parsed.taking_amount, 500);
488 assert_eq!(parsed.unique_id, Some(42));
489 assert_eq!(parsed.expired_at, Some(1_700_000_000));
490 assert_eq!(parsed.fee_bps, Some(25));
491 }
492
493 #[test]
494 fn parse_create_args_without_params_wrapper() {
495 let args = serde_json::json!({
496 "making_amount": 2000_u64,
497 "taking_amount": 1000_u64
498 });
499 let parsed = LimitV2Adapter::parse_create_args(&args).unwrap();
500 assert_eq!(parsed.making_amount, 2000);
501 assert_eq!(parsed.taking_amount, 1000);
502 assert_eq!(parsed.unique_id, None);
503 assert_eq!(parsed.expired_at, None);
504 assert_eq!(parsed.fee_bps, None);
505 }
506
507 #[test]
508 fn parse_create_args_rejects_overflow_values() {
509 let args = serde_json::json!({
510 "making_amount": (i64::MAX as u64) + 1,
511 "taking_amount": 1_u64,
512 "unique_id": (i64::MAX as u64) + 1
513 });
514 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
515 }
516
517 #[test]
518 fn parse_create_args_rejects_fee_bps_out_of_range() {
519 let args = serde_json::json!({
520 "making_amount": 1_u64,
521 "taking_amount": 1_u64,
522 "fee_bps": 65_535_u16
523 });
524 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
525 }
526
527 #[test]
528 fn parse_create_args_rejects_malformed_payload() {
529 let args = serde_json::json!({
530 "making_amount": "bad",
531 "taking_amount": 1_u64
532 });
533 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
534 }
535
536 #[test]
537 fn extract_order_pda_prefers_named_account() {
538 let accounts = vec![
539 account("idx1", None),
540 account("idx2", None),
541 account("named_order", Some("order")),
542 ];
543 let extracted = LimitV2Adapter::extract_order_pda(&accounts, "CancelOrder").unwrap();
544 assert_eq!(extracted, "named_order");
545 }
546
547 #[test]
548 fn extract_order_pda_uses_fallback_indexes() {
549 let init_accounts = vec![
550 account("0", None),
551 account("1", None),
552 account("init_idx2", None),
553 ];
554 assert_eq!(
555 LimitV2Adapter::extract_order_pda(&init_accounts, "InitializeOrder").unwrap(),
556 "init_idx2"
557 );
558
559 let pre_flash_accounts = vec![account("0", None), account("pre_flash_idx1", None)];
560 assert_eq!(
561 LimitV2Adapter::extract_order_pda(&pre_flash_accounts, "PreFlashFillOrder").unwrap(),
562 "pre_flash_idx1"
563 );
564 }
565
566 #[test]
567 fn extract_order_pda_rejects_unknown_instruction() {
568 let err = LimitV2Adapter::extract_order_pda(&[], "Unknown").unwrap_err();
569 let Error::Protocol { reason } = err else {
570 panic!("expected protocol error");
571 };
572 assert_eq!(reason, "unknown Limit v2 instruction: Unknown");
573 }
574
575 #[test]
576 fn extract_order_pda_rejects_out_of_bounds_index() {
577 let err = LimitV2Adapter::extract_order_pda(&[], "CancelOrder").unwrap_err();
578 let Error::Protocol { reason } = err else {
579 panic!("expected protocol error");
580 };
581 assert_eq!(
582 reason,
583 "Limit v2 account index 2 out of bounds for CancelOrder"
584 );
585 }
586
587 #[test]
588 fn extract_create_mints_prefers_named_accounts() {
589 let accounts = vec![
590 account("idx7", None),
591 account("idx8", None),
592 account("named_input", Some("input_mint")),
593 account("named_output", Some("output_mint")),
594 ];
595 let extracted = LimitV2Adapter::extract_create_mints(&accounts).unwrap();
596 assert_eq!(extracted.input_mint, "named_input");
597 assert_eq!(extracted.output_mint, "named_output");
598 }
599
600 #[test]
601 fn extract_create_mints_uses_fallback_indexes() {
602 let accounts = vec![
603 account("0", None),
604 account("1", None),
605 account("2", None),
606 account("3", None),
607 account("4", None),
608 account("5", None),
609 account("6", None),
610 account("fallback_input", None),
611 account("fallback_output", None),
612 ];
613 let extracted = LimitV2Adapter::extract_create_mints(&accounts).unwrap();
614 assert_eq!(extracted.input_mint, "fallback_input");
615 assert_eq!(extracted.output_mint, "fallback_output");
616 }
617
618 #[test]
619 fn extract_create_mints_rejects_missing_input_fallback_index() {
620 let err = LimitV2Adapter::extract_create_mints(&[])
621 .err()
622 .expect("expected error");
623 let Error::Protocol { reason } = err else {
624 panic!("expected protocol error");
625 };
626 assert_eq!(reason, "Limit v2 input_mint index 7 out of bounds");
627 }
628
629 #[test]
630 fn extract_create_mints_rejects_missing_output_fallback_index() {
631 let accounts = vec![
632 account("0", None),
633 account("1", None),
634 account("2", None),
635 account("3", None),
636 account("4", None),
637 account("5", None),
638 account("6", None),
639 account("fallback_input", None),
640 ];
641 let err = LimitV2Adapter::extract_create_mints(&accounts)
642 .err()
643 .expect("expected error");
644 let Error::Protocol { reason } = err else {
645 panic!("expected protocol error");
646 };
647 assert_eq!(reason, "Limit v2 output_mint index 8 out of bounds");
648 }
649
650 #[cfg(feature = "wasm")]
651 #[test]
652 fn instruction_constants_match_classify() {
653 for (name, expected) in INSTRUCTION_EVENT_TYPES {
654 let ix = RawInstruction {
655 id: 1,
656 signature: "sig".to_string(),
657 instruction_index: 0,
658 instruction_path: None,
659 program_id: "p".to_string(),
660 inner_program_id: "p".to_string(),
661 instruction_name: name.to_string(),
662 accounts: None,
663 args: None,
664 slot: 1,
665 };
666 assert_eq!(
667 LimitV2Adapter.classify_instruction(&ix).as_ref(),
668 Some(expected),
669 "INSTRUCTION_EVENT_TYPES mismatch for {name}"
670 );
671 }
672 }
673
674 #[cfg(feature = "wasm")]
675 #[test]
676 fn event_constants_match_resolve() {
677 for (name, expected) in EVENT_EVENT_TYPES {
678 let fields = match *name {
679 "TradeEvent" => {
680 serde_json::json!({(*name): {"order_key": "t", "making_amount": 1_u64, "taking_amount": 1_u64, "remaining_making_amount": 0_u64, "remaining_taking_amount": 0_u64}})
681 }
682 _ => serde_json::json!({(*name): {"order_key": "t"}}),
683 };
684 let result = resolve(fields);
685 let (event_type, _, _) = result.expect("should return Some").expect("should be Ok");
686 assert_eq!(
687 &event_type, expected,
688 "EVENT_EVENT_TYPES mismatch for {name}"
689 );
690 }
691 }
692
693 #[test]
694 fn mirror_enums_cover_all_carbon_variants() {
695 let instruction_variants = [
696 "InitializeOrder",
697 "PreFlashFillOrder",
698 "FlashFillOrder",
699 "CancelOrder",
700 "UpdateFee",
701 "WithdrawFee",
702 ];
703 for name in instruction_variants {
704 let json = serde_json::json!({ name: serde_json::Value::Null });
705 assert!(
706 serde_json::from_value::<LimitV2InstructionKind>(json).is_ok(),
707 "LimitV2InstructionKind missing variant: {name}"
708 );
709 }
710
711 for name in ["CreateOrderEvent", "CancelOrderEvent"] {
712 let json = serde_json::json!({ name: { "order_key": "test" } });
713 assert!(
714 serde_json::from_value::<LimitV2EventEnvelope>(json).is_ok(),
715 "LimitV2EventEnvelope missing variant: {name}"
716 );
717 }
718
719 let trade = serde_json::json!({
720 "TradeEvent": { "order_key": "t", "making_amount": 1_u64, "taking_amount": 1_u64,
721 "remaining_making_amount": 0_u64, "remaining_taking_amount": 0_u64 }
722 });
723 assert!(serde_json::from_value::<LimitV2EventEnvelope>(trade).is_ok());
724 }
725}