├── README.md └── UniversityDegree.sol /README.md: -------------------------------------------------------------------------------- 1 | # Soul bound tokens as University Degrees 2 | Non transferable Soul bound token NFT 3 | 4 | ## Technology Stack & Dependencies 5 | 6 | - Solidity (Writing Smart Contract) 7 | - [Remix](https://remix.ethereum.org/) Remix as IDE to deploy the contract 8 | 9 | 10 | ### 1. Copy/Download the Repository 11 | 12 | ### 2. Paste into Remix and deploy to testnet 13 | 14 | ### 3. Upload The NFT Degree to IPFS using Pinata 15 | 16 | -------------------------------------------------------------------------------- /UniversityDegree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // File: @openzeppelin/contracts/utils/Counters.sol 3 | 4 | 5 | // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol) 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | 10 | 11 | /** 12 | * @title Counters 13 | * @author Matt Condon (@shrugs) 14 | * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number 15 | * of elements in a mapping, issuing ERC721 ids, or counting request ids. 16 | * 17 | * Include with `using Counters for Counters.Counter;` 18 | */ 19 | library Counters { 20 | struct Counter { 21 | // This variable should never be directly accessed by users of the library: interactions must be restricted to 22 | // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add 23 | // this feature: see https://github.com/ethereum/solidity/issues/4637 24 | uint256 _value; // default: 0 25 | } 26 | 27 | function current(Counter storage counter) internal view returns (uint256) { 28 | return counter._value; 29 | } 30 | 31 | function increment(Counter storage counter) internal { 32 | unchecked { 33 | counter._value += 1; 34 | } 35 | } 36 | 37 | function decrement(Counter storage counter) internal { 38 | uint256 value = counter._value; 39 | require(value > 0, "Counter: decrement overflow"); 40 | unchecked { 41 | counter._value = value - 1; 42 | } 43 | } 44 | 45 | function reset(Counter storage counter) internal { 46 | counter._value = 0; 47 | } 48 | } 49 | 50 | // File: @openzeppelin/contracts/utils/Strings.sol 51 | 52 | 53 | // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) 54 | 55 | 56 | /** 57 | * @dev String operations. 58 | */ 59 | library Strings { 60 | bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; 61 | 62 | /** 63 | * @dev Converts a `uint256` to its ASCII `string` decimal representation. 64 | */ 65 | function toString(uint256 value) internal pure returns (string memory) { 66 | // Inspired by OraclizeAPI's implementation - MIT licence 67 | // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol 68 | 69 | if (value == 0) { 70 | return "0"; 71 | } 72 | uint256 temp = value; 73 | uint256 digits; 74 | while (temp != 0) { 75 | digits++; 76 | temp /= 10; 77 | } 78 | bytes memory buffer = new bytes(digits); 79 | while (value != 0) { 80 | digits -= 1; 81 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 82 | value /= 10; 83 | } 84 | return string(buffer); 85 | } 86 | 87 | /** 88 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. 89 | */ 90 | function toHexString(uint256 value) internal pure returns (string memory) { 91 | if (value == 0) { 92 | return "0x00"; 93 | } 94 | uint256 temp = value; 95 | uint256 length = 0; 96 | while (temp != 0) { 97 | length++; 98 | temp >>= 8; 99 | } 100 | return toHexString(value, length); 101 | } 102 | 103 | /** 104 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. 105 | */ 106 | function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { 107 | bytes memory buffer = new bytes(2 * length + 2); 108 | buffer[0] = "0"; 109 | buffer[1] = "x"; 110 | for (uint256 i = 2 * length + 1; i > 1; --i) { 111 | buffer[i] = _HEX_SYMBOLS[value & 0xf]; 112 | value >>= 4; 113 | } 114 | require(value == 0, "Strings: hex length insufficient"); 115 | return string(buffer); 116 | } 117 | } 118 | 119 | // File: @openzeppelin/contracts/utils/Context.sol 120 | 121 | 122 | // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) 123 | 124 | 125 | 126 | /** 127 | * @dev Provides information about the current execution context, including the 128 | * sender of the transaction and its data. While these are generally available 129 | * via msg.sender and msg.data, they should not be accessed in such a direct 130 | * manner, since when dealing with meta-transactions the account sending and 131 | * paying for execution may not be the actual sender (as far as an application 132 | * is concerned). 133 | * 134 | * This contract is only required for intermediate, library-like contracts. 135 | */ 136 | abstract contract Context { 137 | function _msgSender() internal view virtual returns (address) { 138 | return msg.sender; 139 | } 140 | 141 | function _msgData() internal view virtual returns (bytes calldata) { 142 | return msg.data; 143 | } 144 | } 145 | 146 | // File: @openzeppelin/contracts/utils/Address.sol 147 | 148 | 149 | // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol) 150 | 151 | 152 | /** 153 | * @dev Collection of functions related to the address type 154 | */ 155 | library Address { 156 | /** 157 | * @dev Returns true if `account` is a contract. 158 | * 159 | * [IMPORTANT] 160 | * ==== 161 | * It is unsafe to assume that an address for which this function returns 162 | * false is an externally-owned account (EOA) and not a contract. 163 | * 164 | * Among others, `isContract` will return false for the following 165 | * types of addresses: 166 | * 167 | * - an externally-owned account 168 | * - a contract in construction 169 | * - an address where a contract will be created 170 | * - an address where a contract lived, but was destroyed 171 | * ==== 172 | * 173 | * [IMPORTANT] 174 | * ==== 175 | * You shouldn't rely on `isContract` to protect against flash loan attacks! 176 | * 177 | * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets 178 | * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract 179 | * constructor. 180 | * ==== 181 | */ 182 | function isContract(address account) internal view returns (bool) { 183 | // This method relies on extcodesize/address.code.length, which returns 0 184 | // for contracts in construction, since the code is only stored at the end 185 | // of the constructor execution. 186 | 187 | return account.code.length > 0; 188 | } 189 | 190 | /** 191 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 192 | * `recipient`, forwarding all available gas and reverting on errors. 193 | * 194 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 195 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 196 | * imposed by `transfer`, making them unable to receive funds via 197 | * `transfer`. {sendValue} removes this limitation. 198 | * 199 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 200 | * 201 | * IMPORTANT: because control is transferred to `recipient`, care must be 202 | * taken to not create reentrancy vulnerabilities. Consider using 203 | * {ReentrancyGuard} or the 204 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 205 | */ 206 | function sendValue(address payable recipient, uint256 amount) internal { 207 | require(address(this).balance >= amount, "Address: insufficient balance"); 208 | 209 | (bool success, ) = recipient.call{value: amount}(""); 210 | require(success, "Address: unable to send value, recipient may have reverted"); 211 | } 212 | 213 | /** 214 | * @dev Performs a Solidity function call using a low level `call`. A 215 | * plain `call` is an unsafe replacement for a function call: use this 216 | * function instead. 217 | * 218 | * If `target` reverts with a revert reason, it is bubbled up by this 219 | * function (like regular Solidity function calls). 220 | * 221 | * Returns the raw returned data. To convert to the expected return value, 222 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 223 | * 224 | * Requirements: 225 | * 226 | * - `target` must be a contract. 227 | * - calling `target` with `data` must not revert. 228 | * 229 | * _Available since v3.1._ 230 | */ 231 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 232 | return functionCall(target, data, "Address: low-level call failed"); 233 | } 234 | 235 | /** 236 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 237 | * `errorMessage` as a fallback revert reason when `target` reverts. 238 | * 239 | * _Available since v3.1._ 240 | */ 241 | function functionCall( 242 | address target, 243 | bytes memory data, 244 | string memory errorMessage 245 | ) internal returns (bytes memory) { 246 | return functionCallWithValue(target, data, 0, errorMessage); 247 | } 248 | 249 | /** 250 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 251 | * but also transferring `value` wei to `target`. 252 | * 253 | * Requirements: 254 | * 255 | * - the calling contract must have an ETH balance of at least `value`. 256 | * - the called Solidity function must be `payable`. 257 | * 258 | * _Available since v3.1._ 259 | */ 260 | function functionCallWithValue( 261 | address target, 262 | bytes memory data, 263 | uint256 value 264 | ) internal returns (bytes memory) { 265 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 266 | } 267 | 268 | /** 269 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 270 | * with `errorMessage` as a fallback revert reason when `target` reverts. 271 | * 272 | * _Available since v3.1._ 273 | */ 274 | function functionCallWithValue( 275 | address target, 276 | bytes memory data, 277 | uint256 value, 278 | string memory errorMessage 279 | ) internal returns (bytes memory) { 280 | require(address(this).balance >= value, "Address: insufficient balance for call"); 281 | require(isContract(target), "Address: call to non-contract"); 282 | 283 | (bool success, bytes memory returndata) = target.call{value: value}(data); 284 | return verifyCallResult(success, returndata, errorMessage); 285 | } 286 | 287 | /** 288 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 289 | * but performing a static call. 290 | * 291 | * _Available since v3.3._ 292 | */ 293 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 294 | return functionStaticCall(target, data, "Address: low-level static call failed"); 295 | } 296 | 297 | /** 298 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 299 | * but performing a static call. 300 | * 301 | * _Available since v3.3._ 302 | */ 303 | function functionStaticCall( 304 | address target, 305 | bytes memory data, 306 | string memory errorMessage 307 | ) internal view returns (bytes memory) { 308 | require(isContract(target), "Address: static call to non-contract"); 309 | 310 | (bool success, bytes memory returndata) = target.staticcall(data); 311 | return verifyCallResult(success, returndata, errorMessage); 312 | } 313 | 314 | /** 315 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 316 | * but performing a delegate call. 317 | * 318 | * _Available since v3.4._ 319 | */ 320 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 321 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 322 | } 323 | 324 | /** 325 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 326 | * but performing a delegate call. 327 | * 328 | * _Available since v3.4._ 329 | */ 330 | function functionDelegateCall( 331 | address target, 332 | bytes memory data, 333 | string memory errorMessage 334 | ) internal returns (bytes memory) { 335 | require(isContract(target), "Address: delegate call to non-contract"); 336 | 337 | (bool success, bytes memory returndata) = target.delegatecall(data); 338 | return verifyCallResult(success, returndata, errorMessage); 339 | } 340 | 341 | /** 342 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 343 | * revert reason using the provided one. 344 | * 345 | * _Available since v4.3._ 346 | */ 347 | function verifyCallResult( 348 | bool success, 349 | bytes memory returndata, 350 | string memory errorMessage 351 | ) internal pure returns (bytes memory) { 352 | if (success) { 353 | return returndata; 354 | } else { 355 | // Look for revert reason and bubble it up if present 356 | if (returndata.length > 0) { 357 | // The easiest way to bubble the revert reason is using memory via assembly 358 | 359 | assembly { 360 | let returndata_size := mload(returndata) 361 | revert(add(32, returndata), returndata_size) 362 | } 363 | } else { 364 | revert(errorMessage); 365 | } 366 | } 367 | } 368 | } 369 | 370 | // File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol 371 | 372 | 373 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) 374 | 375 | 376 | /** 377 | * @title ERC721 token receiver interface 378 | * @dev Interface for any contract that wants to support safeTransfers 379 | * from ERC721 asset contracts. 380 | */ 381 | interface IERC721Receiver { 382 | /** 383 | * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} 384 | * by `operator` from `from`, this function is called. 385 | * 386 | * It must return its Solidity selector to confirm the token transfer. 387 | * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. 388 | * 389 | * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. 390 | */ 391 | function onERC721Received( 392 | address operator, 393 | address from, 394 | uint256 tokenId, 395 | bytes calldata data 396 | ) external returns (bytes4); 397 | } 398 | 399 | // File: @openzeppelin/contracts/utils/introspection/IERC165.sol 400 | 401 | 402 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) 403 | 404 | 405 | /** 406 | * @dev Interface of the ERC165 standard, as defined in the 407 | * https://eips.ethereum.org/EIPS/eip-165[EIP]. 408 | * 409 | * Implementers can declare support of contract interfaces, which can then be 410 | * queried by others ({ERC165Checker}). 411 | * 412 | * For an implementation, see {ERC165}. 413 | */ 414 | interface IERC165 { 415 | /** 416 | * @dev Returns true if this contract implements the interface defined by 417 | * `interfaceId`. See the corresponding 418 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] 419 | * to learn more about how these ids are created. 420 | * 421 | * This function call must use less than 30 000 gas. 422 | */ 423 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 424 | } 425 | 426 | // File: @openzeppelin/contracts/utils/introspection/ERC165.sol 427 | 428 | 429 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) 430 | 431 | 432 | /** 433 | * @dev Implementation of the {IERC165} interface. 434 | * 435 | * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check 436 | * for the additional interface id that will be supported. For example: 437 | * 438 | * ```solidity 439 | * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 440 | * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); 441 | * } 442 | * ``` 443 | * 444 | * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. 445 | */ 446 | abstract contract ERC165 is IERC165 { 447 | /** 448 | * @dev See {IERC165-supportsInterface}. 449 | */ 450 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 451 | return interfaceId == type(IERC165).interfaceId; 452 | } 453 | } 454 | 455 | // File: @openzeppelin/contracts/token/ERC721/IERC721.sol 456 | 457 | 458 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol) 459 | 460 | 461 | 462 | /** 463 | * @dev Required interface of an ERC721 compliant contract. 464 | */ 465 | interface IERC721 is IERC165 { 466 | /** 467 | * @dev Emitted when `tokenId` token is transferred from `from` to `to`. 468 | */ 469 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 470 | 471 | /** 472 | * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. 473 | */ 474 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 475 | 476 | /** 477 | * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. 478 | */ 479 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 480 | 481 | /** 482 | * @dev Returns the number of tokens in ``owner``'s account. 483 | */ 484 | function balanceOf(address owner) external view returns (uint256 balance); 485 | 486 | /** 487 | * @dev Returns the owner of the `tokenId` token. 488 | * 489 | * Requirements: 490 | * 491 | * - `tokenId` must exist. 492 | */ 493 | function ownerOf(uint256 tokenId) external view returns (address owner); 494 | 495 | /** 496 | * @dev Safely transfers `tokenId` token from `from` to `to`. 497 | * 498 | * Requirements: 499 | * 500 | * - `from` cannot be the zero address. 501 | * - `to` cannot be the zero address. 502 | * - `tokenId` token must exist and be owned by `from`. 503 | * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. 504 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 505 | * 506 | * Emits a {Transfer} event. 507 | */ 508 | // function safeTransferFrom( 509 | // address from, 510 | // address to, 511 | // uint256 tokenId, 512 | // bytes calldata data 513 | // ) external; 514 | 515 | /** 516 | * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 517 | * are aware of the ERC721 protocol to prevent tokens from being forever locked. 518 | * 519 | * Requirements: 520 | * 521 | * - `from` cannot be the zero address. 522 | * - `to` cannot be the zero address. 523 | * - `tokenId` token must exist and be owned by `from`. 524 | * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. 525 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 526 | * 527 | * Emits a {Transfer} event. 528 | */ 529 | // function safeTransferFrom( 530 | // address from, 531 | // address to, 532 | // uint256 tokenId 533 | // ) external; 534 | 535 | /** 536 | * @dev Transfers `tokenId` token from `from` to `to`. 537 | * 538 | * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. 539 | * 540 | * Requirements: 541 | * 542 | * - `from` cannot be the zero address. 543 | * - `to` cannot be the zero address. 544 | * - `tokenId` token must be owned by `from`. 545 | * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. 546 | * 547 | * Emits a {Transfer} event. 548 | */ 549 | function transferFrom( 550 | address from, 551 | address to, 552 | uint256 tokenId 553 | ) external; 554 | 555 | /** 556 | * @dev Gives permission to `to` to transfer `tokenId` token to another account. 557 | * The approval is cleared when the token is transferred. 558 | * 559 | * Only a single account can be approved at a time, so approving the zero address clears previous approvals. 560 | * 561 | * Requirements: 562 | * 563 | * - The caller must own the token or be an approved operator. 564 | * - `tokenId` must exist. 565 | * 566 | * Emits an {Approval} event. 567 | */ 568 | function approve(address to, uint256 tokenId) external; 569 | 570 | /** 571 | * @dev Approve or remove `operator` as an operator for the caller. 572 | * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. 573 | * 574 | * Requirements: 575 | * 576 | * - The `operator` cannot be the caller. 577 | * 578 | * Emits an {ApprovalForAll} event. 579 | */ 580 | function setApprovalForAll(address operator, bool _approved) external; 581 | 582 | /** 583 | * @dev Returns the account approved for `tokenId` token. 584 | * 585 | * Requirements: 586 | * 587 | * - `tokenId` must exist. 588 | */ 589 | function getApproved(uint256 tokenId) external view returns (address operator); 590 | 591 | /** 592 | * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. 593 | * 594 | * See {setApprovalForAll} 595 | */ 596 | function isApprovedForAll(address owner, address operator) external view returns (bool); 597 | } 598 | 599 | // File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol 600 | 601 | 602 | // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) 603 | 604 | 605 | /** 606 | * @title ERC-721 Non-Fungible Token Standard, optional metadata extension 607 | * @dev See https://eips.ethereum.org/EIPS/eip-721 608 | */ 609 | interface IERC721Metadata is IERC721 { 610 | /** 611 | * @dev Returns the token collection name. 612 | */ 613 | function name() external view returns (string memory); 614 | 615 | /** 616 | * @dev Returns the token collection symbol. 617 | */ 618 | function symbol() external view returns (string memory); 619 | 620 | /** 621 | * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. 622 | */ 623 | function tokenURI(uint256 tokenId) external view returns (string memory); 624 | } 625 | 626 | // File: @openzeppelin/contracts/token/ERC721/ERC721.sol 627 | 628 | 629 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/ERC721.sol) 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | /** 640 | * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including 641 | * the Metadata extension, but not including the Enumerable extension, which is available separately as 642 | * {ERC721Enumerable}. 643 | */ 644 | contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { 645 | using Address for address; 646 | using Strings for uint256; 647 | 648 | // Token name 649 | string private _name; 650 | 651 | // Token symbol 652 | string private _symbol; 653 | 654 | // Mapping from token ID to owner address 655 | mapping(uint256 => address) private _owners; 656 | 657 | // Mapping owner address to token count 658 | mapping(address => uint256) private _balances; 659 | 660 | // Mapping from token ID to approved address 661 | mapping(uint256 => address) private _tokenApprovals; 662 | 663 | // Mapping from owner to operator approvals 664 | mapping(address => mapping(address => bool)) private _operatorApprovals; 665 | 666 | /** 667 | * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. 668 | */ 669 | constructor(string memory name_, string memory symbol_) { 670 | _name = name_; 671 | _symbol = symbol_; 672 | } 673 | 674 | /** 675 | * @dev See {IERC165-supportsInterface}. 676 | */ 677 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 678 | return 679 | interfaceId == type(IERC721).interfaceId || 680 | interfaceId == type(IERC721Metadata).interfaceId || 681 | super.supportsInterface(interfaceId); 682 | } 683 | 684 | /** 685 | * @dev See {IERC721-balanceOf}. 686 | */ 687 | function balanceOf(address owner) public view virtual override returns (uint256) { 688 | require(owner != address(0), "ERC721: balance query for the zero address"); 689 | return _balances[owner]; 690 | } 691 | 692 | /** 693 | * @dev See {IERC721-ownerOf}. 694 | */ 695 | function ownerOf(uint256 tokenId) public view virtual override returns (address) { 696 | address owner = _owners[tokenId]; 697 | require(owner != address(0), "ERC721: owner query for nonexistent token"); 698 | return owner; 699 | } 700 | 701 | /** 702 | * @dev See {IERC721Metadata-name}. 703 | */ 704 | function name() public view virtual override returns (string memory) { 705 | return _name; 706 | } 707 | 708 | /** 709 | * @dev See {IERC721Metadata-symbol}. 710 | */ 711 | function symbol() public view virtual override returns (string memory) { 712 | return _symbol; 713 | } 714 | 715 | /** 716 | * @dev See {IERC721Metadata-tokenURI}. 717 | */ 718 | function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { 719 | require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); 720 | 721 | string memory baseURI = _baseURI(); 722 | return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; 723 | } 724 | 725 | /** 726 | * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each 727 | * token will be the concatenation of the `baseURI` and the `tokenId`. Empty 728 | * by default, can be overridden in child contracts. 729 | */ 730 | function _baseURI() internal view virtual returns (string memory) { 731 | return ""; 732 | } 733 | 734 | /** 735 | * @dev See {IERC721-approve}. 736 | */ 737 | function approve(address to, uint256 tokenId) public virtual override { 738 | address owner = ERC721.ownerOf(tokenId); 739 | require(to != owner, "ERC721: approval to current owner"); 740 | 741 | require( 742 | _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 743 | "ERC721: approve caller is not owner nor approved for all" 744 | ); 745 | 746 | _approve(to, tokenId); 747 | } 748 | 749 | /** 750 | * @dev See {IERC721-getApproved}. 751 | */ 752 | function getApproved(uint256 tokenId) public view virtual override returns (address) { 753 | require(_exists(tokenId), "ERC721: approved query for nonexistent token"); 754 | 755 | return _tokenApprovals[tokenId]; 756 | } 757 | 758 | /** 759 | * @dev See {IERC721-setApprovalForAll}. 760 | */ 761 | function setApprovalForAll(address operator, bool approved) public virtual override { 762 | _setApprovalForAll(_msgSender(), operator, approved); 763 | } 764 | 765 | /** 766 | * @dev See {IERC721-isApprovedForAll}. 767 | */ 768 | function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { 769 | return _operatorApprovals[owner][operator]; 770 | } 771 | 772 | /** 773 | * @dev See {IERC721-transferFrom}. 774 | */ 775 | function transferFrom( 776 | address from, 777 | address to, 778 | uint256 tokenId 779 | ) public virtual override { 780 | //solhint-disable-next-line max-line-length 781 | require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 782 | 783 | _transfer(from, to, tokenId); 784 | } 785 | 786 | /** 787 | * @dev See {IERC721-safeTransferFrom}. 788 | */ 789 | // function safeTransferFrom( 790 | // address from, 791 | // address to, 792 | // uint256 tokenId 793 | // ) public virtual override { 794 | // safeTransferFrom(from, to, tokenId, ""); 795 | // } 796 | 797 | /** 798 | * @dev See {IERC721-safeTransferFrom}. 799 | */ 800 | // function safeTransferFrom( 801 | // address from, 802 | // address to, 803 | // uint256 tokenId, 804 | // bytes memory _data 805 | // ) public virtual override { 806 | // require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 807 | // _safeTransfer(from, to, tokenId, _data); 808 | // } 809 | 810 | /** 811 | * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 812 | * are aware of the ERC721 protocol to prevent tokens from being forever locked. 813 | * 814 | * `_data` is additional data, it has no specified format and it is sent in call to `to`. 815 | * 816 | * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. 817 | * implement alternative mechanisms to perform token transfer, such as signature-based. 818 | * 819 | * Requirements: 820 | * 821 | * - `from` cannot be the zero address. 822 | * - `to` cannot be the zero address. 823 | * - `tokenId` token must exist and be owned by `from`. 824 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 825 | * 826 | * Emits a {Transfer} event. 827 | */ 828 | function _safeTransfer( 829 | address from, 830 | address to, 831 | uint256 tokenId, 832 | bytes memory _data 833 | ) internal virtual { 834 | _transfer(from, to, tokenId); 835 | require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); 836 | } 837 | 838 | /** 839 | * @dev Returns whether `tokenId` exists. 840 | * 841 | * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. 842 | * 843 | * Tokens start existing when they are minted (`_mint`), 844 | * and stop existing when they are burned (`_burn`). 845 | */ 846 | function _exists(uint256 tokenId) internal view virtual returns (bool) { 847 | return _owners[tokenId] != address(0); 848 | } 849 | 850 | /** 851 | * @dev Returns whether `spender` is allowed to manage `tokenId`. 852 | * 853 | * Requirements: 854 | * 855 | * - `tokenId` must exist. 856 | */ 857 | function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { 858 | require(_exists(tokenId), "ERC721: operator query for nonexistent token"); 859 | address owner = ERC721.ownerOf(tokenId); 860 | return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); 861 | } 862 | 863 | /** 864 | * @dev Safely mints `tokenId` and transfers it to `to`. 865 | * 866 | * Requirements: 867 | * 868 | * - `tokenId` must not exist. 869 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 870 | * 871 | * Emits a {Transfer} event. 872 | */ 873 | function _safeMint(address to, uint256 tokenId) internal virtual { 874 | _safeMint(to, tokenId, ""); 875 | } 876 | 877 | /** 878 | * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is 879 | * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. 880 | */ 881 | function _safeMint( 882 | address to, 883 | uint256 tokenId, 884 | bytes memory _data 885 | ) internal virtual { 886 | _mint(to, tokenId); 887 | require( 888 | _checkOnERC721Received(address(0), to, tokenId, _data), 889 | "ERC721: transfer to non ERC721Receiver implementer" 890 | ); 891 | } 892 | 893 | /** 894 | * @dev Mints `tokenId` and transfers it to `to`. 895 | * 896 | * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible 897 | * 898 | * Requirements: 899 | * 900 | * - `tokenId` must not exist. 901 | * - `to` cannot be the zero address. 902 | * 903 | * Emits a {Transfer} event. 904 | */ 905 | function _mint(address to, uint256 tokenId) internal virtual { 906 | require(to != address(0), "ERC721: mint to the zero address"); 907 | require(!_exists(tokenId), "ERC721: token already minted"); 908 | 909 | _beforeTokenTransfer(address(0), to, tokenId); 910 | 911 | _balances[to] += 1; 912 | _owners[tokenId] = to; 913 | 914 | emit Transfer(address(0), to, tokenId); 915 | 916 | _afterTokenTransfer(address(0), to, tokenId); 917 | } 918 | 919 | /** 920 | * @dev Destroys `tokenId`. 921 | * The approval is cleared when the token is burned. 922 | * 923 | * Requirements: 924 | * 925 | * - `tokenId` must exist. 926 | * 927 | * Emits a {Transfer} event. 928 | */ 929 | function _burn(uint256 tokenId) internal virtual { 930 | address owner = ERC721.ownerOf(tokenId); 931 | 932 | _beforeTokenTransfer(owner, address(0), tokenId); 933 | 934 | // Clear approvals 935 | _approve(address(0), tokenId); 936 | 937 | _balances[owner] -= 1; 938 | delete _owners[tokenId]; 939 | 940 | emit Transfer(owner, address(0), tokenId); 941 | 942 | _afterTokenTransfer(owner, address(0), tokenId); 943 | } 944 | 945 | /** 946 | * @dev Transfers `tokenId` from `from` to `to`. 947 | * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. 948 | * 949 | * Requirements: 950 | * 951 | * - `to` cannot be the zero address. 952 | * - `tokenId` token must be owned by `from`. 953 | * 954 | * Emits a {Transfer} event. 955 | */ 956 | function _transfer( 957 | address from, 958 | address to, 959 | uint256 tokenId 960 | ) internal virtual { 961 | require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); 962 | require(to != address(0), "ERC721: transfer to the zero address"); 963 | 964 | _beforeTokenTransfer(from, to, tokenId); 965 | 966 | // Clear approvals from the previous owner 967 | _approve(address(0), tokenId); 968 | 969 | _balances[from] -= 1; 970 | _balances[to] += 1; 971 | _owners[tokenId] = to; 972 | 973 | emit Transfer(from, to, tokenId); 974 | 975 | _afterTokenTransfer(from, to, tokenId); 976 | } 977 | 978 | /** 979 | * @dev Approve `to` to operate on `tokenId` 980 | * 981 | * Emits a {Approval} event. 982 | */ 983 | function _approve(address to, uint256 tokenId) internal virtual { 984 | _tokenApprovals[tokenId] = to; 985 | emit Approval(ERC721.ownerOf(tokenId), to, tokenId); 986 | } 987 | 988 | /** 989 | * @dev Approve `operator` to operate on all of `owner` tokens 990 | * 991 | * Emits a {ApprovalForAll} event. 992 | */ 993 | function _setApprovalForAll( 994 | address owner, 995 | address operator, 996 | bool approved 997 | ) internal virtual { 998 | require(owner != operator, "ERC721: approve to caller"); 999 | _operatorApprovals[owner][operator] = approved; 1000 | emit ApprovalForAll(owner, operator, approved); 1001 | } 1002 | 1003 | /** 1004 | * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. 1005 | * The call is not executed if the target address is not a contract. 1006 | * 1007 | * @param from address representing the previous owner of the given token ID 1008 | * @param to target address that will receive the tokens 1009 | * @param tokenId uint256 ID of the token to be transferred 1010 | * @param _data bytes optional data to send along with the call 1011 | * @return bool whether the call correctly returned the expected magic value 1012 | */ 1013 | function _checkOnERC721Received( 1014 | address from, 1015 | address to, 1016 | uint256 tokenId, 1017 | bytes memory _data 1018 | ) private returns (bool) { 1019 | if (to.isContract()) { 1020 | try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { 1021 | return retval == IERC721Receiver.onERC721Received.selector; 1022 | } catch (bytes memory reason) { 1023 | if (reason.length == 0) { 1024 | revert("ERC721: transfer to non ERC721Receiver implementer"); 1025 | } else { 1026 | assembly { 1027 | revert(add(32, reason), mload(reason)) 1028 | } 1029 | } 1030 | } 1031 | } else { 1032 | return true; 1033 | } 1034 | } 1035 | 1036 | /** 1037 | * @dev Hook that is called before any token transfer. This includes minting 1038 | * and burning. 1039 | * 1040 | * Calling conditions: 1041 | * 1042 | * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be 1043 | * transferred to `to`. 1044 | * - When `from` is zero, `tokenId` will be minted for `to`. 1045 | * - When `to` is zero, ``from``'s `tokenId` will be burned. 1046 | * - `from` and `to` are never both zero. 1047 | * 1048 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 1049 | */ 1050 | function _beforeTokenTransfer( 1051 | address from, 1052 | address to, 1053 | uint256 tokenId 1054 | ) internal virtual {} 1055 | 1056 | /** 1057 | * @dev Hook that is called after any transfer of tokens. This includes 1058 | * minting and burning. 1059 | * 1060 | * Calling conditions: 1061 | * 1062 | * - when `from` and `to` are both non-zero. 1063 | * - `from` and `to` are never both zero. 1064 | * 1065 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 1066 | */ 1067 | function _afterTokenTransfer( 1068 | address from, 1069 | address to, 1070 | uint256 tokenId 1071 | ) internal virtual {} 1072 | } 1073 | 1074 | // File: @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol 1075 | 1076 | 1077 | // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721URIStorage.sol) 1078 | 1079 | 1080 | 1081 | /** 1082 | * @dev ERC721 token with storage based token URI management. 1083 | */ 1084 | abstract contract ERC721URIStorage is ERC721 { 1085 | using Strings for uint256; 1086 | 1087 | // Optional mapping for token URIs 1088 | mapping(uint256 => string) private _tokenURIs; 1089 | 1090 | /** 1091 | * @dev See {IERC721Metadata-tokenURI}. 1092 | */ 1093 | function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { 1094 | require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token"); 1095 | 1096 | string memory _tokenURI = _tokenURIs[tokenId]; 1097 | string memory base = _baseURI(); 1098 | 1099 | // If there is no base URI, return the token URI. 1100 | if (bytes(base).length == 0) { 1101 | return _tokenURI; 1102 | } 1103 | // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). 1104 | if (bytes(_tokenURI).length > 0) { 1105 | return string(abi.encodePacked(base, _tokenURI)); 1106 | } 1107 | 1108 | return super.tokenURI(tokenId); 1109 | } 1110 | 1111 | /** 1112 | * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. 1113 | * 1114 | * Requirements: 1115 | * 1116 | * - `tokenId` must exist. 1117 | */ 1118 | function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { 1119 | require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token"); 1120 | _tokenURIs[tokenId] = _tokenURI; 1121 | } 1122 | 1123 | /** 1124 | * @dev Destroys `tokenId`. 1125 | * The approval is cleared when the token is burned. 1126 | * 1127 | * Requirements: 1128 | * 1129 | * - `tokenId` must exist. 1130 | * 1131 | * Emits a {Transfer} event. 1132 | */ 1133 | function _burn(uint256 tokenId) internal virtual override { 1134 | super._burn(tokenId); 1135 | 1136 | if (bytes(_tokenURIs[tokenId]).length != 0) { 1137 | delete _tokenURIs[tokenId]; 1138 | } 1139 | } 1140 | } 1141 | // File: contracts/SoulToken.sol 1142 | 1143 | contract UniversityDegree is ERC721URIStorage { 1144 | 1145 | address owner; 1146 | 1147 | using Counters for Counters.Counter; 1148 | Counters.Counter private _tokenIds; 1149 | 1150 | constructor() ERC721("SomeNFT", "CoolNFT") { 1151 | owner = msg.sender; 1152 | } 1153 | 1154 | mapping(address => string) public personToDegree; 1155 | 1156 | function claimDegree(string memory tokenURI) 1157 | public 1158 | returns (uint256) 1159 | { 1160 | require(issuedDegrees[msg.sender], "Degree is not issued"); 1161 | 1162 | _tokenIds.increment(); 1163 | 1164 | uint256 newItemId = _tokenIds.current(); 1165 | _mint(msg.sender, newItemId); 1166 | _setTokenURI(newItemId, tokenURI); 1167 | 1168 | personToDegree[msg.sender] = tokenURI; 1169 | issuedDegrees[msg.sender] = false; 1170 | 1171 | return newItemId; 1172 | } 1173 | 1174 | function checkDegreeOfPerson(address person) external view returns (string memory) { 1175 | return personToDegree[person]; 1176 | } 1177 | 1178 | modifier onlyOwner() { 1179 | require(msg.sender == owner); 1180 | _; 1181 | } 1182 | 1183 | mapping(address => bool) public issuedDegrees; 1184 | 1185 | function issueDegree(address to) onlyOwner external { 1186 | issuedDegrees[to] = true; 1187 | } 1188 | } --------------------------------------------------------------------------------