1use std::{
2 fmt::{Debug, Display},
3 marker::PhantomData,
4};
5
6use near_contract_standards::fungible_token::core::ext_ft_core;
7#[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
8use near_primitives::action::FunctionCallAction;
9use near_sdk::{
10 env,
11 json_types::U128,
12 near,
13 serde_json::{self, json},
14 AccountId, AccountIdRef, Gas, NearToken, Promise,
15};
16use templar_primitives::number::Decimal;
17
18use crate::panic_with_message;
19
20#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[near(serializers = [json, borsh])]
40pub struct FungibleAsset<T: AssetClass> {
41 #[serde(skip)]
44 #[borsh(skip)]
45 discriminant: PhantomData<T>,
46 #[serde(flatten)]
47 kind: FungibleAssetKind,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
51#[near(serializers = [json, borsh])]
52enum FungibleAssetKind {
53 Nep141(AccountId),
54 Nep245 {
55 contract_id: AccountId,
56 token_id: String,
57 },
58}
59
60impl<T: AssetClass> FungibleAsset<T> {
61 pub const GAS_FT_TRANSFER: Gas = Gas::from_tgas(6);
63
64 pub const GAS_MT_TRANSFER: Gas = Gas::from_tgas(7);
66
67 pub const GAS_FT_TRANSFER_CALL: Gas = Gas::from_tgas(100);
71
72 pub const GAS_MT_TRANSFER_CALL: Gas = Gas::from_tgas(150);
75
76 #[allow(clippy::missing_panics_doc, clippy::unwrap_used)]
77 pub fn transfer(&self, receiver_id: AccountId, amount: FungibleAssetAmount<T>) -> Promise {
78 match self.kind {
79 FungibleAssetKind::Nep141(ref contract_id) => ext_ft_core::ext(contract_id.clone())
80 .with_static_gas(Self::GAS_FT_TRANSFER)
81 .with_attached_deposit(NearToken::from_yoctonear(1))
82 .ft_transfer(receiver_id, u128::from(amount).into(), None),
83 FungibleAssetKind::Nep245 {
84 ref contract_id,
85 ref token_id,
86 } => Promise::new(contract_id.clone()).function_call(
87 "mt_transfer".to_string(),
88 serde_json::to_vec(&json!({
89 "receiver_id": receiver_id,
90 "token_id": token_id,
91 "amount": amount,
92 }))
93 .unwrap(),
94 NearToken::from_yoctonear(1),
95 Self::GAS_MT_TRANSFER,
96 ),
97 }
98 }
99
100 #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
101 pub fn transfer_call_method_name(&self) -> &str {
102 match self.kind {
103 FungibleAssetKind::Nep141(_) => "ft_transfer_call",
104 FungibleAssetKind::Nep245 { .. } => "mt_transfer_call",
105 }
106 }
107
108 #[allow(clippy::missing_panics_doc, clippy::unwrap_used)]
109 pub fn transfer_call(
110 &self,
111 receiver_id: &AccountId,
112 amount: FungibleAssetAmount<T>,
113 msg: Option<&str>,
114 ) -> Promise {
115 let msg = msg.unwrap_or_default().to_string();
116 match self.kind {
117 FungibleAssetKind::Nep141(ref contract_id) => ext_ft_core::ext(contract_id.clone())
118 .with_static_gas(Self::GAS_FT_TRANSFER)
119 .with_attached_deposit(NearToken::from_yoctonear(1))
120 .ft_transfer_call(receiver_id.clone(), u128::from(amount).into(), None, msg),
121 FungibleAssetKind::Nep245 {
122 ref contract_id,
123 ref token_id,
124 } => Promise::new(contract_id.clone()).function_call(
125 "mt_transfer_call".to_string(),
126 serde_json::to_vec(&json!({
127 "receiver_id": receiver_id,
128 "token_id": token_id,
129 "amount": amount,
130 "msg": msg,
131 }))
132 .unwrap(),
133 NearToken::from_yoctonear(1),
134 Self::GAS_MT_TRANSFER,
135 ),
136 }
137 }
138
139 #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
141 pub fn transfer_action(
142 &self,
143 receiver_id: &AccountId,
144 amount: FungibleAssetAmount<T>,
145 ) -> FunctionCallAction {
146 let (method_name, args, gas) = match self.kind {
147 FungibleAssetKind::Nep141(_) => (
148 "ft_transfer",
149 json!({
150 "receiver_id": receiver_id,
151 "amount": u128::from(amount).to_string(),
152 }),
153 Self::GAS_FT_TRANSFER,
154 ),
155 FungibleAssetKind::Nep245 { ref token_id, .. } => (
156 "mt_transfer",
157 json!({
158 "receiver_id": receiver_id,
159 "token_id": token_id,
160 "amount": u128::from(amount).to_string(),
161 }),
162 Self::GAS_MT_TRANSFER,
163 ),
164 };
165
166 FunctionCallAction {
167 method_name: method_name.to_string(),
168 #[allow(clippy::unwrap_used)]
169 args: serde_json::to_vec(&args).unwrap(),
170 gas: near_primitives::gas::Gas::from_gas(gas.as_gas()),
171 deposit: NearToken::from_yoctonear(1), }
173 }
174
175 #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
176 pub fn transfer_call_action(
177 &self,
178 receiver_id: &AccountId,
179 amount: FungibleAssetAmount<T>,
180 msg: &str,
181 ) -> FunctionCallAction {
182 let (args, gas) = match self.kind {
183 FungibleAssetKind::Nep141(_) => (
184 json!({
185 "receiver_id": receiver_id,
186 "amount": u128::from(amount).to_string(),
187 "msg": msg,
188 }),
189 Self::GAS_FT_TRANSFER_CALL,
190 ),
191 FungibleAssetKind::Nep245 { ref token_id, .. } => (
192 json!({
193 "receiver_id": receiver_id,
194 "token_id": token_id,
195 "amount": u128::from(amount).to_string(),
196 "msg": msg,
197 }),
198 Self::GAS_MT_TRANSFER_CALL,
199 ),
200 };
201
202 FunctionCallAction {
203 method_name: self.transfer_call_method_name().to_string(),
204 #[allow(
205 clippy::unwrap_used,
206 reason = "All of the types have infallible serialization"
207 )]
208 args: serde_json::to_vec(&args).unwrap(),
209 gas: near_primitives::gas::Gas::from_gas(gas.as_gas()),
210 deposit: NearToken::from_yoctonear(1),
211 }
212 }
213
214 #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
215 pub fn balance_of_action(&self, account_id: &AccountId) -> FunctionCallAction {
216 let (method_name, args) = match self.kind {
217 FungibleAssetKind::Nep141(_) => (
218 "ft_balance_of",
219 json!({
220 "account_id": account_id,
221 }),
222 ),
223 FungibleAssetKind::Nep245 { ref token_id, .. } => (
224 "mt_balance_of",
225 json!({
226 "account_id": account_id,
227 "token_id": token_id,
228 }),
229 ),
230 };
231
232 FunctionCallAction {
233 method_name: method_name.to_string(),
234 #[allow(
235 clippy::unwrap_used,
236 reason = "All of the types have infallible serialization"
237 )]
238 args: serde_json::to_vec(&args).unwrap(),
239 gas: near_primitives::gas::Gas::from_teragas(3),
240 deposit: NearToken::ZERO,
241 }
242 }
243
244 pub fn nep141(contract_id: AccountId) -> Self {
245 Self {
246 discriminant: PhantomData,
247 kind: FungibleAssetKind::Nep141(contract_id),
248 }
249 }
250
251 pub fn nep245(contract_id: AccountId, token_id: String) -> Self {
252 Self {
253 discriminant: PhantomData,
254 kind: FungibleAssetKind::Nep245 {
255 contract_id,
256 token_id,
257 },
258 }
259 }
260
261 pub fn is_nep141(&self, account_id: &AccountId) -> bool {
262 matches!(self.kind, FungibleAssetKind::Nep141(ref contract_id) if contract_id == account_id)
263 }
264
265 pub fn into_nep141(self) -> Option<AccountId> {
266 match self.kind {
267 FungibleAssetKind::Nep141(contract_id) => Some(contract_id),
268 FungibleAssetKind::Nep245 { .. } => None,
269 }
270 }
271
272 pub fn is_nep245(&self, account_id: &AccountId, token_id: &str) -> bool {
273 let t = token_id;
274 matches!(self.kind, FungibleAssetKind::Nep245 { ref contract_id, ref token_id } if contract_id == account_id && token_id == t)
275 }
276
277 pub fn into_nep245(self) -> Option<(AccountId, String)> {
278 match self.kind {
279 FungibleAssetKind::Nep245 {
280 contract_id,
281 token_id,
282 } => Some((contract_id, token_id)),
283 FungibleAssetKind::Nep141(_) => None,
284 }
285 }
286
287 pub fn nep245_token_id(&self) -> Option<&str> {
291 match self.kind {
292 FungibleAssetKind::Nep245 { ref token_id, .. } => Some(token_id),
293 FungibleAssetKind::Nep141(_) => None,
294 }
295 }
296
297 #[allow(clippy::missing_panics_doc, clippy::unwrap_used)]
298 pub fn current_account_balance(&self) -> Promise {
299 let current_account_id = env::current_account_id();
300 match self.kind {
301 FungibleAssetKind::Nep141(ref account_id) => {
302 ext_ft_core::ext(account_id.clone()).ft_balance_of(current_account_id.clone())
303 }
304 FungibleAssetKind::Nep245 {
305 ref contract_id,
306 ref token_id,
307 } => Promise::new(contract_id.clone()).function_call(
308 "mt_balance_of".to_string(),
309 serde_json::to_vec(&json!({
310 "account_id": current_account_id,
311 "token_id": token_id,
312 }))
313 .unwrap(),
314 NearToken::from_millinear(0),
315 Gas::from_tgas(4),
316 ),
317 }
318 }
319
320 pub fn coerce<U: AssetClass>(self) -> FungibleAsset<U> {
321 FungibleAsset {
322 discriminant: PhantomData,
323 kind: self.kind,
324 }
325 }
326
327 pub fn contract_id(&self) -> &AccountIdRef {
328 match self.kind {
329 FungibleAssetKind::Nep141(ref account_id) => account_id,
330 FungibleAssetKind::Nep245 {
331 ref contract_id, ..
332 } => contract_id,
333 }
334 }
335}
336
337impl<T: AssetClass> Display for FungibleAsset<T> {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 match self.kind {
340 FungibleAssetKind::Nep141(ref contract_id) => {
341 write!(f, "nep141:{contract_id}")
342 }
343 FungibleAssetKind::Nep245 {
344 ref contract_id,
345 ref token_id,
346 } => write!(f, "nep245:{contract_id}:{token_id}"),
347 }
348 }
349}
350
351impl<T: AssetClass> std::str::FromStr for FungibleAsset<T> {
352 type Err = FungibleAssetParseError;
353
354 fn from_str(s: &str) -> Result<Self, Self::Err> {
355 let parts: Vec<&str> = s.splitn(3, ':').collect();
358
359 match parts.as_slice() {
360 ["nep141", contract_id] => {
361 let account_id = contract_id
362 .parse::<AccountId>()
363 .map_err(|e| FungibleAssetParseError::InvalidAccountId(e.to_string()))?;
364 Ok(FungibleAsset::nep141(account_id))
365 }
366 ["nep245", contract_id, token_id] => {
367 let account_id = contract_id
368 .parse::<AccountId>()
369 .map_err(|e| FungibleAssetParseError::InvalidAccountId(e.to_string()))?;
370
371 if token_id.is_empty() {
372 return Err(FungibleAssetParseError::EmptyTokenId);
373 }
374
375 Ok(FungibleAsset::nep245(account_id, (*token_id).to_string()))
376 }
377 _ => Err(FungibleAssetParseError::InvalidFormat),
378 }
379 }
380}
381
382#[derive(Debug, thiserror::Error)]
383pub enum FungibleAssetParseError {
384 #[error(
385 "Invalid format. Expected 'nep141:<contract_id>' or 'nep245:<contract_id>:<token_id>'"
386 )]
387 InvalidFormat,
388 #[error("Invalid account ID: {0}")]
389 InvalidAccountId(String),
390 #[error("Token ID cannot be empty for NEP-245 assets")]
391 EmptyTokenId,
392}
393
394mod sealed {
395 pub trait Sealed {}
396}
397pub trait AssetClass: sealed::Sealed + Copy + Clone + Send + Sync + std::fmt::Debug {}
398
399#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
400#[near(serializers = [borsh, json])]
401pub struct CollateralAsset;
402impl sealed::Sealed for CollateralAsset {}
403impl AssetClass for CollateralAsset {}
404
405#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
406#[near(serializers = [borsh, json])]
407pub struct BorrowAsset;
408impl sealed::Sealed for BorrowAsset {}
409impl AssetClass for BorrowAsset {}
410
411#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
412#[near(serializers = [borsh, json])]
413#[serde(from = "U128", into = "U128")]
414pub struct FungibleAssetAmount<T: AssetClass> {
415 amount: U128,
416 #[borsh(skip)]
417 discriminant: PhantomData<T>,
418}
419
420impl<T: AssetClass> Debug for FungibleAssetAmount<T> {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 write!(f, "{}<{}>", self.amount.0, std::any::type_name::<T>())
423 }
424}
425
426impl<T: AssetClass> Default for FungibleAssetAmount<T> {
427 fn default() -> Self {
428 Self::zero()
429 }
430}
431
432impl<T: AssetClass> From<U128> for FungibleAssetAmount<T> {
433 fn from(amount: U128) -> Self {
434 Self {
435 amount,
436 discriminant: PhantomData,
437 }
438 }
439}
440
441impl<T: AssetClass> From<FungibleAssetAmount<T>> for U128 {
442 fn from(value: FungibleAssetAmount<T>) -> Self {
443 value.amount
444 }
445}
446
447impl<T: AssetClass> From<u128> for FungibleAssetAmount<T> {
448 fn from(value: u128) -> Self {
449 Self::new(value)
450 }
451}
452
453impl<T: AssetClass> FungibleAssetAmount<T> {
454 pub fn new(amount: u128) -> Self {
455 Self {
456 amount: U128(amount),
457 discriminant: PhantomData,
458 }
459 }
460
461 pub const fn zero() -> Self {
462 Self {
463 amount: U128(0),
464 discriminant: PhantomData,
465 }
466 }
467
468 pub fn is_zero(&self) -> bool {
469 self.amount.0 == 0
470 }
471
472 #[must_use]
473 pub fn unwrap_add(self, other: impl Into<Self>, message: &str) -> Self {
474 Self {
475 amount: self
476 .amount
477 .0
478 .checked_add(other.into().amount.0)
479 .unwrap_or_else(|| panic_with_message(&format!("Arithmetic overflow: {message}")))
480 .into(),
481 ..self
482 }
483 }
484
485 #[must_use]
486 pub fn saturating_add(self, other: impl Into<Self>) -> Self {
487 Self {
488 amount: self.amount.0.saturating_add(other.into().amount.0).into(),
489 ..self
490 }
491 }
492
493 #[must_use]
494 pub fn checked_add(self, other: impl Into<Self>) -> Option<Self> {
495 Some(Self {
496 amount: self.amount.0.checked_add(other.into().amount.0)?.into(),
497 ..self
498 })
499 }
500
501 #[must_use]
502 pub fn unwrap_sub(self, other: impl Into<Self>, message: &str) -> Self {
503 Self {
504 amount: self
505 .amount
506 .0
507 .checked_sub(other.into().amount.0)
508 .unwrap_or_else(|| panic_with_message(&format!("Arithmetic underflow: {message}")))
509 .into(),
510 ..self
511 }
512 }
513
514 #[must_use]
515 pub fn saturating_sub(self, other: impl Into<Self>) -> Self {
516 Self {
517 amount: self.amount.0.saturating_sub(other.into().amount.0).into(),
518 ..self
519 }
520 }
521
522 #[must_use]
523 pub fn checked_sub(self, other: impl Into<Self>) -> Option<Self> {
524 Some(Self {
525 amount: self.amount.0.checked_sub(other.into().amount.0)?.into(),
526 ..self
527 })
528 }
529}
530
531impl<T: AssetClass> From<FungibleAssetAmount<T>> for Decimal {
532 fn from(value: FungibleAssetAmount<T>) -> Self {
533 value.amount.0.into()
534 }
535}
536
537impl<T: AssetClass> From<FungibleAssetAmount<T>> for u128 {
538 fn from(value: FungibleAssetAmount<T>) -> Self {
539 value.amount.0
540 }
541}
542
543impl<T: AssetClass> std::fmt::Display for FungibleAssetAmount<T> {
544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545 write!(f, "{}", self.amount.0)
546 }
547}
548
549impl<T: AssetClass, R: Into<Self>> std::ops::Add<R> for FungibleAssetAmount<T> {
550 type Output = Self;
551
552 fn add(self, rhs: R) -> Self::Output {
553 Self {
554 amount: U128(self.amount.0 + rhs.into().amount.0),
555 ..self
556 }
557 }
558}
559
560impl<T: AssetClass, R: Into<Self>> std::ops::AddAssign<R> for FungibleAssetAmount<T> {
561 fn add_assign(&mut self, rhs: R) {
562 self.amount.0 += rhs.into().amount.0;
563 }
564}
565
566impl<T: AssetClass, R: Into<Self>> std::ops::Sub<R> for FungibleAssetAmount<T> {
567 type Output = Self;
568
569 fn sub(self, rhs: R) -> Self::Output {
570 Self {
571 amount: U128(self.amount.0 - rhs.into().amount.0),
572 ..self
573 }
574 }
575}
576
577impl<T: AssetClass, R: Into<Self>> std::ops::SubAssign<R> for FungibleAssetAmount<T> {
578 fn sub_assign(&mut self, rhs: R) {
579 self.amount.0 -= rhs.into().amount.0;
580 }
581}
582
583pub type BorrowAssetAmount = FungibleAssetAmount<BorrowAsset>;
584pub type CollateralAssetAmount = FungibleAssetAmount<CollateralAsset>;
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use near_sdk::serde_json;
590
591 #[test]
592 fn serialization() {
593 let amount = BorrowAssetAmount::new(100);
594 let serialized = serde_json::to_string(&amount).unwrap();
595 assert_eq!(serialized, "\"100\"");
596 let deserialized: BorrowAssetAmount = serde_json::from_str(&serialized).unwrap();
597 assert_eq!(deserialized, amount);
598 }
599
600 #[test]
601 fn checked_add() {
602 let v = BorrowAssetAmount::new(0).checked_add(BorrowAssetAmount::new(0));
603 assert_eq!(v, Some(BorrowAssetAmount::new(0)));
604 let v = BorrowAssetAmount::new(0).checked_add(BorrowAssetAmount::new(100));
605 assert_eq!(v, Some(BorrowAssetAmount::new(100)));
606 let v = BorrowAssetAmount::new(100).checked_add(BorrowAssetAmount::new(0));
607 assert_eq!(v, Some(BorrowAssetAmount::new(100)));
608 let v = BorrowAssetAmount::new(100).checked_add(BorrowAssetAmount::new(100));
609 assert_eq!(v, Some(BorrowAssetAmount::new(200)));
610 let v = BorrowAssetAmount::new(1).checked_add(BorrowAssetAmount::new(u128::MAX));
611 assert_eq!(v, None);
612 }
613
614 #[test]
615 fn checked_sub() {
616 let v = BorrowAssetAmount::new(0).checked_sub(BorrowAssetAmount::new(0));
617 assert_eq!(v, Some(BorrowAssetAmount::new(0)));
618 let v = BorrowAssetAmount::new(0).checked_sub(BorrowAssetAmount::new(100));
619 assert_eq!(v, None);
620 let v = BorrowAssetAmount::new(100).checked_sub(BorrowAssetAmount::new(0));
621 assert_eq!(v, Some(BorrowAssetAmount::new(100)));
622 let v = BorrowAssetAmount::new(100).checked_sub(BorrowAssetAmount::new(100));
623 assert_eq!(v, Some(BorrowAssetAmount::new(0)));
624 let v = BorrowAssetAmount::new(1).checked_sub(BorrowAssetAmount::new(u128::MAX - 33));
625 assert_eq!(v, None);
626 }
627
628 #[test]
629 fn saturating_add() {
630 let v = BorrowAssetAmount::new(0).saturating_add(BorrowAssetAmount::new(0));
631 assert_eq!(v, BorrowAssetAmount::new(0));
632 let v = BorrowAssetAmount::new(0).saturating_add(BorrowAssetAmount::new(100));
633 assert_eq!(v, BorrowAssetAmount::new(100));
634 let v = BorrowAssetAmount::new(100).saturating_add(BorrowAssetAmount::new(0));
635 assert_eq!(v, BorrowAssetAmount::new(100));
636 let v = BorrowAssetAmount::new(100).saturating_add(BorrowAssetAmount::new(100));
637 assert_eq!(v, BorrowAssetAmount::new(200));
638 let v = BorrowAssetAmount::new(100).saturating_add(BorrowAssetAmount::new(u128::MAX - 33));
639 assert_eq!(v, BorrowAssetAmount::new(u128::MAX));
640 }
641
642 #[test]
643 fn saturating_sub() {
644 let v = BorrowAssetAmount::new(0).saturating_sub(BorrowAssetAmount::new(0));
645 assert_eq!(v, BorrowAssetAmount::new(0));
646 let v = BorrowAssetAmount::new(0).saturating_sub(BorrowAssetAmount::new(100));
647 assert_eq!(v, BorrowAssetAmount::new(0));
648 let v = BorrowAssetAmount::new(100).saturating_sub(BorrowAssetAmount::new(0));
649 assert_eq!(v, BorrowAssetAmount::new(100));
650 let v = BorrowAssetAmount::new(100).saturating_sub(BorrowAssetAmount::new(100));
651 assert_eq!(v, BorrowAssetAmount::new(0));
652 let v = BorrowAssetAmount::new(100).saturating_sub(BorrowAssetAmount::new(u128::MAX - 33));
653 assert_eq!(v, BorrowAssetAmount::new(0));
654 }
655
656 #[test]
657 #[should_panic = "overflow"]
658 fn overflow_unwrap_add() {
659 let _ =
660 BorrowAssetAmount::new(100).unwrap_add(BorrowAssetAmount::new(u128::MAX), "overflow");
661 }
662
663 #[test]
664 #[should_panic = "overflow"]
665 fn overflow_unwrap_sub() {
666 let _ =
667 BorrowAssetAmount::new(100).unwrap_sub(BorrowAssetAmount::new(u128::MAX), "overflow");
668 }
669
670 #[test]
671 #[should_panic = "attempt to add with overflow"]
672 fn overflow_add() {
673 let _ = BorrowAssetAmount::new(u128::MAX) + BorrowAssetAmount::new(1);
674 }
675
676 #[test]
677 #[should_panic = "attempt to add with overflow"]
678 fn overflow_add_assign() {
679 let mut v = BorrowAssetAmount::new(u128::MAX);
680 v += BorrowAssetAmount::new(1);
681 }
682
683 #[test]
684 #[should_panic = "attempt to subtract with overflow"]
685 fn overflow_sub() {
686 let _ = BorrowAssetAmount::new(0) - BorrowAssetAmount::new(1);
687 }
688
689 #[test]
690 #[should_panic = "attempt to subtract with overflow"]
691 fn overflow_sub_assign() {
692 let mut v = BorrowAssetAmount::new(1);
693 v -= BorrowAssetAmount::new(u128::MAX);
694 }
695}
696
697#[derive(Clone, Debug)]
698#[near(serializers = [json])]
699pub enum ReturnStyle {
700 Nep141FtTransferCall,
701 Nep245MtTransferCall,
702}
703
704impl ReturnStyle {
705 pub fn serialize(&self, amount: FungibleAssetAmount<impl AssetClass>) -> serde_json::Value {
706 match self {
707 Self::Nep141FtTransferCall => serde_json::json!(amount),
708 Self::Nep245MtTransferCall => serde_json::json!([amount]),
709 }
710 }
711}