1use {
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#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct ObjectIdentity {
19 pub object_id: sui::types::Address,
21}
22
23impl ObjectIdentity {
24 pub const fn new(object_id: sui::types::Address) -> Self {
26 Self { object_id }
27 }
28
29 pub const fn object_id(&self) -> sui::types::Address {
31 self.object_id
32 }
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct SharedRoot {
44 pub object_id: sui::types::Address,
46 pub initial_shared_version: u64,
48}
49
50impl SharedRoot {
51 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 pub const fn object_id(&self) -> sui::types::Address {
61 self.object_id
62 }
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
67#[serde(deny_unknown_fields)]
68pub struct UsTokenConfig {
69 pub package_id: sui::types::Address,
71 pub protected_treasury: sui::types::Address,
73 pub metadata: sui::types::Address,
75}
76
77impl UsTokenConfig {
78 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 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 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 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#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
132#[serde(deny_unknown_fields)]
133pub struct NexusObjects {
134 pub chain_id: String,
136 pub network_id: sui::types::Address,
138 pub tool_registry: SharedRoot,
140 pub network_auth: SharedRoot,
142 pub agent_registry: SharedRoot,
145 pub leader_registry: SharedRoot,
147 pub priority_fee_vault: SharedRoot,
149 pub runtime_authority: SharedRoot,
151 pub leader_admin_cap: ObjectIdentity,
153 pub tool_registry_admin_cap: ObjectIdentity,
155 pub slashing_cap: ObjectIdentity,
157 pub priority_fee_vault_owner_cap: ObjectIdentity,
159 pub initial_leader_cap: ObjectIdentity,
161 pub runtime_authority_cap: ObjectIdentity,
163 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}