drt_sc_modules/
subscription.rs

1drt_sc::imports!();
2drt_sc::derive_imports!();
3
4/// Standard smart contract module for managing a Subscription NFT.
5/// Adaptation of the EIP-5643 for Dharitri, more here https://eips.ethereum.org/EIPS/eip-5643  
6///
7/// This standard is an extension of the Dharitri NFT standard.
8/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions.
9/// The interface includes functions to renew and cancel the subscription.
10///
11/// Since the NFT standard only has one field for adding arbitrary data (attributes),
12/// The module also provides functions for creating NFTs with subscription as well as for reading and updating attributes
13/// This allows developers to add additional data to the subscription expiration
14///
15/// Developers should be careful when interacting with custom attributes at the same time as subscription
16/// They should exclusively use the functions from this module
17/// The use of the generic function for updating nft attributes might result in data loss
18///
19/// The module provides functions for:
20/// * creating a subscription nft
21/// * updating custom attributes
22/// * getting custom attributes
23/// * renewing a subscription
24/// * cancelling a subscription
25/// * getting the expiration
26///
27#[derive(TypeAbi, TopEncode, TopDecode)]
28pub struct SubscriptionAttributes<T: NestedEncode + NestedDecode + TypeAbi> {
29    pub expiration: u64,
30    pub attributes: T,
31}
32
33#[drt_sc::module]
34pub trait SubscriptionModule {
35    // ** NFT and Attributes
36
37    fn create_subscription_nft<T: NestedEncode + NestedDecode + TypeAbi>(
38        &self,
39        token_id: &TokenIdentifier,
40        amount: &BigUint,
41        name: &ManagedBuffer,
42        royalties: &BigUint,
43        hash: &ManagedBuffer,
44        duration: u64,
45        attributes: T,
46        uris: &ManagedVec<ManagedBuffer>,
47    ) -> u64 {
48        let subscription_attributes = SubscriptionAttributes::<T> {
49            expiration: self.blockchain().get_block_timestamp() + duration,
50            attributes,
51        };
52
53        self.send().dcdt_nft_create(
54            token_id,
55            amount,
56            name,
57            royalties,
58            hash,
59            &subscription_attributes,
60            uris,
61        )
62    }
63
64    fn update_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
65        &self,
66        id: &TokenIdentifier,
67        nonce: u64,
68        attributes: T,
69    ) {
70        let subscription_attributes = SubscriptionAttributes::<T> {
71            expiration: self.get_subscription::<T>(id, nonce),
72            attributes,
73        };
74
75        self.send()
76            .nft_update_attributes(id, nonce, &subscription_attributes);
77    }
78
79    // @dev should only be called if the nft is owned by the contract
80    fn get_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
81        &self,
82        id: &TokenIdentifier,
83        nonce: u64,
84    ) -> T {
85        let subscription_attributes: SubscriptionAttributes<T> =
86            self.blockchain().get_token_attributes(id, nonce);
87
88        subscription_attributes.attributes
89    }
90
91    // ** Subscription
92
93    #[event("subscriptionUpdate")]
94    fn subscription_update_event(
95        &self,
96        #[indexed] token_id: &ManagedBuffer,
97        #[indexed] token_nonce: u64,
98        #[indexed] expiration: u64,
99    );
100
101    fn renew_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
102        &self,
103        id: &TokenIdentifier,
104        nonce: u64,
105        duration: u64,
106    ) {
107        let time = self.blockchain().get_block_timestamp();
108        let mut subscription_attributes: SubscriptionAttributes<T> =
109            self.blockchain().get_token_attributes(id, nonce);
110        let expiration = subscription_attributes.expiration;
111
112        subscription_attributes.expiration = if expiration > time {
113            expiration + duration
114        } else {
115            time + duration
116        };
117
118        self.send()
119            .nft_update_attributes(id, nonce, &subscription_attributes);
120
121        self.subscription_update_event(
122            id.as_managed_buffer(),
123            nonce,
124            subscription_attributes.expiration,
125        );
126    }
127
128    fn cancel_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
129        &self,
130        id: &TokenIdentifier,
131        nonce: u64,
132    ) {
133        let mut subscription_attributes: SubscriptionAttributes<T> =
134            self.blockchain().get_token_attributes(id, nonce);
135        subscription_attributes.expiration = 0;
136
137        self.send()
138            .nft_update_attributes(id, nonce, &subscription_attributes);
139
140        self.subscription_update_event(id.as_managed_buffer(), nonce, 0);
141    }
142
143    // @dev should only be called if the nft is owned by the contract
144    fn get_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
145        &self,
146        id: &TokenIdentifier,
147        nonce: u64,
148    ) -> u64 {
149        let subscription_attributes: SubscriptionAttributes<T> =
150            self.blockchain().get_token_attributes(id, nonce);
151
152        subscription_attributes.expiration
153    }
154}