Skip to main content

lichen_client_sdk/
restrictions.rs

1//! Restriction-governance RPC helpers.
2
3use crate::{Client, Error, Pubkey, Result};
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize, Serializer};
6use serde_json::{json, Value};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum RestrictionAddress {
10    Pubkey(Pubkey),
11    String(String),
12}
13
14impl RestrictionAddress {
15    fn as_rpc_string(&self) -> String {
16        match self {
17            Self::Pubkey(pubkey) => pubkey.to_base58(),
18            Self::String(value) => value.clone(),
19        }
20    }
21}
22
23impl Serialize for RestrictionAddress {
24    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
25    where
26        S: Serializer,
27    {
28        serializer.serialize_str(&self.as_rpc_string())
29    }
30}
31
32impl From<Pubkey> for RestrictionAddress {
33    fn from(value: Pubkey) -> Self {
34        Self::Pubkey(value)
35    }
36}
37
38impl From<&Pubkey> for RestrictionAddress {
39    fn from(value: &Pubkey) -> Self {
40        Self::Pubkey(*value)
41    }
42}
43
44impl From<String> for RestrictionAddress {
45    fn from(value: String) -> Self {
46        Self::String(value)
47    }
48}
49
50impl From<&str> for RestrictionAddress {
51    fn from(value: &str) -> Self {
52        Self::String(value.to_string())
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum RestrictionAsset {
58    Pubkey(Pubkey),
59    String(String),
60}
61
62impl RestrictionAsset {
63    fn as_rpc_string(&self) -> String {
64        match self {
65            Self::Pubkey(pubkey) => pubkey.to_base58(),
66            Self::String(value) => value.clone(),
67        }
68    }
69}
70
71impl Serialize for RestrictionAsset {
72    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
73    where
74        S: Serializer,
75    {
76        serializer.serialize_str(&self.as_rpc_string())
77    }
78}
79
80impl From<Pubkey> for RestrictionAsset {
81    fn from(value: Pubkey) -> Self {
82        Self::Pubkey(value)
83    }
84}
85
86impl From<&Pubkey> for RestrictionAsset {
87    fn from(value: &Pubkey) -> Self {
88        Self::Pubkey(*value)
89    }
90}
91
92impl From<String> for RestrictionAsset {
93    fn from(value: String) -> Self {
94        Self::String(value)
95    }
96}
97
98impl From<&str> for RestrictionAsset {
99    fn from(value: &str) -> Self {
100        Self::String(value.to_string())
101    }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum BridgeChain {
106    Solana,
107    Ethereum,
108    Bsc,
109    Bnb,
110    NeoX,
111}
112
113impl BridgeChain {
114    pub const fn as_str(self) -> &'static str {
115        match self {
116            Self::Solana => "solana",
117            Self::Ethereum => "ethereum",
118            Self::Bsc => "bsc",
119            Self::Bnb => "bnb",
120            Self::NeoX => "neox",
121        }
122    }
123}
124
125impl Serialize for BridgeChain {
126    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
127    where
128        S: Serializer,
129    {
130        serializer.serialize_str(self.as_str())
131    }
132}
133
134impl From<BridgeChain> for String {
135    fn from(value: BridgeChain) -> Self {
136        value.as_str().to_string()
137    }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum BridgeAsset {
142    Sol,
143    Eth,
144    Bnb,
145    Gas,
146    Neo,
147    Usdc,
148    Usdt,
149}
150
151impl BridgeAsset {
152    pub const fn as_str(self) -> &'static str {
153        match self {
154            Self::Sol => "sol",
155            Self::Eth => "eth",
156            Self::Bnb => "bnb",
157            Self::Gas => "gas",
158            Self::Neo => "neo",
159            Self::Usdc => "usdc",
160            Self::Usdt => "usdt",
161        }
162    }
163}
164
165impl Serialize for BridgeAsset {
166    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
167    where
168        S: Serializer,
169    {
170        serializer.serialize_str(self.as_str())
171    }
172}
173
174impl From<BridgeAsset> for String {
175    fn from(value: BridgeAsset) -> Self {
176        value.as_str().to_string()
177    }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum RestrictionStringOrU64 {
182    String(String),
183    U64(u64),
184}
185
186impl Serialize for RestrictionStringOrU64 {
187    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
188    where
189        S: Serializer,
190    {
191        match self {
192            Self::String(value) => serializer.serialize_str(value),
193            Self::U64(value) => serializer.serialize_u64(*value),
194        }
195    }
196}
197
198impl From<u64> for RestrictionStringOrU64 {
199    fn from(value: u64) -> Self {
200        Self::U64(value)
201    }
202}
203
204impl From<u8> for RestrictionStringOrU64 {
205    fn from(value: u8) -> Self {
206        Self::U64(value as u64)
207    }
208}
209
210impl From<String> for RestrictionStringOrU64 {
211    fn from(value: String) -> Self {
212        Self::String(value)
213    }
214}
215
216impl From<&str> for RestrictionStringOrU64 {
217    fn from(value: &str) -> Self {
218        Self::String(value.to_string())
219    }
220}
221
222impl From<lichen_core::ProtocolModuleId> for RestrictionStringOrU64 {
223    fn from(value: lichen_core::ProtocolModuleId) -> Self {
224        Self::String(value.as_str().to_string())
225    }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
229pub enum RestrictionReasonInput {
230    Label(String),
231    Id(u8),
232}
233
234impl Serialize for RestrictionReasonInput {
235    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
236    where
237        S: Serializer,
238    {
239        match self {
240            Self::Label(value) => serializer.serialize_str(value),
241            Self::Id(value) => serializer.serialize_u8(*value),
242        }
243    }
244}
245
246impl From<lichen_core::RestrictionReason> for RestrictionReasonInput {
247    fn from(value: lichen_core::RestrictionReason) -> Self {
248        Self::Label(value.as_str().to_string())
249    }
250}
251
252impl From<u8> for RestrictionReasonInput {
253    fn from(value: u8) -> Self {
254        Self::Id(value)
255    }
256}
257
258impl From<String> for RestrictionReasonInput {
259    fn from(value: String) -> Self {
260        Self::Label(value)
261    }
262}
263
264impl From<&str> for RestrictionReasonInput {
265    fn from(value: &str) -> Self {
266        Self::Label(value.to_string())
267    }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub enum RestrictionLiftReasonInput {
272    Label(String),
273    Id(u8),
274}
275
276impl Serialize for RestrictionLiftReasonInput {
277    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
278    where
279        S: Serializer,
280    {
281        match self {
282            Self::Label(value) => serializer.serialize_str(value),
283            Self::Id(value) => serializer.serialize_u8(*value),
284        }
285    }
286}
287
288impl From<lichen_core::RestrictionLiftReason> for RestrictionLiftReasonInput {
289    fn from(value: lichen_core::RestrictionLiftReason) -> Self {
290        Self::Label(value.as_str().to_string())
291    }
292}
293
294impl From<u8> for RestrictionLiftReasonInput {
295    fn from(value: u8) -> Self {
296        Self::Id(value)
297    }
298}
299
300impl From<String> for RestrictionLiftReasonInput {
301    fn from(value: String) -> Self {
302        Self::Label(value)
303    }
304}
305
306impl From<&str> for RestrictionLiftReasonInput {
307    fn from(value: &str) -> Self {
308        Self::Label(value.to_string())
309    }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
313pub enum RestrictionModeInput {
314    Label(String),
315    Id(u8),
316}
317
318impl Serialize for RestrictionModeInput {
319    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
320    where
321        S: Serializer,
322    {
323        match self {
324            Self::Label(value) => serializer.serialize_str(value),
325            Self::Id(value) => serializer.serialize_u8(*value),
326        }
327    }
328}
329
330impl From<lichen_core::RestrictionMode> for RestrictionModeInput {
331    fn from(value: lichen_core::RestrictionMode) -> Self {
332        Self::Label(value.as_str().to_string())
333    }
334}
335
336impl From<u8> for RestrictionModeInput {
337    fn from(value: u8) -> Self {
338        Self::Id(value)
339    }
340}
341
342impl From<String> for RestrictionModeInput {
343    fn from(value: String) -> Self {
344        Self::Label(value)
345    }
346}
347
348impl From<&str> for RestrictionModeInput {
349    fn from(value: &str) -> Self {
350        Self::Label(value.to_string())
351    }
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
355#[serde(tag = "type", rename_all = "snake_case")]
356pub enum RestrictionTargetInput {
357    Account {
358        account: RestrictionAddress,
359    },
360    AccountAsset {
361        account: RestrictionAddress,
362        asset: RestrictionAsset,
363    },
364    Asset {
365        asset: RestrictionAsset,
366    },
367    Contract {
368        contract: RestrictionAddress,
369    },
370    CodeHash {
371        code_hash: String,
372    },
373    BridgeRoute {
374        chain: String,
375        asset: String,
376    },
377    ProtocolModule {
378        module: RestrictionStringOrU64,
379    },
380}
381
382impl RestrictionTargetInput {
383    pub fn account(account: impl Into<RestrictionAddress>) -> Self {
384        Self::Account {
385            account: account.into(),
386        }
387    }
388
389    pub fn account_asset(
390        account: impl Into<RestrictionAddress>,
391        asset: impl Into<RestrictionAsset>,
392    ) -> Self {
393        Self::AccountAsset {
394            account: account.into(),
395            asset: asset.into(),
396        }
397    }
398
399    pub fn asset(asset: impl Into<RestrictionAsset>) -> Self {
400        Self::Asset {
401            asset: asset.into(),
402        }
403    }
404
405    pub fn contract(contract: impl Into<RestrictionAddress>) -> Self {
406        Self::Contract {
407            contract: contract.into(),
408        }
409    }
410
411    pub fn code_hash(code_hash: impl Into<String>) -> Self {
412        Self::CodeHash {
413            code_hash: code_hash.into(),
414        }
415    }
416
417    pub fn bridge_route(chain: impl Into<String>, asset: impl Into<String>) -> Self {
418        Self::BridgeRoute {
419            chain: chain.into(),
420            asset: asset.into(),
421        }
422    }
423
424    pub fn protocol_module(module: impl Into<RestrictionStringOrU64>) -> Self {
425        Self::ProtocolModule {
426            module: module.into(),
427        }
428    }
429}
430
431#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
432pub struct RestrictionListParams {
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub limit: Option<u64>,
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub after_id: Option<u64>,
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub cursor: Option<RestrictionStringOrU64>,
439}
440
441#[derive(Debug, Clone, Serialize)]
442pub struct RestrictionBuilderBaseParams {
443    pub proposer: RestrictionAddress,
444    pub governance_authority: RestrictionAddress,
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub recent_blockhash: Option<String>,
447}
448
449#[derive(Debug, Clone, Serialize)]
450pub struct RestrictCommonParams {
451    pub proposer: RestrictionAddress,
452    pub governance_authority: RestrictionAddress,
453    pub reason: RestrictionReasonInput,
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub recent_blockhash: Option<String>,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub evidence_hash: Option<String>,
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub evidence_uri_hash: Option<String>,
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub expires_at_slot: Option<u64>,
462}
463
464#[derive(Debug, Clone, Serialize)]
465pub struct RestrictAccountParams {
466    pub proposer: RestrictionAddress,
467    pub governance_authority: RestrictionAddress,
468    pub account: RestrictionAddress,
469    pub reason: RestrictionReasonInput,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub mode: Option<RestrictionModeInput>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub recent_blockhash: Option<String>,
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub evidence_hash: Option<String>,
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub evidence_uri_hash: Option<String>,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub expires_at_slot: Option<u64>,
480}
481
482#[derive(Debug, Clone, Serialize)]
483pub struct UnrestrictAccountParams {
484    pub proposer: RestrictionAddress,
485    pub governance_authority: RestrictionAddress,
486    pub account: RestrictionAddress,
487    pub lift_reason: RestrictionLiftReasonInput,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub restriction_id: Option<u64>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub recent_blockhash: Option<String>,
492}
493
494#[derive(Debug, Clone, Serialize)]
495pub struct RestrictAccountAssetParams {
496    pub proposer: RestrictionAddress,
497    pub governance_authority: RestrictionAddress,
498    pub account: RestrictionAddress,
499    pub asset: RestrictionAsset,
500    pub reason: RestrictionReasonInput,
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub mode: Option<RestrictionModeInput>,
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub recent_blockhash: Option<String>,
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub evidence_hash: Option<String>,
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub evidence_uri_hash: Option<String>,
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub expires_at_slot: Option<u64>,
511}
512
513#[derive(Debug, Clone, Serialize)]
514pub struct UnrestrictAccountAssetParams {
515    pub proposer: RestrictionAddress,
516    pub governance_authority: RestrictionAddress,
517    pub account: RestrictionAddress,
518    pub asset: RestrictionAsset,
519    pub lift_reason: RestrictionLiftReasonInput,
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub restriction_id: Option<u64>,
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub recent_blockhash: Option<String>,
524}
525
526#[derive(Debug, Clone, Serialize)]
527pub struct SetFrozenAssetAmountParams {
528    pub proposer: RestrictionAddress,
529    pub governance_authority: RestrictionAddress,
530    pub account: RestrictionAddress,
531    pub asset: RestrictionAsset,
532    pub amount: u64,
533    pub reason: RestrictionReasonInput,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub recent_blockhash: Option<String>,
536    #[serde(skip_serializing_if = "Option::is_none")]
537    pub evidence_hash: Option<String>,
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub evidence_uri_hash: Option<String>,
540    #[serde(skip_serializing_if = "Option::is_none")]
541    pub expires_at_slot: Option<u64>,
542}
543
544#[derive(Debug, Clone, Serialize)]
545pub struct ContractRestrictionParams {
546    pub proposer: RestrictionAddress,
547    pub governance_authority: RestrictionAddress,
548    pub contract: RestrictionAddress,
549    pub reason: RestrictionReasonInput,
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub recent_blockhash: Option<String>,
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub evidence_hash: Option<String>,
554    #[serde(skip_serializing_if = "Option::is_none")]
555    pub evidence_uri_hash: Option<String>,
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub expires_at_slot: Option<u64>,
558}
559
560#[derive(Debug, Clone, Serialize)]
561pub struct ResumeContractParams {
562    pub proposer: RestrictionAddress,
563    pub governance_authority: RestrictionAddress,
564    pub contract: RestrictionAddress,
565    pub lift_reason: RestrictionLiftReasonInput,
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub restriction_id: Option<u64>,
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub recent_blockhash: Option<String>,
570}
571
572#[derive(Debug, Clone, Serialize)]
573pub struct CodeHashRestrictionParams {
574    pub proposer: RestrictionAddress,
575    pub governance_authority: RestrictionAddress,
576    pub code_hash: String,
577    pub reason: RestrictionReasonInput,
578    #[serde(skip_serializing_if = "Option::is_none")]
579    pub recent_blockhash: Option<String>,
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub evidence_hash: Option<String>,
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub evidence_uri_hash: Option<String>,
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub expires_at_slot: Option<u64>,
586}
587
588#[derive(Debug, Clone, Serialize)]
589pub struct UnbanCodeHashParams {
590    pub proposer: RestrictionAddress,
591    pub governance_authority: RestrictionAddress,
592    pub code_hash: String,
593    pub lift_reason: RestrictionLiftReasonInput,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub restriction_id: Option<u64>,
596    #[serde(skip_serializing_if = "Option::is_none")]
597    pub recent_blockhash: Option<String>,
598}
599
600#[derive(Debug, Clone, Serialize)]
601pub struct BridgeRouteRestrictionParams {
602    pub proposer: RestrictionAddress,
603    pub governance_authority: RestrictionAddress,
604    pub chain: String,
605    pub asset: String,
606    pub reason: RestrictionReasonInput,
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub recent_blockhash: Option<String>,
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub evidence_hash: Option<String>,
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub evidence_uri_hash: Option<String>,
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub expires_at_slot: Option<u64>,
615}
616
617#[derive(Debug, Clone, Serialize)]
618pub struct ResumeBridgeRouteParams {
619    pub proposer: RestrictionAddress,
620    pub governance_authority: RestrictionAddress,
621    pub chain: String,
622    pub asset: String,
623    pub lift_reason: RestrictionLiftReasonInput,
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub restriction_id: Option<u64>,
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub recent_blockhash: Option<String>,
628}
629
630#[derive(Debug, Clone, Serialize)]
631pub struct ExtendRestrictionParams {
632    pub proposer: RestrictionAddress,
633    pub governance_authority: RestrictionAddress,
634    pub restriction_id: u64,
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub new_expires_at_slot: Option<u64>,
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub evidence_hash: Option<String>,
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub recent_blockhash: Option<String>,
641}
642
643#[derive(Debug, Clone, Serialize)]
644pub struct LiftRestrictionParams {
645    pub proposer: RestrictionAddress,
646    pub governance_authority: RestrictionAddress,
647    pub restriction_id: u64,
648    pub lift_reason: RestrictionLiftReasonInput,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub recent_blockhash: Option<String>,
651}
652
653#[derive(Debug, Clone, Serialize)]
654pub struct MovementRestrictionParams {
655    pub account: RestrictionAddress,
656    pub asset: RestrictionAsset,
657    #[serde(skip_serializing_if = "Option::is_none")]
658    pub amount: Option<u64>,
659}
660
661#[derive(Debug, Clone, Serialize)]
662pub struct TransferRestrictionParams {
663    pub from: RestrictionAddress,
664    pub to: RestrictionAddress,
665    pub asset: RestrictionAsset,
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub amount: Option<u64>,
668}
669
670#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
671pub struct RestrictionTargetDetails {
672    #[serde(rename = "type")]
673    pub kind: String,
674    #[serde(default)]
675    pub account: Option<String>,
676    #[serde(default)]
677    pub asset: Option<String>,
678    #[serde(default)]
679    pub contract: Option<String>,
680    #[serde(default)]
681    pub code_hash: Option<String>,
682    #[serde(default)]
683    pub chain: Option<String>,
684    #[serde(default)]
685    pub chain_id: Option<String>,
686    #[serde(default)]
687    pub module: Option<String>,
688    #[serde(default)]
689    pub module_id: Option<u64>,
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
693pub struct RestrictionModeDetails {
694    pub kind: String,
695    #[serde(default)]
696    pub frozen_amount: Option<u64>,
697}
698
699#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
700pub struct RestrictionRecord {
701    pub id: u64,
702    pub status: String,
703    pub target_type: String,
704    pub target: String,
705    pub target_details: RestrictionTargetDetails,
706    pub mode: String,
707    pub mode_details: RestrictionModeDetails,
708    #[serde(default)]
709    pub frozen_amount: Option<u64>,
710    pub reason: String,
711    #[serde(default)]
712    pub evidence_hash: Option<String>,
713    #[serde(default)]
714    pub evidence_uri_hash: Option<String>,
715    pub proposer: String,
716    pub authority: String,
717    #[serde(default)]
718    pub approval_authority: Option<String>,
719    pub created_slot: u64,
720    pub created_epoch: u64,
721    #[serde(default)]
722    pub expires_at_slot: Option<u64>,
723    #[serde(default)]
724    pub supersedes: Option<u64>,
725    #[serde(default)]
726    pub lifted_by: Option<String>,
727    #[serde(default)]
728    pub lifted_slot: Option<u64>,
729    #[serde(default)]
730    pub lift_reason: Option<String>,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
734pub struct EffectiveRestrictionRecord {
735    #[serde(flatten)]
736    pub record: RestrictionRecord,
737    pub effective_status: String,
738    pub active: bool,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
742pub struct GetRestrictionResponse {
743    pub id: u64,
744    pub slot: u64,
745    pub found: bool,
746    pub restriction: Option<EffectiveRestrictionRecord>,
747}
748
749#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
750pub struct RestrictionListResponse {
751    pub restrictions: Vec<EffectiveRestrictionRecord>,
752    pub count: u64,
753    pub has_more: bool,
754    #[serde(default)]
755    pub next_cursor: Option<String>,
756    pub slot: u64,
757    #[serde(default)]
758    pub active_only: Option<bool>,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
762pub struct RestrictionTargetStatus {
763    pub slot: u64,
764    pub target_type: String,
765    pub target: String,
766    pub target_details: RestrictionTargetDetails,
767    pub restricted: bool,
768    pub active: bool,
769    pub restriction_ids: Vec<u64>,
770    pub active_restriction_ids: Vec<u64>,
771    pub restrictions: Vec<EffectiveRestrictionRecord>,
772    pub active_restrictions: Vec<EffectiveRestrictionRecord>,
773}
774
775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
776pub struct ContractLifecycleRestrictionStatus {
777    pub contract: String,
778    pub slot: u64,
779    pub found: bool,
780    pub is_executable: bool,
781    pub lifecycle_status: String,
782    pub lifecycle_updated_slot: u64,
783    #[serde(default)]
784    pub lifecycle_restriction_id: Option<u64>,
785    pub derived_from_restriction: bool,
786    pub active: bool,
787    pub active_restriction_ids: Vec<u64>,
788    pub active_restrictions: Vec<EffectiveRestrictionRecord>,
789}
790
791#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
792pub struct CodeHashRestrictionStatus {
793    pub code_hash: String,
794    pub slot: u64,
795    pub blocked: bool,
796    pub deploy_blocked: bool,
797    pub active_restriction_ids: Vec<u64>,
798    pub active_restrictions: Vec<RestrictionRecord>,
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
802pub struct BridgeRouteRestrictionStatus {
803    pub chain: String,
804    pub chain_id: String,
805    pub asset: String,
806    pub slot: u64,
807    pub paused: bool,
808    pub route_paused: bool,
809    pub active_restriction_ids: Vec<u64>,
810    pub active_restrictions: Vec<RestrictionRecord>,
811}
812
813#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
814pub struct MovementRestrictionStatus {
815    pub operation: String,
816    pub account: String,
817    pub asset: String,
818    pub amount: u64,
819    pub spendable: u64,
820    pub slot: u64,
821    pub allowed: bool,
822    pub blocked: bool,
823    pub active_restriction_ids: Vec<u64>,
824    pub active_restrictions: Vec<RestrictionRecord>,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
828pub struct TransferRestrictionStatus {
829    pub operation: String,
830    pub from: String,
831    pub to: String,
832    pub asset: String,
833    pub amount: u64,
834    pub source_spendable: u64,
835    pub recipient_spendable: u64,
836    pub slot: u64,
837    pub allowed: bool,
838    pub blocked: bool,
839    pub send_allowed: bool,
840    pub receive_allowed: bool,
841    pub source_blocked: bool,
842    pub recipient_blocked: bool,
843    pub source_restriction_ids: Vec<u64>,
844    pub source_restrictions: Vec<RestrictionRecord>,
845    pub recipient_restriction_ids: Vec<u64>,
846    pub recipient_restrictions: Vec<RestrictionRecord>,
847    pub active_restriction_ids: Vec<u64>,
848    pub active_restrictions: Vec<RestrictionRecord>,
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
852pub struct RestrictionBuilderInstruction {
853    pub program_id: String,
854    pub accounts: Vec<String>,
855    pub instruction_type: u64,
856    #[serde(default)]
857    pub governance_action_type: Option<u64>,
858    pub data_hex: String,
859}
860
861#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
862pub struct UnsignedRestrictionGovernanceTx {
863    pub method: String,
864    pub unsigned: bool,
865    pub encoding: String,
866    pub wire_format: String,
867    pub tx_type: String,
868    pub transaction_base64: String,
869    pub transaction: String,
870    pub wire_size: u64,
871    pub message_hash: String,
872    pub signature_count: u64,
873    pub recent_blockhash: String,
874    #[serde(default)]
875    pub slot: Option<u64>,
876    pub proposer: String,
877    pub governance_authority: String,
878    pub action_label: String,
879    pub action: Value,
880    pub instruction: RestrictionBuilderInstruction,
881}
882
883#[derive(Debug, Clone)]
884pub struct RestrictionGovernanceClient {
885    client: Client,
886}
887
888impl RestrictionGovernanceClient {
889    pub fn new(client: Client) -> Self {
890        Self { client }
891    }
892
893    pub fn from_rpc_url(rpc_url: impl Into<String>) -> Self {
894        Self::new(Client::new(rpc_url))
895    }
896
897    async fn rpc<T>(&self, method: &str, params: Value) -> Result<T>
898    where
899        T: DeserializeOwned,
900    {
901        let result = self.client.rpc_call(method, params).await?;
902        serde_json::from_value(result).map_err(|err| Error::ParseError(err.to_string()))
903    }
904
905    fn one_param<T: Serialize>(params: T) -> Result<Value> {
906        Ok(json!([serde_json::to_value(params).map_err(|err| {
907            Error::SerializationError(err.to_string())
908        })?]))
909    }
910
911    pub async fn get_restriction(&self, restriction_id: u64) -> Result<GetRestrictionResponse> {
912        self.rpc("getRestriction", json!([restriction_id])).await
913    }
914
915    pub async fn list_restrictions(
916        &self,
917        params: Option<RestrictionListParams>,
918    ) -> Result<RestrictionListResponse> {
919        self.rpc(
920            "listRestrictions",
921            Self::one_param(params.unwrap_or_default())?,
922        )
923        .await
924    }
925
926    pub async fn list_active_restrictions(
927        &self,
928        params: Option<RestrictionListParams>,
929    ) -> Result<RestrictionListResponse> {
930        self.rpc(
931            "listActiveRestrictions",
932            Self::one_param(params.unwrap_or_default())?,
933        )
934        .await
935    }
936
937    pub async fn get_restriction_status(
938        &self,
939        target: RestrictionTargetInput,
940    ) -> Result<RestrictionTargetStatus> {
941        self.rpc("getRestrictionStatus", Self::one_param(target)?)
942            .await
943    }
944
945    pub async fn get_account_restriction_status(
946        &self,
947        account: impl Into<RestrictionAddress>,
948    ) -> Result<RestrictionTargetStatus> {
949        self.rpc(
950            "getAccountRestrictionStatus",
951            json!([account.into().as_rpc_string()]),
952        )
953        .await
954    }
955
956    pub async fn get_asset_restriction_status(
957        &self,
958        asset: impl Into<RestrictionAsset>,
959    ) -> Result<RestrictionTargetStatus> {
960        self.rpc(
961            "getAssetRestrictionStatus",
962            json!([asset.into().as_rpc_string()]),
963        )
964        .await
965    }
966
967    pub async fn get_account_asset_restriction_status(
968        &self,
969        account: impl Into<RestrictionAddress>,
970        asset: impl Into<RestrictionAsset>,
971    ) -> Result<RestrictionTargetStatus> {
972        self.rpc(
973            "getAccountAssetRestrictionStatus",
974            json!([account.into().as_rpc_string(), asset.into().as_rpc_string()]),
975        )
976        .await
977    }
978
979    pub async fn get_contract_lifecycle_status(
980        &self,
981        contract: impl Into<RestrictionAddress>,
982    ) -> Result<ContractLifecycleRestrictionStatus> {
983        self.rpc(
984            "getContractLifecycleStatus",
985            json!([contract.into().as_rpc_string()]),
986        )
987        .await
988    }
989
990    pub async fn get_code_hash_restriction_status(
991        &self,
992        code_hash: impl Into<String>,
993    ) -> Result<CodeHashRestrictionStatus> {
994        self.rpc("getCodeHashRestrictionStatus", json!([code_hash.into()]))
995            .await
996    }
997
998    pub async fn get_bridge_route_restriction_status(
999        &self,
1000        chain: impl Into<String>,
1001        asset: impl Into<String>,
1002    ) -> Result<BridgeRouteRestrictionStatus> {
1003        self.rpc(
1004            "getBridgeRouteRestrictionStatus",
1005            json!([chain.into(), asset.into()]),
1006        )
1007        .await
1008    }
1009
1010    pub async fn can_send(
1011        &self,
1012        params: MovementRestrictionParams,
1013    ) -> Result<MovementRestrictionStatus> {
1014        self.rpc("canSend", Self::one_param(params)?).await
1015    }
1016
1017    pub async fn can_receive(
1018        &self,
1019        params: MovementRestrictionParams,
1020    ) -> Result<MovementRestrictionStatus> {
1021        self.rpc("canReceive", Self::one_param(params)?).await
1022    }
1023
1024    pub async fn can_transfer(
1025        &self,
1026        params: TransferRestrictionParams,
1027    ) -> Result<TransferRestrictionStatus> {
1028        self.rpc("canTransfer", Self::one_param(params)?).await
1029    }
1030
1031    pub async fn build_restrict_account_tx(
1032        &self,
1033        params: RestrictAccountParams,
1034    ) -> Result<UnsignedRestrictionGovernanceTx> {
1035        self.rpc("buildRestrictAccountTx", Self::one_param(params)?)
1036            .await
1037    }
1038
1039    pub async fn build_unrestrict_account_tx(
1040        &self,
1041        params: UnrestrictAccountParams,
1042    ) -> Result<UnsignedRestrictionGovernanceTx> {
1043        self.rpc("buildUnrestrictAccountTx", Self::one_param(params)?)
1044            .await
1045    }
1046
1047    pub async fn build_restrict_account_asset_tx(
1048        &self,
1049        params: RestrictAccountAssetParams,
1050    ) -> Result<UnsignedRestrictionGovernanceTx> {
1051        self.rpc("buildRestrictAccountAssetTx", Self::one_param(params)?)
1052            .await
1053    }
1054
1055    pub async fn build_unrestrict_account_asset_tx(
1056        &self,
1057        params: UnrestrictAccountAssetParams,
1058    ) -> Result<UnsignedRestrictionGovernanceTx> {
1059        self.rpc("buildUnrestrictAccountAssetTx", Self::one_param(params)?)
1060            .await
1061    }
1062
1063    pub async fn build_set_frozen_asset_amount_tx(
1064        &self,
1065        params: SetFrozenAssetAmountParams,
1066    ) -> Result<UnsignedRestrictionGovernanceTx> {
1067        self.rpc("buildSetFrozenAssetAmountTx", Self::one_param(params)?)
1068            .await
1069    }
1070
1071    pub async fn build_suspend_contract_tx(
1072        &self,
1073        params: ContractRestrictionParams,
1074    ) -> Result<UnsignedRestrictionGovernanceTx> {
1075        self.rpc("buildSuspendContractTx", Self::one_param(params)?)
1076            .await
1077    }
1078
1079    pub async fn build_resume_contract_tx(
1080        &self,
1081        params: ResumeContractParams,
1082    ) -> Result<UnsignedRestrictionGovernanceTx> {
1083        self.rpc("buildResumeContractTx", Self::one_param(params)?)
1084            .await
1085    }
1086
1087    pub async fn build_quarantine_contract_tx(
1088        &self,
1089        params: ContractRestrictionParams,
1090    ) -> Result<UnsignedRestrictionGovernanceTx> {
1091        self.rpc("buildQuarantineContractTx", Self::one_param(params)?)
1092            .await
1093    }
1094
1095    pub async fn build_terminate_contract_tx(
1096        &self,
1097        params: ContractRestrictionParams,
1098    ) -> Result<UnsignedRestrictionGovernanceTx> {
1099        self.rpc("buildTerminateContractTx", Self::one_param(params)?)
1100            .await
1101    }
1102
1103    pub async fn build_ban_code_hash_tx(
1104        &self,
1105        params: CodeHashRestrictionParams,
1106    ) -> Result<UnsignedRestrictionGovernanceTx> {
1107        self.rpc("buildBanCodeHashTx", Self::one_param(params)?)
1108            .await
1109    }
1110
1111    pub async fn build_unban_code_hash_tx(
1112        &self,
1113        params: UnbanCodeHashParams,
1114    ) -> Result<UnsignedRestrictionGovernanceTx> {
1115        self.rpc("buildUnbanCodeHashTx", Self::one_param(params)?)
1116            .await
1117    }
1118
1119    pub async fn build_pause_bridge_route_tx(
1120        &self,
1121        params: BridgeRouteRestrictionParams,
1122    ) -> Result<UnsignedRestrictionGovernanceTx> {
1123        self.rpc("buildPauseBridgeRouteTx", Self::one_param(params)?)
1124            .await
1125    }
1126
1127    pub async fn build_resume_bridge_route_tx(
1128        &self,
1129        params: ResumeBridgeRouteParams,
1130    ) -> Result<UnsignedRestrictionGovernanceTx> {
1131        self.rpc("buildResumeBridgeRouteTx", Self::one_param(params)?)
1132            .await
1133    }
1134
1135    pub async fn build_extend_restriction_tx(
1136        &self,
1137        params: ExtendRestrictionParams,
1138    ) -> Result<UnsignedRestrictionGovernanceTx> {
1139        self.rpc("buildExtendRestrictionTx", Self::one_param(params)?)
1140            .await
1141    }
1142
1143    pub async fn build_lift_restriction_tx(
1144        &self,
1145        params: LiftRestrictionParams,
1146    ) -> Result<UnsignedRestrictionGovernanceTx> {
1147        self.rpc("buildLiftRestrictionTx", Self::one_param(params)?)
1148            .await
1149    }
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154    use super::*;
1155    use serde_json::json;
1156
1157    fn key(byte: u8) -> Pubkey {
1158        Pubkey([byte; 32])
1159    }
1160
1161    #[test]
1162    fn serializes_read_and_preflight_payloads() {
1163        let account = key(3);
1164        let recipient = key(4);
1165        let asset = key(5);
1166
1167        assert_eq!(
1168            RestrictionGovernanceClient::one_param(RestrictionTargetInput::account_asset(
1169                account, "native"
1170            ))
1171            .unwrap(),
1172            json!([{
1173                "type": "account_asset",
1174                "account": account.to_base58(),
1175                "asset": "native"
1176            }])
1177        );
1178
1179        assert_eq!(
1180            RestrictionGovernanceClient::one_param(RestrictionListParams {
1181                limit: Some(10),
1182                after_id: Some(2),
1183                cursor: None,
1184            })
1185            .unwrap(),
1186            json!([{ "limit": 10, "after_id": 2 }])
1187        );
1188
1189        assert_eq!(
1190            RestrictionGovernanceClient::one_param(TransferRestrictionParams {
1191                from: account.into(),
1192                to: recipient.into(),
1193                asset: asset.into(),
1194                amount: Some(25),
1195            })
1196            .unwrap(),
1197            json!([{
1198                "from": account.to_base58(),
1199                "to": recipient.to_base58(),
1200                "asset": asset.to_base58(),
1201                "amount": 25
1202            }])
1203        );
1204    }
1205
1206    #[test]
1207    fn serializes_builder_payloads() {
1208        let proposer = key(1);
1209        let authority = key(2);
1210        let account = key(3);
1211        let asset = key(5);
1212
1213        assert_eq!(
1214            RestrictionGovernanceClient::one_param(RestrictAccountParams {
1215                proposer: proposer.into(),
1216                governance_authority: authority.into(),
1217                account: account.into(),
1218                reason: "testnet_drill".into(),
1219                mode: Some("outgoing_only".into()),
1220                recent_blockhash: Some("bb".repeat(32)),
1221                evidence_hash: Some("aa".repeat(32)),
1222                evidence_uri_hash: None,
1223                expires_at_slot: Some(123),
1224            })
1225            .unwrap(),
1226            json!([{
1227                "proposer": proposer.to_base58(),
1228                "governance_authority": authority.to_base58(),
1229                "account": account.to_base58(),
1230                "reason": "testnet_drill",
1231                "mode": "outgoing_only",
1232                "recent_blockhash": "bb".repeat(32),
1233                "evidence_hash": "aa".repeat(32),
1234                "expires_at_slot": 123
1235            }])
1236        );
1237
1238        assert_eq!(
1239            RestrictionGovernanceClient::one_param(SetFrozenAssetAmountParams {
1240                proposer: proposer.into(),
1241                governance_authority: authority.into(),
1242                account: account.into(),
1243                asset: asset.into(),
1244                amount: 500,
1245                reason: lichen_core::RestrictionReason::StolenFunds.into(),
1246                recent_blockhash: None,
1247                evidence_hash: None,
1248                evidence_uri_hash: None,
1249                expires_at_slot: None,
1250            })
1251            .unwrap(),
1252            json!([{
1253                "proposer": proposer.to_base58(),
1254                "governance_authority": authority.to_base58(),
1255                "account": account.to_base58(),
1256                "asset": asset.to_base58(),
1257                "amount": 500,
1258                "reason": "stolen_funds"
1259            }])
1260        );
1261
1262        assert_eq!(
1263            RestrictionGovernanceClient::one_param(ResumeBridgeRouteParams {
1264                proposer: proposer.into(),
1265                governance_authority: authority.into(),
1266                chain: BridgeChain::NeoX.into(),
1267                asset: BridgeAsset::Gas.into(),
1268                lift_reason: lichen_core::RestrictionLiftReason::TestnetDrillComplete.into(),
1269                restriction_id: Some(12),
1270                recent_blockhash: None,
1271            })
1272            .unwrap(),
1273            json!([{
1274                "proposer": proposer.to_base58(),
1275                "governance_authority": authority.to_base58(),
1276                "chain": "neox",
1277                "asset": "gas",
1278                "lift_reason": "testnet_drill_complete",
1279                "restriction_id": 12
1280            }])
1281        );
1282    }
1283
1284    #[test]
1285    fn deserializes_builder_response() {
1286        let response: UnsignedRestrictionGovernanceTx = serde_json::from_value(json!({
1287            "method": "buildRestrictAccountTx",
1288            "unsigned": true,
1289            "encoding": "base64",
1290            "wire_format": "lichen_tx_v1",
1291            "tx_type": "native",
1292            "transaction_base64": "AA==",
1293            "transaction": "AA==",
1294            "wire_size": 1,
1295            "message_hash": "00",
1296            "signature_count": 0,
1297            "recent_blockhash": "00",
1298            "slot": null,
1299            "proposer": "",
1300            "governance_authority": "",
1301            "action_label": "restrict",
1302            "action": {},
1303            "instruction": {
1304                "program_id": "",
1305                "accounts": [],
1306                "instruction_type": 34,
1307                "governance_action_type": 10,
1308                "data_hex": ""
1309            }
1310        }))
1311        .unwrap();
1312
1313        assert!(response.unsigned);
1314        assert_eq!(response.method, "buildRestrictAccountTx");
1315        assert_eq!(response.instruction.instruction_type, 34);
1316    }
1317}