qvtx-developer-kit 1.0.0 → 1.2.0

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 (49) hide show
  1. package/.env.example +108 -0
  2. package/README.md +0 -0
  3. package/abis/QVTXBridge.json +273 -0
  4. package/abis/QVTXDirectPurchase.json +621 -0
  5. package/abis/QVTXGovernance.json +267 -0
  6. package/abis/QVTXNFT.json +370 -0
  7. package/abis/QVTXRewards.json +155 -0
  8. package/abis/QVTXToken.json +311 -0
  9. package/abis/QVTXVesting.json +216 -0
  10. package/abis/index.js +16 -0
  11. package/bin/qvtx-developer-cli.js +99 -0
  12. package/config/index.js +108 -0
  13. package/config/networks.js +247 -0
  14. package/examples/basic-usage.js +39 -0
  15. package/examples/bridge-example.js +123 -0
  16. package/examples/direct-purchase.js +300 -0
  17. package/examples/governance-example.js +140 -0
  18. package/examples/nft-example.js +141 -0
  19. package/examples/staking-example.js +96 -0
  20. package/index.js +145 -0
  21. package/languages/blockchain_ai_sdk.js +0 -0
  22. package/languages/node_sdk.js +0 -0
  23. package/languages/solana_sdk.js +383 -0
  24. package/package.json +30 -3
  25. package/purchase/index.js +567 -0
  26. package/rewards/index.js +71 -0
  27. package/smart-contracts/QVTXBridge.sol +305 -0
  28. package/smart-contracts/QVTXDirectPurchase.sol +543 -0
  29. package/smart-contracts/QVTXGovernance.sol +325 -0
  30. package/smart-contracts/QVTXNFT.sol +338 -0
  31. package/smart-contracts/QVTXRewards.sol +102 -0
  32. package/smart-contracts/QVTXToken.sol +227 -0
  33. package/smart-contracts/QVTXVesting.sol +411 -0
  34. package/smart-contracts/interfaces/IERC20.sol +14 -0
  35. package/smart-contracts/interfaces/IERC20Metadata.sol +8 -0
  36. package/smart-contracts/interfaces/IERC721.sol +18 -0
  37. package/smart-contracts/interfaces/IERC721Metadata.sol +8 -0
  38. package/smart-contracts/interfaces/IERC721Receiver.sol +11 -0
  39. package/storage/index.js +112 -0
  40. package/templates/contract/ERC20Token.sol +116 -0
  41. package/templates/dapp/index.html +93 -0
  42. package/test/index.js +182 -0
  43. package/tools/build-tool.js +63 -0
  44. package/tools/create-template.js +116 -0
  45. package/tools/deploy-tool.js +55 -0
  46. package/tools/generate-docs.js +149 -0
  47. package/tools/init-project.js +138 -0
  48. package/tools/run-tests.js +64 -0
  49. package/types/index.d.ts +386 -0
@@ -0,0 +1,325 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.19;
3
+
4
+ /**
5
+ * @title QVTX Governance
6
+ * @dev DAO governance contract for QuantVestrix ecosystem
7
+ * @author QuantVestrix Tech Team (07-Tech)
8
+ */
9
+
10
+ interface IQVTXToken {
11
+ function balanceOf(address account) external view returns (uint256);
12
+ function totalSupply() external view returns (uint256);
13
+ }
14
+
15
+ contract QVTXGovernance {
16
+ // Governance token
17
+ IQVTXToken public qvtxToken;
18
+ address public owner;
19
+
20
+ // Proposal structure
21
+ struct Proposal {
22
+ uint256 id;
23
+ address proposer;
24
+ string title;
25
+ string description;
26
+ bytes callData;
27
+ address target;
28
+ uint256 value;
29
+ uint256 forVotes;
30
+ uint256 againstVotes;
31
+ uint256 abstainVotes;
32
+ uint256 startBlock;
33
+ uint256 endBlock;
34
+ bool executed;
35
+ bool canceled;
36
+ mapping(address => Receipt) receipts;
37
+ }
38
+
39
+ struct Receipt {
40
+ bool hasVoted;
41
+ uint8 support; // 0 = against, 1 = for, 2 = abstain
42
+ uint256 votes;
43
+ }
44
+
45
+ struct ProposalInfo {
46
+ uint256 id;
47
+ address proposer;
48
+ string title;
49
+ string description;
50
+ uint256 forVotes;
51
+ uint256 againstVotes;
52
+ uint256 abstainVotes;
53
+ uint256 startBlock;
54
+ uint256 endBlock;
55
+ bool executed;
56
+ bool canceled;
57
+ ProposalState state;
58
+ }
59
+
60
+ enum ProposalState {
61
+ Pending,
62
+ Active,
63
+ Canceled,
64
+ Defeated,
65
+ Succeeded,
66
+ Queued,
67
+ Expired,
68
+ Executed
69
+ }
70
+
71
+ // Configuration
72
+ uint256 public proposalThreshold = 100_000 * 10**18; // 100k QVTX to propose
73
+ uint256 public quorumVotes = 4_000_000 * 10**18; // 4M QVTX (0.4% of supply)
74
+ uint256 public votingDelay = 1; // 1 block
75
+ uint256 public votingPeriod = 40320; // ~1 week (assuming 15s blocks)
76
+ uint256 public timelockDelay = 172800; // 2 days in seconds
77
+
78
+ // Storage
79
+ uint256 public proposalCount;
80
+ mapping(uint256 => Proposal) public proposals;
81
+ mapping(address => uint256) public latestProposalIds;
82
+ mapping(uint256 => uint256) public proposalTimelocks; // proposalId => execution timestamp
83
+
84
+ // Events
85
+ event ProposalCreated(
86
+ uint256 indexed id,
87
+ address indexed proposer,
88
+ string title,
89
+ address target,
90
+ uint256 startBlock,
91
+ uint256 endBlock
92
+ );
93
+ event VoteCast(
94
+ address indexed voter,
95
+ uint256 indexed proposalId,
96
+ uint8 support,
97
+ uint256 votes,
98
+ string reason
99
+ );
100
+ event ProposalCanceled(uint256 indexed id);
101
+ event ProposalQueued(uint256 indexed id, uint256 eta);
102
+ event ProposalExecuted(uint256 indexed id);
103
+ event ConfigUpdated(string param, uint256 value);
104
+
105
+ modifier onlyOwner() {
106
+ require(msg.sender == owner, "Governance: not owner");
107
+ _;
108
+ }
109
+
110
+ constructor(address _qvtxToken) {
111
+ qvtxToken = IQVTXToken(_qvtxToken);
112
+ owner = msg.sender;
113
+ }
114
+
115
+ // Create proposal
116
+ function propose(
117
+ string memory title,
118
+ string memory description,
119
+ address target,
120
+ uint256 value,
121
+ bytes memory callData
122
+ ) external returns (uint256) {
123
+ require(
124
+ qvtxToken.balanceOf(msg.sender) >= proposalThreshold,
125
+ "Governance: below proposal threshold"
126
+ );
127
+ require(
128
+ latestProposalIds[msg.sender] == 0 ||
129
+ getProposalState(latestProposalIds[msg.sender]) != ProposalState.Active,
130
+ "Governance: already has active proposal"
131
+ );
132
+
133
+ proposalCount++;
134
+ uint256 proposalId = proposalCount;
135
+
136
+ Proposal storage proposal = proposals[proposalId];
137
+ proposal.id = proposalId;
138
+ proposal.proposer = msg.sender;
139
+ proposal.title = title;
140
+ proposal.description = description;
141
+ proposal.target = target;
142
+ proposal.value = value;
143
+ proposal.callData = callData;
144
+ proposal.startBlock = block.number + votingDelay;
145
+ proposal.endBlock = proposal.startBlock + votingPeriod;
146
+
147
+ latestProposalIds[msg.sender] = proposalId;
148
+
149
+ emit ProposalCreated(
150
+ proposalId,
151
+ msg.sender,
152
+ title,
153
+ target,
154
+ proposal.startBlock,
155
+ proposal.endBlock
156
+ );
157
+
158
+ return proposalId;
159
+ }
160
+
161
+ // Cast vote
162
+ function castVote(uint256 proposalId, uint8 support) external {
163
+ _castVote(msg.sender, proposalId, support, "");
164
+ }
165
+
166
+ function castVoteWithReason(uint256 proposalId, uint8 support, string calldata reason) external {
167
+ _castVote(msg.sender, proposalId, support, reason);
168
+ }
169
+
170
+ function _castVote(
171
+ address voter,
172
+ uint256 proposalId,
173
+ uint8 support,
174
+ string memory reason
175
+ ) internal {
176
+ require(getProposalState(proposalId) == ProposalState.Active, "Governance: voting closed");
177
+ require(support <= 2, "Governance: invalid vote type");
178
+
179
+ Proposal storage proposal = proposals[proposalId];
180
+ Receipt storage receipt = proposal.receipts[voter];
181
+ require(!receipt.hasVoted, "Governance: already voted");
182
+
183
+ uint256 votes = qvtxToken.balanceOf(voter);
184
+ require(votes > 0, "Governance: no voting power");
185
+
186
+ if (support == 0) {
187
+ proposal.againstVotes += votes;
188
+ } else if (support == 1) {
189
+ proposal.forVotes += votes;
190
+ } else {
191
+ proposal.abstainVotes += votes;
192
+ }
193
+
194
+ receipt.hasVoted = true;
195
+ receipt.support = support;
196
+ receipt.votes = votes;
197
+
198
+ emit VoteCast(voter, proposalId, support, votes, reason);
199
+ }
200
+
201
+ // Queue successful proposal
202
+ function queue(uint256 proposalId) external {
203
+ require(
204
+ getProposalState(proposalId) == ProposalState.Succeeded,
205
+ "Governance: proposal not succeeded"
206
+ );
207
+
208
+ proposalTimelocks[proposalId] = block.timestamp + timelockDelay;
209
+
210
+ emit ProposalQueued(proposalId, proposalTimelocks[proposalId]);
211
+ }
212
+
213
+ // Execute queued proposal
214
+ function execute(uint256 proposalId) external payable {
215
+ require(
216
+ getProposalState(proposalId) == ProposalState.Queued,
217
+ "Governance: proposal not queued"
218
+ );
219
+ require(
220
+ block.timestamp >= proposalTimelocks[proposalId],
221
+ "Governance: timelock not passed"
222
+ );
223
+
224
+ Proposal storage proposal = proposals[proposalId];
225
+ proposal.executed = true;
226
+
227
+ (bool success, ) = proposal.target.call{value: proposal.value}(proposal.callData);
228
+ require(success, "Governance: execution failed");
229
+
230
+ emit ProposalExecuted(proposalId);
231
+ }
232
+
233
+ // Cancel proposal
234
+ function cancel(uint256 proposalId) external {
235
+ Proposal storage proposal = proposals[proposalId];
236
+ require(
237
+ msg.sender == proposal.proposer || msg.sender == owner,
238
+ "Governance: not authorized"
239
+ );
240
+ require(!proposal.executed, "Governance: already executed");
241
+
242
+ proposal.canceled = true;
243
+
244
+ emit ProposalCanceled(proposalId);
245
+ }
246
+
247
+ // Get proposal state
248
+ function getProposalState(uint256 proposalId) public view returns (ProposalState) {
249
+ require(proposalId > 0 && proposalId <= proposalCount, "Governance: invalid proposal");
250
+
251
+ Proposal storage proposal = proposals[proposalId];
252
+
253
+ if (proposal.canceled) {
254
+ return ProposalState.Canceled;
255
+ } else if (block.number <= proposal.startBlock) {
256
+ return ProposalState.Pending;
257
+ } else if (block.number <= proposal.endBlock) {
258
+ return ProposalState.Active;
259
+ } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) {
260
+ return ProposalState.Defeated;
261
+ } else if (proposal.executed) {
262
+ return ProposalState.Executed;
263
+ } else if (proposalTimelocks[proposalId] == 0) {
264
+ return ProposalState.Succeeded;
265
+ } else if (block.timestamp < proposalTimelocks[proposalId]) {
266
+ return ProposalState.Queued;
267
+ } else if (block.timestamp >= proposalTimelocks[proposalId] + 14 days) {
268
+ return ProposalState.Expired;
269
+ } else {
270
+ return ProposalState.Queued;
271
+ }
272
+ }
273
+
274
+ // Get proposal info
275
+ function getProposal(uint256 proposalId) external view returns (ProposalInfo memory) {
276
+ Proposal storage proposal = proposals[proposalId];
277
+ return ProposalInfo({
278
+ id: proposal.id,
279
+ proposer: proposal.proposer,
280
+ title: proposal.title,
281
+ description: proposal.description,
282
+ forVotes: proposal.forVotes,
283
+ againstVotes: proposal.againstVotes,
284
+ abstainVotes: proposal.abstainVotes,
285
+ startBlock: proposal.startBlock,
286
+ endBlock: proposal.endBlock,
287
+ executed: proposal.executed,
288
+ canceled: proposal.canceled,
289
+ state: getProposalState(proposalId)
290
+ });
291
+ }
292
+
293
+ // Get vote receipt
294
+ function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory) {
295
+ return proposals[proposalId].receipts[voter];
296
+ }
297
+
298
+ // Admin functions
299
+ function setProposalThreshold(uint256 _threshold) external onlyOwner {
300
+ proposalThreshold = _threshold;
301
+ emit ConfigUpdated("proposalThreshold", _threshold);
302
+ }
303
+
304
+ function setQuorumVotes(uint256 _quorum) external onlyOwner {
305
+ quorumVotes = _quorum;
306
+ emit ConfigUpdated("quorumVotes", _quorum);
307
+ }
308
+
309
+ function setVotingPeriod(uint256 _period) external onlyOwner {
310
+ votingPeriod = _period;
311
+ emit ConfigUpdated("votingPeriod", _period);
312
+ }
313
+
314
+ function setTimelockDelay(uint256 _delay) external onlyOwner {
315
+ timelockDelay = _delay;
316
+ emit ConfigUpdated("timelockDelay", _delay);
317
+ }
318
+
319
+ function transferOwnership(address newOwner) external onlyOwner {
320
+ require(newOwner != address(0), "Governance: zero address");
321
+ owner = newOwner;
322
+ }
323
+
324
+ receive() external payable {}
325
+ }
@@ -0,0 +1,338 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.19;
3
+
4
+ /**
5
+ * @title QVTX NFT
6
+ * @dev ERC721 NFT contract for QuantVestrix ecosystem
7
+ * @author QuantVestrix Tech Team (07-Tech)
8
+ */
9
+
10
+ import "./interfaces/IERC721.sol";
11
+ import "./interfaces/IERC721Metadata.sol";
12
+ import "./interfaces/IERC721Receiver.sol";
13
+
14
+ contract QVTXNFT is IERC721, IERC721Metadata {
15
+ string private _name;
16
+ string private _symbol;
17
+ string private _baseURI;
18
+
19
+ address public owner;
20
+ uint256 public totalSupply;
21
+ uint256 public maxSupply;
22
+ uint256 public mintPrice;
23
+ bool public mintingEnabled;
24
+
25
+ // Token data
26
+ mapping(uint256 => address) private _owners;
27
+ mapping(address => uint256) private _balances;
28
+ mapping(uint256 => address) private _tokenApprovals;
29
+ mapping(address => mapping(address => bool)) private _operatorApprovals;
30
+ mapping(uint256 => string) private _tokenURIs;
31
+
32
+ // Royalties (EIP-2981)
33
+ address public royaltyReceiver;
34
+ uint96 public royaltyBps = 500; // 5%
35
+
36
+ // Whitelist
37
+ mapping(address => bool) public whitelist;
38
+ mapping(address => uint256) public mintedCount;
39
+ uint256 public maxPerWallet = 10;
40
+ bool public whitelistOnly;
41
+
42
+ // Events
43
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
44
+ event BaseURIUpdated(string newBaseURI);
45
+ event MintingToggled(bool enabled);
46
+ event WhitelistUpdated(address indexed account, bool status);
47
+ event RoyaltyUpdated(address receiver, uint96 bps);
48
+
49
+ modifier onlyOwner() {
50
+ require(msg.sender == owner, "NFT: not owner");
51
+ _;
52
+ }
53
+
54
+ constructor(
55
+ string memory name_,
56
+ string memory symbol_,
57
+ string memory baseURI_,
58
+ uint256 _maxSupply,
59
+ uint256 _mintPrice
60
+ ) {
61
+ _name = name_;
62
+ _symbol = symbol_;
63
+ _baseURI = baseURI_;
64
+ maxSupply = _maxSupply;
65
+ mintPrice = _mintPrice;
66
+ owner = msg.sender;
67
+ royaltyReceiver = msg.sender;
68
+ }
69
+
70
+ // ERC721 Metadata
71
+ function name() external view override returns (string memory) {
72
+ return _name;
73
+ }
74
+
75
+ function symbol() external view override returns (string memory) {
76
+ return _symbol;
77
+ }
78
+
79
+ function tokenURI(uint256 tokenId) external view override returns (string memory) {
80
+ require(_exists(tokenId), "NFT: nonexistent token");
81
+
82
+ string memory _tokenURI = _tokenURIs[tokenId];
83
+ if (bytes(_tokenURI).length > 0) {
84
+ return _tokenURI;
85
+ }
86
+
87
+ return string(abi.encodePacked(_baseURI, _toString(tokenId), ".json"));
88
+ }
89
+
90
+ // ERC721 Standard
91
+ function balanceOf(address tokenOwner) external view override returns (uint256) {
92
+ require(tokenOwner != address(0), "NFT: zero address");
93
+ return _balances[tokenOwner];
94
+ }
95
+
96
+ function ownerOf(uint256 tokenId) public view override returns (address) {
97
+ address tokenOwner = _owners[tokenId];
98
+ require(tokenOwner != address(0), "NFT: nonexistent token");
99
+ return tokenOwner;
100
+ }
101
+
102
+ function approve(address to, uint256 tokenId) external override {
103
+ address tokenOwner = ownerOf(tokenId);
104
+ require(to != tokenOwner, "NFT: approval to owner");
105
+ require(
106
+ msg.sender == tokenOwner || isApprovedForAll(tokenOwner, msg.sender),
107
+ "NFT: not authorized"
108
+ );
109
+ _tokenApprovals[tokenId] = to;
110
+ emit Approval(tokenOwner, to, tokenId);
111
+ }
112
+
113
+ function getApproved(uint256 tokenId) public view override returns (address) {
114
+ require(_exists(tokenId), "NFT: nonexistent token");
115
+ return _tokenApprovals[tokenId];
116
+ }
117
+
118
+ function setApprovalForAll(address operator, bool approved) external override {
119
+ require(operator != msg.sender, "NFT: approve to caller");
120
+ _operatorApprovals[msg.sender][operator] = approved;
121
+ emit ApprovalForAll(msg.sender, operator, approved);
122
+ }
123
+
124
+ function isApprovedForAll(address tokenOwner, address operator) public view override returns (bool) {
125
+ return _operatorApprovals[tokenOwner][operator];
126
+ }
127
+
128
+ function transferFrom(address from, address to, uint256 tokenId) public override {
129
+ require(_isApprovedOrOwner(msg.sender, tokenId), "NFT: not authorized");
130
+ _transfer(from, to, tokenId);
131
+ }
132
+
133
+ function safeTransferFrom(address from, address to, uint256 tokenId) external override {
134
+ safeTransferFrom(from, to, tokenId, "");
135
+ }
136
+
137
+ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override {
138
+ require(_isApprovedOrOwner(msg.sender, tokenId), "NFT: not authorized");
139
+ _safeTransfer(from, to, tokenId, data);
140
+ }
141
+
142
+ // Minting
143
+ function mint(uint256 quantity) external payable {
144
+ require(mintingEnabled, "NFT: minting disabled");
145
+ require(totalSupply + quantity <= maxSupply, "NFT: exceeds max supply");
146
+ require(mintedCount[msg.sender] + quantity <= maxPerWallet, "NFT: exceeds max per wallet");
147
+ require(msg.value >= mintPrice * quantity, "NFT: insufficient payment");
148
+
149
+ if (whitelistOnly) {
150
+ require(whitelist[msg.sender], "NFT: not whitelisted");
151
+ }
152
+
153
+ for (uint256 i = 0; i < quantity; i++) {
154
+ totalSupply++;
155
+ _mint(msg.sender, totalSupply);
156
+ }
157
+
158
+ mintedCount[msg.sender] += quantity;
159
+
160
+ // Refund excess
161
+ if (msg.value > mintPrice * quantity) {
162
+ payable(msg.sender).transfer(msg.value - mintPrice * quantity);
163
+ }
164
+ }
165
+
166
+ function ownerMint(address to, uint256 quantity) external onlyOwner {
167
+ require(totalSupply + quantity <= maxSupply, "NFT: exceeds max supply");
168
+
169
+ for (uint256 i = 0; i < quantity; i++) {
170
+ totalSupply++;
171
+ _mint(to, totalSupply);
172
+ }
173
+ }
174
+
175
+ function airdrop(address[] calldata recipients) external onlyOwner {
176
+ require(totalSupply + recipients.length <= maxSupply, "NFT: exceeds max supply");
177
+
178
+ for (uint256 i = 0; i < recipients.length; i++) {
179
+ totalSupply++;
180
+ _mint(recipients[i], totalSupply);
181
+ }
182
+ }
183
+
184
+ // Burn
185
+ function burn(uint256 tokenId) external {
186
+ require(_isApprovedOrOwner(msg.sender, tokenId), "NFT: not authorized");
187
+ _burn(tokenId);
188
+ }
189
+
190
+ // EIP-2981 Royalties
191
+ function royaltyInfo(uint256, uint256 salePrice) external view returns (address, uint256) {
192
+ uint256 royaltyAmount = (salePrice * royaltyBps) / 10000;
193
+ return (royaltyReceiver, royaltyAmount);
194
+ }
195
+
196
+ // Admin functions
197
+ function setBaseURI(string memory baseURI_) external onlyOwner {
198
+ _baseURI = baseURI_;
199
+ emit BaseURIUpdated(baseURI_);
200
+ }
201
+
202
+ function setTokenURI(uint256 tokenId, string memory _tokenURI) external onlyOwner {
203
+ require(_exists(tokenId), "NFT: nonexistent token");
204
+ _tokenURIs[tokenId] = _tokenURI;
205
+ }
206
+
207
+ function setMintPrice(uint256 _price) external onlyOwner {
208
+ mintPrice = _price;
209
+ }
210
+
211
+ function setMaxPerWallet(uint256 _max) external onlyOwner {
212
+ maxPerWallet = _max;
213
+ }
214
+
215
+ function toggleMinting(bool _enabled) external onlyOwner {
216
+ mintingEnabled = _enabled;
217
+ emit MintingToggled(_enabled);
218
+ }
219
+
220
+ function setWhitelistOnly(bool _whitelistOnly) external onlyOwner {
221
+ whitelistOnly = _whitelistOnly;
222
+ }
223
+
224
+ function updateWhitelist(address[] calldata accounts, bool status) external onlyOwner {
225
+ for (uint256 i = 0; i < accounts.length; i++) {
226
+ whitelist[accounts[i]] = status;
227
+ emit WhitelistUpdated(accounts[i], status);
228
+ }
229
+ }
230
+
231
+ function setRoyalty(address receiver, uint96 bps) external onlyOwner {
232
+ require(bps <= 1000, "NFT: royalty too high"); // Max 10%
233
+ royaltyReceiver = receiver;
234
+ royaltyBps = bps;
235
+ emit RoyaltyUpdated(receiver, bps);
236
+ }
237
+
238
+ function withdraw() external onlyOwner {
239
+ uint256 balance = address(this).balance;
240
+ require(balance > 0, "NFT: no balance");
241
+ payable(owner).transfer(balance);
242
+ }
243
+
244
+ function transferOwnership(address newOwner) external onlyOwner {
245
+ require(newOwner != address(0), "NFT: zero address");
246
+ emit OwnershipTransferred(owner, newOwner);
247
+ owner = newOwner;
248
+ }
249
+
250
+ // Internal functions
251
+ function _exists(uint256 tokenId) internal view returns (bool) {
252
+ return _owners[tokenId] != address(0);
253
+ }
254
+
255
+ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
256
+ address tokenOwner = ownerOf(tokenId);
257
+ return (spender == tokenOwner || getApproved(tokenId) == spender || isApprovedForAll(tokenOwner, spender));
258
+ }
259
+
260
+ function _mint(address to, uint256 tokenId) internal {
261
+ require(to != address(0), "NFT: mint to zero address");
262
+ require(!_exists(tokenId), "NFT: token exists");
263
+
264
+ _balances[to]++;
265
+ _owners[tokenId] = to;
266
+
267
+ emit Transfer(address(0), to, tokenId);
268
+ }
269
+
270
+ function _burn(uint256 tokenId) internal {
271
+ address tokenOwner = ownerOf(tokenId);
272
+
273
+ // Clear approvals
274
+ delete _tokenApprovals[tokenId];
275
+
276
+ _balances[tokenOwner]--;
277
+ delete _owners[tokenId];
278
+ delete _tokenURIs[tokenId];
279
+
280
+ emit Transfer(tokenOwner, address(0), tokenId);
281
+ }
282
+
283
+ function _transfer(address from, address to, uint256 tokenId) internal {
284
+ require(ownerOf(tokenId) == from, "NFT: not owner");
285
+ require(to != address(0), "NFT: transfer to zero address");
286
+
287
+ // Clear approvals
288
+ delete _tokenApprovals[tokenId];
289
+
290
+ _balances[from]--;
291
+ _balances[to]++;
292
+ _owners[tokenId] = to;
293
+
294
+ emit Transfer(from, to, tokenId);
295
+ }
296
+
297
+ function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal {
298
+ _transfer(from, to, tokenId);
299
+ require(_checkOnERC721Received(from, to, tokenId, data), "NFT: transfer to non-receiver");
300
+ }
301
+
302
+ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {
303
+ if (to.code.length > 0) {
304
+ try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
305
+ return retval == IERC721Receiver.onERC721Received.selector;
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+ return true;
311
+ }
312
+
313
+ function _toString(uint256 value) internal pure returns (string memory) {
314
+ if (value == 0) return "0";
315
+ uint256 temp = value;
316
+ uint256 digits;
317
+ while (temp != 0) {
318
+ digits++;
319
+ temp /= 10;
320
+ }
321
+ bytes memory buffer = new bytes(digits);
322
+ while (value != 0) {
323
+ digits--;
324
+ buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
325
+ value /= 10;
326
+ }
327
+ return string(buffer);
328
+ }
329
+
330
+ // ERC165
331
+ function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
332
+ return
333
+ interfaceId == 0x01ffc9a7 || // ERC165
334
+ interfaceId == 0x80ac58cd || // ERC721
335
+ interfaceId == 0x5b5e139f || // ERC721Metadata
336
+ interfaceId == 0x2a55205a; // ERC2981
337
+ }
338
+ }