multiversx_sc_modules/
subscription.rs

1multiversx_sc::imports!();
2multiversx_sc::derive_imports!();
3
4/// Standard smart contract module for managing a Subscription NFT.
5/// Adaptation of the EIP-5643 for MultiversX, more here https://eips.ethereum.org/EIPS/eip-5643  
6///
7/// This standard is an extension of the MultiversX 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#[type_abi]
28#[derive(TopEncode, TopDecode)]
29pub struct SubscriptionAttributes<T: NestedEncode + NestedDecode + TypeAbi> {
30    pub expiration: u64,
31    pub attributes: T,
32}
33
34#[multiversx_sc::module]
35pub trait SubscriptionModule {
36    // ** NFT and Attributes
37
38    fn create_subscription_nft<T: NestedEncode + NestedDecode + TypeAbi>(
39        &self,
40        token_id: &TokenIdentifier,
41        amount: &BigUint,
42        name: &ManagedBuffer,
43        royalties: &BigUint,
44        hash: &ManagedBuffer,
45        duration: u64,
46        attributes: T,
47        uris: &ManagedVec<ManagedBuffer>,
48    ) -> u64 {
49        let subscription_attributes = SubscriptionAttributes::<T> {
50            expiration: self.blockchain().get_block_timestamp() + duration,
51            attributes,
52        };
53
54        self.send().esdt_nft_create(
55            token_id,
56            amount,
57            name,
58            royalties,
59            hash,
60            &subscription_attributes,
61            uris,
62        )
63    }
64
65    fn update_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
66        &self,
67        id: &TokenIdentifier,
68        nonce: u64,
69        attributes: T,
70    ) {
71        let subscription_attributes = SubscriptionAttributes::<T> {
72            expiration: self.get_subscription::<T>(id, nonce),
73            attributes,
74        };
75
76        self.send()
77            .nft_update_attributes(id, nonce, &subscription_attributes);
78    }
79
80    // @dev should only be called if the nft is owned by the contract
81    fn get_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
82        &self,
83        id: &TokenIdentifier,
84        nonce: u64,
85    ) -> T {
86        let subscription_attributes: SubscriptionAttributes<T> =
87            self.blockchain().get_token_attributes(id, nonce);
88
89        subscription_attributes.attributes
90    }
91
92    // ** Subscription
93
94    #[event("subscriptionUpdate")]
95    fn subscription_update_event(
96        &self,
97        #[indexed] token_id: &ManagedBuffer,
98        #[indexed] token_nonce: u64,
99        #[indexed] expiration: u64,
100    );
101
102    fn renew_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
103        &self,
104        id: &TokenIdentifier,
105        nonce: u64,
106        duration: u64,
107    ) {
108        let time = self.blockchain().get_block_timestamp();
109        let mut subscription_attributes: SubscriptionAttributes<T> =
110            self.blockchain().get_token_attributes(id, nonce);
111        let expiration = subscription_attributes.expiration;
112
113        subscription_attributes.expiration = if expiration > time {
114            expiration + duration
115        } else {
116            time + duration
117        };
118
119        self.send()
120            .nft_update_attributes(id, nonce, &subscription_attributes);
121
122        self.subscription_update_event(
123            id.as_managed_buffer(),
124            nonce,
125            subscription_attributes.expiration,
126        );
127    }
128
129    fn cancel_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
130        &self,
131        id: &TokenIdentifier,
132        nonce: u64,
133    ) {
134        let mut subscription_attributes: SubscriptionAttributes<T> =
135            self.blockchain().get_token_attributes(id, nonce);
136        subscription_attributes.expiration = 0;
137
138        self.send()
139            .nft_update_attributes(id, nonce, &subscription_attributes);
140
141        self.subscription_update_event(id.as_managed_buffer(), nonce, 0);
142    }
143
144    // @dev should only be called if the nft is owned by the contract
145    fn get_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
146        &self,
147        id: &TokenIdentifier,
148        nonce: u64,
149    ) -> u64 {
150        let subscription_attributes: SubscriptionAttributes<T> =
151            self.blockchain().get_token_attributes(id, nonce);
152
153        subscription_attributes.expiration
154    }
155}