@lukso/lsp8-contracts 0.16.7 → 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +54 -4
  3. package/artifacts/IAccessControlExtended.json +285 -0
  4. package/artifacts/ILSP8CappedBalance.json +27 -0
  5. package/artifacts/ILSP8CappedSupply.json +27 -0
  6. package/artifacts/ILSP8IdentifiableDigitalAsset.json +6 -3
  7. package/artifacts/ILSP8Mintable.json +62 -0
  8. package/artifacts/ILSP8NonTransferable.json +110 -0
  9. package/artifacts/ILSP8Revokable.json +75 -0
  10. package/artifacts/LSP8Burnable.json +7 -4
  11. package/artifacts/LSP8BurnableInitAbstract.json +7 -4
  12. package/artifacts/LSP8CappedBalanceAbstract.json +1285 -0
  13. package/artifacts/LSP8CappedBalanceInitAbstract.json +1293 -0
  14. package/artifacts/{LSP8CappedSupply.json → LSP8CappedSupplyAbstract.json} +8 -15
  15. package/artifacts/LSP8CappedSupplyInitAbstract.json +7 -14
  16. package/artifacts/LSP8CustomizableToken.json +1738 -0
  17. package/artifacts/LSP8CustomizableTokenInit.json +1733 -0
  18. package/artifacts/LSP8Enumerable.json +7 -4
  19. package/artifacts/LSP8EnumerableInitAbstract.json +7 -4
  20. package/artifacts/LSP8IdentifiableDigitalAsset.json +6 -3
  21. package/artifacts/LSP8IdentifiableDigitalAssetInitAbstract.json +6 -3
  22. package/artifacts/LSP8Mintable.json +369 -5
  23. package/artifacts/LSP8MintableAbstract.json +1328 -0
  24. package/artifacts/LSP8MintableInit.json +369 -5
  25. package/artifacts/LSP8MintableInitAbstract.json +1336 -0
  26. package/artifacts/LSP8NonTransferableAbstract.json +1367 -0
  27. package/artifacts/LSP8NonTransferableInitAbstract.json +1375 -0
  28. package/artifacts/LSP8RevokableAbstract.json +1317 -0
  29. package/artifacts/LSP8RevokableInitAbstract.json +1325 -0
  30. package/artifacts/LSP8Votes.json +7 -4
  31. package/artifacts/LSP8VotesInitAbstract.json +7 -4
  32. package/contracts/ILSP8IdentifiableDigitalAsset.sol +1 -1
  33. package/contracts/LSP8Constants.sol +1 -1
  34. package/contracts/LSP8Errors.sol +1 -1
  35. package/contracts/LSP8IdentifiableDigitalAsset.sol +73 -114
  36. package/contracts/LSP8IdentifiableDigitalAssetInitAbstract.sol +69 -116
  37. package/contracts/extensions/AccessControlExtended/AccessControlExtendedAbstract.sol +378 -0
  38. package/contracts/extensions/AccessControlExtended/AccessControlExtendedConstants.sol +13 -0
  39. package/contracts/extensions/AccessControlExtended/AccessControlExtendedErrors.sol +23 -0
  40. package/contracts/extensions/AccessControlExtended/AccessControlExtendedInitAbstract.sol +390 -0
  41. package/contracts/extensions/AccessControlExtended/IAccessControlExtended.sol +51 -0
  42. package/contracts/extensions/{LSP8Burnable.sol → LSP8Burnable/LSP8Burnable.sol} +7 -6
  43. package/contracts/extensions/{LSP8BurnableInitAbstract.sol → LSP8Burnable/LSP8BurnableInitAbstract.sol} +7 -6
  44. package/contracts/extensions/LSP8CappedBalance/ILSP8CappedBalance.sol +11 -0
  45. package/contracts/extensions/LSP8CappedBalance/LSP8CappedBalanceAbstract.sol +124 -0
  46. package/contracts/extensions/LSP8CappedBalance/LSP8CappedBalanceErrors.sol +9 -0
  47. package/contracts/extensions/LSP8CappedBalance/LSP8CappedBalanceInitAbstract.sol +174 -0
  48. package/contracts/extensions/LSP8CappedSupply/ILSP8CappedSupply.sol +11 -0
  49. package/contracts/extensions/LSP8CappedSupply/LSP8CappedSupplyAbstract.sol +59 -0
  50. package/contracts/extensions/LSP8CappedSupply/LSP8CappedSupplyErrors.sol +6 -0
  51. package/contracts/extensions/LSP8CappedSupply/LSP8CappedSupplyInitAbstract.sol +97 -0
  52. package/contracts/extensions/{LSP8Enumerable.sol → LSP8Enumerable/LSP8Enumerable.sol} +2 -2
  53. package/contracts/extensions/{LSP8EnumerableInitAbstract.sol → LSP8Enumerable/LSP8EnumerableInitAbstract.sol} +2 -2
  54. package/contracts/extensions/LSP8Mintable/ILSP8Mintable.sol +27 -0
  55. package/contracts/extensions/LSP8Mintable/LSP8MintableAbstract.sol +105 -0
  56. package/contracts/extensions/LSP8Mintable/LSP8MintableErrors.sol +5 -0
  57. package/contracts/extensions/LSP8Mintable/LSP8MintableInitAbstract.sol +155 -0
  58. package/contracts/extensions/LSP8NonTransferable/ILSP8NonTransferable.sol +48 -0
  59. package/contracts/extensions/LSP8NonTransferable/LSP8NonTransferableAbstract.sol +190 -0
  60. package/contracts/extensions/LSP8NonTransferable/LSP8NonTransferableErrors.sol +14 -0
  61. package/contracts/extensions/LSP8NonTransferable/LSP8NonTransferableInitAbstract.sol +246 -0
  62. package/contracts/extensions/LSP8Revokable/ILSP8Revokable.sol +28 -0
  63. package/contracts/extensions/LSP8Revokable/LSP8RevokableAbstract.sol +132 -0
  64. package/contracts/extensions/LSP8Revokable/LSP8RevokableErrors.sol +4 -0
  65. package/contracts/extensions/LSP8Revokable/LSP8RevokableInitAbstract.sol +178 -0
  66. package/contracts/extensions/{LSP8Votes.sol → LSP8Votes/LSP8Votes.sol} +3 -4
  67. package/contracts/extensions/{LSP8VotesConstants.sol → LSP8Votes/LSP8VotesConstants.sol} +1 -1
  68. package/contracts/extensions/{LSP8VotesInitAbstract.sol → LSP8Votes/LSP8VotesInitAbstract.sol} +3 -3
  69. package/contracts/presets/LSP8CustomizableToken.sol +277 -0
  70. package/contracts/presets/LSP8CustomizableTokenConstants.sol +32 -0
  71. package/contracts/presets/LSP8CustomizableTokenInit.sol +318 -0
  72. package/contracts/presets/LSP8Mintable.sol +13 -28
  73. package/contracts/presets/LSP8MintableInit.sol +13 -6
  74. package/dist/abi.cjs +8233 -158
  75. package/dist/abi.d.cts +12004 -323
  76. package/dist/abi.d.mts +12004 -323
  77. package/dist/abi.d.ts +12004 -323
  78. package/dist/abi.mjs +8217 -158
  79. package/dist/constants.cjs +21 -0
  80. package/dist/constants.d.cts +12 -1
  81. package/dist/constants.d.mts +12 -1
  82. package/dist/constants.d.ts +12 -1
  83. package/dist/constants.mjs +16 -1
  84. package/package.json +32 -9
  85. package/contracts/extensions/LSP8CappedSupply.sol +0 -85
  86. package/contracts/extensions/LSP8CappedSupplyInitAbstract.sol +0 -88
  87. package/contracts/presets/ILSP8Mintable.sol +0 -33
  88. package/contracts/presets/LSP8MintableInitAbstract.sol +0 -62
@@ -0,0 +1,178 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.27;
3
+
4
+ // modules
5
+ import {
6
+ OwnableUpgradeable
7
+ } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
8
+ import {
9
+ LSP8IdentifiableDigitalAssetInitAbstract
10
+ } from "../../LSP8IdentifiableDigitalAssetInitAbstract.sol";
11
+ import {
12
+ AccessControlExtendedInitAbstract
13
+ } from "../AccessControlExtended/AccessControlExtendedInitAbstract.sol";
14
+
15
+ // interfaces
16
+ import {ILSP8Revokable} from "./ILSP8Revokable.sol";
17
+
18
+ // errors
19
+ import {
20
+ AccessControlUnauthorizedAccount
21
+ } from "../AccessControlExtended/AccessControlExtendedErrors.sol";
22
+ import {LSP8RevokableFeatureDisabled} from "./LSP8RevokableErrors.sol";
23
+
24
+ /// @title LSP8RevokableInitAbstract
25
+ /// @dev Abstract contract implementing revokable functionality for LSP8 tokens (initializer version).
26
+ /// Allows addresses with the `REVOKER_ROLE` to revoke NFTs from any holder
27
+ /// back to the contract owner or any other address that also has revoke rights.
28
+ ///
29
+ /// This version is for proxy deployments using the initializer pattern.
30
+ ///
31
+ /// Use cases include:
32
+ /// - Memberships: Revoke membership NFTs when they expire or are terminated
33
+ /// - Role badges: Remove role badge NFTs from community members
34
+ /// - Compliance: Freeze or reverse NFTs for regulatory requirements
35
+ /// - Ticketing: Reclaim tickets or access NFTs when conditions are no longer met
36
+ abstract contract LSP8RevokableInitAbstract is
37
+ ILSP8Revokable,
38
+ LSP8IdentifiableDigitalAssetInitAbstract,
39
+ AccessControlExtendedInitAbstract
40
+ {
41
+ bool internal _isRevokable;
42
+
43
+ /// @dev keccak256("REVOKER_ROLE")
44
+ bytes32 public constant REVOKER_ROLE =
45
+ 0xce3f34913921da558f105cefb578d87278debbbd073a8d552b5de0d168deee30;
46
+
47
+ /// @notice Initializes the LSP8Revokable contract with base token params.
48
+ /// @dev Initializes the LSP8IdentifiableDigitalAsset base contract.
49
+ /// @param name_ The name of the token.
50
+ /// @param symbol_ The symbol of the token.
51
+ /// @param newOwner_ The owner of the contract (implicitly a revoker).
52
+ /// @param lsp4TokenType_ The token type (see LSP4).
53
+ /// @param lsp8TokenIdFormat_ The format of tokenIds (= NFTs) that this contract will create.
54
+ /// @param isRevokable_ Whether token revocation is enabled.
55
+ function __LSP8Revokable_init(
56
+ string memory name_,
57
+ string memory symbol_,
58
+ address newOwner_,
59
+ uint256 lsp4TokenType_,
60
+ uint256 lsp8TokenIdFormat_,
61
+ bool isRevokable_
62
+ ) internal virtual onlyInitializing {
63
+ LSP8IdentifiableDigitalAssetInitAbstract._initialize(
64
+ name_,
65
+ symbol_,
66
+ newOwner_,
67
+ lsp4TokenType_,
68
+ lsp8TokenIdFormat_
69
+ );
70
+ __AccessControlExtended_init();
71
+ __LSP8Revokable_init_unchained(isRevokable_);
72
+ }
73
+
74
+ /// @notice Unchained initializer for LSP8Revokable.
75
+ function __LSP8Revokable_init_unchained(
76
+ bool isRevokable_
77
+ ) internal virtual onlyInitializing {
78
+ _isRevokable = isRevokable_;
79
+
80
+ if (isRevokable_) {
81
+ _grantRole(REVOKER_ROLE, owner());
82
+ }
83
+ }
84
+
85
+ /// @inheritdoc ILSP8Revokable
86
+ function isRevokable() public view virtual override returns (bool) {
87
+ return _isRevokable;
88
+ }
89
+
90
+ /// @inheritdoc ILSP8Revokable
91
+ /// @custom:warning Once this function is called, any address holding the `REVOKER_ROLE` will be inoperable.
92
+ /// @custom:info The list of addresses holding the `REVOKER_ROLE` remains populated after the revokable feature is switched off.
93
+ function disableRevokable() public virtual override onlyOwner {
94
+ require(isRevokable(), LSP8RevokableFeatureDisabled());
95
+ _isRevokable = false;
96
+ emit RevokableStatusChanged({enabled: false});
97
+ }
98
+
99
+ /// @inheritdoc ILSP8Revokable
100
+ function revoke(
101
+ address from,
102
+ address to,
103
+ bytes32 tokenId,
104
+ bytes memory data
105
+ ) public virtual override onlyRole(REVOKER_ROLE) {
106
+ require(isRevokable(), LSP8RevokableFeatureDisabled());
107
+ require(
108
+ to == owner() || hasRole(REVOKER_ROLE, to),
109
+ AccessControlUnauthorizedAccount(to, REVOKER_ROLE)
110
+ );
111
+
112
+ // We assume revokers are trusted when specifying revocation destinations.
113
+ // Therefore, we bypass LSP1 receiver checks.
114
+ _transfer(from, to, tokenId, true, data);
115
+ }
116
+
117
+ function supportsInterface(
118
+ bytes4 interfaceId
119
+ )
120
+ public
121
+ view
122
+ virtual
123
+ override(
124
+ AccessControlExtendedInitAbstract,
125
+ LSP8IdentifiableDigitalAssetInitAbstract
126
+ )
127
+ returns (bool)
128
+ {
129
+ return
130
+ AccessControlExtendedInitAbstract.supportsInterface(interfaceId) ||
131
+ LSP8IdentifiableDigitalAssetInitAbstract.supportsInterface(
132
+ interfaceId
133
+ );
134
+ }
135
+
136
+ /// @dev Overridden function to ensure previous revokers do not persist after contract ownership has been transferred.
137
+ /// The only exception is if the old contract owner had the `REVOKER_ROLE`. This role will be given to the new owner.
138
+ ///
139
+ /// @custom:warning This function clears the entire `REVOKER_ROLE` member set.
140
+ /// - Gas cost scales linearly with the number of addresses with the `REVOKER_ROLE`.
141
+ /// - If the number of addresses with the `REVOKER_ROLE` is large, it might consume a lot of gas,
142
+ /// leading the transaction to approach or exceed the block gas limit and fail.
143
+ /// Consider revoking addresses with the `REVOKER_ROLE` in batches in separate transactions to mitigate this.
144
+ function _transferOwnership(
145
+ address newOwner
146
+ )
147
+ internal
148
+ virtual
149
+ override(AccessControlExtendedInitAbstract, OwnableUpgradeable)
150
+ {
151
+ // restore default admin hierarchy so a previously-installed custom admin
152
+ // cannot grant REVOKER_ROLE to new accounts post-transfer
153
+ _setRoleAdmin(REVOKER_ROLE, DEFAULT_ADMIN_ROLE);
154
+
155
+ // Transfer all roles from old owner to new owner first (including the `REVOKER_ROLE`)
156
+ // before clearing the list of revokers.
157
+ super._transferOwnership(newOwner);
158
+
159
+ address[] memory revokers = getRoleMembers(REVOKER_ROLE);
160
+
161
+ // Exclude the new owner from the list of revokers to delete.
162
+ for (uint256 ii = 0; ii < revokers.length; ++ii) {
163
+ address revoker = revokers[ii];
164
+ if (revoker == newOwner) continue;
165
+ _revokeRole(REVOKER_ROLE, revoker);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * @dev This empty reserved space is put in place to allow future versions to add new
171
+ * variables without shifting down storage in the inheritance chain.
172
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
173
+ *
174
+ * @custom:info The size of the `__gap` array is calculated so that the amount of storage used by the contract
175
+ * always adds up to the same number (in this case 50 storage slots).
176
+ */
177
+ uint256[49] private __gap;
178
+ }
@@ -1,10 +1,9 @@
1
- // SPDX-License-Identifier: MIT
2
-
3
- pragma solidity ^0.8.0;
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.27;
4
3
 
5
4
  import {
6
5
  LSP8IdentifiableDigitalAsset
7
- } from "../LSP8IdentifiableDigitalAsset.sol";
6
+ } from "../../LSP8IdentifiableDigitalAsset.sol";
8
7
  import {Votes} from "@openzeppelin/contracts/governance/utils/Votes.sol";
9
8
  import {
10
9
  _TYPEID_LSP8_VOTESDELEGATOR,
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- pragma solidity ^0.8.4;
2
+ pragma solidity ^0.8.27;
3
3
 
4
4
  // keccak256('LSP8Tokens_VotesDelegatorNotification')
5
5
  bytes32 constant _TYPEID_LSP8_VOTESDELEGATOR = 0x2f6d3f668c2e57dbae4c255f2d9e0b69d47a8848d69a2251cce137529e34743e;
@@ -1,9 +1,9 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.27;
3
3
 
4
4
  import {
5
5
  LSP8IdentifiableDigitalAssetInitAbstract
6
- } from "../LSP8IdentifiableDigitalAssetInitAbstract.sol";
6
+ } from "../../LSP8IdentifiableDigitalAssetInitAbstract.sol";
7
7
  import {
8
8
  VotesUpgradeable
9
9
  } from "@openzeppelin/contracts-upgradeable/governance/utils/VotesUpgradeable.sol";
@@ -0,0 +1,277 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.27;
3
+
4
+ // modules
5
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
6
+ import {
7
+ AccessControlExtendedAbstract
8
+ } from "../extensions/AccessControlExtended/AccessControlExtendedAbstract.sol";
9
+ import {
10
+ LSP8IdentifiableDigitalAsset
11
+ } from "../LSP8IdentifiableDigitalAsset.sol";
12
+
13
+ // extensions
14
+ import {LSP8Burnable} from "../extensions/LSP8Burnable/LSP8Burnable.sol";
15
+ import {
16
+ LSP8MintableAbstract
17
+ } from "../extensions/LSP8Mintable/LSP8MintableAbstract.sol";
18
+ import {
19
+ LSP8CappedSupplyAbstract
20
+ } from "../extensions/LSP8CappedSupply/LSP8CappedSupplyAbstract.sol";
21
+ import {
22
+ LSP8CappedBalanceAbstract
23
+ } from "../extensions/LSP8CappedBalance/LSP8CappedBalanceAbstract.sol";
24
+ import {
25
+ LSP8NonTransferableAbstract
26
+ } from "../extensions/LSP8NonTransferable/LSP8NonTransferableAbstract.sol";
27
+ import {
28
+ LSP8RevokableAbstract
29
+ } from "../extensions/LSP8Revokable/LSP8RevokableAbstract.sol";
30
+
31
+ // constants
32
+ import {
33
+ LSP8MintableParams,
34
+ LSP8NonTransferableParams,
35
+ LSP8CappedParams,
36
+ LSP8RevokableParams
37
+ } from "./LSP8CustomizableTokenConstants.sol";
38
+
39
+ // errors
40
+ import {
41
+ LSP8MintDisabled
42
+ } from "../extensions/LSP8Mintable/LSP8MintableErrors.sol";
43
+ import {
44
+ LSP8CappedSupplyCannotMintOverCap
45
+ } from "../extensions/LSP8CappedSupply/LSP8CappedSupplyErrors.sol";
46
+
47
+ /// @title LSP8CustomizableToken
48
+ /// @dev A customizable LSP8 token that implements multiple features and uses role-based exemptions.
49
+ /// Implements {LSP8Burnable} to allow burning.
50
+ /// Implements {LSP8Mintable} to allow minting.
51
+ /// Implements {LSP8CappedSupply} to set total supply cap.
52
+ /// Implements {LSP8CappedBalance} to set balance caps.
53
+ /// Implements {LSP8NonTransferable} to restrict transfers.
54
+ /// Implements {LSP8Revokable} to allow revoking tokens.
55
+ contract LSP8CustomizableToken is
56
+ LSP8Burnable,
57
+ LSP8MintableAbstract,
58
+ LSP8CappedSupplyAbstract,
59
+ LSP8CappedBalanceAbstract,
60
+ LSP8NonTransferableAbstract,
61
+ LSP8RevokableAbstract
62
+ {
63
+ /// @notice Initializes the token with name, symbol, owner, and customizable features.
64
+ /// @dev Sets up minting, balance cap, transfer restrictions and supply cap. Mints initial tokens if specified. Reverts if initialMintTokenIds length exceeds tokenSupplyCap. Inherits constructor logic from parent contracts.
65
+ /// @param name_ The name of the token.
66
+ /// @param symbol_ The symbol of the token.
67
+ /// @param newOwner_ The initial owner of the token.
68
+ /// @param lsp4TokenType_ The LSP4 token type (e.g., 1 for NFT, 2 for Collection).
69
+ /// @param lsp8TokenIdFormat_ The format of tokenIds (= NFTs) that this contract will create.
70
+ /// @param mintableParams Deployment configuration for minting feature (see above).
71
+ /// @param cappedParams Deployment configuration for capped balance and capped supply features (see above).
72
+ /// @param nonTransferableParams Deployment configuration for non-transferable feature (see above).
73
+ /// @param revokableParams Deployment configuration for revokable feature (see above).
74
+ constructor(
75
+ string memory name_,
76
+ string memory symbol_,
77
+ address newOwner_,
78
+ uint256 lsp4TokenType_,
79
+ uint256 lsp8TokenIdFormat_,
80
+ LSP8MintableParams memory mintableParams,
81
+ LSP8CappedParams memory cappedParams,
82
+ LSP8NonTransferableParams memory nonTransferableParams,
83
+ LSP8RevokableParams memory revokableParams
84
+ )
85
+ LSP8IdentifiableDigitalAsset(
86
+ name_,
87
+ symbol_,
88
+ newOwner_,
89
+ lsp4TokenType_,
90
+ lsp8TokenIdFormat_
91
+ )
92
+ AccessControlExtendedAbstract()
93
+ LSP8MintableAbstract(mintableParams.isMintable)
94
+ LSP8CappedSupplyAbstract(cappedParams.tokenSupplyCap)
95
+ LSP8CappedBalanceAbstract(cappedParams.tokenBalanceCap)
96
+ LSP8NonTransferableAbstract(
97
+ nonTransferableParams.transferLockStart,
98
+ nonTransferableParams.transferLockEnd
99
+ )
100
+ LSP8RevokableAbstract(revokableParams.isRevokable)
101
+ {
102
+ _initialMint({
103
+ to: newOwner_,
104
+ initialMintTokenIds: mintableParams.initialMintTokenIds
105
+ });
106
+ }
107
+
108
+ /// @inheritdoc LSP8CappedSupplyAbstract
109
+ /// @notice Returns the token supply cap.
110
+ /// @dev If minting is enabled, returns the configured supply cap defining the maximum number of NFTs that can be minted.
111
+ /// If minting is disabled, returns the current total supply as the effective cap (no more NFTs can be created).
112
+ function tokenSupplyCap() public view virtual override returns (uint256) {
113
+ return isMintable ? super.tokenSupplyCap() : totalSupply();
114
+ }
115
+
116
+ /// @dev Required override to resolve multiple inheritance. Calls every parent {supportsInterface} functions
117
+ /// via `super` to aggregate the interface IDs supported across all inherited modules.
118
+ function supportsInterface(
119
+ bytes4 interfaceId
120
+ )
121
+ public
122
+ view
123
+ virtual
124
+ override(
125
+ LSP8IdentifiableDigitalAsset,
126
+ LSP8CappedBalanceAbstract,
127
+ LSP8MintableAbstract,
128
+ LSP8NonTransferableAbstract,
129
+ LSP8RevokableAbstract
130
+ )
131
+ returns (bool)
132
+ {
133
+ return super.supportsInterface(interfaceId);
134
+ }
135
+
136
+ /// @dev Override to bypass the non transferable check when revokers revoke users' tokens.
137
+ function _nonTransferableCheck(
138
+ address from,
139
+ address to,
140
+ bytes32 tokenId,
141
+ bool force,
142
+ bytes memory data
143
+ ) internal virtual override {
144
+ if (_isRevocationBypass(to)) return;
145
+ super._nonTransferableCheck(from, to, tokenId, force, data);
146
+ }
147
+
148
+ /// @dev Override to bypass the token balance cap check when revokers revoke users' tokens.
149
+ function _tokenBalanceCapCheck(
150
+ address from,
151
+ address to,
152
+ bytes32 tokenId,
153
+ bool force,
154
+ bytes memory data
155
+ ) internal virtual override {
156
+ if (_isRevocationBypass(to)) return;
157
+ super._tokenBalanceCapCheck(from, to, tokenId, force, data);
158
+ }
159
+
160
+ /// @inheritdoc LSP8MintableAbstract
161
+ /// @dev Overridden function to allow minting only if:
162
+ /// - the minting feature is enabled, from {LSP8MintableAbstract}
163
+ /// - the total number of NFTs does not exceed the capped supply after minting, from {LSP8CappedSupplyAbstract}
164
+ function _mint(
165
+ address to,
166
+ bytes32 tokenId,
167
+ bool force,
168
+ bytes memory data
169
+ )
170
+ internal
171
+ virtual
172
+ override(
173
+ LSP8IdentifiableDigitalAsset,
174
+ LSP8MintableAbstract,
175
+ LSP8CappedSupplyAbstract
176
+ )
177
+ {
178
+ require(isMintable, LSP8MintDisabled());
179
+ LSP8CappedSupplyAbstract._mint(to, tokenId, force, data);
180
+ }
181
+
182
+ /// @notice Hook called before a token transfer to enforce restrictions.
183
+ /// @dev Combines checks from {LSP8CappedBalance} and {LSP8NonTransferable}.
184
+ /// - Bypasses {LSP8NonTransferable} checks for senders (`from`) holding the `NON_TRANSFERABLE_BYPASS_ROLE` role.
185
+ /// - Bypasses {LSP8CappedBalance} checks for recipients (`to`) holding the `UNCAPPED_BALANCE_ROLE` role.
186
+ /// - Allows minting (from address(0)) and burning to address(0) regardless of restrictions.
187
+ /// @param from The address sending the token.
188
+ /// @param to The address receiving the token.
189
+ /// @param tokenId The unique identifier of the token being transferred.
190
+ /// @param force Whether to force the transfer.
191
+ /// @param data Additional data for the transfer.
192
+ function _beforeTokenTransfer(
193
+ address from,
194
+ address to,
195
+ bytes32 tokenId,
196
+ bool force,
197
+ bytes memory data
198
+ )
199
+ internal
200
+ virtual
201
+ override(
202
+ LSP8IdentifiableDigitalAsset,
203
+ LSP8CappedBalanceAbstract,
204
+ LSP8NonTransferableAbstract
205
+ )
206
+ {
207
+ super._beforeTokenTransfer(from, to, tokenId, force, data);
208
+ }
209
+
210
+ /// @dev Required override to resolve multiple inheritance. Calls every parent {_transferOwnership} function
211
+ /// via `super` so that each inherited module updates its ownership-dependent state.
212
+ ///
213
+ /// When contract ownership changes, this will:
214
+ /// - clear the admin role for: `MINTER_ROLE`, `REVOKER_ROLE`, `NON_TRANSFERABLE_BYPASS_ROLE`, `UNCAPPED_BALANCE_ROLE`
215
+ /// - clear the list of addresses holding the `REVOKER_ROLE`
216
+ function _transferOwnership(
217
+ address newOwner
218
+ )
219
+ internal
220
+ virtual
221
+ override(
222
+ Ownable,
223
+ LSP8CappedBalanceAbstract,
224
+ LSP8MintableAbstract,
225
+ LSP8NonTransferableAbstract,
226
+ LSP8RevokableAbstract
227
+ )
228
+ {
229
+ super._transferOwnership(newOwner);
230
+ }
231
+
232
+ /// @dev Mint initial NFTs without enforcing check if the token contract is mintable or not.
233
+ /// Enforces the configured capped-supply value directly (not {tokenSupplyCap} when minting is disabled).
234
+ function _initialMint(
235
+ address to,
236
+ bytes32[] memory initialMintTokenIds
237
+ ) private {
238
+ uint256 configuredTokenSupplyCap = LSP8CappedSupplyAbstract
239
+ .tokenSupplyCap();
240
+ bool isCappedSupplyConfigured = configuredTokenSupplyCap > 0;
241
+
242
+ if (isCappedSupplyConfigured) {
243
+ bool mintingExceedsSupplyCap = initialMintTokenIds.length >
244
+ configuredTokenSupplyCap;
245
+
246
+ require(
247
+ !mintingExceedsSupplyCap,
248
+ LSP8CappedSupplyCannotMintOverCap()
249
+ );
250
+ }
251
+
252
+ for (uint256 ii = 0; ii < initialMintTokenIds.length; ++ii) {
253
+ LSP8IdentifiableDigitalAsset._mint(
254
+ to,
255
+ initialMintTokenIds[ii],
256
+ true,
257
+ ""
258
+ );
259
+ }
260
+ }
261
+
262
+ /// @dev Returns whether the current call is a legitimate revocation that should bypass the
263
+ /// {LSP8NonTransferable} and {LSP8CappedBalance} restrictions when revoking tokens from a token holder.
264
+ ///
265
+ /// Returns `true` only when all of the following conditions are met:
266
+ /// - the function being called is {revoke}.
267
+ /// - the revoking feature is enabled.
268
+ /// - the caller (`msg.sender`) holds the `REVOKER_ROLE`.
269
+ /// - the recipient (`to`) is either the contract `owner()` or a revoker (an address holding the `REVOKER_ROLE`).
270
+ function _isRevocationBypass(address to) private view returns (bool) {
271
+ return
272
+ msg.sig == this.revoke.selector &&
273
+ isRevokable() &&
274
+ hasRole(REVOKER_ROLE, msg.sender) &&
275
+ (to == owner() || hasRole(REVOKER_ROLE, to));
276
+ }
277
+ }
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @dev Deployment configuration for minting feature.
5
+ /// @param isMintable True to enable minting after deployment, false to disable it forever.
6
+ /// @param initialMintTokenIds Array of tokenIds to mint to `newOwner_` on deployment.
7
+ struct LSP8MintableParams {
8
+ bool isMintable;
9
+ bytes32[] initialMintTokenIds;
10
+ }
11
+
12
+ /// @dev Deployment configuration for capped balance and capped supply features.
13
+ /// @param tokenBalanceCap The maximum number of NFTs per address, 0 to disable.
14
+ /// @param tokenSupplyCap The maximum total supply of NFTs, 0 to disable.
15
+ struct LSP8CappedParams {
16
+ uint256 tokenBalanceCap;
17
+ uint256 tokenSupplyCap;
18
+ }
19
+
20
+ /// @dev Deployment configuration for non-transferable feature.
21
+ /// @param transferLockStart The start timestamp of the transfer lock period, 0 to disable.
22
+ /// @param transferLockEnd The end timestamp of the transfer lock period, 0 to disable.
23
+ struct LSP8NonTransferableParams {
24
+ uint256 transferLockStart;
25
+ uint256 transferLockEnd;
26
+ }
27
+
28
+ /// @dev Deployment configuration for revokable feature.
29
+ /// @param isRevokable True to enable token revocation after deployment, false to disable it.
30
+ struct LSP8RevokableParams {
31
+ bool isRevokable;
32
+ }