1mod sudt;
2
3use anyhow::anyhow;
4use ckb_types::{
5 bytes::{BufMut, Bytes, BytesMut},
6 core::{Capacity, TransactionBuilder, TransactionView},
7 packed::{Byte32, CellDep, CellInput, CellOutput, Script},
8 prelude::*,
9};
10use std::collections::HashSet;
11
12use super::{TransferAction, TxBuilder, TxBuilderError};
13use crate::traits::{
14 CellCollector, CellDepResolver, CellQueryOptions, HeaderDepResolver,
15 TransactionDependencyProvider, ValueRangeOption,
16};
17use crate::types::ScriptId;
18
19#[derive(Debug, Eq, PartialEq, Hash, Clone)]
21pub enum UdtType {
22 Sudt,
23 Xudt(Bytes),
25}
26
27impl UdtType {
28 pub fn build_script(&self, script_id: &ScriptId, owner_lock_hash: &Byte32) -> Script {
29 let type_script_args = match self {
30 UdtType::Sudt => owner_lock_hash.as_bytes(),
31 UdtType::Xudt(extra_args) => {
32 let mut data = BytesMut::with_capacity(32 + extra_args.len());
33 data.put(owner_lock_hash.as_slice());
34 data.put(extra_args.as_ref());
35 data.freeze()
36 }
37 };
38 Script::new_builder()
39 .code_hash(script_id.code_hash.pack())
40 .hash_type(script_id.hash_type)
41 .args(type_script_args.pack())
42 .build()
43 }
44}
45
46#[derive(Debug, Eq, PartialEq, Hash, Clone)]
48pub struct UdtTargetReceiver {
49 pub action: TransferAction,
50
51 pub lock_script: Script,
54
55 pub capacity: Option<u64>,
57
58 pub amount: u128,
60
61 pub extra_data: Option<Bytes>,
63}
64
65pub struct ReceiverBuildOutput {
66 pub input: Option<(CellInput, CellDep)>,
67 pub output: CellOutput,
68 pub output_data: Bytes,
69}
70
71impl UdtTargetReceiver {
72 pub fn new(action: TransferAction, lock_script: Script, amount: u128) -> UdtTargetReceiver {
73 UdtTargetReceiver {
74 action,
75 lock_script,
76 capacity: None,
77 amount,
78 extra_data: None,
79 }
80 }
81 #[cfg(not(target_arch = "wasm32"))]
82 pub fn build(
83 &self,
84 type_script: &Script,
85 cell_collector: &mut dyn CellCollector,
86 cell_dep_resolver: &dyn CellDepResolver,
87 ) -> Result<ReceiverBuildOutput, TxBuilderError> {
88 crate::rpc::block_on(self.build_async(type_script, cell_collector, cell_dep_resolver))
89 }
90
91 pub async fn build_async(
92 &self,
93 type_script: &Script,
94 cell_collector: &mut dyn CellCollector,
95 cell_dep_resolver: &dyn CellDepResolver,
96 ) -> Result<ReceiverBuildOutput, TxBuilderError> {
97 match self.action {
98 TransferAction::Create => {
99 let data_len = self
100 .extra_data
101 .as_ref()
102 .map(|data| data.len())
103 .unwrap_or_default()
104 + 16;
105 let mut data = BytesMut::with_capacity(data_len);
106 data.put(&self.amount.to_le_bytes()[..]);
107 if let Some(extra_data) = self.extra_data.as_ref() {
108 data.put(extra_data.as_ref());
109 }
110
111 let base_output = CellOutput::new_builder()
112 .lock(self.lock_script.clone())
113 .type_(Some(type_script.clone()).pack())
114 .build();
115 let base_occupied_capacity = base_output
116 .occupied_capacity(Capacity::bytes(data_len).unwrap())
117 .unwrap()
118 .as_u64();
119 let final_capacity = if let Some(capacity) = self.capacity.as_ref() {
120 if *capacity >= base_occupied_capacity {
121 *capacity
122 } else {
123 return Err(TxBuilderError::Other(anyhow!(
124 "Not enough capacity to hold a receiver cell, min: {}, actual: {}",
125 base_occupied_capacity,
126 *capacity,
127 )));
128 }
129 } else {
130 base_occupied_capacity
131 };
132 let output = base_output.as_builder().capacity(final_capacity).build();
133 Ok(ReceiverBuildOutput {
134 input: None,
135 output,
136 output_data: data.freeze(),
137 })
138 }
139 TransferAction::Update => {
140 let receiver_query = {
141 let mut query = CellQueryOptions::new_lock(self.lock_script.clone());
142 query.secondary_script = Some(type_script.clone());
143 query.data_len_range = Some(ValueRangeOption::new_min(16));
144 query
145 };
146 let (receiver_cells, _) = cell_collector
147 .collect_live_cells_async(&receiver_query, true)
148 .await?;
149 if receiver_cells.is_empty() {
150 return Err(TxBuilderError::Other(anyhow!(
151 "update receiver cell failed, cell not found, lock={:?}",
152 self.lock_script
153 )));
154 }
155
156 let receiver_cell_dep =
157 cell_dep_resolver
158 .resolve(&self.lock_script)
159 .ok_or_else(|| {
160 TxBuilderError::ResolveCellDepFailed(self.lock_script.clone())
161 })?;
162
163 let mut amount_bytes = [0u8; 16];
164 let receiver_cell = &receiver_cells[0];
165 amount_bytes.copy_from_slice(&receiver_cell.output_data.as_ref()[0..16]);
166 let old_amount = u128::from_le_bytes(amount_bytes);
167 let new_amount = old_amount + self.amount;
168 let mut new_data = receiver_cell.output_data.as_ref().to_vec();
169 new_data[0..16].copy_from_slice(&new_amount.to_le_bytes()[..]);
170 let output_data = Bytes::from(new_data);
171
172 let input = CellInput::new(receiver_cell.out_point.clone(), 0);
173 Ok(ReceiverBuildOutput {
174 input: Some((input, receiver_cell_dep)),
175 output: receiver_cell.output.clone(),
176 output_data,
177 })
178 }
179 }
180 }
181}
182
183pub struct UdtIssueBuilder {
185 pub udt_type: UdtType,
187
188 pub script_id: ScriptId,
190
191 pub owner: Script,
196
197 pub receivers: Vec<UdtTargetReceiver>,
199}
200
201#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
202#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
203impl TxBuilder for UdtIssueBuilder {
204 async fn build_base_async(
205 &self,
206 cell_collector: &mut dyn CellCollector,
207 cell_dep_resolver: &dyn CellDepResolver,
208 _header_dep_resolver: &dyn HeaderDepResolver,
209 _tx_dep_provider: &dyn TransactionDependencyProvider,
210 ) -> Result<TransactionView, TxBuilderError> {
211 let owner_query = {
213 let mut query = CellQueryOptions::new_lock(self.owner.clone());
214 query.secondary_script_len_range = Some(ValueRangeOption::new_exact(0));
215 query.data_len_range = Some(ValueRangeOption::new_exact(0));
216 query
217 };
218
219 let (owner_cells, _) = cell_collector
220 .collect_live_cells_async(&owner_query, true)
221 .await?;
222 if owner_cells.is_empty() {
223 return Err(TxBuilderError::Other(anyhow!("owner cell not found")));
224 }
225 let mut inputs = vec![CellInput::new(owner_cells[0].out_point.clone(), 0)];
226
227 let owner_lock_hash = self.owner.calc_script_hash();
229 let type_script = self
230 .udt_type
231 .build_script(&self.script_id, &owner_lock_hash);
232
233 let owner_cell_dep = cell_dep_resolver
234 .resolve(&self.owner)
235 .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.owner.clone()))?;
236 let udt_cell_dep = cell_dep_resolver
237 .resolve(&type_script)
238 .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(type_script.clone()))?;
239 #[allow(clippy::mutable_key_type)]
240 let mut cell_deps = HashSet::new();
241 cell_deps.insert(owner_cell_dep);
242 cell_deps.insert(udt_cell_dep);
243
244 let mut outputs = Vec::new();
246 let mut outputs_data = Vec::new();
247 for receiver in &self.receivers {
248 let ReceiverBuildOutput {
249 input,
250 output,
251 output_data,
252 } = receiver
253 .build_async(&type_script, cell_collector, cell_dep_resolver)
254 .await?;
255 if let Some((input, input_lock_cell_dep)) = input {
256 inputs.push(input);
257 cell_deps.insert(input_lock_cell_dep);
258 }
259 outputs.push(output);
260 outputs_data.push(output_data.pack());
261 }
262 Ok(TransactionBuilder::default()
263 .set_cell_deps(cell_deps.into_iter().collect())
264 .set_inputs(inputs)
265 .set_outputs(outputs)
266 .set_outputs_data(outputs_data)
267 .build())
268 }
269}
270
271pub struct UdtTransferBuilder {
272 pub type_script: Script,
274
275 pub sender: Script,
278
279 pub receivers: Vec<UdtTargetReceiver>,
281}
282
283#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
284#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
285impl TxBuilder for UdtTransferBuilder {
286 async fn build_base_async(
287 &self,
288 cell_collector: &mut dyn CellCollector,
289 cell_dep_resolver: &dyn CellDepResolver,
290 _header_dep_resolver: &dyn HeaderDepResolver,
291 _tx_dep_provider: &dyn TransactionDependencyProvider,
292 ) -> Result<TransactionView, TxBuilderError> {
293 let sender_query = {
294 let mut query = CellQueryOptions::new_lock(self.sender.clone());
295 query.secondary_script = Some(self.type_script.clone());
296 query.data_len_range = Some(ValueRangeOption::new_min(16));
297 query
298 };
299 let (sender_cells, _) = cell_collector
300 .collect_live_cells_async(&sender_query, true)
301 .await?;
302 if sender_cells.is_empty() {
303 return Err(TxBuilderError::Other(anyhow!("sender cell not found")));
304 }
305 let sender_cell = &sender_cells[0];
306
307 let sender_cell_dep = cell_dep_resolver
308 .resolve(&self.sender)
309 .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.sender.clone()))?;
310 let udt_cell_dep = cell_dep_resolver
311 .resolve(&self.type_script)
312 .ok_or_else(|| TxBuilderError::ResolveCellDepFailed(self.type_script.clone()))?;
313 #[allow(clippy::mutable_key_type)]
314 let mut cell_deps = HashSet::new();
315 cell_deps.insert(sender_cell_dep);
316 cell_deps.insert(udt_cell_dep);
317
318 let mut amount_bytes = [0u8; 16];
319 amount_bytes.copy_from_slice(&sender_cell.output_data.as_ref()[0..16]);
320 let input_total = u128::from_le_bytes(amount_bytes);
321 let output_total: u128 = self.receivers.iter().map(|receiver| receiver.amount).sum();
322 if input_total < output_total {
323 return Err(TxBuilderError::Other(anyhow!(
324 "sender udt amount not enough, expected at least: {}, actual: {}",
325 output_total,
326 input_total
327 )));
328 }
329
330 let sender_output_data = {
331 let new_amount = input_total - output_total;
332 let mut new_data = sender_cell.output_data.as_ref().to_vec();
333 new_data[0..16].copy_from_slice(&new_amount.to_le_bytes()[..]);
334 Bytes::from(new_data)
335 };
336
337 let mut inputs = vec![CellInput::new(sender_cell.out_point.clone(), 0)];
338 let mut outputs = vec![sender_cell.output.clone()];
339 let mut outputs_data = vec![sender_output_data.pack()];
340
341 for receiver in &self.receivers {
342 let ReceiverBuildOutput {
343 input,
344 output,
345 output_data,
346 } = receiver
347 .build_async(&self.type_script, cell_collector, cell_dep_resolver)
348 .await?;
349 if let Some((input, input_lock_cell_dep)) = input {
350 inputs.push(input);
351 cell_deps.insert(input_lock_cell_dep);
352 }
353 outputs.push(output);
354 outputs_data.push(output_data.pack());
355 }
356
357 Ok(TransactionBuilder::default()
358 .set_cell_deps(cell_deps.into_iter().collect())
359 .set_inputs(inputs)
360 .set_outputs(outputs)
361 .set_outputs_data(outputs_data)
362 .build())
363 }
364}