iota_rosetta/
types.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Debug, str::FromStr};
6
7use axum::{
8    Json,
9    response::{IntoResponse, Response},
10};
11use fastcrypto::encoding::Hex;
12use iota_sdk::rpc_types::{IotaExecutionStatus, IotaTransactionBlockKind};
13use iota_types::{
14    IOTA_SYSTEM_PACKAGE_ID,
15    base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber, TransactionDigest},
16    crypto::{PublicKey as IotaPublicKey, SignatureScheme},
17    governance::{ADD_STAKE_FUN_NAME, WITHDRAW_STAKE_FUN_NAME},
18    iota_system_state::IOTA_SYSTEM_MODULE_NAME,
19    messages_checkpoint::CheckpointDigest,
20    programmable_transaction_builder::ProgrammableTransactionBuilder,
21    transaction::{Argument, CallArg, Command, ObjectArg, TransactionData},
22};
23use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
24use serde_json::Value;
25use strum_macros::{EnumIter, EnumString};
26
27use crate::{
28    IOTA,
29    errors::{Error, ErrorType},
30    operations::Operations,
31};
32
33pub type BlockHeight = u64;
34
35#[derive(Serialize, Deserialize, Clone, Debug)]
36pub struct NetworkIdentifier {
37    pub blockchain: String,
38    pub network: IotaEnv,
39}
40
41#[derive(
42    Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug, Clone, Copy, EnumString,
43)]
44#[strum(serialize_all = "lowercase")]
45#[serde(rename_all = "lowercase")]
46pub enum IotaEnv {
47    MainNet,
48    DevNet,
49    TestNet,
50    LocalNet,
51}
52
53impl IotaEnv {
54    pub fn check_network_identifier(
55        &self,
56        network_identifier: &NetworkIdentifier,
57    ) -> Result<(), Error> {
58        if &network_identifier.blockchain != "iota" {
59            return Err(Error::UnsupportedBlockchain(
60                network_identifier.blockchain.clone(),
61            ));
62        }
63        if &network_identifier.network != self {
64            return Err(Error::UnsupportedNetwork(network_identifier.network));
65        }
66        Ok(())
67    }
68}
69
70#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
71pub struct AccountIdentifier {
72    pub address: IotaAddress,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub sub_account: Option<SubAccount>,
75}
76
77#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
78pub struct SubAccount {
79    #[serde(rename = "address")]
80    pub account_type: SubAccountType,
81}
82
83#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
84pub enum SubAccountType {
85    Stake,
86    PendingStake,
87    EstimatedReward,
88}
89
90impl From<IotaAddress> for AccountIdentifier {
91    fn from(address: IotaAddress) -> Self {
92        AccountIdentifier {
93            address,
94            sub_account: None,
95        }
96    }
97}
98
99#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
100pub struct Currency {
101    pub symbol: String,
102    pub decimals: u64,
103}
104#[derive(Serialize, Deserialize)]
105pub struct AccountBalanceRequest {
106    pub network_identifier: NetworkIdentifier,
107    pub account_identifier: AccountIdentifier,
108    #[serde(default)]
109    pub block_identifier: PartialBlockIdentifier,
110    #[serde(default, skip_serializing_if = "Vec::is_empty")]
111    pub currencies: Vec<Currency>,
112}
113#[derive(Serialize, Deserialize, Debug)]
114pub struct AccountBalanceResponse {
115    pub block_identifier: BlockIdentifier,
116    pub balances: Vec<Amount>,
117}
118
119impl IntoResponse for AccountBalanceResponse {
120    fn into_response(self) -> Response {
121        Json(self).into_response()
122    }
123}
124
125#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
126pub struct BlockIdentifier {
127    pub index: BlockHeight,
128    pub hash: BlockHash,
129}
130
131pub type BlockHash = CheckpointDigest;
132
133#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
134pub struct Amount {
135    #[serde(with = "str_format")]
136    pub value: i128,
137    pub currency: Currency,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub metadata: Option<AmountMetadata>,
140}
141
142#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
143pub struct AmountMetadata {
144    pub sub_balances: Vec<SubBalance>,
145}
146
147#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
148pub struct SubBalance {
149    pub stake_id: ObjectID,
150    pub validator: IotaAddress,
151    #[serde(with = "str_format")]
152    pub value: i128,
153}
154
155impl Amount {
156    pub fn new(value: i128) -> Self {
157        Self {
158            value,
159            currency: IOTA.clone(),
160            metadata: None,
161        }
162    }
163    pub fn new_from_sub_balances(sub_balances: Vec<SubBalance>) -> Self {
164        let value = sub_balances.iter().map(|b| b.value).sum();
165
166        Self {
167            value,
168            currency: IOTA.clone(),
169            metadata: Some(AmountMetadata { sub_balances }),
170        }
171    }
172}
173
174mod str_format {
175    use std::str::FromStr;
176
177    use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
178
179    pub fn serialize<S>(value: &i128, serializer: S) -> Result<S::Ok, S::Error>
180    where
181        S: Serializer,
182    {
183        value.to_string().serialize(serializer)
184    }
185
186    pub fn deserialize<'de, D>(deserializer: D) -> Result<i128, D::Error>
187    where
188        D: Deserializer<'de>,
189    {
190        let s = String::deserialize(deserializer)?;
191        i128::from_str(&s).map_err(Error::custom)
192    }
193}
194
195#[derive(Deserialize)]
196pub struct AccountCoinsRequest {
197    pub network_identifier: NetworkIdentifier,
198    pub account_identifier: AccountIdentifier,
199    pub include_mempool: bool,
200}
201#[derive(Serialize)]
202pub struct AccountCoinsResponse {
203    pub block_identifier: BlockIdentifier,
204    pub coins: Vec<Coin>,
205}
206impl IntoResponse for AccountCoinsResponse {
207    fn into_response(self) -> Response {
208        Json(self).into_response()
209    }
210}
211#[derive(Serialize)]
212pub struct Coin {
213    pub coin_identifier: CoinIdentifier,
214    pub amount: Amount,
215}
216
217impl From<iota_sdk::rpc_types::Coin> for Coin {
218    fn from(coin: iota_sdk::rpc_types::Coin) -> Self {
219        Self {
220            coin_identifier: CoinIdentifier {
221                identifier: CoinID {
222                    id: coin.coin_object_id,
223                    version: coin.version,
224                },
225            },
226            amount: Amount {
227                value: coin.balance as i128,
228                currency: IOTA.clone(),
229                metadata: None,
230            },
231        }
232    }
233}
234
235#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
236pub struct CoinIdentifier {
237    pub identifier: CoinID,
238}
239
240#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct CoinID {
242    pub id: ObjectID,
243    pub version: SequenceNumber,
244}
245
246impl Serialize for CoinID {
247    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
248    where
249        S: Serializer,
250    {
251        format!("{}:{}", self.id, self.version.value()).serialize(serializer)
252    }
253}
254
255impl<'de> Deserialize<'de> for CoinID {
256    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257    where
258        D: Deserializer<'de>,
259    {
260        let s = String::deserialize(deserializer)?;
261
262        let (id, version) = s.split_at(
263            s.find(':')
264                .ok_or_else(|| D::Error::custom(format!("Malformed Coin id [{s}].")))?,
265        );
266        let version = version.trim_start_matches(':');
267        let id = ObjectID::from_hex_literal(id).map_err(D::Error::custom)?;
268        let version = SequenceNumber::from_u64(u64::from_str(version).map_err(D::Error::custom)?);
269
270        Ok(Self { id, version })
271    }
272}
273
274#[test]
275fn test_coin_id_serde() {
276    let id = ObjectID::random();
277    let coin_id = CoinID {
278        id,
279        version: SequenceNumber::from_u64(10),
280    };
281    let s = serde_json::to_string(&coin_id).unwrap();
282    assert_eq!(format!("\"{}:{}\"", id, 10), s);
283
284    let deserialized: CoinID = serde_json::from_str(&s).unwrap();
285
286    assert_eq!(id, deserialized.id);
287    assert_eq!(SequenceNumber::from_u64(10), deserialized.version)
288}
289
290impl From<ObjectRef> for CoinID {
291    fn from((id, version, _): ObjectRef) -> Self {
292        Self { id, version }
293    }
294}
295
296#[derive(Deserialize)]
297pub struct MetadataRequest {
298    #[serde(default)]
299    pub metadata: Option<Value>,
300}
301
302#[derive(Serialize)]
303pub struct NetworkListResponse {
304    pub network_identifiers: Vec<NetworkIdentifier>,
305}
306
307impl IntoResponse for NetworkListResponse {
308    fn into_response(self) -> Response {
309        Json(self).into_response()
310    }
311}
312
313#[derive(Deserialize)]
314pub struct ConstructionDeriveRequest {
315    pub network_identifier: NetworkIdentifier,
316    pub public_key: PublicKey,
317}
318
319#[derive(Serialize, Deserialize, Debug)]
320pub struct PublicKey {
321    pub hex_bytes: Hex,
322    pub curve_type: CurveType,
323}
324
325impl From<IotaPublicKey> for PublicKey {
326    fn from(pk: IotaPublicKey) -> Self {
327        match pk {
328            IotaPublicKey::Ed25519(k) => PublicKey {
329                hex_bytes: Hex::from_bytes(&k.0),
330                curve_type: CurveType::Edwards25519,
331            },
332            IotaPublicKey::Secp256k1(k) => PublicKey {
333                hex_bytes: Hex::from_bytes(&k.0),
334                curve_type: CurveType::Secp256k1,
335            },
336            IotaPublicKey::Secp256r1(k) => PublicKey {
337                hex_bytes: Hex::from_bytes(&k.0),
338                curve_type: CurveType::Secp256r1,
339            },
340            IotaPublicKey::ZkLogin(k) => PublicKey {
341                hex_bytes: Hex::from_bytes(&k.0),
342                curve_type: CurveType::ZkLogin, // inaccurate but added for completeness.
343            },
344            IotaPublicKey::Passkey(k) => PublicKey {
345                hex_bytes: Hex::from_bytes(&k.0),
346                curve_type: CurveType::Secp256r1,
347            },
348        }
349    }
350}
351
352impl TryInto<IotaAddress> for PublicKey {
353    type Error = Error;
354
355    fn try_into(self) -> Result<IotaAddress, Self::Error> {
356        let key_bytes = self.hex_bytes.to_vec()?;
357        let pub_key = IotaPublicKey::try_from_bytes(self.curve_type.into(), &key_bytes)?;
358        Ok((&pub_key).into())
359    }
360}
361
362#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
363#[serde(rename_all = "lowercase")]
364pub enum CurveType {
365    Secp256k1,
366    Edwards25519,
367    Secp256r1,
368    ZkLogin,
369}
370
371impl From<CurveType> for SignatureScheme {
372    fn from(type_: CurveType) -> Self {
373        match type_ {
374            CurveType::Secp256k1 => SignatureScheme::Secp256k1,
375            CurveType::Edwards25519 => SignatureScheme::ED25519,
376            CurveType::Secp256r1 => SignatureScheme::Secp256r1,
377            CurveType::ZkLogin => SignatureScheme::ZkLoginAuthenticator,
378        }
379    }
380}
381
382#[derive(Serialize)]
383pub struct ConstructionDeriveResponse {
384    pub account_identifier: AccountIdentifier,
385}
386
387impl IntoResponse for ConstructionDeriveResponse {
388    fn into_response(self) -> Response {
389        Json(self).into_response()
390    }
391}
392
393#[derive(Serialize, Deserialize)]
394pub struct ConstructionPayloadsRequest {
395    pub network_identifier: NetworkIdentifier,
396    pub operations: Operations,
397    #[serde(default, skip_serializing_if = "Option::is_none")]
398    pub metadata: Option<ConstructionMetadata>,
399    #[serde(default, skip_serializing_if = "Vec::is_empty")]
400    pub public_keys: Vec<PublicKey>,
401}
402
403#[derive(Deserialize, Serialize, Copy, Clone, Debug, EnumIter, Eq, PartialEq)]
404pub enum OperationType {
405    // Balance changing operations from TransactionEffect
406    Gas,
407    IotaBalanceChange,
408    StakeReward,
409    StakePrinciple,
410    // iota-rosetta supported operation type
411    PayIota,
412    Stake,
413    WithdrawStake,
414    // All other IOTA transaction types, readonly
415    EpochChange,
416    Genesis,
417    ConsensusCommitPrologue,
418    ProgrammableTransaction,
419    AuthenticatorStateUpdateV1,
420    RandomnessStateUpdate,
421    EndOfEpochTransaction,
422}
423
424impl From<&IotaTransactionBlockKind> for OperationType {
425    fn from(tx: &IotaTransactionBlockKind) -> Self {
426        match tx {
427            IotaTransactionBlockKind::Genesis(_) => OperationType::Genesis,
428            IotaTransactionBlockKind::ConsensusCommitPrologueV1(_) => {
429                OperationType::ConsensusCommitPrologue
430            }
431            IotaTransactionBlockKind::ProgrammableTransaction(_) => {
432                OperationType::ProgrammableTransaction
433            }
434            IotaTransactionBlockKind::AuthenticatorStateUpdateV1(_) => {
435                OperationType::AuthenticatorStateUpdateV1
436            }
437            IotaTransactionBlockKind::RandomnessStateUpdate(_) => {
438                OperationType::RandomnessStateUpdate
439            }
440            IotaTransactionBlockKind::EndOfEpochTransaction(_) => {
441                OperationType::EndOfEpochTransaction
442            }
443        }
444    }
445}
446
447#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)]
448pub struct OperationIdentifier {
449    index: u64,
450    #[serde(default, skip_serializing_if = "Option::is_none")]
451    network_index: Option<u64>,
452}
453
454impl From<u64> for OperationIdentifier {
455    fn from(index: u64) -> Self {
456        OperationIdentifier {
457            index,
458            network_index: None,
459        }
460    }
461}
462
463#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
464pub struct CoinChange {
465    pub coin_identifier: CoinIdentifier,
466    pub coin_action: CoinAction,
467}
468
469#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
470#[serde(rename_all = "snake_case")]
471pub enum CoinAction {
472    CoinCreated,
473    CoinSpent,
474}
475
476#[derive(Serialize, Deserialize, Debug)]
477pub struct ConstructionPayloadsResponse {
478    pub unsigned_transaction: Hex,
479    pub payloads: Vec<SigningPayload>,
480}
481
482impl IntoResponse for ConstructionPayloadsResponse {
483    fn into_response(self) -> Response {
484        Json(self).into_response()
485    }
486}
487
488#[derive(Serialize, Deserialize, Debug, Clone)]
489pub struct SigningPayload {
490    pub account_identifier: AccountIdentifier,
491    // Rosetta need the hex string without `0x`, cannot use fastcrypto's Hex
492    pub hex_bytes: String,
493    #[serde(default, skip_serializing_if = "Option::is_none")]
494    pub signature_type: Option<SignatureType>,
495}
496
497#[derive(Deserialize, Serialize, Debug, Clone)]
498#[serde(rename_all = "lowercase")]
499pub enum SignatureType {
500    Ed25519,
501    Ecdsa,
502}
503
504#[derive(Deserialize, Serialize)]
505pub struct ConstructionCombineRequest {
506    pub network_identifier: NetworkIdentifier,
507    pub unsigned_transaction: Hex,
508    pub signatures: Vec<Signature>,
509}
510
511#[derive(Deserialize, Serialize)]
512pub struct Signature {
513    pub signing_payload: SigningPayload,
514    pub public_key: PublicKey,
515    pub signature_type: SignatureType,
516    pub hex_bytes: Hex,
517}
518
519#[derive(Serialize, Deserialize, Debug)]
520pub struct ConstructionCombineResponse {
521    pub signed_transaction: Hex,
522}
523
524impl IntoResponse for ConstructionCombineResponse {
525    fn into_response(self) -> Response {
526        Json(self).into_response()
527    }
528}
529
530#[derive(Serialize, Deserialize)]
531pub struct ConstructionSubmitRequest {
532    pub network_identifier: NetworkIdentifier,
533    pub signed_transaction: Hex,
534}
535#[derive(Serialize, Deserialize, Debug)]
536pub struct TransactionIdentifierResponse {
537    pub transaction_identifier: TransactionIdentifier,
538    #[serde(default, skip_serializing_if = "Option::is_none")]
539    pub metadata: Option<Value>,
540}
541
542impl IntoResponse for TransactionIdentifierResponse {
543    fn into_response(self) -> Response {
544        Json(self).into_response()
545    }
546}
547
548#[derive(Serialize, Deserialize, Clone, Debug)]
549pub struct TransactionIdentifier {
550    pub hash: TransactionDigest,
551}
552
553#[derive(Serialize, Deserialize)]
554pub struct ConstructionPreprocessRequest {
555    pub network_identifier: NetworkIdentifier,
556    pub operations: Operations,
557    #[serde(default, skip_serializing_if = "Option::is_none")]
558    pub metadata: Option<PreprocessMetadata>,
559}
560
561#[derive(Serialize, Deserialize)]
562pub struct PreprocessMetadata {
563    #[serde(default, skip_serializing_if = "Option::is_none")]
564    pub budget: Option<u64>,
565}
566
567#[derive(Serialize, Deserialize, Debug)]
568pub struct ConstructionPreprocessResponse {
569    #[serde(default, skip_serializing_if = "Option::is_none")]
570    pub options: Option<MetadataOptions>,
571    #[serde(default, skip_serializing_if = "Vec::is_empty")]
572    pub required_public_keys: Vec<AccountIdentifier>,
573}
574
575#[derive(Serialize, Deserialize, Debug)]
576pub struct MetadataOptions {
577    pub internal_operation: InternalOperation,
578    #[serde(default, skip_serializing_if = "Option::is_none")]
579    pub budget: Option<u64>,
580}
581
582impl IntoResponse for ConstructionPreprocessResponse {
583    fn into_response(self) -> Response {
584        Json(self).into_response()
585    }
586}
587#[derive(Deserialize)]
588pub struct ConstructionHashRequest {
589    pub network_identifier: NetworkIdentifier,
590    pub signed_transaction: Hex,
591}
592
593#[derive(Serialize, Deserialize)]
594pub struct ConstructionMetadataRequest {
595    pub network_identifier: NetworkIdentifier,
596    #[serde(default, skip_serializing_if = "Option::is_none")]
597    pub options: Option<MetadataOptions>,
598    #[serde(default, skip_serializing_if = "Vec::is_empty")]
599    pub public_keys: Vec<PublicKey>,
600}
601
602#[derive(Serialize, Deserialize, Debug)]
603pub struct ConstructionMetadataResponse {
604    pub metadata: ConstructionMetadata,
605    #[serde(default)]
606    pub suggested_fee: Vec<Amount>,
607}
608
609#[derive(Serialize, Deserialize, Debug)]
610pub struct ConstructionMetadata {
611    pub sender: IotaAddress,
612    pub coins: Vec<ObjectRef>,
613    pub objects: Vec<ObjectRef>,
614    pub total_coin_value: u64,
615    pub gas_price: u64,
616    pub budget: u64,
617}
618
619impl IntoResponse for ConstructionMetadataResponse {
620    fn into_response(self) -> Response {
621        Json(self).into_response()
622    }
623}
624
625#[derive(Deserialize)]
626pub struct ConstructionParseRequest {
627    pub network_identifier: NetworkIdentifier,
628    pub signed: bool,
629    pub transaction: Hex,
630}
631
632#[derive(Serialize)]
633pub struct ConstructionParseResponse {
634    pub operations: Operations,
635    pub account_identifier_signers: Vec<AccountIdentifier>,
636    #[serde(default, skip_serializing_if = "Option::is_none")]
637    pub metadata: Option<Value>,
638}
639
640impl IntoResponse for ConstructionParseResponse {
641    fn into_response(self) -> Response {
642        Json(self).into_response()
643    }
644}
645
646#[derive(Deserialize)]
647pub struct NetworkRequest {
648    pub network_identifier: NetworkIdentifier,
649    #[serde(default, skip_serializing_if = "Option::is_none")]
650    pub metadata: Option<Value>,
651}
652
653#[derive(Serialize)]
654pub struct NetworkStatusResponse {
655    pub current_block_identifier: BlockIdentifier,
656    pub current_block_timestamp: u64,
657    pub genesis_block_identifier: BlockIdentifier,
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub oldest_block_identifier: Option<BlockIdentifier>,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub sync_status: Option<SyncStatus>,
662    pub peers: Vec<Peer>,
663}
664
665impl IntoResponse for NetworkStatusResponse {
666    fn into_response(self) -> Response {
667        Json(self).into_response()
668    }
669}
670
671#[derive(Serialize)]
672pub struct SyncStatus {
673    #[serde(default, skip_serializing_if = "Option::is_none")]
674    pub current_index: Option<u64>,
675    #[serde(default, skip_serializing_if = "Option::is_none")]
676    pub target_index: Option<u64>,
677    #[serde(default, skip_serializing_if = "Option::is_none")]
678    pub stage: Option<String>,
679    #[serde(default, skip_serializing_if = "Option::is_none")]
680    pub synced: Option<bool>,
681}
682#[derive(Serialize)]
683pub struct Peer {
684    pub peer_id: IotaAddress,
685    #[serde(default, skip_serializing_if = "Option::is_none")]
686    pub metadata: Option<Value>,
687}
688
689#[derive(Serialize)]
690pub struct NetworkOptionsResponse {
691    pub version: Version,
692    pub allow: Allow,
693}
694
695impl IntoResponse for NetworkOptionsResponse {
696    fn into_response(self) -> Response {
697        Json(self).into_response()
698    }
699}
700
701#[derive(Serialize)]
702pub struct Version {
703    pub rosetta_version: String,
704    pub node_version: String,
705    #[serde(default, skip_serializing_if = "Option::is_none")]
706    pub middleware_version: Option<String>,
707    #[serde(default, skip_serializing_if = "Option::is_none")]
708    pub metadata: Option<Value>,
709}
710
711#[derive(Serialize)]
712pub struct Allow {
713    pub operation_statuses: Vec<Value>,
714    pub operation_types: Vec<OperationType>,
715    pub errors: Vec<ErrorType>,
716    pub historical_balance_lookup: bool,
717    #[serde(default, skip_serializing_if = "Option::is_none")]
718    pub timestamp_start_index: Option<u64>,
719    pub call_methods: Vec<String>,
720    pub balance_exemptions: Vec<BalanceExemption>,
721    pub mempool_coins: bool,
722    #[serde(default, skip_serializing_if = "Option::is_none")]
723    pub block_hash_case: Option<Case>,
724    #[serde(default, skip_serializing_if = "Option::is_none")]
725    pub transaction_hash_case: Option<Case>,
726}
727
728#[derive(Copy, Clone, Deserialize, Serialize, Debug, Eq, PartialEq)]
729#[serde(rename_all = "UPPERCASE")]
730pub enum OperationStatus {
731    Success,
732    Failure,
733}
734
735impl From<IotaExecutionStatus> for OperationStatus {
736    fn from(es: IotaExecutionStatus) -> Self {
737        match es {
738            IotaExecutionStatus::Success => OperationStatus::Success,
739            IotaExecutionStatus::Failure { .. } => OperationStatus::Failure,
740        }
741    }
742}
743
744#[derive(Serialize)]
745pub struct BalanceExemption {
746    #[serde(default, skip_serializing_if = "Option::is_none")]
747    pub sub_account_address: Option<String>,
748    #[serde(default, skip_serializing_if = "Option::is_none")]
749    pub currency: Option<Currency>,
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub exemption_type: Option<ExemptionType>,
752}
753
754#[derive(Serialize)]
755#[serde(rename_all = "snake_case")]
756pub enum ExemptionType {
757    GreaterOrEqual,
758    LessOrEqual,
759    Dynamic,
760}
761
762#[derive(Serialize)]
763#[serde(rename_all = "snake_case")]
764pub enum Case {
765    UpperCase,
766    LowerCase,
767    CaseSensitive,
768    Null,
769}
770
771#[derive(Serialize, Deserialize, Clone, Debug)]
772pub struct Block {
773    pub block_identifier: BlockIdentifier,
774    pub parent_block_identifier: BlockIdentifier,
775    pub timestamp: u64,
776    pub transactions: Vec<Transaction>,
777    #[serde(default, skip_serializing_if = "Option::is_none")]
778    pub metadata: Option<Value>,
779}
780
781#[derive(Serialize, Deserialize, Clone, Debug)]
782pub struct Transaction {
783    pub transaction_identifier: TransactionIdentifier,
784    pub operations: Operations,
785    #[serde(default, skip_serializing_if = "Vec::is_empty")]
786    pub related_transactions: Vec<RelatedTransaction>,
787    #[serde(default, skip_serializing_if = "Option::is_none")]
788    pub metadata: Option<Value>,
789}
790
791#[derive(Serialize, Deserialize, Clone, Debug)]
792pub struct RelatedTransaction {
793    network_identifier: NetworkIdentifier,
794    transaction_identifier: TransactionIdentifier,
795    direction: Direction,
796}
797
798#[derive(Serialize, Deserialize, Clone, Debug)]
799#[serde(rename_all = "lowercase")]
800pub enum Direction {
801    Forward,
802    Backward,
803}
804
805#[derive(Serialize, Deserialize, Clone, Debug)]
806pub struct BlockResponse {
807    pub block: Block,
808    #[serde(default, skip_serializing_if = "Vec::is_empty")]
809    pub other_transactions: Vec<TransactionIdentifier>,
810}
811
812impl IntoResponse for BlockResponse {
813    fn into_response(self) -> Response {
814        Json(self).into_response()
815    }
816}
817#[derive(Serialize, Deserialize, Default, Debug)]
818pub struct PartialBlockIdentifier {
819    #[serde(default, skip_serializing_if = "Option::is_none")]
820    pub index: Option<u64>,
821    #[serde(default, skip_serializing_if = "Option::is_none")]
822    pub hash: Option<BlockHash>,
823}
824#[derive(Deserialize)]
825pub struct BlockRequest {
826    pub network_identifier: NetworkIdentifier,
827    #[serde(default)]
828    pub block_identifier: PartialBlockIdentifier,
829}
830
831#[derive(Deserialize)]
832pub struct BlockTransactionRequest {
833    pub network_identifier: NetworkIdentifier,
834    pub block_identifier: BlockIdentifier,
835    pub transaction_identifier: TransactionIdentifier,
836}
837
838#[derive(Serialize)]
839pub struct BlockTransactionResponse {
840    pub transaction: Transaction,
841}
842
843impl IntoResponse for BlockTransactionResponse {
844    fn into_response(self) -> Response {
845        Json(self).into_response()
846    }
847}
848
849#[derive(Serialize, Clone)]
850pub struct PrefundedAccount {
851    pub privkey: String,
852    pub account_identifier: AccountIdentifier,
853    pub curve_type: CurveType,
854    pub currency: Currency,
855}
856
857#[derive(Serialize, Deserialize, Debug)]
858pub enum InternalOperation {
859    PayIota {
860        sender: IotaAddress,
861        recipients: Vec<IotaAddress>,
862        amounts: Vec<u64>,
863    },
864    Stake {
865        sender: IotaAddress,
866        validator: IotaAddress,
867        amount: Option<u64>,
868    },
869    WithdrawStake {
870        sender: IotaAddress,
871        #[serde(default, skip_serializing_if = "Vec::is_empty")]
872        stake_ids: Vec<ObjectID>,
873    },
874}
875
876impl InternalOperation {
877    pub fn sender(&self) -> IotaAddress {
878        match self {
879            InternalOperation::PayIota { sender, .. }
880            | InternalOperation::Stake { sender, .. }
881            | InternalOperation::WithdrawStake { sender, .. } => *sender,
882        }
883    }
884    /// Combine with ConstructionMetadata to form the TransactionData
885    pub fn try_into_data(self, metadata: ConstructionMetadata) -> Result<TransactionData, Error> {
886        let pt = match self {
887            Self::PayIota {
888                recipients,
889                amounts,
890                ..
891            } => {
892                let mut builder = ProgrammableTransactionBuilder::new();
893                builder.pay_iota(recipients, amounts)?;
894                builder.finish()
895            }
896            InternalOperation::Stake {
897                validator, amount, ..
898            } => {
899                let mut builder = ProgrammableTransactionBuilder::new();
900
901                // [WORKAROUND] - this is a hack to work out if the staking ops is for a
902                // selected amount or None amount (whole wallet). if amount is
903                // none, validator input will be created after the system object input
904                let (validator, system_state, amount) = if let Some(amount) = amount {
905                    let amount = builder.pure(amount)?;
906                    let validator = builder.input(CallArg::Pure(bcs::to_bytes(&validator)?))?;
907                    let state = builder.input(CallArg::IOTA_SYSTEM_MUT)?;
908                    (validator, state, amount)
909                } else {
910                    let amount = builder.pure(metadata.total_coin_value - metadata.budget)?;
911                    let state = builder.input(CallArg::IOTA_SYSTEM_MUT)?;
912                    let validator = builder.input(CallArg::Pure(bcs::to_bytes(&validator)?))?;
913                    (validator, state, amount)
914                };
915                let coin = builder.command(Command::SplitCoins(Argument::GasCoin, vec![amount]));
916
917                let arguments = vec![system_state, coin, validator];
918
919                builder.command(Command::move_call(
920                    IOTA_SYSTEM_PACKAGE_ID,
921                    IOTA_SYSTEM_MODULE_NAME.to_owned(),
922                    ADD_STAKE_FUN_NAME.to_owned(),
923                    vec![],
924                    arguments,
925                ));
926                builder.finish()
927            }
928            InternalOperation::WithdrawStake { stake_ids, .. } => {
929                let mut builder = ProgrammableTransactionBuilder::new();
930
931                for stake_id in metadata.objects {
932                    // [WORKAROUND] - this is a hack to work out if the withdraw stake ops is for
933                    // selected stake_ids or None (all stakes) using the index of the call args.
934                    // if stake_ids is not empty, id input will be created after the system object
935                    // input
936                    let (system_state, id) = if !stake_ids.is_empty() {
937                        let system_state = builder.input(CallArg::IOTA_SYSTEM_MUT)?;
938                        let id = builder.obj(ObjectArg::ImmOrOwnedObject(stake_id))?;
939                        (system_state, id)
940                    } else {
941                        let id = builder.obj(ObjectArg::ImmOrOwnedObject(stake_id))?;
942                        let system_state = builder.input(CallArg::IOTA_SYSTEM_MUT)?;
943                        (system_state, id)
944                    };
945
946                    let arguments = vec![system_state, id];
947                    builder.command(Command::move_call(
948                        IOTA_SYSTEM_PACKAGE_ID,
949                        IOTA_SYSTEM_MODULE_NAME.to_owned(),
950                        WITHDRAW_STAKE_FUN_NAME.to_owned(),
951                        vec![],
952                        arguments,
953                    ));
954                }
955                builder.finish()
956            }
957        };
958
959        Ok(TransactionData::new_programmable(
960            metadata.sender,
961            metadata.coins,
962            pt,
963            metadata.budget,
964            metadata.gas_price,
965        ))
966    }
967}