1use crate::balances::credits::TokenAmount;
2use crate::block::block_info::BlockInfo;
3use crate::data_contract::accessors::v0::DataContractV0Getters;
4use crate::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem;
5use crate::data_contract::associated_token::token_distribution_key::TokenDistributionTypeWithResolvedRecipient;
6use crate::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionResolvedRecipient;
7use crate::data_contract::document_type::DocumentTypeRef;
8use crate::document::{Document, DocumentV0};
9use crate::fee::Credits;
10use crate::prelude::{
11 DataContract, DerivationEncryptionKeyIndex, IdentityNonce, RootEncryptionKeyIndex,
12};
13#[cfg(feature = "json-conversion")]
14use crate::serialization::JsonConvertible;
15#[cfg(feature = "value-conversion")]
16use crate::serialization::ValueConvertible;
17use bincode::{Decode, Encode};
18use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
19use platform_value::Identifier;
20use platform_version::version::PlatformVersion;
21use std::collections::BTreeMap;
22use std::fmt;
23
24pub type TokenEventPublicNote = Option<String>;
25pub type TokenEventSharedEncryptedNote = Option<SharedEncryptedNote>;
26pub type TokenEventPersonalEncryptedNote = Option<(
27 RootEncryptionKeyIndex,
28 DerivationEncryptionKeyIndex,
29 Vec<u8>,
30)>;
31use crate::serialization::PlatformSerializableWithPlatformVersion;
32use crate::tokens::emergency_action::TokenEmergencyAction;
33use crate::tokens::token_pricing_schedule::TokenPricingSchedule;
34use crate::tokens::SharedEncryptedNote;
35use crate::ProtocolError;
36
37pub type RecipientIdentifier = Identifier;
39
40pub type BurnFromIdentifier = Identifier;
42
43pub type PurchaserIdentifier = Identifier;
45
46pub type FrozenIdentifier = Identifier;
48
49#[derive(
61 Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize,
62)]
63#[cfg_attr(
64 feature = "serde-conversion",
65 derive(serde::Serialize, serde::Deserialize),
66 serde(tag = "type", content = "data", rename_all = "camelCase")
67)]
68#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
69#[platform_serialize(unversioned)]
70pub enum TokenEvent {
71 Mint(TokenAmount, RecipientIdentifier, TokenEventPublicNote),
77
78 Burn(TokenAmount, BurnFromIdentifier, TokenEventPublicNote),
84
85 Freeze(FrozenIdentifier, TokenEventPublicNote),
90
91 Unfreeze(FrozenIdentifier, TokenEventPublicNote),
96
97 DestroyFrozenFunds(FrozenIdentifier, TokenAmount, TokenEventPublicNote),
103
104 Transfer(
112 RecipientIdentifier,
113 TokenEventPublicNote,
114 TokenEventSharedEncryptedNote,
115 TokenEventPersonalEncryptedNote,
116 TokenAmount,
117 ),
118
119 Claim(
125 TokenDistributionTypeWithResolvedRecipient,
126 TokenAmount,
127 TokenEventPublicNote,
128 ),
129
130 EmergencyAction(TokenEmergencyAction, TokenEventPublicNote),
135
136 ConfigUpdate(TokenConfigurationChangeItem, TokenEventPublicNote),
141
142 ChangePriceForDirectPurchase(Option<TokenPricingSchedule>, TokenEventPublicNote),
147
148 DirectPurchase(TokenAmount, Credits),
153}
154
155#[cfg(feature = "json-conversion")]
162impl JsonConvertible for TokenEvent {}
163
164impl fmt::Display for TokenEvent {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 TokenEvent::Mint(amount, recipient, note) => {
168 write!(f, "Mint {} to {}{}", amount, recipient, format_note(note))
169 }
170 TokenEvent::Burn(amount, burn_from_identifier, note) => {
171 write!(
172 f,
173 "Burn {} from {}{}",
174 amount,
175 burn_from_identifier,
176 format_note(note)
177 )
178 }
179 TokenEvent::Freeze(identity, note) => {
180 write!(f, "Freeze {}{}", identity, format_note(note))
181 }
182 TokenEvent::Unfreeze(identity, note) => {
183 write!(f, "Unfreeze {}{}", identity, format_note(note))
184 }
185 TokenEvent::DestroyFrozenFunds(identity, amount, note) => {
186 write!(
187 f,
188 "Destroy {} frozen from {}{}",
189 amount,
190 identity,
191 format_note(note)
192 )
193 }
194 TokenEvent::Transfer(to, note, _, _, amount) => {
195 write!(f, "Transfer {} to {}{}", amount, to, format_note(note))
196 }
197 TokenEvent::Claim(recipient, amount, note) => {
198 write!(
199 f,
200 "Claim {} by {:?}{}",
201 amount,
202 recipient,
203 format_note(note)
204 )
205 }
206 TokenEvent::EmergencyAction(action, note) => {
207 write!(f, "Emergency action {:?}{}", action, format_note(note))
208 }
209 TokenEvent::ConfigUpdate(change, note) => {
210 write!(f, "Configuration update {:?}{}", change, format_note(note))
211 }
212 TokenEvent::ChangePriceForDirectPurchase(schedule, note) => match schedule {
213 Some(s) => write!(f, "Change price schedule to {:?}{}", s, format_note(note)),
214 None => write!(f, "Disable direct purchase{}", format_note(note)),
215 },
216 TokenEvent::DirectPurchase(amount, credits) => {
217 write!(f, "Direct purchase of {} for {} credits", amount, credits)
218 }
219 }
220 }
221}
222
223fn format_note(note: &Option<String>) -> String {
224 match note {
225 Some(n) => format!(" (note: {})", n),
226 None => String::new(),
227 }
228}
229
230impl TokenEvent {
231 pub fn associated_document_type_name(&self) -> &str {
232 match self {
233 TokenEvent::Mint(..) => "mint",
234 TokenEvent::Burn(..) => "burn",
235 TokenEvent::Freeze(..) => "freeze",
236 TokenEvent::Unfreeze(..) => "unfreeze",
237 TokenEvent::DestroyFrozenFunds(..) => "destroyFrozenFunds",
238 TokenEvent::Transfer(..) => "transfer",
239 TokenEvent::Claim(..) => "claim",
240 TokenEvent::EmergencyAction(..) => "emergencyAction",
241 TokenEvent::ConfigUpdate(..) => "configUpdate",
242 TokenEvent::DirectPurchase(..) => "directPurchase",
243 TokenEvent::ChangePriceForDirectPurchase(..) => "directPricing",
244 }
245 }
246
247 pub fn public_note(&self) -> Option<&str> {
249 match self {
250 TokenEvent::Mint(_, _, Some(note))
251 | TokenEvent::Burn(_, _, Some(note))
252 | TokenEvent::Freeze(_, Some(note))
253 | TokenEvent::Unfreeze(_, Some(note))
254 | TokenEvent::DestroyFrozenFunds(_, _, Some(note))
255 | TokenEvent::Transfer(_, Some(note), _, _, _)
256 | TokenEvent::Claim(_, _, Some(note))
257 | TokenEvent::EmergencyAction(_, Some(note))
258 | TokenEvent::ConfigUpdate(_, Some(note))
259 | TokenEvent::ChangePriceForDirectPurchase(_, Some(note)) => Some(note),
260 _ => None,
261 }
262 }
263
264 pub fn associated_document_type<'a>(
265 &self,
266 token_history_contract: &'a DataContract,
267 ) -> Result<DocumentTypeRef<'a>, ProtocolError> {
268 Ok(token_history_contract.document_type_for_name(self.associated_document_type_name())?)
269 }
270
271 pub fn build_historical_document_owned(
272 self,
273 token_id: Identifier,
274 owner_id: Identifier,
275 owner_nonce: IdentityNonce,
276 block_info: &BlockInfo,
277 platform_version: &PlatformVersion,
278 ) -> Result<Document, ProtocolError> {
279 let document_id = Document::generate_document_id_v0(
280 &token_id,
281 &owner_id,
282 format!("history_{}", self.associated_document_type_name()).as_str(),
283 owner_nonce.to_be_bytes().as_slice(),
284 );
285
286 let properties = match self {
287 TokenEvent::Mint(mint_amount, recipient_id, public_note) => {
288 let mut properties = BTreeMap::from([
289 ("tokenId".to_string(), token_id.into()),
290 ("recipientId".to_string(), recipient_id.into()),
291 ("amount".to_string(), mint_amount.into()),
292 ]);
293 if let Some(note) = public_note {
294 properties.insert("note".to_string(), note.into());
295 }
296 properties
297 }
298 TokenEvent::Burn(burn_amount, burn_from_identifier, public_note) => {
299 let mut properties = BTreeMap::from([
300 ("tokenId".to_string(), token_id.into()),
301 ("burnFromId".to_string(), burn_from_identifier.into()),
302 ("amount".to_string(), burn_amount.into()),
303 ]);
304 if let Some(note) = public_note {
305 properties.insert("note".to_string(), note.into());
306 }
307 properties
308 }
309 TokenEvent::Transfer(
310 to,
311 public_note,
312 token_event_shared_encrypted_note,
313 token_event_personal_encrypted_note,
314 amount,
315 ) => {
316 let mut properties = BTreeMap::from([
317 ("tokenId".to_string(), token_id.into()),
318 ("amount".to_string(), amount.into()),
319 ("toIdentityId".to_string(), to.into()),
320 ]);
321 if let Some(note) = public_note {
322 properties.insert("publicNote".to_string(), note.into());
323 }
324 if let Some((sender_key_index, recipient_key_index, note)) =
325 token_event_shared_encrypted_note
326 {
327 properties.insert("encryptedSharedNote".to_string(), note.into());
328 properties.insert("senderKeyIndex".to_string(), sender_key_index.into());
329 properties.insert("recipientKeyIndex".to_string(), recipient_key_index.into());
330 }
331
332 if let Some((root_encryption_key_index, derivation_encryption_key_index, note)) =
333 token_event_personal_encrypted_note
334 {
335 properties.insert("encryptedPersonalNote".to_string(), note.into());
336 properties.insert(
337 "rootEncryptionKeyIndex".to_string(),
338 root_encryption_key_index.into(),
339 );
340 properties.insert(
341 "derivationEncryptionKeyIndex".to_string(),
342 derivation_encryption_key_index.into(),
343 );
344 }
345 properties
346 }
347 TokenEvent::Freeze(frozen_identity_id, public_note) => {
348 let mut properties = BTreeMap::from([
349 ("tokenId".to_string(), token_id.into()),
350 ("frozenIdentityId".to_string(), frozen_identity_id.into()),
351 ]);
352 if let Some(note) = public_note {
353 properties.insert("note".to_string(), note.into());
354 }
355 properties
356 }
357 TokenEvent::Unfreeze(frozen_identity_id, public_note) => {
358 let mut properties = BTreeMap::from([
359 ("tokenId".to_string(), token_id.into()),
360 ("frozenIdentityId".to_string(), frozen_identity_id.into()),
361 ]);
362 if let Some(note) = public_note {
363 properties.insert("note".to_string(), note.into());
364 }
365 properties
366 }
367 TokenEvent::DestroyFrozenFunds(frozen_identity_id, amount, public_note) => {
368 let mut properties = BTreeMap::from([
369 ("tokenId".to_string(), token_id.into()),
370 ("frozenIdentityId".to_string(), frozen_identity_id.into()),
371 ("destroyedAmount".to_string(), amount.into()),
372 ]);
373 if let Some(note) = public_note {
374 properties.insert("note".to_string(), note.into());
375 }
376 properties
377 }
378 TokenEvent::EmergencyAction(action, public_note) => {
379 let mut properties = BTreeMap::from([
380 ("tokenId".to_string(), token_id.into()),
381 ("action".to_string(), (action as u8).into()),
382 ]);
383 if let Some(note) = public_note {
384 properties.insert("note".to_string(), note.into());
385 }
386 properties
387 }
388 TokenEvent::ConfigUpdate(configuration_change_item, public_note) => {
389 let mut properties = BTreeMap::from([
390 ("tokenId".to_string(), token_id.into()),
391 (
392 "changeItemType".to_string(),
393 configuration_change_item.u8_item_index().into(),
394 ),
395 (
396 "changeItem".to_string(),
397 configuration_change_item
398 .serialize_consume_to_bytes_with_platform_version(platform_version)?
399 .into(),
400 ),
401 ]);
402 if let Some(note) = public_note {
403 properties.insert("note".to_string(), note.into());
404 }
405 properties
406 }
407 TokenEvent::Claim(recipient, amount, public_note) => {
408 let (recipient_type, recipient_id, distribution_type) = match recipient {
409 TokenDistributionTypeWithResolvedRecipient::PreProgrammed(identifier) => {
410 (1u8, identifier, 0u8)
411 }
412 TokenDistributionTypeWithResolvedRecipient::Perpetual(
413 TokenDistributionResolvedRecipient::ContractOwnerIdentity(identifier),
414 ) => (0, identifier, 1),
415 TokenDistributionTypeWithResolvedRecipient::Perpetual(
416 TokenDistributionResolvedRecipient::Identity(identifier),
417 ) => (1, identifier, 1),
418 TokenDistributionTypeWithResolvedRecipient::Perpetual(
419 TokenDistributionResolvedRecipient::Evonode(identifier),
420 ) => (2, identifier, 1),
421 };
422
423 let mut properties = BTreeMap::from([
424 ("tokenId".to_string(), token_id.into()),
425 ("recipientType".to_string(), recipient_type.into()),
426 ("recipientId".to_string(), recipient_id.into()),
427 ("distributionType".to_string(), distribution_type.into()),
428 ("amount".to_string(), amount.into()),
429 ]);
430
431 if let Some(note) = public_note {
432 properties.insert("note".to_string(), note.into());
433 }
434 properties
435 }
436 TokenEvent::ChangePriceForDirectPurchase(price, note) => {
437 let mut properties = BTreeMap::from([("tokenId".to_string(), token_id.into())]);
438
439 if let Some(price_schedule) = price {
440 properties.insert(
441 "priceSchedule".to_string(),
442 price_schedule
443 .serialize_consume_to_bytes_with_platform_version(platform_version)?
444 .into(),
445 );
446 }
447
448 if let Some(note) = note {
449 properties.insert("note".to_string(), note.into());
450 }
451
452 properties
453 }
454 TokenEvent::DirectPurchase(amount, total_cost) => BTreeMap::from([
455 ("tokenId".to_string(), token_id.into()),
456 ("tokenAmount".to_string(), amount.into()),
457 ("purchaseCost".to_string(), total_cost.into()),
458 ]),
459 };
460
461 let document: Document = DocumentV0 {
462 id: document_id,
463 owner_id,
464 properties,
465 revision: None,
466 created_at: Some(block_info.time_ms),
467 updated_at: None,
468 transferred_at: None,
469 created_at_block_height: Some(block_info.height),
470 updated_at_block_height: None,
471 transferred_at_block_height: None,
472 created_at_core_block_height: None,
473 updated_at_core_block_height: None,
474 transferred_at_core_block_height: None,
475 creator_id: None,
476 }
477 .into();
478
479 Ok(document)
480 }
481}