From 6763cdb52aed5a360d28067cf3ae8768b38860dc Mon Sep 17 00:00:00 2001
From: Thounyy <thouny@tuta.io>
Date: Wed, 27 Sep 2023 10:39:26 +0700
Subject: [PATCH 1/3] add nft subscription module

---
 Cargo.lock                                    |  17 ++
 Cargo.toml                                    |   2 +
 .../examples/nft-subscription/.gitignore      |   7 +
 .../examples/nft-subscription/Cargo.toml      |  21 ++
 .../examples/nft-subscription/meta/Cargo.toml |  13 +
 .../nft-subscription/meta/src/main.rs         |   3 +
 .../examples/nft-subscription/multiversx.json |   3 +
 .../nft-subscription/scenarios/init.scen.json |  78 ++++++
 .../scenarios/mint_nft.scen.json              |  93 +++++++
 .../scenarios/test_subscription.scen.json     | 255 ++++++++++++++++++
 .../examples/nft-subscription/src/lib.rs      |  55 ++++
 .../nft_subscription_scenario_rs_test.rs      |  23 ++
 .../tests/scenario_go_test.rs                 |  20 ++
 .../examples/nft-subscription/wasm/Cargo.lock | 217 +++++++++++++++
 .../examples/nft-subscription/wasm/Cargo.toml |  26 ++
 .../examples/nft-subscription/wasm/src/lib.rs |  36 +++
 contracts/modules/src/lib.rs                  |   1 +
 contracts/modules/src/subscription.rs         |  51 ++++
 18 files changed, 921 insertions(+)
 create mode 100644 contracts/examples/nft-subscription/.gitignore
 create mode 100644 contracts/examples/nft-subscription/Cargo.toml
 create mode 100644 contracts/examples/nft-subscription/meta/Cargo.toml
 create mode 100644 contracts/examples/nft-subscription/meta/src/main.rs
 create mode 100644 contracts/examples/nft-subscription/multiversx.json
 create mode 100644 contracts/examples/nft-subscription/scenarios/init.scen.json
 create mode 100644 contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
 create mode 100644 contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
 create mode 100644 contracts/examples/nft-subscription/src/lib.rs
 create mode 100644 contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs
 create mode 100644 contracts/examples/nft-subscription/tests/scenario_go_test.rs
 create mode 100644 contracts/examples/nft-subscription/wasm/Cargo.lock
 create mode 100644 contracts/examples/nft-subscription/wasm/Cargo.toml
 create mode 100644 contracts/examples/nft-subscription/wasm/src/lib.rs
 create mode 100644 contracts/modules/src/subscription.rs

diff --git a/Cargo.lock b/Cargo.lock
index a2e1cd41fe..60f615f584 100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2300,6 +2300,23 @@ dependencies = [
  "nft-storage-prepay",
 ]
 
+[[package]]
+name = "nft-subscription"
+version = "0.0.0"
+dependencies = [
+ "multiversx-sc",
+ "multiversx-sc-modules",
+ "multiversx-sc-scenario",
+]
+
+[[package]]
+name = "nft-subscription-meta"
+version = "0.0.0"
+dependencies = [
+ "multiversx-sc-meta",
+ "nft-subscription",
+]
+
 [[package]]
 name = "nibble_vec"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 3e2a20a57d..ef92a8a7b9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -82,6 +82,8 @@ members = [
   "contracts/examples/multisig/interact",
   "contracts/examples/nft-minter",
   "contracts/examples/nft-minter/meta",
+  "contracts/examples/nft-subscription",
+  "contracts/examples/nft-subscription/meta",
   "contracts/examples/nft-storage-prepay",
   "contracts/examples/nft-storage-prepay/meta",
   "contracts/examples/order-book/factory",
diff --git a/contracts/examples/nft-subscription/.gitignore b/contracts/examples/nft-subscription/.gitignore
new file mode 100644
index 0000000000..9494cb146e
--- /dev/null
+++ b/contracts/examples/nft-subscription/.gitignore
@@ -0,0 +1,7 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+*/target/
+
+# The mxpy output
+output
diff --git a/contracts/examples/nft-subscription/Cargo.toml b/contracts/examples/nft-subscription/Cargo.toml
new file mode 100644
index 0000000000..f97cd8b819
--- /dev/null
+++ b/contracts/examples/nft-subscription/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "nft-subscription"
+version = "0.0.0"
+authors = ["Thouny <thouny@tuta.io>"]
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies.multiversx-sc]
+version = "0.43.4"
+path = "../../../framework/base"
+
+[dependencies.multiversx-sc-modules]
+version = "0.43.4"
+path = "../../../contracts/modules"
+
+[dev-dependencies.multiversx-sc-scenario]
+version = "0.43.4"
+path = "../../../framework/scenario"
diff --git a/contracts/examples/nft-subscription/meta/Cargo.toml b/contracts/examples/nft-subscription/meta/Cargo.toml
new file mode 100644
index 0000000000..5eed1a06b6
--- /dev/null
+++ b/contracts/examples/nft-subscription/meta/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "nft-subscription-meta"
+version = "0.0.0"
+authors = ["Thouny <thouny@tuta.io>"]
+edition = "2021"
+publish = false
+
+[dependencies.nft-subscription]
+path = ".."
+
+[dependencies.multiversx-sc-meta]
+version = "0.43.4"
+path = "../../../../framework/meta"
diff --git a/contracts/examples/nft-subscription/meta/src/main.rs b/contracts/examples/nft-subscription/meta/src/main.rs
new file mode 100644
index 0000000000..27c3b0f0ad
--- /dev/null
+++ b/contracts/examples/nft-subscription/meta/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+    multiversx_sc_meta::cli_main::<nft_subscription::AbiProvider>();
+}
diff --git a/contracts/examples/nft-subscription/multiversx.json b/contracts/examples/nft-subscription/multiversx.json
new file mode 100644
index 0000000000..7365539625
--- /dev/null
+++ b/contracts/examples/nft-subscription/multiversx.json
@@ -0,0 +1,3 @@
+{
+    "language": "rust"
+}
\ No newline at end of file
diff --git a/contracts/examples/nft-subscription/scenarios/init.scen.json b/contracts/examples/nft-subscription/scenarios/init.scen.json
new file mode 100644
index 0000000000..40a0f7da51
--- /dev/null
+++ b/contracts/examples/nft-subscription/scenarios/init.scen.json
@@ -0,0 +1,78 @@
+{
+    "name": "init",
+    "steps": [
+        {
+            "step": "setState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "0",
+                    "balance": "0"
+                },
+                "address:buyer": {
+                    "nonce": "0",
+                    "balance": "1000"
+                }
+            },
+            "newAddresses": [
+                {
+                    "creatorAddress": "address:owner",
+                    "creatorNonce": "0",
+                    "newAddress": "sc:nft-subscription"
+                }
+            ]
+        },
+        {
+            "step": "scDeploy",
+            "id": "deploy",
+            "tx": {
+                "from": "address:owner",
+                "contractCode": "file:../output/nft-subscription.wasm",
+                "arguments": [],
+                "gasLimit": "10,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "logs": [],
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "sc:nft-subscription": {
+                    "nonce": "0",
+                    "balance": "0",
+                    "storage": {},
+                    "code": "file:../output/nft-subscription.wasm"
+                },
+                "+": ""
+            }
+        },
+        {
+            "step": "setState",
+            "accounts": {
+                "sc:nft-subscription": {
+                    "nonce": "0",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "lastNonce": "0",
+                            "roles": [
+                                "ESDTRoleNFTCreate",
+                                "ESDTRoleNFTUpdateAttributes"
+                            ]
+                        }
+                    },
+                    "storage": {
+                        "str:tokenId": "str:NFT-123456"
+                    },
+                    "code": "file:../output/nft-subscription.wasm",
+                    "owner": "address:owner"
+                }
+            }
+        }
+    ]
+}
diff --git a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
new file mode 100644
index 0000000000..045284cc60
--- /dev/null
+++ b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
@@ -0,0 +1,93 @@
+{
+    "name": "mint nfts",
+    "steps": [
+        {
+            "step": "externalSteps",
+            "path": "init.scen.json"
+        },
+        {
+            "step": "scCall",
+            "id": "create-NFT-1",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "function": "mint",
+                "arguments": [],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "scCall",
+            "id": "create-NFT-2",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "function": "mint",
+                "arguments": [],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "3",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                },
+                                {
+                                    "nonce": "2",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                }
+                            ]
+                        }
+                    }
+                },
+                "sc:nft-subscription": {
+                    "nonce": "0",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "lastNonce": "2",
+                            "roles": [
+                                "ESDTRoleNFTCreate",
+                                "ESDTRoleNFTUpdateAttributes"
+                            ]
+                        }
+                    },
+                    "storage": {
+                        "str:tokenId": "str:NFT-123456"
+                    },
+                    "code": "file:../output/nft-subscription.wasm",
+                    "owner": "address:owner"
+                },
+                "+": ""
+            }
+        }
+    ]
+}
diff --git a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
new file mode 100644
index 0000000000..8820a92b2b
--- /dev/null
+++ b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
@@ -0,0 +1,255 @@
+{
+    "name": "subscription",
+    "steps": [
+        {
+            "step": "externalSteps",
+            "path": "mint_nft.scen.json"
+        },
+        {
+            "step": "setState",
+            "currentBlockInfo": {
+                "blockTimestamp": "1"
+            }
+        },
+        {
+            "step": "scCall",
+            "id": "add-subscription",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "esdtValue": [
+                    {
+                        "tokenIdentifier": "str:NFT-123456",
+                        "nonce": "1",
+                        "value": "1"
+                    }
+                ],
+                "function": "renew",
+                "arguments": [
+                    "2"
+                ],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "*",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "3"
+                                },
+                                {
+                                    "nonce": "2",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                }
+                            ]
+                        }
+                    }
+                },
+                "+": ""
+            }
+        },
+        {
+            "step": "setState",
+            "currentBlockInfo": {
+                "blockTimestamp": "2"
+            }
+        },
+        {
+            "step": "scCall",
+            "id": "renew-subscription-not-ended",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "esdtValue": [
+                    {
+                        "tokenIdentifier": "str:NFT-123456",
+                        "nonce": "1",
+                        "value": "1"
+                    }
+                ],
+                "function": "renew",
+                "arguments": [
+                    "3"
+                ],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "*",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "6"
+                                },
+                                {
+                                    "nonce": "2",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                }
+                            ]
+                        }
+                    }
+                },
+                "+": ""
+            }
+        },
+        {
+            "step": "setState",
+            "currentBlockInfo": {
+                "blockTimestamp": "10"
+            }
+        },
+        {
+            "step": "scCall",
+            "id": "renew-subscription-already-ended",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "esdtValue": [
+                    {
+                        "tokenIdentifier": "str:NFT-123456",
+                        "nonce": "1",
+                        "value": "1"
+                    }
+                ],
+                "function": "renew",
+                "arguments": [
+                    "5"
+                ],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "*",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "15"
+                                },
+                                {
+                                    "nonce": "2",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                }
+                            ]
+                        }
+                    }
+                },
+                "+": ""
+            }
+        },
+        {
+            "step": "setState",
+            "currentBlockInfo": {
+                "blockTimestamp": "11"
+            }
+        },
+        {
+            "step": "scCall",
+            "id": "cancel-subscription",
+            "tx": {
+                "from": "address:owner",
+                "to": "sc:nft-subscription",
+                "esdtValue": [
+                    {
+                        "tokenIdentifier": "str:NFT-123456",
+                        "nonce": "1",
+                        "value": "1"
+                    }
+                ],
+                "function": "cancel",
+                "arguments": [],
+                "gasLimit": "20,000,000",
+                "gasPrice": "0"
+            },
+            "expect": {
+                "out": [],
+                "status": "0",
+                "message": "",
+                "gas": "*",
+                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "*",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                },
+                                {
+                                    "nonce": "2",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": "0"
+                                }
+                            ]
+                        }
+                    }
+                },
+                "+": ""
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs
new file mode 100644
index 0000000000..9120206671
--- /dev/null
+++ b/contracts/examples/nft-subscription/src/lib.rs
@@ -0,0 +1,55 @@
+#![no_std]
+
+multiversx_sc::imports!();
+multiversx_sc::derive_imports!();
+
+use multiversx_sc_modules::default_issue_callbacks;
+use multiversx_sc_modules::subscription;
+
+#[multiversx_sc::contract]
+pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule {
+    #[init]
+    fn init(&self) {}
+
+    #[endpoint]
+    fn issue(&self) {
+        self.token_id().issue_and_set_all_roles(
+            EsdtTokenType::NonFungible, 
+            self.call_value().egld_value().clone_value(), 
+            ManagedBuffer::from(b"Subscription"), 
+            ManagedBuffer::from(b"SUB"), 
+            0, 
+            None
+        )
+    }
+
+    #[endpoint]
+    fn mint(&self) {
+        self.token_id().nft_create_and_send(&self.blockchain().get_caller(), BigUint::from(1u8), &0u64);
+    }
+
+    #[payable("*")]
+    #[endpoint]
+    fn renew(&self, duration: u64) {
+        let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
+        self.renew_subscription(&id, nonce, duration);
+        self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8));
+    }
+
+    #[payable("*")]
+    #[endpoint]
+    fn cancel(&self) {
+        let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
+        self.cancel_subscription(&id, nonce);
+        self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8));
+    }
+
+    #[view]
+    fn expires(&self, id: &TokenIdentifier, nonce: u64) -> u64 {
+        self.expires_at(id, nonce)
+    }
+
+    #[storage_mapper("tokenId")]
+    fn token_id(&self) -> NonFungibleTokenMapper;
+
+}
diff --git a/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs b/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs
new file mode 100644
index 0000000000..d37faa2bca
--- /dev/null
+++ b/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs
@@ -0,0 +1,23 @@
+use multiversx_sc_scenario::*;
+
+fn world() -> ScenarioWorld {
+    todo!()
+}
+
+#[test]
+#[ignore = "not supported"]
+fn test_subscription_rs() {
+    world().run("scenarios/test_subscription.scen.json");
+}
+
+#[test]
+#[ignore = "not supported"]
+fn mint_nft_rs() {
+    world().run("scenarios/mint_nft.scen.json");
+}
+
+#[test]
+#[ignore = "not supported"]
+fn init_rs() {
+    world().run("scenarios/init.scen.json");
+}
diff --git a/contracts/examples/nft-subscription/tests/scenario_go_test.rs b/contracts/examples/nft-subscription/tests/scenario_go_test.rs
new file mode 100644
index 0000000000..504c7992b0
--- /dev/null
+++ b/contracts/examples/nft-subscription/tests/scenario_go_test.rs
@@ -0,0 +1,20 @@
+use multiversx_sc_scenario::*;
+
+fn world() -> ScenarioWorld {
+    ScenarioWorld::vm_go()
+}
+
+#[test]
+fn test_subscription_go() {
+    world().run("scenarios/test_subscription.scen.json");
+}
+
+#[test]
+fn mint_nft_go() {
+    world().run("scenarios/mint_nft.scen.json");
+}
+
+#[test]
+fn init_go() {
+    world().run("scenarios/init.scen.json");
+}
diff --git a/contracts/examples/nft-subscription/wasm/Cargo.lock b/contracts/examples/nft-subscription/wasm/Cargo.lock
new file mode 100644
index 0000000000..f618f1a62d
--- /dev/null
+++ b/contracts/examples/nft-subscription/wasm/Cargo.lock
@@ -0,0 +1,217 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
+
+[[package]]
+name = "multiversx-sc"
+version = "0.43.4"
+dependencies = [
+ "bitflags",
+ "hashbrown",
+ "hex-literal",
+ "multiversx-sc-codec",
+ "multiversx-sc-derive",
+ "num-traits",
+]
+
+[[package]]
+name = "multiversx-sc-codec"
+version = "0.18.1"
+dependencies = [
+ "arrayvec",
+ "multiversx-sc-codec-derive",
+]
+
+[[package]]
+name = "multiversx-sc-codec-derive"
+version = "0.18.1"
+dependencies = [
+ "hex",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "multiversx-sc-derive"
+version = "0.43.4"
+dependencies = [
+ "hex",
+ "proc-macro2",
+ "quote",
+ "radix_trie",
+ "syn",
+]
+
+[[package]]
+name = "multiversx-sc-modules"
+version = "0.43.4"
+dependencies = [
+ "multiversx-sc",
+]
+
+[[package]]
+name = "multiversx-sc-wasm-adapter"
+version = "0.43.4"
+dependencies = [
+ "multiversx-sc",
+]
+
+[[package]]
+name = "nft-subscription"
+version = "0.0.0"
+dependencies = [
+ "multiversx-sc",
+ "multiversx-sc-modules",
+]
+
+[[package]]
+name = "nft-subscription-wasm"
+version = "0.0.0"
+dependencies = [
+ "multiversx-sc-wasm-adapter",
+ "nft-subscription",
+]
+
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
diff --git a/contracts/examples/nft-subscription/wasm/Cargo.toml b/contracts/examples/nft-subscription/wasm/Cargo.toml
new file mode 100644
index 0000000000..4cfb2cf9a4
--- /dev/null
+++ b/contracts/examples/nft-subscription/wasm/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "nft-subscription-wasm"
+version = "0.0.0"
+authors = ["Thouny <thouny@tuta.io>"]
+edition = "2021"
+publish = false
+
+[lib]
+crate-type = ["cdylib"]
+
+[profile.release]
+codegen-units = 1
+opt-level = "z"
+lto = true
+debug = false
+panic = "abort"
+
+[dependencies.nft-subscription]
+path = ".."
+
+[dependencies.multiversx-sc-wasm-adapter]
+version = "0.43.4"
+path = "../../../../framework/wasm-adapter"
+
+[workspace]
+members = ["."]
diff --git a/contracts/examples/nft-subscription/wasm/src/lib.rs b/contracts/examples/nft-subscription/wasm/src/lib.rs
new file mode 100644
index 0000000000..eba09fdde8
--- /dev/null
+++ b/contracts/examples/nft-subscription/wasm/src/lib.rs
@@ -0,0 +1,36 @@
+// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT.
+
+////////////////////////////////////////////////////
+////////////////// AUTO-GENERATED //////////////////
+////////////////////////////////////////////////////
+
+// Init:                                 1
+// Endpoints:                            8
+// Async Callback:                       1
+// Total number of exported functions:  10
+
+#![no_std]
+
+// Configuration that works with rustc < 1.73.0.
+// TODO: Recommended rustc version: 1.73.0 or newer.
+#![feature(lang_items)]
+
+multiversx_sc_wasm_adapter::allocator!();
+multiversx_sc_wasm_adapter::panic_handler!();
+
+multiversx_sc_wasm_adapter::endpoints! {
+    nft_subscription
+    (
+        init => init
+        issue => issue
+        mint => mint
+        renew => renew
+        cancel => cancel
+        expires => expires
+        renewSubscription => renew_subscription
+        cancelSubscription => cancel_subscription
+        getExpiresAt => expires_at
+    )
+}
+
+multiversx_sc_wasm_adapter::async_callback! { nft_subscription }
diff --git a/contracts/modules/src/lib.rs b/contracts/modules/src/lib.rs
index 3229e4f742..bb21e297eb 100644
--- a/contracts/modules/src/lib.rs
+++ b/contracts/modules/src/lib.rs
@@ -15,3 +15,4 @@ pub mod staking;
 pub mod token_merge;
 pub mod transfer_role_proxy;
 pub mod users;
+pub mod subscription;
diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs
new file mode 100644
index 0000000000..5fb1a5e938
--- /dev/null
+++ b/contracts/modules/src/subscription.rs
@@ -0,0 +1,51 @@
+multiversx_sc::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 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.
+///
+/// It provides functions for:
+/// * renewing a subscription
+/// * cancelling a subscription
+/// * getting the expiration
+///
+#[multiversx_sc::module]
+pub trait SubscriptionModule {
+    #[event("subscriptionUpdate")]
+    fn subscription_update_event(
+        &self,
+        #[indexed] token_id: &ManagedBuffer,
+        #[indexed] token_nonce: u64,
+        #[indexed] expiration: u64,
+    );
+
+    #[endpoint(renewSubscription)]
+    fn renew_subscription(&self, id: &TokenIdentifier, nonce: u64, duration: u64) {
+        let expiration = self.expires_at(id, nonce);
+        let time = self.blockchain().get_block_timestamp();
+
+        let new_expiration = if expiration > time {
+            expiration + duration
+        } else {
+            time + duration
+        };
+
+        self.send().nft_update_attributes(id, nonce, &new_expiration);
+        self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration);
+    }
+
+	#[endpoint(cancelSubscription)]
+    fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) {
+        self.send().nft_update_attributes(id, nonce, &0);
+        self.subscription_update_event(id.as_managed_buffer(), nonce, 0);
+    }
+
+    // @dev should only be called if the nft is owned by the contract
+	#[view(getExpiresAt)]
+    fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 {
+        self.blockchain().get_token_attributes(id, nonce)
+    }
+}
\ No newline at end of file

From b77ac19416d3cebc138566e05f8745bf697ad5cb Mon Sep 17 00:00:00 2001
From: Thounyy <thouny@tuta.io>
Date: Thu, 28 Sep 2023 11:39:51 +0700
Subject: [PATCH 2/3] cargo fmt

---
 .../examples/nft-subscription/src/lib.rs      | 40 +++++++++++++------
 contracts/modules/src/lib.rs                  |  2 +-
 contracts/modules/src/subscription.rs         | 15 +++----
 3 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs
index 9120206671..6394556e83 100644
--- a/contracts/examples/nft-subscription/src/lib.rs
+++ b/contracts/examples/nft-subscription/src/lib.rs
@@ -3,29 +3,34 @@
 multiversx_sc::imports!();
 multiversx_sc::derive_imports!();
 
-use multiversx_sc_modules::default_issue_callbacks;
-use multiversx_sc_modules::subscription;
+use multiversx_sc_modules::{default_issue_callbacks, subscription};
 
 #[multiversx_sc::contract]
-pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule {
+pub trait NftSubscription:
+    default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule
+{
     #[init]
     fn init(&self) {}
 
     #[endpoint]
     fn issue(&self) {
         self.token_id().issue_and_set_all_roles(
-            EsdtTokenType::NonFungible, 
-            self.call_value().egld_value().clone_value(), 
-            ManagedBuffer::from(b"Subscription"), 
-            ManagedBuffer::from(b"SUB"), 
-            0, 
-            None
+            EsdtTokenType::NonFungible,
+            self.call_value().egld_value().clone_value(),
+            ManagedBuffer::from(b"Subscription"),
+            ManagedBuffer::from(b"SUB"),
+            0,
+            None,
         )
     }
 
     #[endpoint]
     fn mint(&self) {
-        self.token_id().nft_create_and_send(&self.blockchain().get_caller(), BigUint::from(1u8), &0u64);
+        self.token_id().nft_create_and_send(
+            &self.blockchain().get_caller(),
+            BigUint::from(1u8),
+            &0u64,
+        );
     }
 
     #[payable("*")]
@@ -33,7 +38,12 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule
     fn renew(&self, duration: u64) {
         let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
         self.renew_subscription(&id, nonce, duration);
-        self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8));
+        self.send().direct_esdt(
+            &self.blockchain().get_caller(),
+            &id,
+            nonce,
+            &BigUint::from(1u8),
+        );
     }
 
     #[payable("*")]
@@ -41,7 +51,12 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule
     fn cancel(&self) {
         let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
         self.cancel_subscription(&id, nonce);
-        self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8));
+        self.send().direct_esdt(
+            &self.blockchain().get_caller(),
+            &id,
+            nonce,
+            &BigUint::from(1u8),
+        );
     }
 
     #[view]
@@ -51,5 +66,4 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule
 
     #[storage_mapper("tokenId")]
     fn token_id(&self) -> NonFungibleTokenMapper;
-
 }
diff --git a/contracts/modules/src/lib.rs b/contracts/modules/src/lib.rs
index bb21e297eb..b1973e3fc0 100644
--- a/contracts/modules/src/lib.rs
+++ b/contracts/modules/src/lib.rs
@@ -12,7 +12,7 @@ pub mod ongoing_operation;
 pub mod only_admin;
 pub mod pause;
 pub mod staking;
+pub mod subscription;
 pub mod token_merge;
 pub mod transfer_role_proxy;
 pub mod users;
-pub mod subscription;
diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs
index 5fb1a5e938..68c05a57a6 100644
--- a/contracts/modules/src/subscription.rs
+++ b/contracts/modules/src/subscription.rs
@@ -2,9 +2,9 @@ multiversx_sc::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 MultiversX NFT standard. 
-/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. 
+///
+/// This standard is an extension of 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.
 ///
 /// It provides functions for:
@@ -33,19 +33,20 @@ pub trait SubscriptionModule {
             time + duration
         };
 
-        self.send().nft_update_attributes(id, nonce, &new_expiration);
+        self.send()
+            .nft_update_attributes(id, nonce, &new_expiration);
         self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration);
     }
 
-	#[endpoint(cancelSubscription)]
+    #[endpoint(cancelSubscription)]
     fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) {
         self.send().nft_update_attributes(id, nonce, &0);
         self.subscription_update_event(id.as_managed_buffer(), nonce, 0);
     }
 
     // @dev should only be called if the nft is owned by the contract
-	#[view(getExpiresAt)]
+    #[view(getExpiresAt)]
     fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 {
         self.blockchain().get_token_attributes(id, nonce)
     }
-}
\ No newline at end of file
+}

From d226f58ecba373adf50bb5dc2989a1357929dad5 Mon Sep 17 00:00:00 2001
From: Thounyy <thouny@tuta.io>
Date: Wed, 4 Oct 2023 20:15:48 +0700
Subject: [PATCH 3/3] enable any custom attributes in addition to the
 subscription

---
 .../scenarios/mint_nft.scen.json              |  85 ++++++------
 .../scenarios/test_subscription.scen.json     |  72 +++-------
 .../examples/nft-subscription/src/lib.rs      |  39 ++++--
 .../examples/nft-subscription/wasm/src/lib.rs |   9 +-
 contracts/modules/src/subscription.rs         | 130 ++++++++++++++++--
 5 files changed, 206 insertions(+), 129 deletions(-)

diff --git a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
index 045284cc60..b51d81fb3c 100644
--- a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
+++ b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json
@@ -1,5 +1,5 @@
 {
-    "name": "mint nfts",
+    "name": "mint and update nft",
     "steps": [
         {
             "step": "externalSteps",
@@ -15,32 +15,52 @@
                 "arguments": [],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
+            }
+        },
+        {
+            "step": "checkState",
+            "accounts": {
+                "address:owner": {
+                    "nonce": "2",
+                    "balance": "0",
+                    "esdt": {
+                        "str:NFT-123456": {
+                            "instances": [
+                                {
+                                    "nonce": "1",
+                                    "balance": "1",
+                                    "creator": "sc:nft-subscription",
+                                    "attributes": {
+                                        "0-expiration": "u64:0",
+                                        "1-attributes": "nested:str:common"
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                },
+                "+": ""
             }
         },
         {
             "step": "scCall",
-            "id": "create-NFT-2",
+            "id": "update-NFT-1",
             "tx": {
                 "from": "address:owner",
                 "to": "sc:nft-subscription",
-                "function": "mint",
-                "arguments": [],
+                "esdtValue": [
+                    {
+                        "tokenIdentifier": "str:NFT-123456",
+                        "nonce": "1",
+                        "value": "1"
+                    }
+                ],
+                "function": "update_attributes",
+                "arguments": [
+                    "str:rare"
+                ],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
             }
         },
         {
@@ -56,36 +76,15 @@
                                     "nonce": "1",
                                     "balance": "1",
                                     "creator": "sc:nft-subscription",
-                                    "attributes": "0"
-                                },
-                                {
-                                    "nonce": "2",
-                                    "balance": "1",
-                                    "creator": "sc:nft-subscription",
-                                    "attributes": "0"
+                                    "attributes": {
+                                        "0-expiration": "u64:0",
+                                        "1-attributes": "nested:str:rare"
+                                    }
                                 }
                             ]
                         }
                     }
                 },
-                "sc:nft-subscription": {
-                    "nonce": "0",
-                    "balance": "0",
-                    "esdt": {
-                        "str:NFT-123456": {
-                            "lastNonce": "2",
-                            "roles": [
-                                "ESDTRoleNFTCreate",
-                                "ESDTRoleNFTUpdateAttributes"
-                            ]
-                        }
-                    },
-                    "storage": {
-                        "str:tokenId": "str:NFT-123456"
-                    },
-                    "code": "file:../output/nft-subscription.wasm",
-                    "owner": "address:owner"
-                },
                 "+": ""
             }
         }
diff --git a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
index 8820a92b2b..7948b03bcc 100644
--- a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
+++ b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json
@@ -30,13 +30,6 @@
                 ],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
             }
         },
         {
@@ -52,13 +45,10 @@
                                     "nonce": "1",
                                     "balance": "1",
                                     "creator": "sc:nft-subscription",
-                                    "attributes": "3"
-                                },
-                                {
-                                    "nonce": "2",
-                                    "balance": "1",
-                                    "creator": "sc:nft-subscription",
-                                    "attributes": "0"
+                                    "attributes": {
+                                        "0-expiration": "u64:3",
+                                        "1-attributes": "nested:str:rare"
+                                    }
                                 }
                             ]
                         }
@@ -92,13 +82,6 @@
                 ],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
             }
         },
         {
@@ -114,13 +97,10 @@
                                     "nonce": "1",
                                     "balance": "1",
                                     "creator": "sc:nft-subscription",
-                                    "attributes": "6"
-                                },
-                                {
-                                    "nonce": "2",
-                                    "balance": "1",
-                                    "creator": "sc:nft-subscription",
-                                    "attributes": "0"
+                                    "attributes": {
+                                        "0-expiration": "u64:6",
+                                        "1-attributes": "nested:str:rare"
+                                    }
                                 }
                             ]
                         }
@@ -154,13 +134,6 @@
                 ],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
             }
         },
         {
@@ -176,13 +149,10 @@
                                     "nonce": "1",
                                     "balance": "1",
                                     "creator": "sc:nft-subscription",
-                                    "attributes": "15"
-                                },
-                                {
-                                    "nonce": "2",
-                                    "balance": "1",
-                                    "creator": "sc:nft-subscription",
-                                    "attributes": "0"
+                                    "attributes": {
+                                        "0-expiration": "u64:15",
+                                        "1-attributes": "nested:str:rare"
+                                    }
                                 }
                             ]
                         }
@@ -214,13 +184,6 @@
                 "arguments": [],
                 "gasLimit": "20,000,000",
                 "gasPrice": "0"
-            },
-            "expect": {
-                "out": [],
-                "status": "0",
-                "message": "",
-                "gas": "*",
-                "refund": "*"
             }
         },
         {
@@ -236,13 +199,10 @@
                                     "nonce": "1",
                                     "balance": "1",
                                     "creator": "sc:nft-subscription",
-                                    "attributes": "0"
-                                },
-                                {
-                                    "nonce": "2",
-                                    "balance": "1",
-                                    "creator": "sc:nft-subscription",
-                                    "attributes": "0"
+                                    "attributes": {
+                                        "0-expiration": "u64:0",
+                                        "1-attributes": "nested:str:rare"
+                                    }
                                 }
                             ]
                         }
diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs
index 6394556e83..b9c4fcdb8d 100644
--- a/contracts/examples/nft-subscription/src/lib.rs
+++ b/contracts/examples/nft-subscription/src/lib.rs
@@ -26,18 +26,29 @@ pub trait NftSubscription:
 
     #[endpoint]
     fn mint(&self) {
-        self.token_id().nft_create_and_send(
+        let nonce = self.create_subscription_nft(
+            self.token_id().get_token_id_ref(),
+            &BigUint::from(1u8),
+            &ManagedBuffer::new(),
+            &BigUint::from(0u8),
+            &ManagedBuffer::new(),
+            0,
+            ManagedBuffer::from(b"common"),
+            &ManagedVec::new(),
+        );
+        self.send().direct_esdt(
             &self.blockchain().get_caller(),
-            BigUint::from(1u8),
-            &0u64,
+            self.token_id().get_token_id_ref(),
+            nonce,
+            &BigUint::from(1u8),
         );
     }
 
     #[payable("*")]
     #[endpoint]
-    fn renew(&self, duration: u64) {
+    fn update_attributes(&self, attributes: ManagedBuffer) {
         let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
-        self.renew_subscription(&id, nonce, duration);
+        self.update_subscription_attributes::<ManagedBuffer>(&id, nonce, attributes);
         self.send().direct_esdt(
             &self.blockchain().get_caller(),
             &id,
@@ -48,9 +59,9 @@ pub trait NftSubscription:
 
     #[payable("*")]
     #[endpoint]
-    fn cancel(&self) {
+    fn renew(&self, duration: u64) {
         let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
-        self.cancel_subscription(&id, nonce);
+        self.renew_subscription::<ManagedBuffer>(&id, nonce, duration);
         self.send().direct_esdt(
             &self.blockchain().get_caller(),
             &id,
@@ -59,9 +70,17 @@ pub trait NftSubscription:
         );
     }
 
-    #[view]
-    fn expires(&self, id: &TokenIdentifier, nonce: u64) -> u64 {
-        self.expires_at(id, nonce)
+    #[payable("*")]
+    #[endpoint]
+    fn cancel(&self) {
+        let (id, nonce, _) = self.call_value().single_esdt().into_tuple();
+        self.cancel_subscription::<ManagedBuffer>(&id, nonce);
+        self.send().direct_esdt(
+            &self.blockchain().get_caller(),
+            &id,
+            nonce,
+            &BigUint::from(1u8),
+        );
     }
 
     #[storage_mapper("tokenId")]
diff --git a/contracts/examples/nft-subscription/wasm/src/lib.rs b/contracts/examples/nft-subscription/wasm/src/lib.rs
index eba09fdde8..76a79ce530 100644
--- a/contracts/examples/nft-subscription/wasm/src/lib.rs
+++ b/contracts/examples/nft-subscription/wasm/src/lib.rs
@@ -5,9 +5,9 @@
 ////////////////////////////////////////////////////
 
 // Init:                                 1
-// Endpoints:                            8
+// Endpoints:                            5
 // Async Callback:                       1
-// Total number of exported functions:  10
+// Total number of exported functions:   7
 
 #![no_std]
 
@@ -24,12 +24,9 @@ multiversx_sc_wasm_adapter::endpoints! {
         init => init
         issue => issue
         mint => mint
+        update_attributes => update_attributes
         renew => renew
         cancel => cancel
-        expires => expires
-        renewSubscription => renew_subscription
-        cancelSubscription => cancel_subscription
-        getExpiresAt => expires_at
     )
 }
 
diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs
index 68c05a57a6..3f142abbe4 100644
--- a/contracts/modules/src/subscription.rs
+++ b/contracts/modules/src/subscription.rs
@@ -1,19 +1,95 @@
 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 MultiversX NFT standard.
+/// 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.
 ///
-/// It provides functions for:
+/// 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
 ///
+#[derive(TypeAbi, 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,
@@ -22,31 +98,57 @@ pub trait SubscriptionModule {
         #[indexed] expiration: u64,
     );
 
-    #[endpoint(renewSubscription)]
-    fn renew_subscription(&self, id: &TokenIdentifier, nonce: u64, duration: u64) {
-        let expiration = self.expires_at(id, nonce);
+    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;
 
-        let new_expiration = if expiration > time {
+        subscription_attributes.expiration = if expiration > time {
             expiration + duration
         } else {
             time + duration
         };
 
         self.send()
-            .nft_update_attributes(id, nonce, &new_expiration);
-        self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration);
+            .nft_update_attributes(id, nonce, &subscription_attributes);
+
+        self.subscription_update_event(
+            id.as_managed_buffer(),
+            nonce,
+            subscription_attributes.expiration,
+        );
     }
 
-    #[endpoint(cancelSubscription)]
-    fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) {
-        self.send().nft_update_attributes(id, nonce, &0);
+    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
-    #[view(getExpiresAt)]
-    fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 {
-        self.blockchain().get_token_attributes(id, nonce)
+    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
     }
 }