multiversx_sc_modules/
subscription.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
multiversx_sc::imports!();
multiversx_sc::derive_imports!();

/// Standard smart contract module for managing a Subscription NFT.
/// Adaptation of the EIP-5643 for MultiversX, more here https://eips.ethereum.org/EIPS/eip-5643  
///
/// This standard is an extension of the MultiversX NFT standard.
/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions.
/// The interface includes functions to renew and cancel the subscription.
///
/// Since the NFT standard only has one field for adding arbitrary data (attributes),
/// The module also provides functions for creating NFTs with subscription as well as for reading and updating attributes
/// This allows developers to add additional data to the subscription expiration
///
/// Developers should be careful when interacting with custom attributes at the same time as subscription
/// They should exclusively use the functions from this module
/// The use of the generic function for updating nft attributes might result in data loss
///
/// The module provides functions for:
/// * creating a subscription nft
/// * updating custom attributes
/// * getting custom attributes
/// * renewing a subscription
/// * cancelling a subscription
/// * getting the expiration
///
#[type_abi]
#[derive(TopEncode, TopDecode)]
pub struct SubscriptionAttributes<T: NestedEncode + NestedDecode + TypeAbi> {
    pub expiration: u64,
    pub attributes: T,
}

#[multiversx_sc::module]
pub trait SubscriptionModule {
    // ** NFT and Attributes

    fn create_subscription_nft<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        token_id: &TokenIdentifier,
        amount: &BigUint,
        name: &ManagedBuffer,
        royalties: &BigUint,
        hash: &ManagedBuffer,
        duration: u64,
        attributes: T,
        uris: &ManagedVec<ManagedBuffer>,
    ) -> u64 {
        let subscription_attributes = SubscriptionAttributes::<T> {
            expiration: self.blockchain().get_block_timestamp() + duration,
            attributes,
        };

        self.send().esdt_nft_create(
            token_id,
            amount,
            name,
            royalties,
            hash,
            &subscription_attributes,
            uris,
        )
    }

    fn update_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        id: &TokenIdentifier,
        nonce: u64,
        attributes: T,
    ) {
        let subscription_attributes = SubscriptionAttributes::<T> {
            expiration: self.get_subscription::<T>(id, nonce),
            attributes,
        };

        self.send()
            .nft_update_attributes(id, nonce, &subscription_attributes);
    }

    // @dev should only be called if the nft is owned by the contract
    fn get_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        id: &TokenIdentifier,
        nonce: u64,
    ) -> T {
        let subscription_attributes: SubscriptionAttributes<T> =
            self.blockchain().get_token_attributes(id, nonce);

        subscription_attributes.attributes
    }

    // ** Subscription

    #[event("subscriptionUpdate")]
    fn subscription_update_event(
        &self,
        #[indexed] token_id: &ManagedBuffer,
        #[indexed] token_nonce: u64,
        #[indexed] expiration: u64,
    );

    fn renew_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        id: &TokenIdentifier,
        nonce: u64,
        duration: u64,
    ) {
        let time = self.blockchain().get_block_timestamp();
        let mut subscription_attributes: SubscriptionAttributes<T> =
            self.blockchain().get_token_attributes(id, nonce);
        let expiration = subscription_attributes.expiration;

        subscription_attributes.expiration = if expiration > time {
            expiration + duration
        } else {
            time + duration
        };

        self.send()
            .nft_update_attributes(id, nonce, &subscription_attributes);

        self.subscription_update_event(
            id.as_managed_buffer(),
            nonce,
            subscription_attributes.expiration,
        );
    }

    fn cancel_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        id: &TokenIdentifier,
        nonce: u64,
    ) {
        let mut subscription_attributes: SubscriptionAttributes<T> =
            self.blockchain().get_token_attributes(id, nonce);
        subscription_attributes.expiration = 0;

        self.send()
            .nft_update_attributes(id, nonce, &subscription_attributes);

        self.subscription_update_event(id.as_managed_buffer(), nonce, 0);
    }

    // @dev should only be called if the nft is owned by the contract
    fn get_subscription<T: NestedEncode + NestedDecode + TypeAbi>(
        &self,
        id: &TokenIdentifier,
        nonce: u64,
    ) -> u64 {
        let subscription_attributes: SubscriptionAttributes<T> =
            self.blockchain().get_token_attributes(id, nonce);

        subscription_attributes.expiration
    }
}