Skip to main content

nexus_sdk/types/
nexus_objects.rs

1//! Stable Nexus environment identities.
2
3use {
4    crate::{
5        move_bindings::{sui_framework::coin::Coin as MoveCoin, talus::us::US},
6        sui,
7    },
8    serde::{Deserialize, Serialize},
9    talus_sui_move::{MoveStruct, MoveType},
10};
11
12/// Stable identity of one Sui object.
13///
14/// This type is used for capabilities whose current object version and digest
15/// are not part of environment identity.
16#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct ObjectIdentity {
19    /// Stable Sui object ID.
20    pub object_id: sui::types::Address,
21}
22
23impl ObjectIdentity {
24    /// Creates an identity for `object_id`.
25    pub const fn new(object_id: sui::types::Address) -> Self {
26        Self { object_id }
27    }
28
29    /// Returns the stable Sui object ID.
30    pub const fn object_id(&self) -> sui::types::Address {
31        self.object_id
32    }
33}
34
35/// Stable identity and initial shared version of one canonical root.
36///
37/// A [`sui::types::ObjectReference`] cannot represent this invariant because
38/// it includes the current mutable version and digest. A shared transaction
39/// input instead requires the stable object ID and the version at which the
40/// object first became shared.
41#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct SharedRoot {
44    /// Stable Sui object ID.
45    pub object_id: sui::types::Address,
46    /// Version at which the object first became shared.
47    pub initial_shared_version: u64,
48}
49
50impl SharedRoot {
51    /// Creates a canonical root identity.
52    pub const fn new(object_id: sui::types::Address, initial_shared_version: u64) -> Self {
53        Self {
54            object_id,
55            initial_shared_version,
56        }
57    }
58
59    /// Returns the stable Sui object ID.
60    pub const fn object_id(&self) -> sui::types::Address {
61        self.object_id
62    }
63}
64
65/// Stable identities for the external US token.
66#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
67#[serde(deny_unknown_fields)]
68pub struct UsTokenConfig {
69    /// Package that defines [`US`].
70    pub package_id: sui::types::Address,
71    /// Protected treasury object for [`US`].
72    pub protected_treasury: sui::types::Address,
73    /// Coin metadata object for [`US`].
74    pub metadata: sui::types::Address,
75}
76
77impl UsTokenConfig {
78    /// Creates the complete external US token identity.
79    pub const fn new(
80        package_id: sui::types::Address,
81        protected_treasury: sui::types::Address,
82        metadata: sui::types::Address,
83    ) -> Self {
84        Self {
85            package_id,
86            protected_treasury,
87            metadata,
88        }
89    }
90
91    /// Returns the configured [`US`] type tag.
92    pub fn type_tag(&self) -> sui::types::TypeTag {
93        crate::move_bindings::talus::with_packages(
94            self.package_id,
95            self.package_id,
96            US::type_tag_static,
97        )
98    }
99
100    /// Returns the configured `Coin<US>` struct tag.
101    pub fn coin_type_tag(&self) -> sui::types::StructTag {
102        crate::move_bindings::sui_framework::with_packages(
103            sui::types::Address::from_static("0x2"),
104            sui::types::Address::from_static("0x2"),
105            || {
106                crate::move_bindings::talus::with_packages(
107                    self.package_id,
108                    self.package_id,
109                    MoveCoin::<US>::struct_tag_static,
110                )
111            },
112        )
113    }
114
115    /// Returns the fully qualified configured [`US`] type name.
116    pub fn qualified_type(&self) -> String {
117        let tag = crate::move_bindings::talus::with_packages(
118            self.package_id,
119            self.package_id,
120            US::struct_tag_static,
121        );
122        format!("{}::{}::{}", tag.address(), tag.module(), tag.name())
123    }
124}
125
126/// Stable identities required to operate in one Nexus environment.
127///
128/// Package authority and live derived values are intentionally absent. An
129/// operation resolves those values from object witnesses and immutable package
130/// metadata when it needs them.
131#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
132#[serde(deny_unknown_fields)]
133pub struct NexusObjects {
134    /// Complete genesis checkpoint digest expected from the Sui service.
135    pub chain_id: String,
136    /// Stable Nexus network identity.
137    pub network_id: sui::types::Address,
138    /// Canonical [`ToolRegistry`](crate::move_bindings::tool::tool_registry::ToolRegistry) root.
139    pub tool_registry: SharedRoot,
140    /// Canonical [`NetworkAuth`](crate::move_bindings::registry::network_auth::NetworkAuth) root.
141    pub network_auth: SharedRoot,
142    /// Canonical [`AgentRegistry`](crate::move_bindings::registry::agent_registry::AgentRegistry)
143    /// root.
144    pub agent_registry: SharedRoot,
145    /// Canonical [`LeaderRegistry`](crate::move_bindings::registry::leader::LeaderRegistry) root.
146    pub leader_registry: SharedRoot,
147    /// Canonical priority fee vault root.
148    pub priority_fee_vault: SharedRoot,
149    /// Fixed authority selecting the Scheduler package allowed to produce protocol effects.
150    pub runtime_authority: SharedRoot,
151    /// Authority that manages leader capabilities.
152    pub leader_admin_cap: ObjectIdentity,
153    /// Authority that manages the canonical [`ToolRegistry`](crate::move_bindings::tool::tool_registry::ToolRegistry).
154    pub tool_registry_admin_cap: ObjectIdentity,
155    /// Authority that records slashing decisions.
156    pub slashing_cap: ObjectIdentity,
157    /// Authority that manages the canonical priority fee vault.
158    pub priority_fee_vault_owner_cap: ObjectIdentity,
159    /// Leader capability created during initial publication.
160    pub initial_leader_cap: ObjectIdentity,
161    /// Authority for emergency pause and WorkAdmission policy changes.
162    pub runtime_authority_cap: ObjectIdentity,
163    /// External [`US`] token identities.
164    pub us_token: UsTokenConfig,
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    const ENVIRONMENT: &str = r#"
172chain_id = "test-chain"
173network_id = "0x01"
174
175[tool_registry]
176object_id = "0x02"
177initial_shared_version = 2
178
179[network_auth]
180object_id = "0x03"
181initial_shared_version = 3
182
183[agent_registry]
184object_id = "0x04"
185initial_shared_version = 3
186
187[leader_registry]
188object_id = "0x05"
189initial_shared_version = 3
190
191[priority_fee_vault]
192object_id = "0x06"
193initial_shared_version = 3
194
195[runtime_authority]
196object_id = "0x0f"
197initial_shared_version = 3
198
199[leader_admin_cap]
200object_id = "0x07"
201
202[tool_registry_admin_cap]
203object_id = "0x08"
204
205[slashing_cap]
206object_id = "0x09"
207
208[priority_fee_vault_owner_cap]
209object_id = "0x0a"
210
211[initial_leader_cap]
212object_id = "0x0b"
213
214[runtime_authority_cap]
215object_id = "0x10"
216
217[us_token]
218package_id = "0x0c"
219protected_treasury = "0x0d"
220metadata = "0x0e"
221"#;
222
223    #[test]
224    fn stable_environment_round_trips_through_toml() {
225        let objects: NexusObjects = toml::from_str(ENVIRONMENT).unwrap();
226        let encoded = toml::to_string(&objects).unwrap();
227        let decoded: NexusObjects = toml::from_str(&encoded).unwrap();
228
229        assert_eq!(decoded, objects);
230        assert_eq!(objects.tool_registry.initial_shared_version, 2);
231        assert_eq!(objects.runtime_authority.object_id, address("0x0f"));
232        assert_eq!(objects.us_token.metadata, address("0x0e"));
233    }
234
235    #[test]
236    fn stable_environment_requires_every_root() {
237        let without_root = ENVIRONMENT.replace(
238            "[leader_registry]\nobject_id = \"0x05\"\ninitial_shared_version = 3\n\n",
239            "",
240        );
241        let error = toml::from_str::<NexusObjects>(&without_root)
242            .unwrap_err()
243            .to_string();
244
245        assert!(error.contains("missing field `leader_registry`"));
246    }
247
248    #[test]
249    fn shared_root_rejects_current_object_truth() {
250        let with_digest = ENVIRONMENT.replace(
251            "[tool_registry]\nobject_id = \"0x02\"\ninitial_shared_version = 2",
252            "[tool_registry]\nobject_id = \"0x02\"\ninitial_shared_version = 2\ndigest = \"mutable\"",
253        );
254        let error = toml::from_str::<NexusObjects>(&with_digest)
255            .unwrap_err()
256            .to_string();
257
258        assert!(error.contains("unknown field `digest`"));
259    }
260
261    #[test]
262    fn stable_environment_rejects_package_authority() {
263        let with_packages = format!("{ENVIRONMENT}\n[packages]\nregistry = \"0x0f\"\n");
264        let error = toml::from_str::<NexusObjects>(&with_packages)
265            .unwrap_err()
266            .to_string();
267
268        assert!(error.contains("unknown field `packages`"));
269    }
270
271    #[test]
272    fn token_type_helpers_use_the_configured_package() {
273        let token = UsTokenConfig::new(address("0xc1"), address("0xc2"), address("0xc3"));
274        let type_tag = token.type_tag();
275        let sui::types::TypeTag::Struct(type_tag) = type_tag else {
276            panic!("expected US struct tag");
277        };
278        assert_eq!(*type_tag.address(), address("0xc1"));
279        assert_eq!(
280            token.coin_type_tag().type_params().first(),
281            Some(&sui::types::TypeTag::Struct(type_tag))
282        );
283        assert!(token
284            .qualified_type()
285            .starts_with(&address("0xc1").to_string()));
286    }
287
288    fn address(value: &'static str) -> sui::types::Address {
289        sui::types::Address::from_static(value)
290    }
291}