1use {
4 crate::{
5 move_boundary::NexusPtbBuilder,
6 nexus::{
7 client::NexusClient,
8 error::NexusError,
9 scheduler::resolve,
10 signer::ExecutedTransaction,
11 },
12 scheduler::{DispatchOffer, OccurrenceRef, Schedule, SchedulerError, TaskSpec},
13 sui,
14 transactions::scheduler::{
15 compile_append_dispatch_occurrence,
16 compile_append_expire_occurrence,
17 TaskDraftCompiler,
18 },
19 types::{NexusContext, PackageRole},
20 },
21 std::sync::Arc,
22};
23
24#[derive(Clone, Copy, Debug)]
25enum TransactionSource {
26 Object(sui::types::Address),
27 Creator {
28 package: sui::types::Address,
29 role: PackageRole,
30 },
31}
32
33#[must_use = "dropping a Nexus transaction discards its commands"]
37pub struct NexusTransaction {
38 client: NexusClient,
39 source: TransactionSource,
40 transaction: NexusPtbBuilder,
41}
42
43impl NexusTransaction {
44 pub(super) fn for_object(
45 client: NexusClient,
46 context: Arc<NexusContext>,
47 object_id: sui::types::Address,
48 ) -> Self {
49 Self {
50 client,
51 source: TransactionSource::Object(object_id),
52 transaction: NexusPtbBuilder::new(context),
53 }
54 }
55
56 pub(super) fn for_creator(
57 client: NexusClient,
58 context: Arc<NexusContext>,
59 package: sui::types::Address,
60 role: PackageRole,
61 ) -> Self {
62 Self {
63 client,
64 source: TransactionSource::Creator { package, role },
65 transaction: NexusPtbBuilder::new(context),
66 }
67 }
68
69 pub fn scheduler(&mut self) -> SchedulerTransaction<'_> {
71 SchedulerTransaction {
72 client: &self.client,
73 source: self.source,
74 transaction: &mut self.transaction,
75 }
76 }
77
78 #[must_use]
80 pub fn finish(self) -> sui::types::ProgrammableTransaction {
81 self.transaction.finish()
82 }
83
84 pub async fn submit(self) -> Result<ExecutedTransaction, NexusError> {
90 let sender = self.client.owner()?;
91 let transaction = self.transaction.finish();
92 self.client.submit_transaction(transaction, sender).await
93 }
94}
95
96pub struct SchedulerTransaction<'transaction> {
98 client: &'transaction NexusClient,
99 source: TransactionSource,
100 transaction: &'transaction mut NexusPtbBuilder,
101}
102
103impl SchedulerTransaction<'_> {
104 fn require_task_source(
105 &self,
106 task: &sui::types::ObjectReference,
107 ) -> Result<(), SchedulerError> {
108 match self.source {
109 TransactionSource::Object(object_id) if object_id == *task.object_id() => Ok(()),
110 TransactionSource::Object(object_id) => Err(SchedulerError::InvalidRequest {
111 message: format!(
112 "transaction source object '{object_id}' differs from Task '{}'",
113 task.object_id()
114 ),
115 }),
116 TransactionSource::Creator { package, role } => Err(SchedulerError::InvalidRequest {
117 message: format!(
118 "Task commands require an object source, but package '{package}' selected \
119 the '{}' creator role",
120 role.as_str()
121 ),
122 }),
123 }
124 }
125
126 pub async fn create_task<'draft>(
135 &'draft mut self,
136 task: &TaskSpec,
137 ) -> Result<TaskDraft<'draft>, SchedulerError> {
138 if !matches!(
139 self.source,
140 TransactionSource::Creator {
141 role: PackageRole::Scheduler,
142 ..
143 }
144 ) {
145 return Err(SchedulerError::InvalidRequest {
146 message: "Task creation requires an explicit Scheduler creator package".to_owned(),
147 });
148 }
149 let prepared = resolve::prepare_task(self.client, self.transaction.context(), task).await?;
150 let compiler = TaskDraftCompiler::create(self.transaction, &prepared)?;
151 Ok(TaskDraft {
152 client: self.client,
153 compiler,
154 })
155 }
156
157 pub fn dispatch_occurrence(
165 &mut self,
166 offer: &DispatchOffer,
167 task: &sui::types::ObjectReference,
168 dag: &sui::types::ObjectReference,
169 leader_cap: &sui::types::ObjectReference,
170 gas_charge: u64,
171 ) -> Result<(), SchedulerError> {
172 self.require_task_source(task)?;
173 if offer.occurrence().task_id() != *task.object_id() {
174 return Err(SchedulerError::InvalidRequest {
175 message: format!(
176 "dispatch offer Task '{}' differs from object '{}'",
177 offer.occurrence().task_id(),
178 task.object_id()
179 ),
180 });
181 }
182 compile_append_dispatch_occurrence(
183 self.transaction,
184 task,
185 dag,
186 leader_cap,
187 offer.occurrence().occurrence_id(),
188 gas_charge,
189 )
190 }
191
192 pub fn expire_occurrence(
199 &mut self,
200 occurrence: OccurrenceRef,
201 task: &sui::types::ObjectReference,
202 ) -> Result<(), SchedulerError> {
203 self.require_task_source(task)?;
204 if occurrence.task_id() != *task.object_id() {
205 return Err(SchedulerError::InvalidRequest {
206 message: format!(
207 "occurrence Task '{}' differs from object '{}'",
208 occurrence.task_id(),
209 task.object_id()
210 ),
211 });
212 }
213 compile_append_expire_occurrence(self.transaction, task, occurrence.occurrence_id())
214 }
215}
216
217#[must_use = "a Task draft must be shared before its transaction is finished"]
219pub struct TaskDraft<'builder> {
220 client: &'builder NexusClient,
221 compiler: TaskDraftCompiler<'builder>,
222}
223
224impl TaskDraft<'_> {
225 pub async fn schedule(self, schedule: &Schedule) -> Result<Self, SchedulerError> {
234 let prepared = resolve::prepare_schedule(self.client, schedule).await?;
235 let compiler = self.compiler.schedule(&prepared)?;
236 Ok(Self {
237 client: self.client,
238 compiler,
239 })
240 }
241
242 pub fn share(self) -> Result<(), SchedulerError> {
248 let owner = self.client.owner().map_err(SchedulerError::transport)?;
249 self.compiler.share(owner)
250 }
251}