├── .solhintignore ├── .solcover.js ├── .eslintignore ├── .husky └── pre-commit ├── .prettierignore ├── audits └── Starton Security Analysis by Pessimistic.pdf ├── .gitignore ├── .vscode └── settings.json ├── contracts ├── abstracts │ ├── AStartonLock.sol │ ├── AStartonWhitelist.sol │ ├── AStartonPausable.sol │ ├── AStartonMintLock.sol │ ├── AStartonContextMixin.sol │ ├── AStartonMetadataLock.sol │ ├── AStartonEIP712Base.sol │ ├── AStartonOwnable.sol │ └── AStartonNativeMetaTransaction.sol ├── interfaces │ ├── IStartonERC721.sol │ ├── IStartonERC1155.sol │ ├── IStartonERC5192.sol │ └── IERC4907.sol ├── tools │ ├── StartonPaymentSplitter.sol │ ├── StartonTimelockController.sol │ ├── StartonMultiSendERC721.sol │ ├── StartonMultiSendERC20.sol │ └── StartonMultiSendERC1155.sol ├── deprecated │ ├── StartonERC20Pause.sol │ ├── StartonERC20BurnPause.sol │ ├── StartonERC20MintBurnPause.sol │ ├── polygon │ │ ├── ChildStartonERC20Pause.sol │ │ ├── ChildStartonERC20BurnPause.sol │ │ ├── ChildStartonERC20MintBurnPause.sol │ │ ├── ChildStartonERC1155.sol │ │ ├── ChildStartonERC721Capped.sol │ │ └── ChildStartonERC721.sol │ ├── StartonERC721Capped.sol │ ├── StartonERC1155.sol │ └── StartonERC721.sol ├── non-fungible │ ├── StartonERC1155BaseRoyalties.sol │ ├── StartonERC721BaseRoyalties.sol │ ├── StartonERC721Capped.sol │ ├── StartonERC721CappedRoyalties.sol │ ├── StartonERC5192.sol │ ├── StartonERC4907.sol │ ├── StartonERC1155Base.sol │ └── StartonERC721Base.sol ├── fungible │ ├── StartonERC20Mintable.sol │ └── StartonERC20Base.sol └── nft-sales │ ├── StartonERC721WhitelistSale.sol │ ├── StartonERC1155WhitelistSale.sol │ ├── StartonERC721BaseSale.sol │ ├── StartonERC1155BaseSale.sol │ ├── StartonERC721AuctionSale.sol │ └── StartonERC1155AuctionSale.sol ├── tsconfig.json ├── .solhint.json ├── .prettierrc ├── .eslintrc.js ├── scripts ├── single-line-contract.ts ├── smart-contract-template.ts ├── flatten-contracts-folder.ts └── generate-doc.ts ├── hardhat.config.ts ├── README.md ├── .gitlab-ci.yml ├── test ├── StartonMultiSendERC20.test.ts ├── StartonMultiSendERC721.test.ts ├── StartonERC5192.test.ts ├── StartonMultiSendERC1155.test.ts ├── StartonERC20Base.test.ts └── StartonERC20Mintable.test.ts ├── package.json ├── flatten └── tools │ ├── StartonMultiSendERC20.sol │ ├── StartonMultiSendERC1155.sol │ └── StartonMultiSendERC721.sol └── CODE_OF_CONDUCT.md /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | flatten 3 | deprecated -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['deprecated'] 3 | }; -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | typechain-types -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | flatten 6 | contracts/deprecated -------------------------------------------------------------------------------- /audits/Starton Security Analysis by Pessimistic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starton-io/smart-contract-templates/HEAD/audits/Starton Security Analysis by Pessimistic.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | .idea 6 | .DS_Store 7 | seeder.ts 8 | 9 | #Hardhat files 10 | cache 11 | artifacts 12 | typechain-types 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "solidity.formatter": "prettier", 4 | "[solidity]": { 5 | "editor.defaultFormatter": "JuanBlanco.solidity" 6 | }, 7 | "solidity.compileUsingRemoteVersion": "v0.8.17" 8 | } 9 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | 7 | abstract contract AStartonLock is AccessControl { 8 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["./scripts", "./test"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IStartonERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; 6 | 7 | interface IStartonERC721 is IERC721Enumerable { 8 | function mint(address to, string memory uri) external; 9 | } 10 | -------------------------------------------------------------------------------- /contracts/tools/StartonPaymentSplitter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/finance/PaymentSplitter.sol"; 6 | 7 | contract StartonPaymentSplitter is PaymentSplitter { 8 | constructor(address[] memory payees, uint256[] memory shares) payable PaymentSplitter(payees, shares) {} 9 | } 10 | -------------------------------------------------------------------------------- /contracts/tools/StartonTimelockController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/governance/TimelockController.sol"; 6 | 7 | contract StartonTimeLockController is TimelockController { 8 | constructor( 9 | uint256 minDelay, 10 | address[] memory proposers, 11 | address[] memory executors, 12 | address admin 13 | ) payable TimelockController(minDelay, proposers, executors, admin) {} 14 | } 15 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": [], 4 | "rules": { 5 | "reason-string": ["warn", { "maxLength": 64 }], 6 | "compiler-version": ["error", "^0.8.0"], 7 | "const-name-snakecase": "error", 8 | "contract-name-camelcase": "error", 9 | "event-name-camelcase": "error", 10 | "func-name-mixedcase": "error", 11 | "func-param-name-mixedcase": "error", 12 | "modifier-name-mixedcase": "error", 13 | "private-vars-leading-underscore": "error", 14 | "var-name-mixedcase": "error", 15 | "imports-on-top": "error", 16 | "func-visibility": ["warn", { "ignoreConstructors": true }], 17 | "no-empty-blocks": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "compiler": "0.8.17", 7 | "singleQuote": false, 8 | "printWidth": 120, 9 | "explicitTypes": "always" 10 | } 11 | }, 12 | { 13 | "files": "*.ts", 14 | "options": { 15 | "printWidth": 120, 16 | "tabWidth": 4, 17 | "useTabs": true, 18 | "semi": false, 19 | "singleQuote": false, 20 | "quoteProps": "as-needed", 21 | "trailingComma": "all", 22 | "bracketSpacing": true, 23 | "arrowParens": "always", 24 | "proseWrap": "preserve", 25 | "endOfLine": "lf" 26 | } 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:node/recommended", 13 | ], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | rules: { 19 | "prettier/prettier": "off", 20 | "node/no-unsupported-features/es-syntax": [ 21 | "error", 22 | { ignores: ["modules"] }, 23 | ], 24 | "node/no-unpublished-import": ["off", {}], 25 | "no-process-exit": ["off"], 26 | "node/no-missing-import": "off", 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonWhitelist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 6 | import "@openzeppelin/contracts/utils/Context.sol"; 7 | 8 | abstract contract AStartonWhitelist is Context { 9 | // Root of the merkle tree for the whitelisted address 10 | bytes32 internal _merkleRoot; 11 | 12 | /** @dev Modifier that reverts when the sender is not whitelisted */ 13 | modifier isWhitelisted(bytes32[] calldata merkleProof) { 14 | bytes32 leaf = keccak256(abi.encodePacked(_msgSender())); 15 | require(MerkleProof.verify(merkleProof, _merkleRoot, leaf), "Invalid proof"); 16 | _; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonPausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract AStartonPausable is Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | 11 | /** 12 | * @notice Pause the contract 13 | * @custom:requires PAUSER_ROLE 14 | */ 15 | function pause() public virtual onlyRole(PAUSER_ROLE) { 16 | _pause(); 17 | } 18 | 19 | /** 20 | * @notice Unpause the contract 21 | * @custom:requires PAUSER_ROLE 22 | */ 23 | function unpause() public virtual onlyRole(PAUSER_ROLE) { 24 | _unpause(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/IStartonERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 6 | 7 | interface IStartonERC1155 is IERC1155 { 8 | function mint( 9 | address to, 10 | uint256 id, 11 | uint256 amount, 12 | bytes memory data 13 | ) external; 14 | 15 | function mint( 16 | address to, 17 | uint256 id, 18 | uint256 amount 19 | ) external payable; 20 | 21 | function mintBatch( 22 | address to, 23 | uint256[] memory ids, 24 | uint256[] memory amounts, 25 | bytes memory data 26 | ) external; 27 | 28 | function mintBatch( 29 | address to, 30 | uint256[] memory ids, 31 | uint256[] memory amounts 32 | ) external; 33 | } 34 | -------------------------------------------------------------------------------- /scripts/single-line-contract.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | function printUsage(): void { 4 | console.log("USAGE\n\tts-node single-line-contract input out"); 5 | console.log(); 6 | console.log("DESCRIPTION\n\tinput\tThe input contract file to convert"); 7 | console.log("\tout\tThe output file where to write the converted contract"); 8 | } 9 | 10 | function main() { 11 | if (process.argv.length !== 4) { 12 | printUsage(); 13 | return; 14 | } 15 | 16 | const contractFilePath = process.argv[2]; 17 | const outFilePath = process.argv[3]; 18 | 19 | const fileContents = readFileSync(contractFilePath, { 20 | encoding: "utf8", 21 | flag: "r", 22 | }).toString(); 23 | 24 | writeFileSync( 25 | outFilePath, 26 | fileContents.replace(/\n/g, "\\n").replace(/'/g, "\\'") 27 | ); 28 | } 29 | main(); 30 | -------------------------------------------------------------------------------- /contracts/interfaces/IStartonERC5192.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IStartonERC5192 { 6 | /// @notice Emitted when the locking status is changed to locked. 7 | /// @dev If a token is minted and the status is locked, this event should be emitted. 8 | /// @param tokenId The identifier for a token. 9 | event Locked(uint256 tokenId); 10 | 11 | /// @notice Emitted when the locking status is changed to unlocked. 12 | /// @dev If a token is minted and the status is unlocked, this event should be emitted. 13 | /// @param tokenId The identifier for a token. 14 | event Unlocked(uint256 tokenId); 15 | 16 | /// @notice Returns the locking status of an Soulbound Token 17 | /// @dev SBTs assigned to zero address are considered invalid, and queries 18 | /// about them do throw. 19 | /// @param tokenId The identifier for an SBT. 20 | function locked(uint256 tokenId) external view returns (bool); 21 | } 22 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonMintLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "./AStartonPausable.sol"; 7 | import "./AStartonLock.sol"; 8 | 9 | abstract contract AStartonMintLock is AStartonPausable, AStartonLock { 10 | bool internal _isMintAllowed; 11 | 12 | /** @notice Event emitted when the minting is locked */ 13 | event MintingLocked(address indexed account); 14 | 15 | /** @dev Modifier that reverts when the minting is locked */ 16 | modifier mintingNotLocked() { 17 | require(_isMintAllowed, "Minting is locked"); 18 | _; 19 | } 20 | 21 | /** 22 | * @notice Lock the metadats and won't allow any changes anymore if the contract is not paused 23 | * @custom:requires LOCKER_ROLE 24 | */ 25 | function lockMint() public virtual whenNotPaused onlyRole(LOCKER_ROLE) { 26 | _isMintAllowed = false; 27 | emit MintingLocked(_msgSender()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonContextMixin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title AStartonContextMixin 6 | /// @author Starton 7 | /// @notice Utility smart contract that track the signer of a meta transaction 8 | abstract contract AStartonContextMixin { 9 | /** 10 | * @dev Returns the address of the current signer. 11 | * @return sender The address of the signer of the current meta transaction 12 | */ 13 | function _msgSender() internal view virtual returns (address sender) { 14 | if (msg.sender == address(this)) { 15 | bytes memory array = msg.data; 16 | uint256 index = msg.data.length; 17 | assembly { 18 | // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. 19 | sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff) 20 | } 21 | } else { 22 | sender = msg.sender; 23 | } 24 | return sender; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonMetadataLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "./AStartonPausable.sol"; 7 | import "./AStartonLock.sol"; 8 | 9 | abstract contract AStartonMetadataLock is AStartonPausable, AStartonLock { 10 | bool internal _isMetadataChangingAllowed; 11 | 12 | /** @notice Event emitted when the metadata are locked */ 13 | event MetadataLocked(address indexed account); 14 | 15 | /** @dev Modifier that reverts when the metadatas are locked */ 16 | modifier metadataNotLocked() { 17 | require(_isMetadataChangingAllowed, "Metadatas are locked"); 18 | _; 19 | } 20 | 21 | /** 22 | * @notice Lock the metadats and won't allow any changes anymore if the contract is not paused 23 | * @custom:requires LOCKER_ROLE 24 | */ 25 | function lockMetadata() public virtual whenNotPaused onlyRole(LOCKER_ROLE) { 26 | _isMetadataChangingAllowed = false; 27 | emit MetadataLocked(_msgSender()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/smart-contract-template.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | enum SmartContractTemplateCategories { 4 | OTHER = "SmartContractTemplateCategories.OTHER", 5 | FUNGIBLE = "SmartContractTemplateCategories.FUNGIBLE", 6 | SALE = "SmartContractTemplateCategories.SALE", 7 | NFT = "SmartContractTemplateCategories.NFT", 8 | DEPRECATED = "SmartContractTemplateCategories.DEPRECATED", 9 | } 10 | 11 | type CompilationDetails = { 12 | runs?: number; 13 | source?: string; 14 | bytecode?: string; 15 | contractName: string; 16 | compilerVersion?: string; 17 | optimizationUsed?: boolean; 18 | }; 19 | 20 | type SmartContractTemplate = { 21 | id: string; 22 | name: string; 23 | description: string; 24 | shortDescription: string; 25 | githubUrl?: string; 26 | blockchains: string[]; 27 | compilationDetails: CompilationDetails; 28 | tags: SmartContractTemplateCategories[]; 29 | category: SmartContractTemplateCategories; 30 | abi?: Array>; 31 | form?: Record; 32 | isAudited?: boolean; 33 | isActivated?: boolean; 34 | order?: number; 35 | }; 36 | 37 | export { SmartContractTemplate, SmartContractTemplateCategories }; 38 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC20Pause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract StartonERC20Pause is ERC20, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | 11 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 12 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 13 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 14 | _mint(ownerOrMultiSigContract, initialSupply); 15 | } 16 | 17 | function pause() public { 18 | require(hasRole(PAUSER_ROLE, msg.sender)); 19 | _pause(); 20 | } 21 | 22 | function unpause() public { 23 | require(hasRole(PAUSER_ROLE, msg.sender)); 24 | _unpause(); 25 | } 26 | 27 | function _beforeTokenTransfer(address from, address to, uint256 amount) 28 | internal 29 | whenNotPaused 30 | override 31 | { 32 | super._beforeTokenTransfer(from, to, amount); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/tools/StartonMultiSendERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | /// @title StartonMultiSendERC721 8 | /// @author Starton 9 | /// @notice This contract allows to send multiple ERC721 tokens in a single transaction 10 | contract StartonMultiSendERC721 { 11 | /** 12 | * @notice Send multiple ERC721 tokens in a single transaction 13 | * @param token The address of the ERC721 token 14 | * @param ids The ids of tokens to send 15 | * @param addresses The addresses to send the tokens to 16 | */ 17 | function multiSend( 18 | address token, 19 | uint256[] calldata ids, 20 | address[] calldata addresses 21 | ) external { 22 | require(ids.length == addresses.length, "Arrays must be of equal length"); 23 | 24 | uint256 length = ids.length; 25 | for (uint256 i = 0; i < length; ) { 26 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 27 | IERC721(token).transferFrom(msg.sender, addresses[i], ids[i]); 28 | unchecked { 29 | i += 1; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/tools/StartonMultiSendERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /// @title StartonMultiSendERC20 8 | /// @author Starton 9 | /// @notice This contract allows to send multiple ERC20 tokens in a single transaction 10 | contract StartonMultiSendERC20 { 11 | /** 12 | * @notice Send multiple ERC20 tokens in a single transaction 13 | * @param token The address of the ERC20 token 14 | * @param amounts The amounts of tokens to send 15 | * @param addresses The addresses to send the tokens to 16 | */ 17 | function multiSend( 18 | address token, 19 | uint256[] calldata amounts, 20 | address[] calldata addresses 21 | ) external { 22 | require(amounts.length == addresses.length, "Arrays must be of equal length"); 23 | 24 | uint256 length = amounts.length; 25 | for (uint256 i = 0; i < length; ) { 26 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 27 | IERC20(token).transferFrom(msg.sender, addresses[i], amounts[i]); 28 | unchecked { 29 | i += 1; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC20BurnPause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract StartonERC20BurnPause is ERC20Burnable, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | 11 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 12 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 13 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 14 | _mint(ownerOrMultiSigContract, initialSupply); 15 | } 16 | 17 | function pause() public { 18 | require(hasRole(PAUSER_ROLE, msg.sender)); 19 | _pause(); 20 | } 21 | 22 | function unpause() public { 23 | require(hasRole(PAUSER_ROLE, msg.sender)); 24 | _unpause(); 25 | } 26 | 27 | function _beforeTokenTransfer(address from, address to, uint256 amount) 28 | internal 29 | whenNotPaused 30 | override 31 | { 32 | super._beforeTokenTransfer(from, to, amount); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; 2 | import { HardhatUserConfig, task, subtask } from "hardhat/config"; 3 | import "@nomicfoundation/hardhat-chai-matchers"; 4 | import "@nomiclabs/hardhat-ethers"; 5 | import "@typechain/hardhat"; 6 | import "hardhat-gas-reporter"; 7 | import "solidity-coverage"; 8 | 9 | // This is a sample Hardhat task. To learn how to create your own go to 10 | // https://hardhat.org/guides/create-task.html 11 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 12 | const accounts = await hre.ethers.getSigners(); 13 | 14 | for (const account of accounts) { 15 | console.log(account.address); 16 | } 17 | }); 18 | 19 | subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( 20 | async (_, __, runSuper) => { 21 | const paths = await runSuper(); 22 | 23 | return paths.filter((p: string) => !p.includes("deprecated")); 24 | } 25 | ); 26 | 27 | // You need to export an object to set up your config 28 | // Go to https://hardhat.org/config/ to learn more 29 | 30 | const config: HardhatUserConfig = { 31 | solidity: { 32 | version: "0.8.17", 33 | settings: { 34 | optimizer: { 35 | enabled: true, 36 | runs: 200, 37 | }, 38 | }, 39 | }, 40 | }; 41 | 42 | export default config; 43 | -------------------------------------------------------------------------------- /contracts/tools/StartonMultiSendERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 6 | 7 | /// @title StartonMultiSendERC11555 8 | /// @author Starton 9 | /// @notice This contract allows to send multiple ERC1155 tokens in a single transaction 10 | contract StartonMultiSendERC1155 { 11 | /** 12 | * @notice Send multiple ERC1155 tokens in a single transaction 13 | * @param token The address of the ERC1155 token 14 | * @param ids The ids of tokens to send 15 | * @param amounts The amounts of tokens to send 16 | * @param addresses The addresses to send the tokens to 17 | */ 18 | function multiSend( 19 | address token, 20 | uint256[] calldata ids, 21 | uint256[] calldata amounts, 22 | address[] calldata addresses 23 | ) external { 24 | require(ids.length == addresses.length, "Arrays must be of equal length"); 25 | require(addresses.length == amounts.length, "Arrays must be of equal length"); 26 | 27 | uint256 length = ids.length; 28 | for (uint256 i = 0; i < length; ) { 29 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 30 | IERC1155(token).safeTransferFrom(msg.sender, addresses[i], ids[i], amounts[i], ""); 31 | unchecked { 32 | i += 1; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC1155BaseRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/common/ERC2981.sol"; 6 | import "./StartonERC1155Base.sol"; 7 | 8 | /// @title StartonERC1155Base 9 | /// @author Starton 10 | /// @notice ERC1155 tokens that can be blacklisted, paused, locked, burned, have a access management and handle meta transactions 11 | contract StartonERC1155BaseRoyalties is StartonERC1155Base, ERC2981 { 12 | constructor( 13 | string memory definitiveName, 14 | uint96 definitiveRoyaltyFee, 15 | address definitiveFeeReceiver, 16 | string memory initialTokenURI, 17 | string memory initialContractURI, 18 | address initialOwnerOrMultiSigContract 19 | ) StartonERC1155Base(definitiveName, initialTokenURI, initialContractURI, initialOwnerOrMultiSigContract) { 20 | // Set the royalty fee and the fee receiver 21 | _setDefaultRoyalty(definitiveFeeReceiver, definitiveRoyaltyFee); 22 | } 23 | 24 | /** 25 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 26 | * @return True if the interface is supported 27 | */ 28 | function supportsInterface(bytes4 interfaceId) 29 | public 30 | view 31 | virtual 32 | override(StartonERC1155Base, ERC2981) 33 | returns (bool) 34 | { 35 | return super.supportsInterface(interfaceId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC4907.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IERC4907 { 6 | // Logged when the user of an NFT is changed or expires is changed 7 | /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed 8 | /// The zero address for user indicates that there is no user address 9 | event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); 10 | 11 | /// @notice set the user and expires of an NFT 12 | /// @dev The zero address indicates there is no user 13 | /// Throws if `tokenId` is not valid NFT 14 | /// @param user The new user of the NFT 15 | /// @param expires UNIX timestamp, The new user could use the NFT before expires 16 | function setUser( 17 | uint256 tokenId, 18 | address user, 19 | uint64 expires 20 | ) external; 21 | 22 | /// @notice Get the user address of an NFT 23 | /// @dev The zero address indicates that there is no user or the user is expired 24 | /// @param tokenId The NFT to get the user address for 25 | /// @return The user address for this NFT 26 | function userOf(uint256 tokenId) external view returns (address); 27 | 28 | /// @notice Get the user expires of an NFT 29 | /// @dev The zero value indicates that there is no user 30 | /// @param tokenId The NFT to get the user expires for 31 | /// @return The user expires for this NFT 32 | function userExpires(uint256 tokenId) external view returns (uint256); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC20MintBurnPause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract StartonERC20MintBurnPause is ERC20Burnable, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 11 | 12 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 13 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 14 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 15 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 16 | _mint(ownerOrMultiSigContract, initialSupply); 17 | } 18 | 19 | function pause() public { 20 | require(hasRole(PAUSER_ROLE, msg.sender)); 21 | _pause(); 22 | } 23 | 24 | function unpause() public { 25 | require(hasRole(PAUSER_ROLE, msg.sender)); 26 | _unpause(); 27 | } 28 | 29 | function mint(address to, uint256 amount) public { 30 | require(hasRole(MINTER_ROLE, msg.sender)); 31 | _mint(to, amount); 32 | } 33 | 34 | function _beforeTokenTransfer(address from, address to, uint256 amount) 35 | internal 36 | whenNotPaused 37 | override 38 | { 39 | super._beforeTokenTransfer(from, to, amount); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC721BaseRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/common/ERC2981.sol"; 6 | import "./StartonERC721Base.sol"; 7 | 8 | /// @title StartonERC721Base 9 | /// @author Starton 10 | /// @notice ERC721 tokens that can be blacklisted, paused, locked, burned, have a access management and handle meta transactions 11 | contract StartonERC721BaseRoyalties is StartonERC721Base, ERC2981 { 12 | constructor( 13 | string memory definitiveName, 14 | string memory definitiveSymbol, 15 | uint96 definitiveRoyaltyFee, 16 | address definitiveFeeReceiver, 17 | string memory initialBaseTokenURI, 18 | string memory initialContractURI, 19 | address initialOwnerOrMultiSigContract 20 | ) 21 | StartonERC721Base( 22 | definitiveName, 23 | definitiveSymbol, 24 | initialBaseTokenURI, 25 | initialContractURI, 26 | initialOwnerOrMultiSigContract 27 | ) 28 | { 29 | // Set the default royalty fee and receiver 30 | _setDefaultRoyalty(definitiveFeeReceiver, definitiveRoyaltyFee); 31 | } 32 | 33 | /** 34 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 35 | * @return True if the interface is supported 36 | */ 37 | function supportsInterface(bytes4 interfaceId) 38 | public 39 | view 40 | virtual 41 | override(StartonERC721Base, ERC2981) 42 | returns (bool) 43 | { 44 | return super.supportsInterface(interfaceId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC721Capped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./StartonERC721Base.sol"; 6 | 7 | /// @title StartonERC721Capped 8 | /// @author Starton 9 | /// @notice ERC721 tokens that can be blacklisted, paused, locked, burned, have a access management, max number of tokens and handle meta transactions 10 | contract StartonERC721Capped is StartonERC721Base { 11 | using Counters for Counters.Counter; 12 | 13 | uint256 public immutable maxSupply; 14 | 15 | constructor( 16 | string memory definitiveName, 17 | string memory definitiveSymbol, 18 | uint256 definitiveMaxSupply, 19 | string memory initialBaseTokenURI, 20 | string memory initialContractURI, 21 | address initialOwnerOrMultiSigContract 22 | ) 23 | StartonERC721Base( 24 | definitiveName, 25 | definitiveSymbol, 26 | initialBaseTokenURI, 27 | initialContractURI, 28 | initialOwnerOrMultiSigContract 29 | ) 30 | { 31 | require(definitiveMaxSupply > 0, "maxSupply must be greater than 0"); 32 | 33 | maxSupply = definitiveMaxSupply; 34 | } 35 | 36 | /** 37 | * @notice Mint a new token to the given address and set the token metadata while minting is not locked 38 | * @param to The address that will receive the token 39 | * @param uri The URI of the token metadata 40 | * @custom:requires MINTER_ROLE 41 | */ 42 | function mint(address to, string memory uri) public virtual override { 43 | require(_tokenIdCounter.current() < maxSupply, "Max supply reached"); 44 | 45 | super.mint(to, uri); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/fungible/StartonERC20Mintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../abstracts/AStartonMintLock.sol"; 6 | import "./StartonERC20Base.sol"; 7 | 8 | /// @title StartonERC20Mintable 9 | /// @author Starton 10 | /// @notice ERC20 tokens that can be paused, locked, burned, have a access management and handle meta transactions 11 | contract StartonERC20Mintable is StartonERC20Base, AStartonMintLock { 12 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 13 | 14 | constructor( 15 | string memory definitiveName, 16 | string memory definitiveSymbol, 17 | uint256 initialSupply, 18 | address initialOwnerOrMultiSigContract 19 | ) StartonERC20Base(definitiveName, definitiveSymbol, initialSupply, initialOwnerOrMultiSigContract) { 20 | // Set all default roles for initialOwnerOrMultiSigContract 21 | _setupRole(MINTER_ROLE, initialOwnerOrMultiSigContract); 22 | _setupRole(LOCKER_ROLE, initialOwnerOrMultiSigContract); 23 | 24 | // Allow minting of new tokens 25 | _isMintAllowed = true; 26 | } 27 | 28 | /** 29 | * @notice Mint new tokens 30 | * @param to The address that will receive the minted tokens 31 | * @param amount The amount of tokens to mint 32 | * @custom:requires MINTER_ROLE 33 | */ 34 | function mint(address to, uint256 amount) public mintingNotLocked onlyRole(MINTER_ROLE) { 35 | _mint(to, amount); 36 | } 37 | 38 | /** 39 | * @dev Specify the _msgSender in case the forwarder calls a function to the real sender 40 | * @return The sender of the message 41 | */ 42 | function _msgSender() internal view virtual override(StartonERC20Base, Context) returns (address) { 43 | return super._msgSender(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC721CappedRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./StartonERC721BaseRoyalties.sol"; 6 | 7 | /// @title StartonERC721Capped 8 | /// @author Starton 9 | /// @notice ERC721 tokens that can be blacklisted, paused, locked, burned, have a access management, max number of tokens and handle meta transactions 10 | contract StartonERC721CappedRoyalties is StartonERC721BaseRoyalties { 11 | using Counters for Counters.Counter; 12 | 13 | uint256 public immutable maxSupply; 14 | 15 | constructor( 16 | string memory definitiveName, 17 | string memory definitiveSymbol, 18 | uint96 definitiveRoyaltyFee, 19 | address definitiveFeeReceiver, 20 | uint256 definitiveMaxSupply, 21 | string memory initialBaseTokenURI, 22 | string memory initialContractURI, 23 | address initialOwnerOrMultiSigContract 24 | ) 25 | StartonERC721BaseRoyalties( 26 | definitiveName, 27 | definitiveSymbol, 28 | definitiveRoyaltyFee, 29 | definitiveFeeReceiver, 30 | initialBaseTokenURI, 31 | initialContractURI, 32 | initialOwnerOrMultiSigContract 33 | ) 34 | { 35 | require(definitiveMaxSupply > 0, "maxSupply must be greater than 0"); 36 | 37 | maxSupply = definitiveMaxSupply; 38 | } 39 | 40 | /** 41 | * @notice Mint a new token to the given address and set the token metadata while minting is not locked 42 | * @param to The address that will receive the token 43 | * @param uri The URI of the token metadata 44 | * @custom:requires MINTER_ROLE 45 | */ 46 | function mint(address to, string memory uri) public virtual override { 47 | require(_tokenIdCounter.current() < maxSupply, "Max supply reached"); 48 | 49 | super.mint(to, uri); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Starton Banner](https://github.com/starton-io/.github/blob/master/github-banner.jpg?raw=true) 2 | 3 | # Starton - Smart Contracts 4 | 5 | This repository has all the templates provided by Starton that you can access and deploy using the official API. 6 | 7 | ## Requirements 8 | 9 | You will need : 10 | 11 | - [NodeJS](https://nodejs.org/en) (we recommend the use of [nvm](https://github.com/nvm-sh/nvm)) 12 | - [Yarn](https://yarnpkg.com/) 13 | 14 | ## Installation 15 | 16 | To install the project, first clone the repository and go inside project directory, then : 17 | 18 | - With [yarn](https://yarnpkg.com/) : 19 | ```bash 20 | $ yarn install 21 | ``` 22 | 23 | ## Tests 24 | 25 | You can find all the tests in the test directory 26 | 27 | You can run them using: 28 | 29 | ```shell 30 | yarn test 31 | ``` 32 | 33 | If you wants to get the coverage use: 34 | 35 | ```shell 36 | yarn coverage 37 | ``` 38 | 39 | When you're going to run the tests you will get all the informations about gas comsuption of every functions inside the contracts 40 | 41 | ## Documentation 42 | 43 | A script is available to generate the documentation of the contracts 44 | 45 | ```shell 46 | yarn doc FILENAME 47 | ``` 48 | 49 | This script will take all the informations as input from the doc.ts file and fill the abi, bytecode and compilation details. PS: You need to specify the contractName in the compilation details for it to works 50 | 51 | ## Audits and Security 52 | 53 | You can find the audit of the contracts by Pessimistic: [Report](./audits/Starton%20Security%20Analysis%20by%20Pessimistic.pdf) 54 | 55 | ## Contributing 56 | 57 | Contributions are always welcome! 58 | 59 | See [CONTRIBUTING.md](/CONTRIBUTING.md) for ways to get started. 60 | 61 | Please adhere to Starton's [Code of Conduct](/CODE_OF_CONDUCT.md). 62 | 63 | ## License 64 | 65 | [Apache License 2.0](/LICENSE.md) 66 | 67 | ## Authors 68 | 69 | - Starton [support@starton.com](mailto:support@starton.com) 70 | - Matéo Viel [mateo@starton.com](mailto:mateo@starton.com) 71 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC721WhitelistSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../abstracts/AStartonWhitelist.sol"; 6 | import "./StartonERC721BaseSale.sol"; 7 | 8 | /// @title StartonERC721WhitelistSale 9 | /// @author Starton 10 | /// @notice Sell ERC721 tokens through a whitelist sale with a limited available supply, start and end time as well as max tokens per address 11 | contract StartonERC721WhitelistSale is StartonERC721BaseSale, AStartonWhitelist { 12 | constructor( 13 | address definitiveTokenAddress, 14 | bytes32 definitiveMerkleRoot, 15 | uint256 definitivePrice, 16 | uint256 definitiveStartTime, 17 | uint256 definitiveEndTime, 18 | uint256 definitiveMaxTokensPerAddress, 19 | uint256 definitiveMaxSupply, 20 | address definitiveFeeReceiver 21 | ) 22 | StartonERC721BaseSale( 23 | definitiveTokenAddress, 24 | definitivePrice, 25 | definitiveStartTime, 26 | definitiveEndTime, 27 | definitiveMaxTokensPerAddress, 28 | definitiveMaxSupply, 29 | definitiveFeeReceiver 30 | ) 31 | { 32 | _merkleRoot = definitiveMerkleRoot; 33 | } 34 | 35 | /** 36 | * @notice Mint a token to a given address for a price if the given address is whitelisted 37 | * @param to The address to mint the token to 38 | * @param merkleProof The merkle proof of the address in the whitelist 39 | */ 40 | function mint(address to, bytes32[] calldata merkleProof) public payable override isWhitelisted(merkleProof) { 41 | super.mint(to, merkleProof); 42 | } 43 | 44 | /** 45 | * @notice Mint multiple tokens to a given address for a price if the given address is whitelisted 46 | * @param to The address to mint the token to 47 | * @param merkleProof The merkle proof of the address in the whitelist 48 | */ 49 | function mintBatch( 50 | address to, 51 | uint256 amount, 52 | bytes32[] calldata merkleProof 53 | ) public payable override isWhitelisted(merkleProof) { 54 | super.mintBatch(to, amount, merkleProof); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/fungible/StartonERC20Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | import "../abstracts/AStartonNativeMetaTransaction.sol"; 8 | import "../abstracts/AStartonContextMixin.sol"; 9 | import "../abstracts/AStartonPausable.sol"; 10 | 11 | /// @title StartonERC20Base 12 | /// @author Starton 13 | /// @notice ERC20 tokens that can be paused, burned, have a access management and handle meta transactions 14 | contract StartonERC20Base is ERC20Burnable, AStartonPausable, AStartonContextMixin, AStartonNativeMetaTransaction { 15 | constructor( 16 | string memory definitiveName, 17 | string memory definitiveSymbol, 18 | uint256 definitiveSupply, 19 | address initialOwnerOrMultiSigContract 20 | ) ERC20(definitiveName, definitiveSymbol) { 21 | // Set all default roles for initialOwnerOrMultiSigContract 22 | _setupRole(DEFAULT_ADMIN_ROLE, initialOwnerOrMultiSigContract); 23 | _setupRole(PAUSER_ROLE, initialOwnerOrMultiSigContract); 24 | 25 | // Mint definitiveSupply to initialOwnerOrMultiSigContract 26 | _mint(initialOwnerOrMultiSigContract, definitiveSupply); 27 | 28 | // Intialize the EIP712 so we can perform metatransactions 29 | _initializeEIP712(definitiveName); 30 | } 31 | 32 | /** 33 | * @dev Stop the transfer if the contract is paused 34 | * @param from The address that will send the token 35 | * @param to The address that will receive the token 36 | * @param amount The of token to be transfered 37 | */ 38 | function _beforeTokenTransfer( 39 | address from, 40 | address to, 41 | uint256 amount 42 | ) internal virtual override whenNotPaused { 43 | super._beforeTokenTransfer(from, to, amount); 44 | } 45 | 46 | /** 47 | * @dev Specify the _msgSender in case the forwarder calls a function to the real sender 48 | * @return The sender of the message 49 | */ 50 | function _msgSender() internal view virtual override(Context, AStartonContextMixin) returns (address) { 51 | return super._msgSender(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC20Pause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract ChildStartonERC20Pause is ERC20, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 11 | 12 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 13 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 14 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 15 | _mint(ownerOrMultiSigContract, initialSupply); 16 | } 17 | 18 | function pause() public { 19 | require(hasRole(PAUSER_ROLE, msg.sender)); 20 | _pause(); 21 | } 22 | 23 | function unpause() public { 24 | require(hasRole(PAUSER_ROLE, msg.sender)); 25 | _unpause(); 26 | } 27 | 28 | function _beforeTokenTransfer(address from, address to, uint256 amount) 29 | internal 30 | whenNotPaused 31 | override 32 | { 33 | super._beforeTokenTransfer(from, to, amount); 34 | } 35 | 36 | /** 37 | * @notice called when token is deposited on root chain 38 | * @dev Should be callable only by ChildChainManager 39 | * Should handle deposit by minting the required amount for user 40 | * Make sure minting is done only by this function 41 | * @param user user address for whom deposit is being done 42 | * @param depositData abi encoded amount 43 | */ 44 | function deposit(address user, bytes calldata depositData) 45 | external 46 | whenNotPaused 47 | { 48 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 49 | uint256 amount = abi.decode(depositData, (uint256)); 50 | _mint(user, amount); 51 | } 52 | 53 | /** 54 | * @notice called when user wants to withdraw tokens back to root chain 55 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 56 | * @param amount amount of tokens to withdraw 57 | */ 58 | function withdraw(uint256 amount) external whenNotPaused { 59 | _burn(_msgSender(), amount); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonEIP712Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 6 | 7 | /// @title AStartonEIP712Base 8 | /// @author Starton 9 | /// @notice Utility smart contract that can create types messages 10 | abstract contract AStartonEIP712Base is Initializable { 11 | struct EIP712Domain { 12 | string name; 13 | string version; 14 | address verifyingContract; 15 | bytes32 salt; 16 | } 17 | 18 | string public constant ERC712_VERSION = "1"; 19 | 20 | bytes32 internal constant _EIP712_DOMAIN_TYPEHASH = 21 | keccak256(bytes("EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)")); 22 | bytes32 internal _domainSeparator; 23 | 24 | // supposed to be called once while initializing. 25 | // one of the contractsa that inherits this contract follows proxy pattern 26 | // so it is not possible to do this in a constructor 27 | function _initializeEIP712(string memory name) internal initializer { 28 | _setDomainSeparator(name); 29 | } 30 | 31 | function _setDomainSeparator(string memory name) internal { 32 | _domainSeparator = keccak256( 33 | abi.encode( 34 | _EIP712_DOMAIN_TYPEHASH, 35 | keccak256(bytes(name)), 36 | keccak256(bytes(ERC712_VERSION)), 37 | address(this), 38 | bytes32(getChainId()) 39 | ) 40 | ); 41 | } 42 | 43 | function getDomainSeparator() public view returns (bytes32) { 44 | return _domainSeparator; 45 | } 46 | 47 | function getChainId() public view returns (uint256) { 48 | uint256 id; 49 | assembly { 50 | id := chainid() 51 | } 52 | return id; 53 | } 54 | 55 | /** 56 | * Accept message hash and returns hash message in EIP712 compatible form 57 | * So that it can be used to recover signer from signature signed using EIP712 formatted data 58 | * https://eips.ethereum.org/EIPS/eip-712 59 | * "\\x19" makes the encoding deterministic 60 | * "\\x01" is the version byte to make it compatible to EIP-191 61 | */ 62 | function _toTypedMessageHash(bytes32 messageHash) internal view returns (bytes32) { 63 | return keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), messageHash)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC20BurnPause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract ChildStartonERC20BurnPause is ERC20Burnable, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 11 | 12 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 13 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 14 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 15 | _mint(ownerOrMultiSigContract, initialSupply); 16 | } 17 | 18 | function pause() public { 19 | require(hasRole(PAUSER_ROLE, msg.sender)); 20 | _pause(); 21 | } 22 | 23 | function unpause() public { 24 | require(hasRole(PAUSER_ROLE, msg.sender)); 25 | _unpause(); 26 | } 27 | 28 | function _beforeTokenTransfer(address from, address to, uint256 amount) 29 | internal 30 | whenNotPaused 31 | override 32 | { 33 | super._beforeTokenTransfer(from, to, amount); 34 | } 35 | 36 | /** 37 | * @notice called when token is deposited on root chain 38 | * @dev Should be callable only by ChildChainManager 39 | * Should handle deposit by minting the required amount for user 40 | * Make sure minting is done only by this function 41 | * @param user user address for whom deposit is being done 42 | * @param depositData abi encoded amount 43 | */ 44 | function deposit(address user, bytes calldata depositData) 45 | external 46 | whenNotPaused 47 | { 48 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 49 | uint256 amount = abi.decode(depositData, (uint256)); 50 | _mint(user, amount); 51 | } 52 | 53 | /** 54 | * @notice called when user wants to withdraw tokens back to root chain 55 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 56 | * @param amount amount of tokens to withdraw 57 | */ 58 | function withdraw(uint256 amount) external whenNotPaused { 59 | _burn(_msgSender(), amount); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC1155WhitelistSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../abstracts/AStartonWhitelist.sol"; 6 | import "./StartonERC1155BaseSale.sol"; 7 | 8 | /// @title StartonERC1155WhitelistSale 9 | /// @author Starton 10 | /// @notice Sell ERC721 tokens through a whitelist sale with a limited available supply, start and end time as well as max tokens per address 11 | contract StartonERC1155WhitelistSale is StartonERC1155BaseSale, AStartonWhitelist { 12 | constructor( 13 | address definitiveTokenAddress, 14 | bytes32 definitiveMerkleRoot, 15 | uint256 definitiveStartTime, 16 | uint256 definitiveEndTime, 17 | uint256 definitiveMaxTokensPerAddress, 18 | uint256 definitiveMaxSupply, 19 | address definitiveFeeReceiver 20 | ) 21 | StartonERC1155BaseSale( 22 | definitiveTokenAddress, 23 | definitiveStartTime, 24 | definitiveEndTime, 25 | definitiveMaxTokensPerAddress, 26 | definitiveMaxSupply, 27 | definitiveFeeReceiver 28 | ) 29 | { 30 | _merkleRoot = definitiveMerkleRoot; 31 | } 32 | 33 | /** 34 | * @notice Mint a token to a given address for a price if the given address is whitelisted 35 | * @param to The address to mint the token to 36 | * @param id The id of the token 37 | * @param amount The amount of tokens to mint 38 | * @param merkleProof The merkle proof of the address in the whitelist 39 | */ 40 | function mint( 41 | address to, 42 | uint256 id, 43 | uint256 amount, 44 | bytes32[] calldata merkleProof 45 | ) public payable override isWhitelisted(merkleProof) { 46 | super.mint(to, id, amount, merkleProof); 47 | } 48 | 49 | /** 50 | * @notice Mint multiple tokens to a given address for a price if the given address is whitelisted 51 | * @param to The address to mint the token to 52 | * @param ids The ids of the token to mint 53 | * @param amounts The amounts of tokens to mint 54 | * @param merkleProof The merkle proof of the address in the whitelist 55 | */ 56 | function mintBatch( 57 | address to, 58 | uint256[] calldata ids, 59 | uint256[] calldata amounts, 60 | bytes32[] calldata merkleProof 61 | ) public payable override isWhitelisted(merkleProof) { 62 | super.mintBatch(to, ids, amounts, merkleProof); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | 7 | /// @title AStartonAcessControl 8 | /// @author Starton 9 | /// @notice Utility smart contract that can ease the transfer of ownership between one user to another 10 | abstract contract AStartonOwnable is AccessControl { 11 | address private _owner; 12 | 13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 14 | 15 | /** 16 | * @dev Initializes the contract setting the deployer as the initial owner. 17 | */ 18 | constructor(address initialOwnerOrMultiSigContract) { 19 | _owner = initialOwnerOrMultiSigContract; 20 | _setupRole(DEFAULT_ADMIN_ROLE, initialOwnerOrMultiSigContract); 21 | } 22 | 23 | /** 24 | * @dev Throws if called by any account other than the owner. 25 | */ 26 | modifier onlyOwner() { 27 | _checkOwner(); 28 | _; 29 | } 30 | 31 | /** 32 | * @dev Returns the address of the current owner. 33 | */ 34 | function owner() public view virtual returns (address) { 35 | return _owner; 36 | } 37 | 38 | /** 39 | * @dev Throws if the sender is not the owner. 40 | */ 41 | function _checkOwner() internal view virtual { 42 | require(owner() == _msgSender(), "Ownable: caller is not the owner"); 43 | } 44 | 45 | /** 46 | * @dev Leaves the contract without owner. It will not be possible to call 47 | * `onlyOwner` functions. Can only be called by the current owner. 48 | * 49 | * NOTE: Renouncing ownership will leave the contract without an owner, 50 | * thereby disabling any functionality that is only available to the owner. 51 | */ 52 | function renounceOwnership() public virtual onlyOwner { 53 | _transferOwnership(address(0)); 54 | } 55 | 56 | /** 57 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 58 | * Can only be called by the current owner. 59 | */ 60 | function transferOwnership(address newOwner) public virtual onlyOwner { 61 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 62 | _transferOwnership(newOwner); 63 | } 64 | 65 | /** 66 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 67 | * Internal function without access restriction. 68 | */ 69 | function _transferOwnership(address newOwner) internal virtual { 70 | address oldOwner = _owner; 71 | _owner = newOwner; 72 | emit OwnershipTransferred(oldOwner, newOwner); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - deploy 4 | 5 | cache: 6 | paths: 7 | - node_modules/ 8 | 9 | build: 10 | stage: build 11 | image: node:18-bookworm-slim 12 | rules: 13 | - if: $CI_COMMIT_BRANCH == "master" 14 | - if: $CI_COMMIT_BRANCH == "develop" 15 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 16 | script: 17 | - yarn install 18 | - yarn compile 19 | 20 | test: 21 | image: node:18-bookworm-slim 22 | stage: build 23 | rules: 24 | - if: $CI_COMMIT_BRANCH == "master" 25 | - if: $CI_COMMIT_BRANCH == "develop" 26 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 27 | script: 28 | - yarn install 29 | - yarn test 30 | 31 | coverage: 32 | image: node:18-bookworm-slim 33 | stage: build 34 | rules: 35 | - if: $CI_COMMIT_BRANCH == "master" 36 | - if: $CI_COMMIT_BRANCH == "develop" 37 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 38 | script: 39 | - yarn install 40 | - yarn coverage 41 | lint: 42 | image: node:18-bookworm-slim 43 | stage: build 44 | rules: 45 | - if: $CI_COMMIT_BRANCH == "master" 46 | - if: $CI_COMMIT_BRANCH == "develop" 47 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 48 | script: 49 | - yarn install 50 | - yarn lint 51 | 52 | deploy: 53 | image: node:18-bookworm-slim 54 | stage: deploy 55 | tags: ["starton-standard"] 56 | rules: 57 | - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" 58 | before_script: 59 | - "which ssh-agent || ( apt-get update -y && apt-get install openssh-client libssl-dev git -y )" 60 | - eval $(ssh-agent -s) 61 | - ssh-add <(echo "${SSH_PRIV_KEY}" | base64 --decode | tr -d "\r") 62 | - git config --global user.name "${GITLAB_USER_NAME}" 63 | - git config --global user.email "${GITLAB_USER_EMAIL}" 64 | - mkdir -p ~/.ssh 65 | - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config 66 | - chmod 700 ~/.ssh 67 | - echo gitlab.com >> ~/.ssh/known_hosts 68 | script: 69 | - yarn install 70 | - yarn doc result.ts 71 | - git clone git@gitlab.com:${PROJECT_PATH}.git 72 | - cd ${PROJECT_NAME} 73 | # we must close the mr in the connect-backend before launch second mr in the same branch 74 | - git checkout -b ${COMMIT_BRANCH} 75 | - cp ../result.ts ./apps/library/src/db/seeders/template.ts 76 | - > 77 | if [[ `git status --porcelain` ]]; then 78 | git add . 79 | git commit -m "chore: update template.ts - ${CI_COMMIT_SHA}" 80 | git push origin HEAD:${COMMIT_BRANCH} -o merge_request.create -o merge_request.target=${TARGET_BRANCH} -o merge_request.assign=${MR_ASSIGNEE} -o merge_request.title="Merge smart contract branch ($CI_COMMIT_REF_NAME) into connect-backend (develop)" 81 | fi 82 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC20MintBurnPause.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | 8 | contract ChildStartonERC20MintBurnPause is ERC20Burnable, Pausable, AccessControl { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 11 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 12 | 13 | constructor(string memory name, string memory symbol, uint256 initialSupply, address ownerOrMultiSigContract) ERC20(name, symbol) { 14 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 15 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 16 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 17 | _mint(ownerOrMultiSigContract, initialSupply); 18 | } 19 | 20 | function pause() public { 21 | require(hasRole(PAUSER_ROLE, msg.sender)); 22 | _pause(); 23 | } 24 | 25 | function unpause() public { 26 | require(hasRole(PAUSER_ROLE, msg.sender)); 27 | _unpause(); 28 | } 29 | 30 | function mint(address to, uint256 amount) public { 31 | require(hasRole(MINTER_ROLE, msg.sender)); 32 | _mint(to, amount); 33 | } 34 | 35 | function _beforeTokenTransfer(address from, address to, uint256 amount) 36 | internal 37 | whenNotPaused 38 | override 39 | { 40 | super._beforeTokenTransfer(from, to, amount); 41 | } 42 | 43 | 44 | /** 45 | * @notice called when token is deposited on root chain 46 | * @dev Should be callable only by ChildChainManager 47 | * Should handle deposit by minting the required amount for user 48 | * Make sure minting is done only by this function 49 | * @param user user address for whom deposit is being done 50 | * @param depositData abi encoded amount 51 | */ 52 | function deposit(address user, bytes calldata depositData) 53 | external 54 | whenNotPaused 55 | { 56 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 57 | uint256 amount = abi.decode(depositData, (uint256)); 58 | _mint(user, amount); 59 | } 60 | 61 | /** 62 | * @notice called when user wants to withdraw tokens back to root chain 63 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 64 | * @param amount amount of tokens to withdraw 65 | */ 66 | function withdraw(uint256 amount) external whenNotPaused { 67 | _burn(_msgSender(), amount); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/StartonMultiSendERC20.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | import { 6 | StartonMultiSendERC20, 7 | StartonMultiSendERC20__factory, // eslint-disable-line camelcase 8 | StartonERC20Base, 9 | StartonERC20Base__factory, // eslint-disable-line camelcase 10 | } from "../typechain-types"; 11 | 12 | let Multisend: StartonMultiSendERC20__factory; // eslint-disable-line camelcase 13 | let ERC20: StartonERC20Base__factory; // eslint-disable-line camelcase 14 | 15 | describe("StartonMultiSendERC20", () => { 16 | let instanceMultisend: StartonMultiSendERC20; 17 | let instanceERC20: StartonERC20Base; 18 | let owner: SignerWithAddress; 19 | let addr1: SignerWithAddress; 20 | let addr2: SignerWithAddress; 21 | let addrs: SignerWithAddress[]; 22 | 23 | before(async () => { 24 | // Get the Signers here 25 | [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); 26 | 27 | // Create factory 28 | Multisend = new StartonMultiSendERC20__factory(owner); 29 | ERC20 = new StartonERC20Base__factory(owner); 30 | }); 31 | 32 | beforeEach(async () => { 33 | instanceMultisend = (await Multisend.deploy()) as StartonMultiSendERC20; 34 | await instanceMultisend.deployed(); 35 | 36 | instanceERC20 = (await ERC20.deploy( 37 | "StartonToken", 38 | "ST", 39 | "1000000000000000000000000000", 40 | owner.address 41 | )) as StartonERC20Base; 42 | await instanceERC20.deployed(); 43 | }); 44 | 45 | describe("Deployment", () => { 46 | it("Should deploy", async () => {}); 47 | }); 48 | 49 | describe("MultiSend", () => { 50 | it("Shouldn't send tokens if arrays not equal ", async () => { 51 | await instanceERC20.approve( 52 | instanceMultisend.address, 53 | "1000000000000000000000000000" 54 | ); 55 | await expect( 56 | instanceMultisend.multiSend( 57 | instanceERC20.address, 58 | ["1000", "100000", "100"], 59 | [addr1.address, addr2.address] 60 | ) 61 | ).to.be.revertedWith("Arrays must be of equal length"); 62 | }); 63 | 64 | it("Should send tokens to multiple addresses", async () => { 65 | await instanceERC20.approve( 66 | instanceMultisend.address, 67 | "1000000000000000000000000000" 68 | ); 69 | await instanceMultisend.multiSend( 70 | instanceERC20.address, 71 | ["1000", "100000", "100"], 72 | [addr1.address, addr2.address, addrs[3].address] 73 | ); 74 | 75 | expect(await instanceERC20.balanceOf(addr1.address)).to.equal("1000"); 76 | expect(await instanceERC20.balanceOf(addr2.address)).to.equal("100000"); 77 | expect(await instanceERC20.balanceOf(addrs[3].address)).to.equal("100"); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /scripts/flatten-contracts-folder.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { exec, execSync } from "child_process"; 4 | 5 | // directory path 6 | const dir = "contracts/"; 7 | const outDir = "flatten/"; 8 | 9 | function getAllFiles(dirPath: string, arrayOfFiles: string[]): string[] { 10 | const arrayOfDirectoriesToIgnore = [ 11 | "utils", 12 | "abstracts", 13 | "deprecated", 14 | "interfaces", 15 | ]; 16 | 17 | const files = fs.readdirSync(dirPath); 18 | 19 | arrayOfFiles = arrayOfFiles || []; 20 | 21 | files.forEach((file) => { 22 | if (fs.statSync(dirPath + "/" + file).isDirectory()) { 23 | if (!arrayOfDirectoriesToIgnore.includes(file)) { 24 | arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles); 25 | } 26 | } else { 27 | arrayOfFiles.push(path.join(dirPath, "/", file)); 28 | } 29 | }); 30 | 31 | return arrayOfFiles; 32 | } 33 | 34 | function filterLicensesInFile(filePath: string): void { 35 | console.log("Filtering licenses lines in " + filePath); 36 | 37 | let fileContent = ""; 38 | let isFirstLicenseOccurenceFound = false; 39 | 40 | fs.readFileSync(filePath) 41 | .toString() 42 | .split("\n") 43 | .forEach((line, index, arr) => { 44 | if (index === arr.length - 1 && line === "") { 45 | return; 46 | } 47 | 48 | if (line.indexOf("SPDX-License-Identifier") !== -1) { 49 | if (isFirstLicenseOccurenceFound) { 50 | return; 51 | } else { 52 | isFirstLicenseOccurenceFound = true; 53 | } 54 | } 55 | 56 | fileContent += line + "\n"; 57 | }); 58 | 59 | fs.writeFileSync(filePath, fileContent); 60 | } 61 | 62 | function printUsage(): void { 63 | console.log("USAGE\n\tts-node flatten-contracts-folder [--git] files ..."); 64 | console.log(); 65 | console.log("DESCRIPTION\n\t--git\tStage the files in git"); 66 | console.log("\tfiles\tlist of files / directories to flatten"); 67 | } 68 | 69 | function main() { 70 | if (process.argv.includes("--help") || process.argv.includes("-h")) { 71 | printUsage(); 72 | return; 73 | } 74 | 75 | const git = process.argv.includes("--git"); 76 | 77 | const filesToFlatten = getAllFiles(dir, []); 78 | 79 | console.log("Flattening files : " + filesToFlatten); 80 | 81 | for (const file of filesToFlatten) { 82 | const relativeFilePath = file.replace("\\", "/").split(dir)[1]; 83 | const dest = outDir + relativeFilePath; 84 | const source = dir + relativeFilePath; 85 | 86 | console.log("Flattening : " + source + " to " + dest); 87 | exec( 88 | "npx hardhat flatten " + source + " > " + dest, 89 | { 90 | encoding: "utf-8", 91 | }, 92 | () => { 93 | filterLicensesInFile(dest); 94 | if (git) execSync("git add " + dest); 95 | } 96 | ); 97 | } 98 | } 99 | main(); 100 | -------------------------------------------------------------------------------- /contracts/abstracts/AStartonNativeMetaTransaction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./AStartonEIP712Base.sol"; 6 | 7 | /// @title AStartonNativeMetaTransaction 8 | /// @author Starton 9 | /// @notice Utility smart contract that enable gasless transactions 10 | abstract contract AStartonNativeMetaTransaction is AStartonEIP712Base { 11 | bytes32 private constant _META_TRANSACTION_TYPEHASH = 12 | keccak256(bytes("MetaTransaction(uint256 nonce,address from,bytes functionSignature)")); 13 | event MetaTransactionExecuted(address userAddress, address payable relayerAddress, bytes functionSignature); 14 | mapping(address => uint256) private _nonces; 15 | 16 | /* 17 | * Meta transaction structure. 18 | * No point of including value field here as if user is doing value transfer then he has the funds to pay for gas 19 | * He should call the desired function directly in that case. 20 | */ 21 | struct MetaTransaction { 22 | uint256 nonce; 23 | address from; 24 | bytes functionSignature; 25 | } 26 | 27 | function executeMetaTransaction( 28 | address userAddress, 29 | bytes memory functionSignature, 30 | bytes32 sigR, 31 | bytes32 sigS, 32 | uint8 sigV 33 | ) public payable returns (bytes memory) { 34 | MetaTransaction memory metaTx = MetaTransaction({ 35 | nonce: _nonces[userAddress], 36 | from: userAddress, 37 | functionSignature: functionSignature 38 | }); 39 | 40 | require(_verify(userAddress, metaTx, sigR, sigS, sigV), "Signer and signature do not match"); 41 | 42 | // increase nonce for user (to avoid re-use) 43 | _nonces[userAddress] = _nonces[userAddress] + 1; 44 | 45 | emit MetaTransactionExecuted(userAddress, payable(msg.sender), functionSignature); 46 | 47 | // Append userAddress and relayer address at the end to extract it from calling context 48 | (bool success, bytes memory returnData) = address(this).call(abi.encodePacked(functionSignature, userAddress)); 49 | require(success, "Function call not successful"); 50 | 51 | return returnData; 52 | } 53 | 54 | function _hashMetaTransaction(MetaTransaction memory metaTx) internal pure returns (bytes32) { 55 | return 56 | keccak256( 57 | abi.encode(_META_TRANSACTION_TYPEHASH, metaTx.nonce, metaTx.from, keccak256(metaTx.functionSignature)) 58 | ); 59 | } 60 | 61 | function getNonce(address user) public view returns (uint256 nonce) { 62 | nonce = _nonces[user]; 63 | } 64 | 65 | function _verify( 66 | address signer, 67 | MetaTransaction memory metaTx, 68 | bytes32 sigR, 69 | bytes32 sigS, 70 | uint8 sigV 71 | ) internal view returns (bool) { 72 | require(signer != address(0), "NativeMetaTransaction: INVALID_SIGNER"); 73 | return signer == ecrecover(_toTypedMessageHash(_hashMetaTransaction(metaTx)), sigV, sigR, sigS); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-contract-templates", 3 | "version": "1.0.0", 4 | "description": "This is templates being used and created by starton.", 5 | "main": "hardhat.config.ts", 6 | "scripts": { 7 | "test": "hardhat compile && hardhat test", 8 | "compile": "hardhat compile", 9 | "clean": "hardhat clean", 10 | "coverage": "hardhat compile && hardhat coverage", 11 | "flatten": "ts-node scripts/flatten-contracts-folder.ts", 12 | "single-line": "ts-node scripts/single-line-contract.ts", 13 | "doc": "yarn compile && ts-node scripts/generate-doc.ts", 14 | "lint": "yarn prettier && yarn eslint && yarn solhint", 15 | "lint:fix": "yarn prettier:fix && yarn eslint:fix && yarn solhint:fix", 16 | "prettier": "prettier '**/*.{json,sol,md}' --check", 17 | "prettier:fix": "prettier '**/*.{json,sol,md}' --write", 18 | "solhint": "solhint 'contracts/**/*.sol'", 19 | "solhint:fix": "solhint 'contracts/**/*.sol' --fix", 20 | "eslint": "eslint '**/*.{js,ts}'", 21 | "eslint:fix": "eslint '**/*.{js,ts}' --fix", 22 | "prepare": "husky install" 23 | }, 24 | "keywords": [], 25 | "author": "starton", 26 | "license": "Apache-2.0", 27 | "devDependencies": { 28 | "@ethersproject/abi": "^5.6.4", 29 | "@ethersproject/contracts": "^5.6.2", 30 | "@ethersproject/providers": "^5.6.8", 31 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", 32 | "@nomicfoundation/hardhat-network-helpers": "^1.0.3", 33 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 34 | "@nomiclabs/hardhat-ethers": "^2.1.0", 35 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 36 | "@typechain/ethers-v5": "^10.1.0", 37 | "@typechain/hardhat": "^6.1.2", 38 | "@types/chai": "^4.3.1", 39 | "@types/mocha": "^9.1.1", 40 | "@types/node": "^18.6.1", 41 | "@typescript-eslint/eslint-plugin": "^5.30.7", 42 | "@typescript-eslint/parser": "^5.30.7", 43 | "chai": "^4.3.6", 44 | "eslint": "^8.20.0", 45 | "eslint-config-prettier": "^8.5.0", 46 | "eslint-config-standard": "^17.0.0", 47 | "eslint-plugin-import": "^2.26.0", 48 | "eslint-plugin-n": "^15.2.4", 49 | "eslint-plugin-node": "^11.1.0", 50 | "eslint-plugin-prettier": "^4.2.1", 51 | "eslint-plugin-promise": "^6.0.0", 52 | "ethers": "^5.6.9", 53 | "fs": "^0.0.1-security", 54 | "hardhat": "^2.9.4", 55 | "hardhat-gas-reporter": "^1.0.8", 56 | "husky": "^8.0.3", 57 | "merkletreejs": "^0.2.32", 58 | "prettier": "^2.7.1", 59 | "prettier-plugin-solidity": "^1.0.0-dev.23", 60 | "solhint": "^3.3.7", 61 | "solidity-coverage": "^0.7.21", 62 | "ts-node": "^10.9.1", 63 | "typechain": "^8.1.0", 64 | "typescript": "^4.7.4" 65 | }, 66 | "dependencies": { 67 | "@openzeppelin/contracts": "^4.9.2", 68 | "operator-filter-registry": "^1.4.0" 69 | }, 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "lint-staged" 73 | } 74 | }, 75 | "lint-staged": { 76 | "*.ts": [ 77 | "eslint --fix" 78 | ], 79 | "contracts/**/*.sol": [ 80 | "solhint --fix", 81 | "yarn flatten --git" 82 | ], 83 | "*.json|*.md|*.sol": [ 84 | "prettier --write" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC721Capped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | 11 | contract StartonERC721Capped is ERC721Enumerable, ERC721URIStorage, Pausable, AccessControl, ERC721Burnable { 12 | using Counters for Counters.Counter; 13 | 14 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 15 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 16 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 17 | Counters.Counter private _tokenIdCounter; 18 | string private _uri; 19 | uint256 private _maxSupply; 20 | 21 | bool private _isMintAllowed; 22 | 23 | constructor(string memory name, string memory symbol, string memory baseUri, uint256 tokenMaxSupply, address ownerOrMultiSigContract) ERC721(name, symbol) { 24 | require(tokenMaxSupply > 0, "maxSupply: must be > 0"); 25 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 26 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 27 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 28 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 29 | _uri = baseUri; 30 | _maxSupply = tokenMaxSupply; 31 | _isMintAllowed = true; 32 | } 33 | 34 | function _baseURI() internal view override returns (string memory) { 35 | return _uri; 36 | } 37 | 38 | function maxSupply() public view returns (uint256) { 39 | return _maxSupply; 40 | } 41 | 42 | function lockMint() public { 43 | require(hasRole(LOCKER_ROLE, msg.sender)); 44 | _isMintAllowed = false; 45 | } 46 | 47 | function safeMint(address to) public { 48 | require(hasRole(MINTER_ROLE, msg.sender)); 49 | require(_isMintAllowed); 50 | require(_tokenIdCounter.current() < _maxSupply, "maxSupply: reached"); 51 | _safeMint(to, _tokenIdCounter.current()); 52 | _tokenIdCounter.increment(); 53 | } 54 | 55 | function pause() public { 56 | require(hasRole(PAUSER_ROLE, msg.sender)); 57 | _pause(); 58 | } 59 | 60 | function unpause() public { 61 | require(hasRole(PAUSER_ROLE, msg.sender)); 62 | _unpause(); 63 | } 64 | 65 | function _beforeTokenTransfer(address from, address to, uint256 tokenId) 66 | internal 67 | whenNotPaused 68 | override(ERC721, ERC721Enumerable) 69 | { 70 | super._beforeTokenTransfer(from, to, tokenId); 71 | } 72 | 73 | function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { 74 | super._burn(tokenId); 75 | } 76 | 77 | function tokenURI(uint256 tokenId) 78 | public 79 | view 80 | override(ERC721, ERC721URIStorage) 81 | returns (string memory) 82 | { 83 | return super.tokenURI(tokenId); 84 | } 85 | 86 | function supportsInterface(bytes4 interfaceId) 87 | public 88 | view 89 | override(ERC721, ERC721Enumerable, AccessControl) 90 | returns (bool) 91 | { 92 | return super.supportsInterface(interfaceId); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; 7 | 8 | contract StartonERC1155 is AccessControl, Pausable, ERC1155Burnable { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 11 | bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE"); 12 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 13 | 14 | 15 | string private _contractUriSuffix; 16 | string private _baseContractUri; 17 | 18 | bool private _isMintAllowed; 19 | 20 | constructor(string memory name, string memory baseUri, string memory contractUriSuffix, address ownerOrMultiSigContract) ERC1155(name) { 21 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 22 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 23 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 24 | _setupRole(URI_SETTER_ROLE, ownerOrMultiSigContract); 25 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 26 | 27 | _setURI(baseUri); 28 | _contractUriSuffix = contractUriSuffix; 29 | _baseContractUri = "https://ipfs.io/ipfs/"; 30 | _isMintAllowed = true; 31 | } 32 | 33 | function setURI(string memory newuri) public { 34 | require(hasRole(URI_SETTER_ROLE, msg.sender)); 35 | _setURI(newuri); 36 | } 37 | 38 | function contractURI() public view returns (string memory) { 39 | return bytes(_baseContractUri).length > 0 40 | ? string(abi.encodePacked(_baseContractUri, _contractUriSuffix)) 41 | : ''; 42 | } 43 | 44 | function setBaseContractURI(string memory newBaseContractUri) public { 45 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender)); 46 | 47 | _baseContractUri = newBaseContractUri; 48 | } 49 | 50 | function pause() public { 51 | require(hasRole(PAUSER_ROLE, msg.sender)); 52 | _pause(); 53 | } 54 | 55 | function unpause() public { 56 | require(hasRole(PAUSER_ROLE, msg.sender)); 57 | _unpause(); 58 | } 59 | 60 | function lockMint() public { 61 | require(hasRole(LOCKER_ROLE, msg.sender)); 62 | _isMintAllowed = false; 63 | } 64 | 65 | function mint(address account, uint256 id, uint256 amount, bytes memory data) 66 | public 67 | { 68 | require(hasRole(MINTER_ROLE, msg.sender)); 69 | require(_isMintAllowed); 70 | 71 | _mint(account, id, amount, data); 72 | } 73 | 74 | function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) 75 | public 76 | { 77 | require(hasRole(MINTER_ROLE, msg.sender)); 78 | require(_isMintAllowed); 79 | 80 | _mintBatch(to, ids, amounts, data); 81 | } 82 | 83 | function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) 84 | internal 85 | whenNotPaused 86 | override 87 | { 88 | super._beforeTokenTransfer(operator, from, to, ids, amounts, data); 89 | } 90 | 91 | function supportsInterface(bytes4 interfaceId) 92 | public 93 | view 94 | override(ERC1155, AccessControl) 95 | returns (bool) 96 | { 97 | return super.supportsInterface(interfaceId); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC5192.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../interfaces/IStartonERC5192.sol"; 6 | import "./StartonERC721BaseRoyalties.sol"; 7 | 8 | /// @title StartonERC5192 9 | /// @author Starton 10 | /// @notice ERC5192 token that is locked by default and which is based of the StartonERC721Base 11 | contract StartonERC5192 is StartonERC721BaseRoyalties, IStartonERC5192 { 12 | using Counters for Counters.Counter; 13 | 14 | /** @dev Modifier that reverts because the token is locked */ 15 | modifier checkLock() { 16 | revert("Token locked"); 17 | _; 18 | } 19 | 20 | constructor( 21 | string memory definitiveName, 22 | string memory definitiveSymbol, 23 | uint96 definitiveRoyaltyFee, 24 | address definitiveFeeReceiver, 25 | string memory initialBaseTokenURI, 26 | string memory initialContractURI, 27 | address initialOwnerOrMultiSigContract 28 | ) 29 | StartonERC721BaseRoyalties( 30 | definitiveName, 31 | definitiveSymbol, 32 | definitiveRoyaltyFee, 33 | definitiveFeeReceiver, 34 | initialBaseTokenURI, 35 | initialContractURI, 36 | initialOwnerOrMultiSigContract 37 | ) 38 | {} 39 | 40 | /** 41 | * @notice Mint a new token to the given address and set the token metadata while minting is not locked 42 | * @param to The address that will receive the token 43 | * @param uri The URI of the token metadata 44 | * @custom:requires MINTER_ROLE 45 | */ 46 | function mint(address to, string memory uri) public virtual override { 47 | super.mint(to, uri); 48 | 49 | emit Locked(_tokenIdCounter.current() - 1); 50 | } 51 | 52 | /** 53 | * @notice Check if a token is locked 54 | * @param tokenId The ID of the token to check 55 | * @return True if the token is locked 56 | */ 57 | function locked(uint256 tokenId) public view override returns (bool) { 58 | require(_exists(tokenId), "Token not found"); 59 | 60 | return (true); 61 | } 62 | 63 | /** 64 | * @notice Block the safeTransferFrom because it is a SBT 65 | */ 66 | function safeTransferFrom( 67 | address from, 68 | address to, 69 | uint256 tokenId, 70 | bytes memory data 71 | ) public override checkLock {} 72 | 73 | /** 74 | * @notice Block the safeTransferFrom because it is a SBT 75 | */ 76 | function safeTransferFrom( 77 | address from, 78 | address to, 79 | uint256 tokenId 80 | ) public override checkLock {} 81 | 82 | /** 83 | * @notice Block the transferFrom because it is a SBT 84 | */ 85 | function transferFrom( 86 | address from, 87 | address to, 88 | uint256 tokenId 89 | ) public override checkLock {} 90 | 91 | /** 92 | * @notice Block the approve because it is a SBT 93 | */ 94 | function approve(address approved, uint256 tokenId) public override checkLock {} 95 | 96 | /** 97 | * @notice Block the setApprovalForAll because it is a SBT 98 | */ 99 | function setApprovalForAll(address operator, bool approved) public override(ERC721, IERC721) checkLock {} 100 | 101 | /** 102 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 103 | * Implements the IERC5192 interface 104 | * @return True if the interface is supported 105 | */ 106 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 107 | return interfaceId == type(IStartonERC5192).interfaceId || super.supportsInterface(interfaceId); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/deprecated/StartonERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | 11 | contract StartonERC721 is ERC721Enumerable, ERC721URIStorage, Pausable, AccessControl, ERC721Burnable { 12 | using Counters for Counters.Counter; 13 | 14 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 15 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 16 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 17 | Counters.Counter private _tokenIdCounter; 18 | 19 | string private _uri; 20 | string private _contractUriSuffix; 21 | string private _baseContractUri; 22 | 23 | bool private _isMintAllowed; 24 | 25 | constructor(string memory name, string memory symbol, string memory baseUri, string memory contractUriSuffix, address ownerOrMultiSigContract) ERC721(name, symbol) { 26 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 27 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 28 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 29 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 30 | _uri = baseUri; 31 | _contractUriSuffix = contractUriSuffix; 32 | _baseContractUri = "https://ipfs.io/ipfs/"; 33 | _isMintAllowed = true; 34 | } 35 | 36 | function lockMint() public { 37 | require(hasRole(LOCKER_ROLE, msg.sender)); 38 | _isMintAllowed = false; 39 | } 40 | 41 | function contractURI() public view returns (string memory) { 42 | return bytes(_baseContractUri).length > 0 43 | ? string(abi.encodePacked(_baseContractUri, _contractUriSuffix)) 44 | : ''; 45 | } 46 | 47 | function setBaseContractURI(string memory newBaseContractUri) public { 48 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender)); 49 | 50 | _baseContractUri = newBaseContractUri; 51 | } 52 | 53 | function _baseURI() internal view override returns (string memory) { 54 | return _uri; 55 | } 56 | 57 | function safeMint(address to, string memory metadataURI) public { 58 | require(hasRole(MINTER_ROLE, msg.sender)); 59 | require(_isMintAllowed); 60 | 61 | _safeMint(to, _tokenIdCounter.current()); 62 | _setTokenURI(_tokenIdCounter.current(), metadataURI); 63 | _tokenIdCounter.increment(); 64 | } 65 | 66 | function pause() public { 67 | require(hasRole(PAUSER_ROLE, msg.sender)); 68 | _pause(); 69 | } 70 | 71 | function unpause() public { 72 | require(hasRole(PAUSER_ROLE, msg.sender)); 73 | _unpause(); 74 | } 75 | 76 | function _beforeTokenTransfer(address from, address to, uint256 tokenId) 77 | internal 78 | whenNotPaused 79 | override(ERC721, ERC721Enumerable) 80 | { 81 | super._beforeTokenTransfer(from, to, tokenId); 82 | } 83 | 84 | function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { 85 | super._burn(tokenId); 86 | } 87 | 88 | function tokenURI(uint256 tokenId) 89 | public 90 | view 91 | override(ERC721, ERC721URIStorage) 92 | returns (string memory) 93 | { 94 | return super.tokenURI(tokenId); 95 | } 96 | 97 | function supportsInterface(bytes4 interfaceId) 98 | public 99 | view 100 | override(ERC721, ERC721Enumerable, AccessControl) 101 | returns (bool) 102 | { 103 | return super.supportsInterface(interfaceId); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/StartonMultiSendERC721.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | import { 6 | StartonMultiSendERC721, 7 | StartonMultiSendERC721__factory, // eslint-disable-line camelcase 8 | StartonERC721Base, 9 | StartonERC721Base__factory, // eslint-disable-line camelcase 10 | } from "../typechain-types"; 11 | 12 | let Multisend: StartonMultiSendERC721__factory; // eslint-disable-line camelcase 13 | let ERC721: StartonERC721Base__factory; // eslint-disable-line camelcase 14 | 15 | describe("StartonMultiSendERC721", () => { 16 | let instanceMultisend: StartonMultiSendERC721; 17 | let instanceERC721: StartonERC721Base; 18 | let owner: SignerWithAddress; 19 | let addr1: SignerWithAddress; 20 | let addr2: SignerWithAddress; 21 | let addrs: SignerWithAddress[]; 22 | 23 | before(async () => { 24 | // Get the Signers here 25 | [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); 26 | 27 | // Create factory 28 | Multisend = new StartonMultiSendERC721__factory(owner); 29 | ERC721 = new StartonERC721Base__factory(owner); 30 | }); 31 | 32 | beforeEach(async () => { 33 | instanceMultisend = (await Multisend.deploy()) as StartonMultiSendERC721; 34 | await instanceMultisend.deployed(); 35 | 36 | instanceERC721 = (await ERC721.deploy( 37 | "StartonToken", 38 | "ST", 39 | "https://ipfs.io/", 40 | "https://ipfs.io/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", 41 | owner.address 42 | )) as StartonERC721Base; 43 | await instanceERC721.deployed(); 44 | }); 45 | 46 | describe("Deployment", () => { 47 | it("Should deploy", async () => {}); 48 | }); 49 | 50 | describe("MultiSend", () => { 51 | it("Shouldn't send tokens if arrays not equal ", async () => { 52 | await instanceERC721.mint( 53 | owner.address, 54 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 55 | ); 56 | await instanceERC721.mint( 57 | owner.address, 58 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 59 | ); 60 | await instanceERC721.mint( 61 | owner.address, 62 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 63 | ); 64 | await instanceERC721.setApprovalForAll(instanceMultisend.address, true); 65 | 66 | await expect( 67 | instanceMultisend.multiSend( 68 | instanceERC721.address, 69 | [0, 1, 2], 70 | [addr1.address, addr2.address] 71 | ) 72 | ).to.be.revertedWith("Arrays must be of equal length"); 73 | }); 74 | 75 | it("Should send tokens to multiple addresses", async () => { 76 | // mint tokens 77 | await instanceERC721.mint( 78 | owner.address, 79 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 80 | ); 81 | await instanceERC721.mint( 82 | owner.address, 83 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 84 | ); 85 | await instanceERC721.mint( 86 | owner.address, 87 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 88 | ); 89 | 90 | await instanceERC721.setApprovalForAll(instanceMultisend.address, true); 91 | await instanceMultisend.multiSend( 92 | instanceERC721.address, 93 | [0, 1, 2], 94 | [addr1.address, addr2.address, addrs[3].address] 95 | ); 96 | 97 | expect(await instanceERC721.balanceOf(addr1.address)).to.equal(1); 98 | expect(await instanceERC721.balanceOf(addr2.address)).to.equal(1); 99 | expect(await instanceERC721.balanceOf(addrs[3].address)).to.equal(1); 100 | expect(await instanceERC721.balanceOf(owner.address)).to.equal(0); 101 | 102 | expect(await instanceERC721.ownerOf(0)).to.equal(addr1.address); 103 | expect(await instanceERC721.ownerOf(1)).to.equal(addr2.address); 104 | expect(await instanceERC721.ownerOf(2)).to.equal(addrs[3].address); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/StartonERC5192.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | import { 6 | StartonERC5192, 7 | StartonERC5192__factory, // eslint-disable-line camelcase 8 | } from "../typechain-types"; 9 | 10 | let ERC5192: StartonERC5192__factory; // eslint-disable-line camelcase 11 | 12 | describe("StartonERC5192", () => { 13 | let instanceERC5192: StartonERC5192; 14 | let owner: SignerWithAddress; 15 | let addr1: SignerWithAddress; 16 | let addr2: SignerWithAddress; 17 | 18 | before(async () => { 19 | // Get the Signers here 20 | [owner, addr1, addr2] = await ethers.getSigners(); 21 | 22 | // Create factory 23 | ERC5192 = new StartonERC5192__factory(owner); 24 | }); 25 | 26 | beforeEach(async () => { 27 | instanceERC5192 = (await ERC5192.deploy( 28 | "StartonToken", 29 | "ST", 30 | "1000", 31 | owner.address, 32 | "https://ipfs.io/", 33 | "https://ipfs.io/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", 34 | owner.address 35 | )) as StartonERC5192; 36 | await instanceERC5192.deployed(); 37 | }); 38 | 39 | describe("Deployment", () => { 40 | it("Should deploy", async () => {}); 41 | 42 | it("Should set the correct owner", async () => { 43 | expect(await instanceERC5192.owner()).to.equal(owner.address); 44 | }); 45 | }); 46 | 47 | describe("Minting", () => { 48 | it("Should lock when minting", async () => { 49 | await expect( 50 | instanceERC5192.mint( 51 | addr1.address, 52 | "QmQT4UPwNY6614CFCA5MWKCnHExC4UME7m8hi6nYBm17u1" 53 | ) 54 | ).to.emit(instanceERC5192, "Locked"); 55 | expect(await instanceERC5192.locked(0)).to.equal(true); 56 | }); 57 | }); 58 | 59 | describe("Locked", () => { 60 | it("Should revert if token doesn't exist", async () => { 61 | await expect(instanceERC5192.locked(0)).to.be.revertedWith( 62 | "Token not found" 63 | ); 64 | }); 65 | }); 66 | 67 | describe("SupportsInterface", () => { 68 | it("Should support ERC721", async () => { 69 | expect(await instanceERC5192.supportsInterface("0x80ac58cd")).to.equal( 70 | true 71 | ); 72 | }); 73 | 74 | it("should support ERC5192Enumerable", async () => { 75 | expect(await instanceERC5192.supportsInterface("0x780e9d63")).to.equal( 76 | true 77 | ); 78 | }); 79 | 80 | it("Should support AccessControl", async () => { 81 | expect(await instanceERC5192.supportsInterface("0x7965db0b")).to.equal( 82 | true 83 | ); 84 | }); 85 | 86 | it("Should support ERC5192", async () => { 87 | expect(await instanceERC5192.supportsInterface("0xb45a3c0e")).to.equal( 88 | true 89 | ); 90 | }); 91 | }); 92 | 93 | describe("Transfer", () => { 94 | it("Shouldn't safeTransferFrom", async () => { 95 | await expect( 96 | instanceERC5192["safeTransferFrom(address,address,uint256)"]( 97 | addr1.address, 98 | addr2.address, 99 | 0 100 | ) 101 | ).to.be.revertedWith("Token locked"); 102 | }); 103 | 104 | it("Shouldn't safeTransferFrom with data", async () => { 105 | await expect( 106 | instanceERC5192["safeTransferFrom(address,address,uint256,bytes)"]( 107 | addr1.address, 108 | addr2.address, 109 | 0, 110 | ethers.constants.AddressZero 111 | ) 112 | ).to.be.revertedWith("Token locked"); 113 | }); 114 | 115 | it("Shouldn't transferFrom", async () => { 116 | await expect( 117 | instanceERC5192.transferFrom(addr1.address, addr2.address, 0) 118 | ).to.be.revertedWith("Token locked"); 119 | }); 120 | 121 | it("Shouldn't approve", async () => { 122 | await expect( 123 | instanceERC5192.approve(addr2.address, 0) 124 | ).to.be.revertedWith("Token locked"); 125 | }); 126 | 127 | it("Shouldn't setApprovalForAll", async () => { 128 | await expect( 129 | instanceERC5192.setApprovalForAll(addr2.address, true) 130 | ).to.be.revertedWith("Token locked"); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC4907.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./StartonERC721BaseRoyalties.sol"; 6 | import "../interfaces/IERC4907.sol"; 7 | 8 | /// @title StartonERC4907 9 | /// @author Starton 10 | /// @notice This contract allows to rent NFTs 11 | contract StartonERC4907 is StartonERC721BaseRoyalties, IERC4907 { 12 | /** @notice Structure of data reprensenting the user and expiration of usage */ 13 | struct UserInfo { 14 | address user; 15 | uint64 expires; 16 | } 17 | 18 | mapping(uint256 => UserInfo) internal _users; 19 | 20 | constructor( 21 | string memory definitiveName, 22 | string memory definitiveSymbol, 23 | uint96 definitiveRoyaltyFee, 24 | address definitiveFeeReceiver, 25 | string memory initialBaseTokenURI, 26 | string memory initialContractURI, 27 | address initialOwnerOrMultiSigContract 28 | ) 29 | StartonERC721BaseRoyalties( 30 | definitiveName, 31 | definitiveSymbol, 32 | definitiveRoyaltyFee, 33 | definitiveFeeReceiver, 34 | initialBaseTokenURI, 35 | initialContractURI, 36 | initialOwnerOrMultiSigContract 37 | ) 38 | {} 39 | 40 | /** 41 | * @notice Get the user address of an NFT 42 | * @dev The zero address indicates that there is no user or the user is expired 43 | * @param tokenId The NFT to get the user address for 44 | * @return The user address for this NFT 45 | */ 46 | function userOf(uint256 tokenId) public view virtual returns (address) { 47 | if (uint256(_users[tokenId].expires) >= block.timestamp) { 48 | return _users[tokenId].user; 49 | } else { 50 | return address(0); 51 | } 52 | } 53 | 54 | /** 55 | * @notice Get the user expires of an NFT 56 | * @dev The zero value indicates that there is no user 57 | * @param tokenId The NFT to get the user expires for 58 | * @return The user expires for this NFT 59 | */ 60 | function userExpires(uint256 tokenId) public view virtual returns (uint256) { 61 | return _users[tokenId].expires; 62 | } 63 | 64 | /** 65 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 66 | * @return True if the interface is supported 67 | */ 68 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 69 | return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId); 70 | } 71 | 72 | /** 73 | * @notice Set the user and expires of an NFT 74 | * @dev The zero address indicates there is no user 75 | * Throws if `tokenId` is not valid NFT 76 | * @param user The new user of the NFT 77 | * @param expires UNIX timestamp, The new user could use the NFT before expires 78 | */ 79 | function setUser( 80 | uint256 tokenId, 81 | address user, 82 | uint64 expires 83 | ) public virtual { 84 | require(_isApprovedOrOwner(_msgSender(), tokenId), "Caller is not owner nor approved"); 85 | 86 | UserInfo storage info = _users[tokenId]; 87 | info.user = user; 88 | info.expires = expires; 89 | emit UpdateUser(tokenId, user, expires); 90 | } 91 | 92 | /** 93 | * @dev Call the inherited contract _beforeTokenTransfer function 94 | * then delete the user of the NFT if the transfer is not a mint or a burn 95 | * @param from The address that owns the NFT 96 | * @param to The address that will receive the NFT 97 | * @param tokenId The NFT to transfer 98 | * @param batchSize The number of NFTs to transfer 99 | */ 100 | function _beforeTokenTransfer( 101 | address from, 102 | address to, 103 | uint256 tokenId, 104 | uint256 batchSize 105 | ) internal virtual override { 106 | super._beforeTokenTransfer(from, to, tokenId, batchSize); 107 | 108 | if (from != to && _users[tokenId].user != address(0)) { 109 | delete _users[tokenId]; 110 | emit UpdateUser(tokenId, address(0), 0); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC721BaseSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | import "@openzeppelin/contracts/utils/Context.sol"; 7 | import "../interfaces/IStartonERC721.sol"; 8 | 9 | /// @title StartonERC721BaseSale 10 | /// @author Starton 11 | /// @notice Sell ERC721 tokens through a public sale with a limited available supply, start and end time as well as max tokens per address 12 | contract StartonERC721BaseSale is Context { 13 | address private immutable _feeReceiver; 14 | 15 | IStartonERC721 public immutable token; 16 | 17 | uint256 public immutable price; 18 | uint256 public immutable startTime; 19 | uint256 public immutable endTime; 20 | uint256 public immutable maxTokensPerAddress; 21 | 22 | uint256 public leftSupply; 23 | 24 | mapping(address => uint256) public tokensClaimed; 25 | 26 | /** @dev Modifier that reverts when the block timestamp is not during the sale */ 27 | modifier isTimeCorrect() { 28 | require(startTime <= block.timestamp, "Minting not started"); 29 | require(endTime >= block.timestamp, "Minting finished"); 30 | _; 31 | } 32 | 33 | constructor( 34 | address definitiveTokenAddress, 35 | uint256 definitivePrice, 36 | uint256 definitiveStartTime, 37 | uint256 definitiveEndTime, 38 | uint256 definitiveMaxTokensPerAddress, 39 | uint256 definitiveMaxSupply, 40 | address definitiveFeeReceiver 41 | ) { 42 | // Check if the end time is after the starting time 43 | require(definitiveStartTime < definitiveEndTime, "End time after start time"); 44 | 45 | token = IStartonERC721(definitiveTokenAddress); 46 | _feeReceiver = definitiveFeeReceiver; 47 | price = definitivePrice; 48 | startTime = definitiveStartTime; 49 | endTime = definitiveEndTime; 50 | maxTokensPerAddress = definitiveMaxTokensPerAddress; 51 | leftSupply = definitiveMaxSupply; 52 | } 53 | 54 | /** 55 | * @notice Mint a token to a given address for a price 56 | * @param to The address to mint the token to 57 | */ 58 | function mint( 59 | address to, 60 | bytes32[] calldata /*data*/ 61 | ) public payable virtual isTimeCorrect { 62 | require(msg.value >= price, "Insufficient funds"); 63 | 64 | uint256 totalSupply = token.totalSupply(); 65 | if (totalSupply == 0) { 66 | _mint(to, Strings.toString(uint256(0))); 67 | } else { 68 | _mint(to, Strings.toString(token.tokenByIndex(totalSupply - 1) + 1)); 69 | } 70 | } 71 | 72 | /** 73 | * @notice Mint multiple tokens to a given address for a price 74 | * @param to The address to mint the token to 75 | */ 76 | function mintBatch( 77 | address to, 78 | uint256 amount, 79 | bytes32[] calldata /*data*/ 80 | ) public payable virtual isTimeCorrect { 81 | require(msg.value >= price * amount, "Insufficient funds"); 82 | 83 | // Compute the next token id 84 | uint256 totalSupply = token.totalSupply(); 85 | uint256 tokenId; 86 | if (totalSupply == 0) tokenId = 0; 87 | else tokenId = token.tokenByIndex(totalSupply - 1) + 1; 88 | 89 | for (uint256 i = 0; i < amount; ++i) { 90 | _mint(to, Strings.toString(tokenId)); 91 | tokenId += 1; 92 | } 93 | } 94 | 95 | /** 96 | * @notice Withdraw funds from the smart contract to the feeReceiver 97 | */ 98 | function withdraw() public virtual { 99 | payable(_feeReceiver).transfer(address(this).balance); 100 | } 101 | 102 | /** 103 | * @dev Mint a token to the given address and updates state variables for the sale 104 | * @param to The address to mint the token to 105 | * @param tokenURI The URI of the token 106 | */ 107 | function _mint(address to, string memory tokenURI) internal virtual { 108 | require(tokensClaimed[_msgSender()] < maxTokensPerAddress, "Max tokens reached"); 109 | require(leftSupply != 0, "Max supply reached"); 110 | 111 | leftSupply -= 1; 112 | tokensClaimed[_msgSender()] += 1; 113 | token.mint(to, tokenURI); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/StartonMultiSendERC1155.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | import { 6 | StartonMultiSendERC1155, 7 | StartonMultiSendERC1155__factory, // eslint-disable-line camelcase 8 | StartonERC1155Base, 9 | StartonERC1155Base__factory, // eslint-disable-line camelcase 10 | } from "../typechain-types"; 11 | 12 | let Multisend: StartonMultiSendERC1155__factory; // eslint-disable-line camelcase 13 | let ERC1155: StartonERC1155Base__factory; // eslint-disable-line camelcase 14 | 15 | describe("StartonMultiSendERC1155", () => { 16 | let instanceMultisend: StartonMultiSendERC1155; 17 | let instanceERC1155: StartonERC1155Base; 18 | let owner: SignerWithAddress; 19 | let addr1: SignerWithAddress; 20 | let addr2: SignerWithAddress; 21 | let addrs: SignerWithAddress[]; 22 | 23 | before(async () => { 24 | // Get the Signers here 25 | [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); 26 | 27 | // Create factory 28 | Multisend = new StartonMultiSendERC1155__factory(owner); 29 | ERC1155 = new StartonERC1155Base__factory(owner); 30 | }); 31 | 32 | beforeEach(async () => { 33 | instanceMultisend = (await Multisend.deploy()) as StartonMultiSendERC1155; 34 | await instanceMultisend.deployed(); 35 | 36 | instanceERC1155 = (await ERC1155.deploy( 37 | "StartonToken", 38 | "https://ipfs.io/QmbWqibQSuvvsGVDUVvDCGdgcdCDCfycDFC3VV4v4Ghgc4/{id}", 39 | "https://ipfs.io/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", 40 | owner.address 41 | )) as StartonERC1155Base; 42 | await instanceERC1155.deployed(); 43 | }); 44 | 45 | describe("Deployment", () => { 46 | it("Should deploy", async () => {}); 47 | }); 48 | 49 | describe("MultiSend", () => { 50 | it("Shouldn't send tokens if arrays not equal ", async () => { 51 | // mint tokens 52 | await instanceERC1155["mint(address,uint256,uint256)"]( 53 | owner.address, 54 | 1536, 55 | 11 56 | ); 57 | await instanceERC1155["mint(address,uint256,uint256)"]( 58 | owner.address, 59 | 156, 60 | 20 61 | ); 62 | 63 | await instanceERC1155.setApprovalForAll(instanceMultisend.address, true); 64 | await expect( 65 | instanceMultisend.multiSend( 66 | instanceERC1155.address, 67 | [1536, 1536, 156], 68 | [4, 7, 20], 69 | [addr1.address, addr2.address] 70 | ) 71 | ).to.be.revertedWith("Arrays must be of equal length"); 72 | 73 | await expect( 74 | instanceMultisend.multiSend( 75 | instanceERC1155.address, 76 | [1536, 1536], 77 | [4, 7, 20], 78 | [addr1.address, addr2.address, addrs[3].address] 79 | ) 80 | ).to.be.revertedWith("Arrays must be of equal length"); 81 | 82 | await expect( 83 | instanceMultisend.multiSend( 84 | instanceERC1155.address, 85 | [1536, 1536, 156], 86 | [4, 7], 87 | [addr1.address, addr2.address, addrs[3].address] 88 | ) 89 | ).to.be.revertedWith("Arrays must be of equal length"); 90 | }); 91 | 92 | it("Should send tokens to multiple addresses", async () => { 93 | // mint tokens 94 | await instanceERC1155["mint(address,uint256,uint256)"]( 95 | owner.address, 96 | 1536, 97 | 11 98 | ); 99 | await instanceERC1155["mint(address,uint256,uint256)"]( 100 | owner.address, 101 | 156, 102 | 20 103 | ); 104 | 105 | await instanceERC1155.setApprovalForAll(instanceMultisend.address, true); 106 | await instanceMultisend.multiSend( 107 | instanceERC1155.address, 108 | [1536, 1536, 156], 109 | [4, 7, 20], 110 | [addr1.address, addr2.address, addrs[3].address] 111 | ); 112 | 113 | expect(await instanceERC1155.balanceOf(addr1.address, 1536)).to.equal(4); 114 | expect(await instanceERC1155.balanceOf(addr2.address, 1536)).to.equal(7); 115 | expect(await instanceERC1155.balanceOf(addrs[3].address, 156)).to.equal( 116 | 20 117 | ); 118 | expect(await instanceERC1155.balanceOf(owner.address, 1536)).to.equal(0); 119 | expect(await instanceERC1155.balanceOf(owner.address, 156)).to.equal(0); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /flatten/tools/StartonMultiSendERC20.sol: -------------------------------------------------------------------------------- 1 | // Sources flattened with hardhat v2.10.1 https://hardhat.org 2 | 3 | // File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.9.2 4 | 5 | // SPDX-License-Identifier: MIT 6 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) 7 | 8 | pragma solidity ^0.8.0; 9 | 10 | /** 11 | * @dev Interface of the ERC20 standard as defined in the EIP. 12 | */ 13 | interface IERC20 { 14 | /** 15 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 16 | * another (`to`). 17 | * 18 | * Note that `value` may be zero. 19 | */ 20 | event Transfer(address indexed from, address indexed to, uint256 value); 21 | 22 | /** 23 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 24 | * a call to {approve}. `value` is the new allowance. 25 | */ 26 | event Approval(address indexed owner, address indexed spender, uint256 value); 27 | 28 | /** 29 | * @dev Returns the amount of tokens in existence. 30 | */ 31 | function totalSupply() external view returns (uint256); 32 | 33 | /** 34 | * @dev Returns the amount of tokens owned by `account`. 35 | */ 36 | function balanceOf(address account) external view returns (uint256); 37 | 38 | /** 39 | * @dev Moves `amount` tokens from the caller's account to `to`. 40 | * 41 | * Returns a boolean value indicating whether the operation succeeded. 42 | * 43 | * Emits a {Transfer} event. 44 | */ 45 | function transfer(address to, uint256 amount) external returns (bool); 46 | 47 | /** 48 | * @dev Returns the remaining number of tokens that `spender` will be 49 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 50 | * zero by default. 51 | * 52 | * This value changes when {approve} or {transferFrom} are called. 53 | */ 54 | function allowance(address owner, address spender) external view returns (uint256); 55 | 56 | /** 57 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 58 | * 59 | * Returns a boolean value indicating whether the operation succeeded. 60 | * 61 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 62 | * that someone may use both the old and the new allowance by unfortunate 63 | * transaction ordering. One possible solution to mitigate this race 64 | * condition is to first reduce the spender's allowance to 0 and set the 65 | * desired value afterwards: 66 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 67 | * 68 | * Emits an {Approval} event. 69 | */ 70 | function approve(address spender, uint256 amount) external returns (bool); 71 | 72 | /** 73 | * @dev Moves `amount` tokens from `from` to `to` using the 74 | * allowance mechanism. `amount` is then deducted from the caller's 75 | * allowance. 76 | * 77 | * Returns a boolean value indicating whether the operation succeeded. 78 | * 79 | * Emits a {Transfer} event. 80 | */ 81 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 82 | } 83 | 84 | 85 | // File contracts/tools/StartonMultiSendERC20.sol 86 | 87 | 88 | pragma solidity 0.8.17; 89 | 90 | /// @title StartonMultiSendERC20 91 | /// @author Starton 92 | /// @notice This contract allows to send multiple ERC20 tokens in a single transaction 93 | contract StartonMultiSendERC20 { 94 | /** 95 | * @notice Send multiple ERC20 tokens in a single transaction 96 | * @param token The address of the ERC20 token 97 | * @param amounts The amounts of tokens to send 98 | * @param addresses The addresses to send the tokens to 99 | */ 100 | function multiSend( 101 | address token, 102 | uint256[] calldata amounts, 103 | address[] calldata addresses 104 | ) external { 105 | require(amounts.length == addresses.length, "Arrays must be of equal length"); 106 | 107 | uint256 length = amounts.length; 108 | for (uint256 i = 0; i < length; ) { 109 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 110 | IERC20(token).transferFrom(msg.sender, addresses[i], amounts[i]); 111 | unchecked { 112 | i += 1; 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /scripts/generate-doc.ts: -------------------------------------------------------------------------------- 1 | import { exec } from "child_process" 2 | import { readdirSync, readFileSync, writeFileSync } from "fs" 3 | import doc from "../doc" 4 | import { SmartContractTemplateCategories } from "./smart-contract-template" 5 | 6 | function replaceAll(str: string, find: string, replace: string) { 7 | return str.replace(new RegExp(find, "g"), replace) 8 | } 9 | 10 | function parseAbi(contractMetadata: any): any { 11 | return contractMetadata.abi 12 | } 13 | 14 | function parseBytecode(contractMetadata: any): string { 15 | return contractMetadata.evm.bytecode.object 16 | } 17 | 18 | function parseRuns(fileContent: any): number { 19 | return fileContent.input.settings.optimizer.runs 20 | } 21 | 22 | function parseOptimizer(fileContent: any): boolean { 23 | return fileContent.input.settings.optimizer.enabled 24 | } 25 | 26 | function parseCompilerVersion(fileContent: any): string { 27 | return fileContent.solcLongVersion 28 | } 29 | 30 | function parseSourceCode(contractFilePath: any): string { 31 | const fileContents = readFileSync(contractFilePath, { 32 | encoding: "utf8", 33 | flag: "r", 34 | }).toString() 35 | 36 | return fileContents.replace(/\n/g, "\\n").replace(/'/g, "\\'") 37 | } 38 | 39 | function getCompilationFileContent(contractName: string): any | undefined { 40 | const baseBuildPath = "./artifacts/build-info" 41 | const files = readdirSync(baseBuildPath) 42 | 43 | const compilationFiles: string[] = [] 44 | files.forEach((file) => { 45 | compilationFiles.push( 46 | readFileSync(baseBuildPath + "/" + file, { 47 | encoding: "utf8", 48 | flag: "r", 49 | }).toString(), 50 | ) 51 | }) 52 | 53 | const compilationFile = compilationFiles.find((file) => file.includes("/" + contractName + ".sol")) 54 | 55 | if (!compilationFile) { 56 | return undefined 57 | } 58 | 59 | return JSON.parse(compilationFile) 60 | } 61 | 62 | function findOutputInsideCompilationFile(compilationFile: any, contractName: string): any { 63 | let contractMetadata 64 | for (const contract in compilationFile.output.contracts) { 65 | if (contract.includes("/" + contractName + ".sol")) { 66 | contractMetadata = compilationFile.output.contracts[contract][contractName] 67 | } 68 | } 69 | 70 | if (!contractMetadata) { 71 | throw new Error(`Contract ${contractName} not found inside compilation file`) 72 | } 73 | 74 | return contractMetadata 75 | } 76 | 77 | function findFlattenPath(compilationFile: any, contractName: string): string { 78 | let contractMetadata 79 | for (const contract in compilationFile.output.contracts) { 80 | if (contract.includes("/" + contractName + ".sol")) { 81 | contractMetadata = contract 82 | } 83 | } 84 | 85 | if (!contractMetadata) { 86 | throw new Error(`Contract ${contractName} not found inside compilation file`) 87 | } 88 | 89 | return contractMetadata.replace("contracts/", "flatten/") 90 | } 91 | 92 | function printUsage(): void { 93 | console.log("USAGE\n\tts-node generate-doc output") 94 | console.log() 95 | console.log("DESCRIPTION\n\toutput\tThe output file") 96 | } 97 | 98 | function main() { 99 | if (process.argv.length !== 3 || process.argv[1] === "--help" || process.argv[1] === "-h") { 100 | printUsage() 101 | return 102 | } 103 | 104 | doc.forEach((contract) => { 105 | console.log(`Handling the ${contract.compilationDetails.contractName} contract`) 106 | 107 | const compilationFile = getCompilationFileContent(contract.compilationDetails.contractName) 108 | 109 | if (!compilationFile) { 110 | return 111 | } 112 | 113 | const flattenPath = findFlattenPath(compilationFile, contract.compilationDetails.contractName) 114 | 115 | contract.compilationDetails.runs = parseRuns(compilationFile) 116 | contract.compilationDetails.compilerVersion = parseCompilerVersion(compilationFile) 117 | contract.compilationDetails.optimizationUsed = parseOptimizer(compilationFile) 118 | 119 | const outputContractMetadata = findOutputInsideCompilationFile( 120 | compilationFile, 121 | contract.compilationDetails.contractName, 122 | ) 123 | 124 | contract.compilationDetails.bytecode = parseBytecode(outputContractMetadata) 125 | contract.abi = parseAbi(outputContractMetadata) 126 | 127 | contract.compilationDetails.source = parseSourceCode(flattenPath) 128 | }) 129 | 130 | let stringifiedContent = JSON.stringify(doc, null, 2) 131 | 132 | // Remove the quotes of the enum values 133 | for (const item in SmartContractTemplateCategories) { 134 | stringifiedContent = replaceAll( 135 | stringifiedContent, 136 | `"SmartContractTemplateCategories.${item}"`, 137 | `SmartContractTemplateCategories.${item}`, 138 | ) 139 | } 140 | 141 | writeFileSync( 142 | process.argv[2], 143 | 'import { SmartContractTemplate } from "../../module/smart-contract-template/smart-contract-template.entity"\nimport { SmartContractTemplateCategories } from "../../module/interface"\n\nexport const templateSeeder: Partial[] = ' + 144 | stringifiedContent + "\n\n" + readFileSync('scripts/suffix.txt', { encoding: 'utf-8' }), 145 | ) 146 | 147 | exec("prettier " + process.argv[2] + " --write", { 148 | encoding: "utf-8", 149 | }) 150 | } 151 | main() 152 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ![Starton Banner](https://github.com/starton-io/.github/blob/master/github-banner.jpg?raw=true) 2 | 3 | # Code of Conduct - Starton 4 | 5 | ## Our Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | - Demonstrating empathy and kindness toward other people 14 | - Being respectful of differing opinions, viewpoints, and experiences 15 | - Giving and gracefully accepting constructive feedback 16 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | - Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | - The use of sexualized language or imagery, and sexual attention or advances 22 | - Trolling, insulting or derogatory comments, and personal or political attacks 23 | - Public or private harassment 24 | - Publishing others' private information, such as a physical or email address, without their explicit permission 25 | - Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | ## Our Responsibilities 28 | 29 | Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 32 | 33 | ## Scope 34 | 35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 36 | 37 | ## Enforcement 38 | 39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [support@starton.com](mailto:support@starton.com). All complaints will be reviewed and investigated promptly and fairly. 40 | 41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 42 | 43 | ## Enforcement Guidelines 44 | 45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 46 | 47 | ### 1. Correction 48 | 49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 50 | 51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 52 | 53 | ### 2. Warning 54 | 55 | **Community Impact**: A violation through a single incident or series of actions. 56 | 57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 58 | 59 | ### 3. Temporary Ban 60 | 61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 62 | 63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 64 | 65 | ### 4. Permanent Ban 66 | 67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 68 | 69 | **Consequence**: A permanent ban from any sort of public interaction within the community. 70 | 71 | ## Attribution 72 | 73 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version [1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and [2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md), and was generated by [contributing-gen](https://github.com/bttger/contributing-gen) and modified by [Starton](https://www.starton.com). [Make your own](https://github.com/bttger/contributing-gen)! 74 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import "@openzeppelin/contracts/security/Pausable.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; 7 | 8 | contract ChildStartonERC1155 is AccessControl, Pausable, ERC1155Burnable { 9 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 10 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 11 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 12 | bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE"); 13 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 14 | 15 | string private _contractUriSuffix; 16 | string private _baseContractUri; 17 | 18 | bool private _isMintAllowed; 19 | 20 | 21 | constructor(string memory name, string memory baseUri, string memory contractUriSuffix, address ownerOrMultiSigContract) ERC1155(name) { 22 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 23 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 24 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 25 | _setupRole(URI_SETTER_ROLE, ownerOrMultiSigContract); 26 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 27 | _setURI(baseUri); 28 | _contractUriSuffix = contractUriSuffix; 29 | _baseContractUri = "https://ipfs.io/ipfs/"; 30 | _isMintAllowed = true; 31 | } 32 | 33 | function setURI(string memory newuri) public { 34 | require(hasRole(URI_SETTER_ROLE, msg.sender)); 35 | _setURI(newuri); 36 | } 37 | 38 | function contractURI() public view returns (string memory) { 39 | return bytes(_baseContractUri).length > 0 40 | ? string(abi.encodePacked(_baseContractUri, _contractUriSuffix)) 41 | : ''; 42 | } 43 | 44 | function setBaseContractURI(string memory newBaseContractUri) public { 45 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender)); 46 | 47 | _baseContractUri = newBaseContractUri; 48 | } 49 | 50 | function pause() public { 51 | require(hasRole(PAUSER_ROLE, msg.sender)); 52 | _pause(); 53 | } 54 | 55 | function unpause() public { 56 | require(hasRole(PAUSER_ROLE, msg.sender)); 57 | _unpause(); 58 | } 59 | 60 | function lockMint() public { 61 | require(hasRole(LOCKER_ROLE, msg.sender)); 62 | _isMintAllowed = false; 63 | } 64 | 65 | function mint(address account, uint256 id, uint256 amount, bytes memory data) 66 | public 67 | { 68 | require(hasRole(MINTER_ROLE, msg.sender)); 69 | require(_isMintAllowed); 70 | 71 | _mint(account, id, amount, data); 72 | } 73 | 74 | function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) 75 | public 76 | { 77 | require(hasRole(MINTER_ROLE, msg.sender)); 78 | require(_isMintAllowed); 79 | 80 | _mintBatch(to, ids, amounts, data); 81 | } 82 | 83 | function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) 84 | internal 85 | whenNotPaused 86 | override 87 | { 88 | super._beforeTokenTransfer(operator, from, to, ids, amounts, data); 89 | } 90 | 91 | function supportsInterface(bytes4 interfaceId) 92 | public 93 | view 94 | override(ERC1155, AccessControl) 95 | returns (bool) 96 | { 97 | return super.supportsInterface(interfaceId); 98 | } 99 | 100 | /** 101 | * @notice called when tokens are deposited on root chain 102 | * @dev Should be callable only by ChildChainManager 103 | * Should handle deposit by minting the required tokens for user 104 | * Make sure minting is done only by this function 105 | * @param user user address for whom deposit is being done 106 | * @param depositData abi encoded ids array and amounts array 107 | */ 108 | function deposit(address user, bytes calldata depositData) 109 | external 110 | { 111 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 112 | 113 | ( 114 | uint256[] memory ids, 115 | uint256[] memory amounts, 116 | bytes memory data 117 | ) = abi.decode(depositData, (uint256[], uint256[], bytes)); 118 | 119 | require( 120 | user != address(0), 121 | "ChildMintableERC1155: INVALID_DEPOSIT_USER" 122 | ); 123 | 124 | _mintBatch(user, ids, amounts, data); 125 | } 126 | 127 | /** 128 | * @notice called when user wants to withdraw single token back to root chain 129 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 130 | * @param id id to withdraw 131 | * @param amount amount to withdraw 132 | */ 133 | function withdrawSingle(uint256 id, uint256 amount) external { 134 | _burn(_msgSender(), id, amount); 135 | } 136 | 137 | /** 138 | * @notice called when user wants to batch withdraw tokens back to root chain 139 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 140 | * @param ids ids to withdraw 141 | * @param amounts amounts to withdraw 142 | */ 143 | function withdrawBatch(uint256[] calldata ids, uint256[] calldata amounts) 144 | external 145 | { 146 | _burnBatch(_msgSender(), ids, amounts); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC1155BaseSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "../interfaces/IStartonERC1155.sol"; 7 | 8 | /// @title StartonERC1155BaseSale 9 | /// @author Starton 10 | /// @notice Sell ERC1155 tokens through a public sale with a limited available supply, start and end time as well as max tokens per address 11 | contract StartonERC1155BaseSale is Ownable { 12 | struct TokenInformations { 13 | uint256 price; 14 | bool isSet; 15 | } 16 | 17 | mapping(uint256 => TokenInformations) private _pricePerToken; 18 | 19 | address private immutable _feeReceiver; 20 | 21 | IStartonERC1155 public immutable token; 22 | 23 | uint256 public immutable startTime; 24 | uint256 public immutable endTime; 25 | uint256 public immutable maxTokensPerAddress; 26 | 27 | uint256 public leftSupply; 28 | 29 | mapping(address => uint256) public tokensClaimed; 30 | 31 | /** @dev Modifier that reverts when the pice is not set yet */ 32 | modifier isPriceSet(uint256 id) { 33 | require(_pricePerToken[id].isSet, "Price not set"); 34 | _; 35 | } 36 | 37 | /** @dev Modifier that reverts when the block timestamp is not during the sale */ 38 | modifier isTimeCorrect() { 39 | require(startTime <= block.timestamp, "Minting not started"); 40 | require(endTime >= block.timestamp, "Minting finished"); 41 | _; 42 | } 43 | 44 | constructor( 45 | address definitiveTokenAddress, 46 | uint256 definitiveStartTime, 47 | uint256 definitiveEndTime, 48 | uint256 definitiveMaxTokensPerAddress, 49 | uint256 definitiveMaxSupply, 50 | address definitiveFeeReceiver 51 | ) { 52 | // Check if the end time is after the starting time 53 | require(definitiveStartTime < definitiveEndTime, "End time after start time"); 54 | 55 | token = IStartonERC1155(definitiveTokenAddress); 56 | _feeReceiver = definitiveFeeReceiver; 57 | startTime = definitiveStartTime; 58 | endTime = definitiveEndTime; 59 | maxTokensPerAddress = definitiveMaxTokensPerAddress; 60 | leftSupply = definitiveMaxSupply; 61 | } 62 | 63 | /** 64 | * @notice Mint a token to a given address for a price 65 | * @param to The address to mint the token to 66 | * @param id The id of the token 67 | * @param amount The amount of tokens to mint 68 | */ 69 | function mint( 70 | address to, 71 | uint256 id, 72 | uint256 amount, 73 | bytes32[] calldata /*data*/ 74 | ) public payable virtual isPriceSet(id) isTimeCorrect { 75 | require(msg.value >= _pricePerToken[id].price * amount, "Insufficient funds"); 76 | 77 | _mint(to, id, amount); 78 | } 79 | 80 | /** 81 | * @notice Mint multiple tokens to a given address for a price 82 | * @param to The address to mint the tokens to 83 | * @param ids The ids of the token to mint 84 | * @param amounts The amounts of tokens to mint 85 | */ 86 | function mintBatch( 87 | address to, 88 | uint256[] calldata ids, 89 | uint256[] calldata amounts, 90 | bytes32[] calldata /*data*/ 91 | ) public payable virtual isTimeCorrect { 92 | require(ids.length == amounts.length, "Ids and amounts length mismatch"); 93 | 94 | uint256 value = msg.value; 95 | uint256 totalAmount = 0; 96 | for (uint256 i = 0; i < ids.length; ++i) { 97 | require(_pricePerToken[ids[i]].isSet, "Price not set"); 98 | 99 | totalAmount += _pricePerToken[ids[i]].price * amounts[i]; 100 | require(value >= totalAmount, "Insufficient funds"); 101 | 102 | _mint(to, ids[i], amounts[i]); 103 | } 104 | } 105 | 106 | /** 107 | * @notice Set the price of a batch of tokens 108 | * @param ids The ids of the tokens 109 | * @param prices The prices of the tokens 110 | */ 111 | function setPrices(uint256[] calldata ids, uint256[] calldata prices) public virtual onlyOwner { 112 | require(ids.length == prices.length, "Ids and prices length mismatch"); 113 | 114 | for (uint256 i = 0; i < ids.length; ++i) { 115 | _pricePerToken[ids[i]] = TokenInformations(prices[i], true); 116 | } 117 | } 118 | 119 | /** 120 | * @notice Withdraw funds from the smart contract to the feeReceiver 121 | */ 122 | function withdraw() public virtual { 123 | payable(_feeReceiver).transfer(address(this).balance); 124 | } 125 | 126 | /** 127 | * @notice Get the price of a token 128 | * @param id The id of the token 129 | * @return The price of the token 130 | */ 131 | function pricePerToken(uint256 id) public view virtual isPriceSet(id) returns (uint256) { 132 | return _pricePerToken[id].price; 133 | } 134 | 135 | /** 136 | * @dev Mint a token to the given address and updates state variables for the sale 137 | * @param to The address to mint the token to 138 | * @param id The id of the token 139 | * @param amount The amount of tokens to mint 140 | */ 141 | function _mint( 142 | address to, 143 | uint256 id, 144 | uint256 amount 145 | ) internal virtual { 146 | require(tokensClaimed[_msgSender()] + amount <= maxTokensPerAddress, "Max tokens reached"); 147 | require(leftSupply >= amount, "Max supply reached"); 148 | 149 | unchecked { 150 | leftSupply -= amount; 151 | } 152 | tokensClaimed[_msgSender()] += amount; 153 | token.mint(to, id, amount); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC721AuctionSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "../interfaces/IStartonERC721.sol"; 8 | 9 | /// @title StartonERC721AuctionSale 10 | /// @author Starton 11 | /// @notice Sell ERC721 tokens through an auction 12 | contract StartonERC721AuctionSale is Ownable, ReentrancyGuard { 13 | address private immutable _feeReceiver; 14 | 15 | IStartonERC721 public immutable token; 16 | 17 | uint256 public currentPrice; 18 | uint256 public minPriceDifference; 19 | address public currentAuctionWinner; 20 | uint256 public startTime; 21 | uint256 public endTime; 22 | 23 | // Mapping of old auction winners and their outdated bids 24 | mapping(address => uint256) public oldBidsAmount; 25 | 26 | // Information of the token to be sold 27 | string public tokenURI; 28 | 29 | // If the token as been claimed or not yet 30 | bool private _claimed; 31 | 32 | /** @notice Event emitted when an auction started */ 33 | event AuctionStarted(uint256 startTime, uint256 endTime); 34 | 35 | /** @notice Event emitted when an auction winner has claimed his prize */ 36 | event AuctionClaimed(address indexed winner, uint256 price); 37 | 38 | /** @notice Event emitted when an account bided on an auction */ 39 | event Bided(address indexed bidder, uint256 amount); 40 | 41 | constructor( 42 | address definitiveTokenAddress, 43 | address definitiveFeeReceiver, 44 | uint256 initialStartingPrice, 45 | uint256 initialMinPriceDifference, 46 | uint256 initialStartTime, 47 | uint256 initialEndTime, 48 | string memory initialTokenURI 49 | ) { 50 | // Check if the end time is after the starting time 51 | require(initialStartTime < initialEndTime, "End time after start time"); 52 | 53 | token = IStartonERC721(definitiveTokenAddress); 54 | _feeReceiver = definitiveFeeReceiver; 55 | currentPrice = initialStartingPrice; 56 | minPriceDifference = initialMinPriceDifference; 57 | startTime = initialStartTime; 58 | endTime = initialEndTime; 59 | tokenURI = initialTokenURI; 60 | 61 | // Set inititial states of the auction to no winner and not claimed 62 | currentAuctionWinner = address(0); 63 | _claimed = false; 64 | 65 | // Emit the event when the auction starts 66 | emit AuctionStarted(initialStartTime, initialEndTime); 67 | } 68 | 69 | /** 70 | * @notice Bid for the current auction 71 | */ 72 | function bid() public payable nonReentrant { 73 | require(startTime <= block.timestamp, "Bidding not started"); 74 | require(endTime >= block.timestamp, "Bidding finished"); 75 | require(currentPrice + minPriceDifference <= msg.value, "Bid is too low"); 76 | 77 | // Store the old auction winner and price 78 | address oldAuctionWinner = currentAuctionWinner; 79 | uint256 oldPrice = currentPrice; 80 | 81 | currentPrice = msg.value; 82 | currentAuctionWinner = _msgSender(); 83 | emit Bided(_msgSender(), msg.value); 84 | 85 | // If there is a current winner, send back the money or add the money to claimable amount 86 | if (oldAuctionWinner != address(0)) { 87 | (bool success, ) = payable(oldAuctionWinner).call{value: oldPrice}(""); 88 | if (!success) oldBidsAmount[oldAuctionWinner] += oldPrice; 89 | } 90 | } 91 | 92 | /** 93 | * @notice Claim the prize of the current auction 94 | */ 95 | function claim() public { 96 | require(endTime < block.timestamp, "Minting hasn't finished yet"); 97 | require(!_claimed, "Token has already been claimed"); 98 | 99 | _claimed = true; 100 | emit AuctionClaimed(currentAuctionWinner, currentPrice); 101 | token.mint(currentAuctionWinner, tokenURI); 102 | } 103 | 104 | /** 105 | * @notice Start a new auction for a new NFT 106 | * @param newStartingPrice the starting price of the new auction 107 | * @param newStartTime the time when the auction starts 108 | * @param newEndTime the time when the auction ends 109 | * @param newTokenURI the token URI of the new NFT 110 | */ 111 | function startNewAuction( 112 | uint256 newStartingPrice, 113 | uint256 newMinPriceDifference, 114 | uint256 newStartTime, 115 | uint256 newEndTime, 116 | string memory newTokenURI 117 | ) public onlyOwner { 118 | require(_claimed, "The auction hasn't been claimed yet"); 119 | require(newStartTime < newEndTime, "Start time must be before end time"); 120 | 121 | // Reset the state variables for a new auction to begin 122 | _claimed = false; 123 | currentPrice = newStartingPrice; 124 | minPriceDifference = newMinPriceDifference; 125 | currentAuctionWinner = address(0); 126 | startTime = newStartTime; 127 | endTime = newEndTime; 128 | tokenURI = newTokenURI; 129 | 130 | emit AuctionStarted(startTime, endTime); 131 | } 132 | 133 | /** 134 | * @notice Withdraw the olds bids amount of the sender 135 | */ 136 | function withdrawOldBids() public { 137 | require(oldBidsAmount[_msgSender()] > 0, "No old bids to withdraw"); 138 | 139 | uint256 amount = oldBidsAmount[_msgSender()]; 140 | oldBidsAmount[_msgSender()] = 0; 141 | 142 | (bool success, ) = payable(_msgSender()).call{value: amount}(""); 143 | require(success, "Failed to withdraw"); 144 | } 145 | 146 | /** 147 | * @notice Withdraw funds from the smart contract to the feeReceiver 148 | * @dev send everything except the current price if there is an auction ongoing 149 | */ 150 | function withdraw() public { 151 | if (currentAuctionWinner != address(0) && !_claimed) { 152 | payable(_feeReceiver).transfer(address(this).balance - currentPrice); 153 | } else { 154 | payable(_feeReceiver).transfer(address(this).balance); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /contracts/nft-sales/StartonERC1155AuctionSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "../interfaces/IStartonERC1155.sol"; 8 | 9 | /// @title StartonERC1155AuctionSale 10 | /// @author Starton 11 | /// @notice Sell ERC1155 tokens through an auction 12 | contract StartonERC1155AuctionSale is Ownable, ReentrancyGuard { 13 | address private immutable _feeReceiver; 14 | 15 | IStartonERC1155 public immutable token; 16 | 17 | uint256 public currentPrice; 18 | uint256 public minPriceDifference; 19 | address public currentAuctionWinner; 20 | uint256 public startTime; 21 | uint256 public endTime; 22 | 23 | // Mapping of old auction winners and their outdated bids 24 | mapping(address => uint256) public oldBidsAmount; 25 | 26 | // Informations of the token to be sold 27 | uint256 public tokenId; 28 | uint256 public tokenAmount; 29 | 30 | // If the token as been claimed or not yet 31 | bool private _claimed; 32 | 33 | /** @notice Event emitted when an auction started */ 34 | event AuctionStarted(uint256 startTime, uint256 endTime); 35 | 36 | /** @notice Event emitted when an auction winner has claimed his prize */ 37 | event AuctionClaimed(address indexed winner, uint256 price); 38 | 39 | /** @notice Event emitted when an account bided on an auction */ 40 | event Bided(address indexed bidder, uint256 amount); 41 | 42 | constructor( 43 | address definitiveTokenAddress, 44 | address definitiveFeeReceiver, 45 | uint256 initialStartingPrice, 46 | uint256 initialMinPriceDifference, 47 | uint256 initialStartTime, 48 | uint256 initialEndTime, 49 | uint256 initialTokenId, 50 | uint256 initialTokenAmount 51 | ) { 52 | // Check if the end time is after the starting time 53 | require(initialStartTime < initialEndTime, "End time after start time"); 54 | 55 | token = IStartonERC1155(definitiveTokenAddress); 56 | _feeReceiver = definitiveFeeReceiver; 57 | currentPrice = initialStartingPrice; 58 | minPriceDifference = initialMinPriceDifference; 59 | startTime = initialStartTime; 60 | endTime = initialEndTime; 61 | tokenId = initialTokenId; 62 | tokenAmount = initialTokenAmount; 63 | 64 | // Set inititial states of the auction to no winner and not claimed 65 | currentAuctionWinner = address(0); 66 | _claimed = false; 67 | 68 | // Emit the event when the auction starts 69 | emit AuctionStarted(initialStartTime, initialEndTime); 70 | } 71 | 72 | /** 73 | * @notice Bid for the current auction 74 | */ 75 | function bid() public payable nonReentrant { 76 | require(startTime <= block.timestamp, "Bidding not started"); 77 | require(endTime >= block.timestamp, "Bidding finished"); 78 | require(currentPrice + minPriceDifference <= msg.value, "Bid is too low"); 79 | 80 | // Store the old auction winner and price 81 | address oldAuctionWinner = currentAuctionWinner; 82 | uint256 oldPrice = currentPrice; 83 | 84 | currentPrice = msg.value; 85 | currentAuctionWinner = _msgSender(); 86 | emit Bided(_msgSender(), msg.value); 87 | 88 | // If there is a current winner, send back the money or add the money to claimable amount 89 | if (oldAuctionWinner != address(0)) { 90 | (bool success, ) = payable(oldAuctionWinner).call{value: oldPrice}(""); 91 | if (!success) oldBidsAmount[oldAuctionWinner] += oldPrice; 92 | } 93 | } 94 | 95 | /** 96 | * @notice Claim the prize of the current auction 97 | */ 98 | function claim() public { 99 | require(endTime < block.timestamp, "Minting hasn't finished yet"); 100 | require(!_claimed, "Token has already been claimed"); 101 | 102 | _claimed = true; 103 | emit AuctionClaimed(currentAuctionWinner, currentPrice); 104 | token.mint(currentAuctionWinner, tokenId, tokenAmount); 105 | } 106 | 107 | /** 108 | * @notice Start a new auction for a new NFT 109 | * @param newStartingPrice the starting price of the new auction 110 | * @param newStartTime the time when the auction starts 111 | * @param newEndTime the time when the auction ends 112 | * @param newTokenId the id of the token to be sold 113 | * @param newTokenAmount the amount of the token to be sold 114 | */ 115 | function startNewAuction( 116 | uint256 newStartingPrice, 117 | uint256 newMinPriceDifference, 118 | uint256 newStartTime, 119 | uint256 newEndTime, 120 | uint256 newTokenId, 121 | uint256 newTokenAmount 122 | ) public onlyOwner { 123 | require(_claimed, "The auction hasn't been claimed yet"); 124 | require(newStartTime < newEndTime, "Start time must be before end time"); 125 | 126 | // Reset the state variables for a new auction to begin 127 | _claimed = false; 128 | currentPrice = newStartingPrice; 129 | minPriceDifference = newMinPriceDifference; 130 | currentAuctionWinner = address(0); 131 | startTime = newStartTime; 132 | endTime = newEndTime; 133 | tokenId = newTokenId; 134 | tokenAmount = newTokenAmount; 135 | 136 | emit AuctionStarted(startTime, endTime); 137 | } 138 | 139 | /** 140 | * @notice Withdraw the olds bids amount of the sender 141 | */ 142 | function withdrawOldBids() public { 143 | require(oldBidsAmount[_msgSender()] > 0, "No old bids to withdraw"); 144 | 145 | uint256 amount = oldBidsAmount[_msgSender()]; 146 | oldBidsAmount[_msgSender()] = 0; 147 | 148 | (bool success, ) = payable(_msgSender()).call{value: amount}(""); 149 | require(success, "Failed to withdraw"); 150 | } 151 | 152 | /** 153 | * @notice Withdraw funds from the smart contract to the feeReceiver 154 | */ 155 | function withdraw() public { 156 | if (currentAuctionWinner != address(0) && !_claimed) { 157 | payable(_feeReceiver).transfer(address(this).balance - currentPrice); 158 | } else { 159 | payable(_feeReceiver).transfer(address(this).balance); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /flatten/tools/StartonMultiSendERC1155.sol: -------------------------------------------------------------------------------- 1 | // Sources flattened with hardhat v2.10.1 https://hardhat.org 2 | 3 | // File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.9.2 4 | 5 | // SPDX-License-Identifier: MIT 6 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) 7 | 8 | pragma solidity ^0.8.0; 9 | 10 | /** 11 | * @dev Interface of the ERC165 standard, as defined in the 12 | * https://eips.ethereum.org/EIPS/eip-165[EIP]. 13 | * 14 | * Implementers can declare support of contract interfaces, which can then be 15 | * queried by others ({ERC165Checker}). 16 | * 17 | * For an implementation, see {ERC165}. 18 | */ 19 | interface IERC165 { 20 | /** 21 | * @dev Returns true if this contract implements the interface defined by 22 | * `interfaceId`. See the corresponding 23 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] 24 | * to learn more about how these ids are created. 25 | * 26 | * This function call must use less than 30 000 gas. 27 | */ 28 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 29 | } 30 | 31 | 32 | // File @openzeppelin/contracts/token/ERC1155/IERC1155.sol@v4.9.2 33 | 34 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol) 35 | 36 | pragma solidity ^0.8.0; 37 | 38 | /** 39 | * @dev Required interface of an ERC1155 compliant contract, as defined in the 40 | * https://eips.ethereum.org/EIPS/eip-1155[EIP]. 41 | * 42 | * _Available since v3.1._ 43 | */ 44 | interface IERC1155 is IERC165 { 45 | /** 46 | * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. 47 | */ 48 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); 49 | 50 | /** 51 | * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all 52 | * transfers. 53 | */ 54 | event TransferBatch( 55 | address indexed operator, 56 | address indexed from, 57 | address indexed to, 58 | uint256[] ids, 59 | uint256[] values 60 | ); 61 | 62 | /** 63 | * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to 64 | * `approved`. 65 | */ 66 | event ApprovalForAll(address indexed account, address indexed operator, bool approved); 67 | 68 | /** 69 | * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. 70 | * 71 | * If an {URI} event was emitted for `id`, the standard 72 | * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value 73 | * returned by {IERC1155MetadataURI-uri}. 74 | */ 75 | event URI(string value, uint256 indexed id); 76 | 77 | /** 78 | * @dev Returns the amount of tokens of token type `id` owned by `account`. 79 | * 80 | * Requirements: 81 | * 82 | * - `account` cannot be the zero address. 83 | */ 84 | function balanceOf(address account, uint256 id) external view returns (uint256); 85 | 86 | /** 87 | * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. 88 | * 89 | * Requirements: 90 | * 91 | * - `accounts` and `ids` must have the same length. 92 | */ 93 | function balanceOfBatch( 94 | address[] calldata accounts, 95 | uint256[] calldata ids 96 | ) external view returns (uint256[] memory); 97 | 98 | /** 99 | * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, 100 | * 101 | * Emits an {ApprovalForAll} event. 102 | * 103 | * Requirements: 104 | * 105 | * - `operator` cannot be the caller. 106 | */ 107 | function setApprovalForAll(address operator, bool approved) external; 108 | 109 | /** 110 | * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. 111 | * 112 | * See {setApprovalForAll}. 113 | */ 114 | function isApprovedForAll(address account, address operator) external view returns (bool); 115 | 116 | /** 117 | * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. 118 | * 119 | * Emits a {TransferSingle} event. 120 | * 121 | * Requirements: 122 | * 123 | * - `to` cannot be the zero address. 124 | * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. 125 | * - `from` must have a balance of tokens of type `id` of at least `amount`. 126 | * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the 127 | * acceptance magic value. 128 | */ 129 | function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; 130 | 131 | /** 132 | * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. 133 | * 134 | * Emits a {TransferBatch} event. 135 | * 136 | * Requirements: 137 | * 138 | * - `ids` and `amounts` must have the same length. 139 | * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the 140 | * acceptance magic value. 141 | */ 142 | function safeBatchTransferFrom( 143 | address from, 144 | address to, 145 | uint256[] calldata ids, 146 | uint256[] calldata amounts, 147 | bytes calldata data 148 | ) external; 149 | } 150 | 151 | 152 | // File contracts/tools/StartonMultiSendERC1155.sol 153 | 154 | 155 | pragma solidity 0.8.17; 156 | 157 | /// @title StartonMultiSendERC11555 158 | /// @author Starton 159 | /// @notice This contract allows to send multiple ERC1155 tokens in a single transaction 160 | contract StartonMultiSendERC1155 { 161 | /** 162 | * @notice Send multiple ERC1155 tokens in a single transaction 163 | * @param token The address of the ERC1155 token 164 | * @param ids The ids of tokens to send 165 | * @param amounts The amounts of tokens to send 166 | * @param addresses The addresses to send the tokens to 167 | */ 168 | function multiSend( 169 | address token, 170 | uint256[] calldata ids, 171 | uint256[] calldata amounts, 172 | address[] calldata addresses 173 | ) external { 174 | require(ids.length == addresses.length, "Arrays must be of equal length"); 175 | require(addresses.length == amounts.length, "Arrays must be of equal length"); 176 | 177 | uint256 length = ids.length; 178 | for (uint256 i = 0; i < length; ) { 179 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 180 | IERC1155(token).safeTransferFrom(msg.sender, addresses[i], ids[i], amounts[i], ""); 181 | unchecked { 182 | i += 1; 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /flatten/tools/StartonMultiSendERC721.sol: -------------------------------------------------------------------------------- 1 | // Sources flattened with hardhat v2.10.1 https://hardhat.org 2 | 3 | // File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.9.2 4 | 5 | // SPDX-License-Identifier: MIT 6 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) 7 | 8 | pragma solidity ^0.8.0; 9 | 10 | /** 11 | * @dev Interface of the ERC165 standard, as defined in the 12 | * https://eips.ethereum.org/EIPS/eip-165[EIP]. 13 | * 14 | * Implementers can declare support of contract interfaces, which can then be 15 | * queried by others ({ERC165Checker}). 16 | * 17 | * For an implementation, see {ERC165}. 18 | */ 19 | interface IERC165 { 20 | /** 21 | * @dev Returns true if this contract implements the interface defined by 22 | * `interfaceId`. See the corresponding 23 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] 24 | * to learn more about how these ids are created. 25 | * 26 | * This function call must use less than 30 000 gas. 27 | */ 28 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 29 | } 30 | 31 | 32 | // File @openzeppelin/contracts/token/ERC721/IERC721.sol@v4.9.2 33 | 34 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol) 35 | 36 | pragma solidity ^0.8.0; 37 | 38 | /** 39 | * @dev Required interface of an ERC721 compliant contract. 40 | */ 41 | interface IERC721 is IERC165 { 42 | /** 43 | * @dev Emitted when `tokenId` token is transferred from `from` to `to`. 44 | */ 45 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 46 | 47 | /** 48 | * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. 49 | */ 50 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 51 | 52 | /** 53 | * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. 54 | */ 55 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 56 | 57 | /** 58 | * @dev Returns the number of tokens in ``owner``'s account. 59 | */ 60 | function balanceOf(address owner) external view returns (uint256 balance); 61 | 62 | /** 63 | * @dev Returns the owner of the `tokenId` token. 64 | * 65 | * Requirements: 66 | * 67 | * - `tokenId` must exist. 68 | */ 69 | function ownerOf(uint256 tokenId) external view returns (address owner); 70 | 71 | /** 72 | * @dev Safely transfers `tokenId` token from `from` to `to`. 73 | * 74 | * Requirements: 75 | * 76 | * - `from` cannot be the zero address. 77 | * - `to` cannot be the zero address. 78 | * - `tokenId` token must exist and be owned by `from`. 79 | * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. 80 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 81 | * 82 | * Emits a {Transfer} event. 83 | */ 84 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 85 | 86 | /** 87 | * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 88 | * are aware of the ERC721 protocol to prevent tokens from being forever locked. 89 | * 90 | * Requirements: 91 | * 92 | * - `from` cannot be the zero address. 93 | * - `to` cannot be the zero address. 94 | * - `tokenId` token must exist and be owned by `from`. 95 | * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. 96 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 97 | * 98 | * Emits a {Transfer} event. 99 | */ 100 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 101 | 102 | /** 103 | * @dev Transfers `tokenId` token from `from` to `to`. 104 | * 105 | * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 106 | * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must 107 | * understand this adds an external call which potentially creates a reentrancy vulnerability. 108 | * 109 | * Requirements: 110 | * 111 | * - `from` cannot be the zero address. 112 | * - `to` cannot be the zero address. 113 | * - `tokenId` token must be owned by `from`. 114 | * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. 115 | * 116 | * Emits a {Transfer} event. 117 | */ 118 | function transferFrom(address from, address to, uint256 tokenId) external; 119 | 120 | /** 121 | * @dev Gives permission to `to` to transfer `tokenId` token to another account. 122 | * The approval is cleared when the token is transferred. 123 | * 124 | * Only a single account can be approved at a time, so approving the zero address clears previous approvals. 125 | * 126 | * Requirements: 127 | * 128 | * - The caller must own the token or be an approved operator. 129 | * - `tokenId` must exist. 130 | * 131 | * Emits an {Approval} event. 132 | */ 133 | function approve(address to, uint256 tokenId) external; 134 | 135 | /** 136 | * @dev Approve or remove `operator` as an operator for the caller. 137 | * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. 138 | * 139 | * Requirements: 140 | * 141 | * - The `operator` cannot be the caller. 142 | * 143 | * Emits an {ApprovalForAll} event. 144 | */ 145 | function setApprovalForAll(address operator, bool approved) external; 146 | 147 | /** 148 | * @dev Returns the account approved for `tokenId` token. 149 | * 150 | * Requirements: 151 | * 152 | * - `tokenId` must exist. 153 | */ 154 | function getApproved(uint256 tokenId) external view returns (address operator); 155 | 156 | /** 157 | * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. 158 | * 159 | * See {setApprovalForAll} 160 | */ 161 | function isApprovedForAll(address owner, address operator) external view returns (bool); 162 | } 163 | 164 | 165 | // File contracts/tools/StartonMultiSendERC721.sol 166 | 167 | 168 | pragma solidity 0.8.17; 169 | 170 | /// @title StartonMultiSendERC721 171 | /// @author Starton 172 | /// @notice This contract allows to send multiple ERC721 tokens in a single transaction 173 | contract StartonMultiSendERC721 { 174 | /** 175 | * @notice Send multiple ERC721 tokens in a single transaction 176 | * @param token The address of the ERC721 token 177 | * @param ids The ids of tokens to send 178 | * @param addresses The addresses to send the tokens to 179 | */ 180 | function multiSend( 181 | address token, 182 | uint256[] calldata ids, 183 | address[] calldata addresses 184 | ) external { 185 | require(ids.length == addresses.length, "Arrays must be of equal length"); 186 | 187 | uint256 length = ids.length; 188 | for (uint256 i = 0; i < length; ) { 189 | // Don't check if the transfer is successfull because we still wants to continue to send the other tokens 190 | IERC721(token).transferFrom(msg.sender, addresses[i], ids[i]); 191 | unchecked { 192 | i += 1; 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC1155Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "operator-filter-registry/src/DefaultOperatorFilterer.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; 7 | import "@openzeppelin/contracts/token/common/ERC2981.sol"; 8 | import "../abstracts/AStartonNativeMetaTransaction.sol"; 9 | import "../abstracts/AStartonContextMixin.sol"; 10 | import "../abstracts/AStartonOwnable.sol"; 11 | import "../abstracts/AStartonPausable.sol"; 12 | import "../abstracts/AStartonMintLock.sol"; 13 | import "../abstracts/AStartonMetadataLock.sol"; 14 | 15 | /// @title StartonERC1155Base 16 | /// @author Starton 17 | /// @notice ERC1155 tokens that can be blacklisted, paused, locked, burned, have a access management and handle meta transactions 18 | contract StartonERC1155Base is 19 | ERC1155Burnable, 20 | AStartonOwnable, 21 | AStartonPausable, 22 | AStartonContextMixin, 23 | AStartonNativeMetaTransaction, 24 | AStartonMintLock, 25 | AStartonMetadataLock, 26 | DefaultOperatorFilterer 27 | { 28 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 29 | bytes32 public constant METADATA_ROLE = keccak256("METADATA_ROLE"); 30 | 31 | string public name; 32 | 33 | string private _contractURI; 34 | 35 | constructor( 36 | string memory definitiveName, 37 | string memory initialTokenURI, 38 | string memory initialContractURI, 39 | address initialOwnerOrMultiSigContract 40 | ) ERC1155(initialTokenURI) AStartonOwnable(initialOwnerOrMultiSigContract) { 41 | // Set all default roles for initialOwnerOrMultiSigContract 42 | _setupRole(PAUSER_ROLE, initialOwnerOrMultiSigContract); 43 | _setupRole(MINTER_ROLE, initialOwnerOrMultiSigContract); 44 | _setupRole(METADATA_ROLE, initialOwnerOrMultiSigContract); 45 | _setupRole(LOCKER_ROLE, initialOwnerOrMultiSigContract); 46 | 47 | name = definitiveName; 48 | _contractURI = initialContractURI; 49 | _isMintAllowed = true; 50 | _isMetadataChangingAllowed = true; 51 | 52 | // Intialize the EIP712 so we can perform metatransactions 53 | _initializeEIP712(definitiveName); 54 | } 55 | 56 | /** 57 | * @notice Mint a new amount of tokens to a given address and by the given id if the minting is not locked and the contract is not paused 58 | * @param to The address to mint the tokens to 59 | * @param id The id of the token to mint 60 | * @param amount The amount of tokens to mint 61 | * @param data Extra data if necessary 62 | * @custom:requires MINTER_ROLE 63 | */ 64 | function mint( 65 | address to, 66 | uint256 id, 67 | uint256 amount, 68 | bytes memory data 69 | ) public virtual whenNotPaused mintingNotLocked onlyRole(MINTER_ROLE) { 70 | _mint(to, id, amount, data); 71 | } 72 | 73 | /** 74 | * @notice Mint a new amount of tokens to a given address and by the given id if the minting is not locked and the contract is not paused 75 | * @param to The address to mint the tokens to 76 | * @param id The id of the token to mint 77 | * @param amount The amount of tokens to mint 78 | * @custom:requires MINTER_ROLE 79 | */ 80 | function mint( 81 | address to, 82 | uint256 id, 83 | uint256 amount 84 | ) public virtual { 85 | mint(to, id, amount, ""); 86 | } 87 | 88 | /** 89 | * @notice Batch mint a new amount of tokens to a given address and by the given id if the minting is not locked and the contract is not paused 90 | * @param to The address to mint the tokens to 91 | * @param ids The ids of the token to mint 92 | * @param amounts The amounts of tokens to mint 93 | * @param data Extra data if necessary 94 | * @custom:requires MINTER_ROLE 95 | */ 96 | function mintBatch( 97 | address to, 98 | uint256[] memory ids, 99 | uint256[] memory amounts, 100 | bytes memory data 101 | ) public virtual whenNotPaused mintingNotLocked onlyRole(MINTER_ROLE) { 102 | _mintBatch(to, ids, amounts, data); 103 | } 104 | 105 | /** 106 | * @notice Batch mint a new amount of tokens to a given address and by the given id if the minting is not locked and the contract is not paused 107 | * @param to The address to mint the tokens to 108 | * @param ids The ids of the token to mint 109 | * @param amounts The amounts of tokens to mint 110 | * @custom:requires MINTER_ROLE 111 | */ 112 | function mintBatch( 113 | address to, 114 | uint256[] memory ids, 115 | uint256[] memory amounts 116 | ) public virtual { 117 | mintBatch(to, ids, amounts, ""); 118 | } 119 | 120 | /** 121 | * @notice Set the URI if the token if the metadata are not locked and the contract is not paused 122 | * @param newTokenURI The new URI of the token 123 | * For ERC1155 there isn't any base uri so it's the whole uri with {id} in it 124 | * example: ipfs://QmW77ZQQ7Jm9q8WuLbH8YZg2K7T9Qnjbzm7jYVQQrJY5Y/{id} 125 | * @custom:requires METADATA_ROLE 126 | */ 127 | function setTokenURI(string memory newTokenURI) 128 | public 129 | virtual 130 | whenNotPaused 131 | metadataNotLocked 132 | onlyRole(METADATA_ROLE) 133 | { 134 | _setURI(newTokenURI); 135 | } 136 | 137 | /** 138 | * @notice Set the URI of the contract if the metadata are not locked and the contract is not paused 139 | * @param newContractURI The new URI of the contract 140 | * @custom:requires METADATA_ROLE 141 | */ 142 | function setContractURI(string memory newContractURI) 143 | public 144 | virtual 145 | whenNotPaused 146 | metadataNotLocked 147 | onlyRole(METADATA_ROLE) 148 | { 149 | _contractURI = newContractURI; 150 | } 151 | 152 | /** 153 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 154 | * @return True if the interface is supported 155 | */ 156 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, AccessControl) returns (bool) { 157 | return super.supportsInterface(interfaceId); 158 | } 159 | 160 | /** 161 | * @notice Returns the metadata of the contract 162 | * @return Contract URI of the token 163 | */ 164 | function contractURI() public view virtual returns (string memory) { 165 | return _contractURI; 166 | } 167 | 168 | /** 169 | * @dev Stop approval of token if the contract is paused or the sender is blacklisted 170 | * @param owner The owner of the token 171 | * @param operator The operator of the token 172 | * @param approved Approve or not the approval of the token 173 | */ 174 | function _setApprovalForAll( 175 | address owner, 176 | address operator, 177 | bool approved 178 | ) internal virtual override whenNotPaused { 179 | super._setApprovalForAll(owner, operator, approved); 180 | } 181 | 182 | /** 183 | * @dev Stop transfer if the contract is paused or the sender is blacklisted 184 | * @param operator The address that will send the token 185 | * @param from The address that will send the token 186 | * @param to The address that will receive the token 187 | * @param ids The ID of the token to be transferred 188 | * @param amounts The address that will send the token 189 | * @param data The address that will send the token 190 | */ 191 | function _beforeTokenTransfer( 192 | address operator, 193 | address from, 194 | address to, 195 | uint256[] memory ids, 196 | uint256[] memory amounts, 197 | bytes memory data 198 | ) internal virtual override whenNotPaused { 199 | super._beforeTokenTransfer(operator, from, to, ids, amounts, data); 200 | } 201 | 202 | /** 203 | * @dev Specify the _msgSender in case the forwarder calls a function to the real sender 204 | * @return The sender of the message 205 | */ 206 | function _msgSender() internal view virtual override(Context, AStartonContextMixin) returns (address) { 207 | return super._msgSender(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /contracts/non-fungible/StartonERC721Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "operator-filter-registry/src/DefaultOperatorFilterer.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | import "../abstracts/AStartonNativeMetaTransaction.sol"; 11 | import "../abstracts/AStartonContextMixin.sol"; 12 | import "../abstracts/AStartonOwnable.sol"; 13 | import "../abstracts/AStartonPausable.sol"; 14 | import "../abstracts/AStartonMintLock.sol"; 15 | import "../abstracts/AStartonMetadataLock.sol"; 16 | 17 | /// @title StartonERC721Base 18 | /// @author Starton 19 | /// @notice ERC721 tokens that can be blacklisted, paused, locked, burned, have a access management and handle meta transactions 20 | contract StartonERC721Base is 21 | ERC721Enumerable, 22 | ERC721URIStorage, 23 | ERC721Burnable, 24 | AStartonPausable, 25 | AStartonOwnable, 26 | AStartonContextMixin, 27 | AStartonNativeMetaTransaction, 28 | AStartonMintLock, 29 | AStartonMetadataLock, 30 | DefaultOperatorFilterer 31 | { 32 | using Counters for Counters.Counter; 33 | 34 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 35 | bytes32 public constant METADATA_ROLE = keccak256("METADATA_ROLE"); 36 | 37 | Counters.Counter internal _tokenIdCounter; 38 | 39 | string private _baseTokenURI; 40 | string private _contractURI; 41 | 42 | constructor( 43 | string memory definitiveName, 44 | string memory definitiveSymbol, 45 | string memory initialBaseTokenURI, 46 | string memory initialContractURI, 47 | address initialOwnerOrMultiSigContract 48 | ) ERC721(definitiveName, definitiveSymbol) AStartonOwnable(initialOwnerOrMultiSigContract) { 49 | // Set all default roles for initialOwnerOrMultiSigContract 50 | _setupRole(PAUSER_ROLE, initialOwnerOrMultiSigContract); 51 | _setupRole(MINTER_ROLE, initialOwnerOrMultiSigContract); 52 | _setupRole(METADATA_ROLE, initialOwnerOrMultiSigContract); 53 | _setupRole(LOCKER_ROLE, initialOwnerOrMultiSigContract); 54 | 55 | _baseTokenURI = initialBaseTokenURI; 56 | _contractURI = initialContractURI; 57 | _isMintAllowed = true; 58 | _isMetadataChangingAllowed = true; 59 | 60 | // Intialize the EIP712 so we can perform metatransactions 61 | _initializeEIP712(definitiveName); 62 | } 63 | 64 | /** 65 | * @notice Mint a new token to the given address and set the token metadata while minting is not locked 66 | * @param to The address that will receive the token 67 | * @param uri The URI of the token metadata 68 | * @custom:requires MINTER_ROLE 69 | */ 70 | function mint(address to, string memory uri) public virtual whenNotPaused mintingNotLocked onlyRole(MINTER_ROLE) { 71 | _mint(to, _tokenIdCounter.current()); 72 | _setTokenURI(_tokenIdCounter.current(), uri); 73 | _tokenIdCounter.increment(); 74 | } 75 | 76 | /** 77 | * @notice Set the URI of the contract if the metadata are not locked and the contract is not paused 78 | * @param newContractURI The new URI of the contract 79 | * @custom:requires METADATA_ROLE 80 | */ 81 | function setContractURI(string memory newContractURI) 82 | public 83 | virtual 84 | whenNotPaused 85 | metadataNotLocked 86 | onlyRole(METADATA_ROLE) 87 | { 88 | _contractURI = newContractURI; 89 | } 90 | 91 | /** 92 | * @notice Set the base URI of the token if the metadata are not locked and the contract is not paused 93 | * @param newBaseTokenURI The new base URI of the token 94 | * @custom:requires METADATA_ROLE 95 | */ 96 | function setBaseTokenURI(string memory newBaseTokenURI) 97 | public 98 | virtual 99 | whenNotPaused 100 | metadataNotLocked 101 | onlyRole(METADATA_ROLE) 102 | { 103 | _baseTokenURI = newBaseTokenURI; 104 | } 105 | 106 | /** 107 | * @notice Returns the metadata of the contract 108 | * @return Contract URI of the token 109 | */ 110 | function contractURI() public view virtual returns (string memory) { 111 | return _contractURI; 112 | } 113 | 114 | /** 115 | * @notice Returns the metadata of token with the given token id 116 | * @param tokenId The token id of the token 117 | * @return Contract URI of the token 118 | */ 119 | function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) { 120 | return super.tokenURI(tokenId); 121 | } 122 | 123 | /** 124 | * @dev Call the inherited contract supportsInterface function to know the interfaces as EIP165 says 125 | * @return True if the interface is supported 126 | */ 127 | function supportsInterface(bytes4 interfaceId) 128 | public 129 | view 130 | virtual 131 | override(ERC721, AccessControl, ERC721Enumerable, ERC721URIStorage) 132 | returns (bool) 133 | { 134 | return super.supportsInterface(interfaceId); 135 | } 136 | 137 | /** 138 | * @dev Stop approval of token if the contract is paused or the sender is blacklisted 139 | * @param owner The owner of the token 140 | * @param operator The operator of the token 141 | * @param approved Approve or not the approval of the token 142 | */ 143 | function _setApprovalForAll( 144 | address owner, 145 | address operator, 146 | bool approved 147 | ) internal virtual override whenNotPaused onlyAllowedOperatorApproval(operator) { 148 | super._setApprovalForAll(owner, operator, approved); 149 | } 150 | 151 | /** 152 | * @dev Stop transfer if the contract is paused or the sender is blacklisted 153 | * @param from The address that will send the token 154 | * @param to The address that will receive the token 155 | * @param tokenId The ID of the token to be transferred 156 | * @param batchSize The number of tokens to be transferred 157 | */ 158 | function _beforeTokenTransfer( 159 | address from, 160 | address to, 161 | uint256 tokenId, 162 | uint256 batchSize 163 | ) internal virtual override(ERC721, ERC721Enumerable) whenNotPaused { 164 | super._beforeTokenTransfer(from, to, tokenId, batchSize); 165 | } 166 | 167 | /** 168 | * @dev Fix the inheritence problem for the _burn between ERC721 and ERC721URIStorage 169 | * @param tokenId Id of the token that will be burnt 170 | */ 171 | function _burn(uint256 tokenId) internal virtual override(ERC721, ERC721URIStorage) { 172 | super._burn(tokenId); 173 | } 174 | 175 | /** 176 | * @notice Returns the first part of the uri being used for the token metadata 177 | * @return Base URI of the token 178 | */ 179 | function _baseURI() internal view virtual override returns (string memory) { 180 | return _baseTokenURI; 181 | } 182 | 183 | /** 184 | * @dev Specify the _msgSender in case the forwarder calls a function to the real sender 185 | * @return The sender of the message 186 | */ 187 | function _msgSender() internal view virtual override(Context, AStartonContextMixin) returns (address) { 188 | return super._msgSender(); 189 | } 190 | 191 | function approve(address operator, uint256 tokenId) 192 | public 193 | virtual 194 | override(IERC721, ERC721) 195 | onlyAllowedOperatorApproval(operator) 196 | { 197 | super.approve(operator, tokenId); 198 | } 199 | 200 | function transferFrom( 201 | address from, 202 | address to, 203 | uint256 tokenId 204 | ) public virtual override(IERC721, ERC721) onlyAllowedOperator(from) { 205 | super.transferFrom(from, to, tokenId); 206 | } 207 | 208 | function safeTransferFrom( 209 | address from, 210 | address to, 211 | uint256 tokenId 212 | ) public virtual override(IERC721, ERC721) onlyAllowedOperator(from) { 213 | super.safeTransferFrom(from, to, tokenId); 214 | } 215 | 216 | function safeTransferFrom( 217 | address from, 218 | address to, 219 | uint256 tokenId, 220 | bytes memory data 221 | ) public virtual override(IERC721, ERC721) onlyAllowedOperator(from) { 222 | super.safeTransferFrom(from, to, tokenId, data); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC721Capped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | 11 | contract ChildStartonERC721Capped is ERC721Enumerable, ERC721URIStorage, Pausable, AccessControl, ERC721Burnable { 12 | using Counters for Counters.Counter; 13 | 14 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 15 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 16 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 17 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 18 | 19 | Counters.Counter private _tokenIdCounter; 20 | string private _uri; 21 | uint256 private _maxSupply; 22 | 23 | bool private _isMintAllowed; 24 | 25 | mapping (uint256 => bool) public withdrawnTokens; 26 | 27 | // limit batching of tokens due to gas limit restrictions 28 | uint256 public constant BATCH_LIMIT = 20; 29 | 30 | event WithdrawnBatch(address indexed user, uint256[] tokenIds); 31 | event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); 32 | 33 | constructor(string memory name, string memory symbol, string memory baseUri, uint256 tokenMaxSupply, address ownerOrMultiSigContract) ERC721(name, symbol) { 34 | require(tokenMaxSupply > 0, "maxSupply: must be > 0"); 35 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 36 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 37 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 38 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 39 | 40 | _uri = baseUri; 41 | _maxSupply = tokenMaxSupply; 42 | _isMintAllowed = true; 43 | } 44 | 45 | function _baseURI() internal view override returns (string memory) { 46 | return _uri; 47 | } 48 | 49 | function maxSupply() public view returns (uint256) { 50 | return _maxSupply; 51 | } 52 | 53 | function lockMint() public { 54 | require(hasRole(LOCKER_ROLE, msg.sender)); 55 | _isMintAllowed = false; 56 | } 57 | 58 | function safeMint(address to) public { 59 | require(hasRole(MINTER_ROLE, msg.sender)); 60 | require(_isMintAllowed); 61 | require(_tokenIdCounter.current() < _maxSupply, "maxSupply: reached"); 62 | _safeMint(to, _tokenIdCounter.current()); 63 | _tokenIdCounter.increment(); 64 | } 65 | 66 | function pause() public { 67 | require(hasRole(PAUSER_ROLE, msg.sender)); 68 | _pause(); 69 | } 70 | 71 | function unpause() public { 72 | require(hasRole(PAUSER_ROLE, msg.sender)); 73 | _unpause(); 74 | } 75 | 76 | function _beforeTokenTransfer(address from, address to, uint256 tokenId) 77 | internal 78 | whenNotPaused 79 | override(ERC721, ERC721Enumerable) 80 | { 81 | super._beforeTokenTransfer(from, to, tokenId); 82 | } 83 | 84 | function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { 85 | super._burn(tokenId); 86 | } 87 | 88 | function tokenURI(uint256 tokenId) 89 | public 90 | view 91 | override(ERC721, ERC721URIStorage) 92 | returns (string memory) 93 | { 94 | return super.tokenURI(tokenId); 95 | } 96 | 97 | function supportsInterface(bytes4 interfaceId) 98 | public 99 | view 100 | override(ERC721, ERC721Enumerable, AccessControl) 101 | returns (bool) 102 | { 103 | return super.supportsInterface(interfaceId); 104 | } 105 | 106 | /** 107 | * @notice called when token is deposited on root chain 108 | * @dev Should be callable only by ChildChainManager 109 | * Should handle deposit by minting the required tokenId(s) for user 110 | * Should set `withdrawnTokens` mapping to `false` for the tokenId being deposited 111 | * Minting can also be done by other functions 112 | * @param user user address for whom deposit is being done 113 | * @param depositData abi encoded tokenIds. Batch deposit also supported. 114 | */ 115 | function deposit(address user, bytes calldata depositData) 116 | external 117 | { 118 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 119 | 120 | // deposit single 121 | if (depositData.length == 32) { 122 | uint256 tokenId = abi.decode(depositData, (uint256)); 123 | withdrawnTokens[tokenId] = false; 124 | _mint(user, tokenId); 125 | 126 | // deposit batch 127 | } else { 128 | uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); 129 | uint256 length = tokenIds.length; 130 | for (uint256 i; i < length; i++) { 131 | withdrawnTokens[tokenIds[i]] = false; 132 | _mint(user, tokenIds[i]); 133 | } 134 | } 135 | 136 | } 137 | 138 | /** 139 | * @notice called when user wants to withdraw token back to root chain 140 | * @dev Should handle withraw by burning user's token. 141 | * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn 142 | * This transaction will be verified when exiting on root chain 143 | * @param tokenId tokenId to withdraw 144 | */ 145 | function withdraw(uint256 tokenId) external { 146 | require(_msgSender() == ownerOf(tokenId), "ChildMintableERC721: INVALID_TOKEN_OWNER"); 147 | withdrawnTokens[tokenId] = true; 148 | _burn(tokenId); 149 | } 150 | 151 | /** 152 | * @notice called when user wants to withdraw multiple tokens back to root chain 153 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 154 | * @param tokenIds tokenId list to withdraw 155 | */ 156 | function withdrawBatch(uint256[] calldata tokenIds) external { 157 | 158 | uint256 length = tokenIds.length; 159 | require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); 160 | 161 | // Iteratively burn ERC721 tokens, for performing 162 | // batch withdraw 163 | for (uint256 i; i < length; i++) { 164 | 165 | uint256 tokenId = tokenIds[i]; 166 | 167 | require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); 168 | withdrawnTokens[tokenId] = true; 169 | _burn(tokenId); 170 | 171 | } 172 | 173 | // At last emit this event, which will be used 174 | // in MintableERC721 predicate contract on L1 175 | // while verifying burn proof 176 | emit WithdrawnBatch(_msgSender(), tokenIds); 177 | 178 | } 179 | 180 | /** 181 | * @notice called when user wants to withdraw token back to root chain with token URI 182 | * @dev Should handle withraw by burning user's token. 183 | * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn 184 | * This transaction will be verified when exiting on root chain 185 | * 186 | * @param tokenId tokenId to withdraw 187 | */ 188 | function withdrawWithMetadata(uint256 tokenId) external { 189 | 190 | require(_msgSender() == ownerOf(tokenId), "ChildMintableERC721: INVALID_TOKEN_OWNER"); 191 | withdrawnTokens[tokenId] = true; 192 | 193 | // Encoding metadata associated with tokenId & emitting event 194 | emit TransferWithMetadata(ownerOf(tokenId), address(0), tokenId, this.encodeTokenMetadata(tokenId)); 195 | 196 | _burn(tokenId); 197 | 198 | } 199 | 200 | /** 201 | * @notice This method is supposed to be called by client when withdrawing token with metadata 202 | * and pass return value of this function as second paramter of `withdrawWithMetadata` method 203 | * 204 | * It can be overridden by clients to encode data in a different form, which needs to 205 | * be decoded back by them correctly during exiting 206 | * 207 | * @param tokenId Token for which URI to be fetched 208 | */ 209 | function encodeTokenMetadata(uint256 tokenId) external view virtual returns (bytes memory) { 210 | 211 | // You're always free to change this default implementation 212 | // and pack more data in byte array which can be decoded back 213 | // in L1 214 | return abi.encode(tokenURI(tokenId)); 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /contracts/deprecated/polygon/ChildStartonERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | 11 | contract ChildStartonERC721 is ERC721Enumerable, ERC721URIStorage, Pausable, AccessControl, ERC721Burnable { 12 | using Counters for Counters.Counter; 13 | 14 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 15 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 16 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 17 | bytes32 public constant LOCKER_ROLE = keccak256("LOCKER_ROLE"); 18 | 19 | Counters.Counter private _tokenIdCounter; 20 | string private _uri; 21 | string private _contractUriSuffix; 22 | string private _baseContractUri; 23 | 24 | bool private _isMintAllowed; 25 | 26 | mapping (uint256 => bool) public withdrawnTokens; 27 | 28 | // limit batching of tokens due to gas limit restrictions 29 | uint256 public constant BATCH_LIMIT = 20; 30 | 31 | event WithdrawnBatch(address indexed user, uint256[] tokenIds); 32 | event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); 33 | 34 | constructor(string memory name, string memory symbol, string memory baseUri, string memory contractUriSuffix, address ownerOrMultiSigContract) ERC721(name, symbol) { 35 | _setupRole(DEFAULT_ADMIN_ROLE, ownerOrMultiSigContract); 36 | _setupRole(PAUSER_ROLE, ownerOrMultiSigContract); 37 | _setupRole(MINTER_ROLE, ownerOrMultiSigContract); 38 | _setupRole(LOCKER_ROLE, ownerOrMultiSigContract); 39 | 40 | _uri = baseUri; 41 | _contractUriSuffix = contractUriSuffix; 42 | _baseContractUri = "https://ipfs.io/ipfs/"; 43 | _isMintAllowed = true; 44 | } 45 | 46 | function contractURI() public view returns (string memory) { 47 | return bytes(_baseContractUri).length > 0 48 | ? string(abi.encodePacked(_baseContractUri, _contractUriSuffix)) 49 | : ''; 50 | } 51 | 52 | function setBaseContractURI(string memory newBaseContractUri) public { 53 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender)); 54 | 55 | _baseContractUri = newBaseContractUri; 56 | } 57 | 58 | function _baseURI() internal view override returns (string memory) { 59 | return _uri; 60 | } 61 | 62 | function lockMint() public { 63 | require(hasRole(LOCKER_ROLE, msg.sender)); 64 | _isMintAllowed = false; 65 | } 66 | 67 | function safeMint(address to, string memory metadataURI) public { 68 | require(hasRole(MINTER_ROLE, msg.sender)); 69 | require(_isMintAllowed); 70 | 71 | _safeMint(to, _tokenIdCounter.current()); 72 | _setTokenURI(_tokenIdCounter.current(), metadataURI); 73 | _tokenIdCounter.increment(); 74 | } 75 | 76 | function pause() public { 77 | require(hasRole(PAUSER_ROLE, msg.sender)); 78 | _pause(); 79 | } 80 | 81 | function unpause() public { 82 | require(hasRole(PAUSER_ROLE, msg.sender)); 83 | _unpause(); 84 | } 85 | 86 | function _beforeTokenTransfer(address from, address to, uint256 tokenId) 87 | internal 88 | whenNotPaused 89 | override(ERC721, ERC721Enumerable) 90 | { 91 | super._beforeTokenTransfer(from, to, tokenId); 92 | } 93 | 94 | function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { 95 | super._burn(tokenId); 96 | } 97 | 98 | function tokenURI(uint256 tokenId) 99 | public 100 | view 101 | override(ERC721, ERC721URIStorage) 102 | returns (string memory) 103 | { 104 | return super.tokenURI(tokenId); 105 | } 106 | 107 | function supportsInterface(bytes4 interfaceId) 108 | public 109 | view 110 | override(ERC721, ERC721Enumerable, AccessControl) 111 | returns (bool) 112 | { 113 | return super.supportsInterface(interfaceId); 114 | } 115 | 116 | /** 117 | * @notice called when token is deposited on root chain 118 | * @dev Should be callable only by ChildChainManager 119 | * Should handle deposit by minting the required tokenId(s) for user 120 | * Should set `withdrawnTokens` mapping to `false` for the tokenId being deposited 121 | * Minting can also be done by other functions 122 | * @param user user address for whom deposit is being done 123 | * @param depositData abi encoded tokenIds. Batch deposit also supported. 124 | */ 125 | function deposit(address user, bytes calldata depositData) 126 | external 127 | { 128 | require(hasRole(DEPOSITOR_ROLE, msg.sender)); 129 | 130 | // deposit single 131 | if (depositData.length == 32) { 132 | uint256 tokenId = abi.decode(depositData, (uint256)); 133 | withdrawnTokens[tokenId] = false; 134 | _mint(user, tokenId); 135 | 136 | // deposit batch 137 | } else { 138 | uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); 139 | uint256 length = tokenIds.length; 140 | for (uint256 i; i < length; i++) { 141 | withdrawnTokens[tokenIds[i]] = false; 142 | _mint(user, tokenIds[i]); 143 | } 144 | } 145 | 146 | } 147 | 148 | /** 149 | * @notice called when user wants to withdraw token back to root chain 150 | * @dev Should handle withraw by burning user's token. 151 | * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn 152 | * This transaction will be verified when exiting on root chain 153 | * @param tokenId tokenId to withdraw 154 | */ 155 | function withdraw(uint256 tokenId) external { 156 | require(_msgSender() == ownerOf(tokenId), "ChildMintableERC721: INVALID_TOKEN_OWNER"); 157 | withdrawnTokens[tokenId] = true; 158 | _burn(tokenId); 159 | } 160 | 161 | /** 162 | * @notice called when user wants to withdraw multiple tokens back to root chain 163 | * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 164 | * @param tokenIds tokenId list to withdraw 165 | */ 166 | function withdrawBatch(uint256[] calldata tokenIds) external { 167 | 168 | uint256 length = tokenIds.length; 169 | require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); 170 | 171 | // Iteratively burn ERC721 tokens, for performing 172 | // batch withdraw 173 | for (uint256 i; i < length; i++) { 174 | 175 | uint256 tokenId = tokenIds[i]; 176 | 177 | require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); 178 | withdrawnTokens[tokenId] = true; 179 | _burn(tokenId); 180 | 181 | } 182 | 183 | // At last emit this event, which will be used 184 | // in MintableERC721 predicate contract on L1 185 | // while verifying burn proof 186 | emit WithdrawnBatch(_msgSender(), tokenIds); 187 | 188 | } 189 | 190 | /** 191 | * @notice called when user wants to withdraw token back to root chain with token URI 192 | * @dev Should handle withraw by burning user's token. 193 | * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn 194 | * This transaction will be verified when exiting on root chain 195 | * 196 | * @param tokenId tokenId to withdraw 197 | */ 198 | function withdrawWithMetadata(uint256 tokenId) external { 199 | 200 | require(_msgSender() == ownerOf(tokenId), "ChildMintableERC721: INVALID_TOKEN_OWNER"); 201 | withdrawnTokens[tokenId] = true; 202 | 203 | // Encoding metadata associated with tokenId & emitting event 204 | emit TransferWithMetadata(ownerOf(tokenId), address(0), tokenId, this.encodeTokenMetadata(tokenId)); 205 | 206 | _burn(tokenId); 207 | 208 | } 209 | 210 | /** 211 | * @notice This method is supposed to be called by client when withdrawing token with metadata 212 | * and pass return value of this function as second paramter of `withdrawWithMetadata` method 213 | * 214 | * It can be overridden by clients to encode data in a different form, which needs to 215 | * be decoded back by them correctly during exiting 216 | * 217 | * @param tokenId Token for which URI to be fetched 218 | */ 219 | function encodeTokenMetadata(uint256 tokenId) external view virtual returns (bytes memory) { 220 | 221 | // You're always free to change this default implementation 222 | // and pack more data in byte array which can be decoded back 223 | // in L1 224 | return abi.encode(tokenURI(tokenId)); 225 | 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /test/StartonERC20Base.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | import { BigNumber } from "ethers"; 5 | 6 | import { 7 | StartonERC20Base, 8 | StartonERC20Base__factory, // eslint-disable-line camelcase 9 | } from "../typechain-types"; 10 | 11 | let ERC20: StartonERC20Base__factory; // eslint-disable-line camelcase 12 | 13 | describe("StartonERC20Base", () => { 14 | let instanceERC20: StartonERC20Base; 15 | let owner: SignerWithAddress; 16 | let addr1: SignerWithAddress; 17 | let addr2: SignerWithAddress; 18 | 19 | before(async () => { 20 | // Get the Signers here 21 | [owner, addr1, addr2] = await ethers.getSigners(); 22 | 23 | // Create factory 24 | ERC20 = new StartonERC20Base__factory(owner); 25 | }); 26 | 27 | beforeEach(async () => { 28 | instanceERC20 = (await ERC20.deploy( 29 | "StartonToken", 30 | "ST", 31 | BigNumber.from("1000000000000000000000000000"), 32 | owner.address 33 | )) as StartonERC20Base; 34 | await instanceERC20.deployed(); 35 | }); 36 | 37 | describe("Deployment", () => { 38 | it("Should deploy", async () => {}); 39 | 40 | it("Should set admin role to owner", async () => { 41 | const adminRole = await instanceERC20.DEFAULT_ADMIN_ROLE(); 42 | 43 | expect(await instanceERC20.hasRole(adminRole, owner.address)).to.equal( 44 | true 45 | ); 46 | }); 47 | 48 | it("Should set default roles to owner", async () => { 49 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 50 | 51 | expect(await instanceERC20.hasRole(pauserRole, owner.address)).to.equal( 52 | true 53 | ); 54 | }); 55 | 56 | it("Should set correctly the name", async () => { 57 | expect(await instanceERC20.name()).to.equal("StartonToken"); 58 | }); 59 | 60 | it("Should set correctly the symbol", async () => { 61 | expect(await instanceERC20.symbol()).to.equal("ST"); 62 | }); 63 | 64 | it("Should not be paused", async () => { 65 | expect(await instanceERC20.paused()).to.equal(false); 66 | }); 67 | 68 | it("Should set correctly the total supply", async () => { 69 | expect(await instanceERC20.totalSupply()).to.equal( 70 | BigNumber.from("1000000000000000000000000000") 71 | ); 72 | }); 73 | }); 74 | 75 | describe("Allowance", () => { 76 | it("Should return 0", async () => { 77 | expect( 78 | await instanceERC20.allowance(owner.address, addr1.address) 79 | ).to.equal(0); 80 | }); 81 | 82 | it("Should increase allowance", async () => { 83 | await instanceERC20.increaseAllowance( 84 | addr1.address, 85 | BigNumber.from("100") 86 | ); 87 | expect( 88 | await instanceERC20.allowance(owner.address, addr1.address) 89 | ).to.equal(BigNumber.from("100")); 90 | 91 | await instanceERC20.increaseAllowance( 92 | addr1.address, 93 | BigNumber.from("100") 94 | ); 95 | expect( 96 | await instanceERC20.allowance(owner.address, addr1.address) 97 | ).to.equal(BigNumber.from("200")); 98 | }); 99 | 100 | it("Should approve the correct amount", async () => { 101 | await instanceERC20.approve(addr1.address, BigNumber.from("100")); 102 | expect( 103 | await instanceERC20.allowance(owner.address, addr1.address) 104 | ).to.equal(BigNumber.from("100")); 105 | }); 106 | }); 107 | 108 | describe("Transfer", () => { 109 | it("Shouldn't transferFrom without enough allowance", async () => { 110 | await expect( 111 | instanceERC20 112 | .connect(addr2) 113 | .transferFrom( 114 | addr1.address, 115 | addr2.address, 116 | BigNumber.from("1000000000000000000000000000") 117 | ) 118 | ).to.be.revertedWith("ERC20: insufficient allowance"); 119 | }); 120 | 121 | it("Should transfer without approval while owner", async () => { 122 | await instanceERC20.transfer( 123 | addr2.address, 124 | BigNumber.from("1000000000000000000000000000") 125 | ); 126 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 127 | expect(await instanceERC20.balanceOf(addr2.address)).to.equal( 128 | BigNumber.from("1000000000000000000000000000") 129 | ); 130 | }); 131 | 132 | it("Should transfer with approval", async () => { 133 | await instanceERC20.approve( 134 | addr1.address, 135 | BigNumber.from("1000000000000000000000000000") 136 | ); 137 | await instanceERC20 138 | .connect(addr1) 139 | .transferFrom( 140 | owner.address, 141 | addr2.address, 142 | BigNumber.from("1000000000000000000000000000") 143 | ); 144 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 145 | expect(await instanceERC20.balanceOf(addr2.address)).to.equal( 146 | BigNumber.from("1000000000000000000000000000") 147 | ); 148 | }); 149 | }); 150 | 151 | describe("Pause", () => { 152 | it("Should pause correctly", async () => { 153 | await instanceERC20.pause(); 154 | expect(await instanceERC20.paused()).to.equal(true); 155 | }); 156 | 157 | it("Should unpause correctly", async () => { 158 | await instanceERC20.pause(); 159 | await instanceERC20.unpause(); 160 | 161 | expect(await instanceERC20.paused()).to.equal(false); 162 | }); 163 | }); 164 | 165 | describe("Roles", () => { 166 | it("Should assign roles accordingly", async () => { 167 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 168 | 169 | await instanceERC20.grantRole(pauserRole, addr1.address); 170 | expect(await instanceERC20.hasRole(pauserRole, addr1.address)).to.equal( 171 | true 172 | ); 173 | }); 174 | 175 | it("Should revoke roles accordingly", async () => { 176 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 177 | 178 | await instanceERC20.grantRole(pauserRole, addr1.address); 179 | 180 | await instanceERC20.revokeRole(pauserRole, addr1.address); 181 | expect(await instanceERC20.hasRole(pauserRole, addr1.address)).to.equal( 182 | false 183 | ); 184 | }); 185 | 186 | it("Shouldn't let anyone without the pauser role to be able to pause or unpause the contract", async () => { 187 | await expect(instanceERC20.connect(addr1).pause()).to.be.reverted; 188 | await expect(instanceERC20.connect(addr1).unpause()).to.be.reverted; 189 | }); 190 | 191 | it("Should let anyone with the pauser role to be able to pause or unpause the contract", async () => { 192 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 193 | await instanceERC20.grantRole(pauserRole, addr1.address); 194 | 195 | await instanceERC20.connect(addr1).pause(); 196 | await instanceERC20.connect(addr1).unpause(); 197 | }); 198 | }); 199 | 200 | describe("Burn", () => { 201 | it("Should be able to burn tokens", async () => { 202 | await instanceERC20.burn(BigNumber.from("1000000000000000000000000000")); 203 | 204 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 205 | }); 206 | }); 207 | 208 | describe("Forwarder", () => { 209 | it("Should be able to send a forwarded transaction", async () => { 210 | const metaTransactionType = [ 211 | { 212 | name: "nonce", 213 | type: "uint256", 214 | }, 215 | { 216 | name: "from", 217 | type: "address", 218 | }, 219 | { 220 | name: "functionSignature", 221 | type: "bytes", 222 | }, 223 | ]; 224 | 225 | const name = await instanceERC20.name(); 226 | const nonce = await instanceERC20.getNonce(addr1.address); 227 | const { chainId } = await ethers.provider.getNetwork(); 228 | 229 | const domainType = { 230 | name, 231 | version: "1", 232 | verifyingContract: instanceERC20.address, 233 | salt: "0x" + chainId.toString(16).padStart(64, "0"), 234 | }; 235 | 236 | const functionSignature = instanceERC20.interface.encodeFunctionData( 237 | "approve", 238 | [addr2.address, BigNumber.from("1000000000000000000000000000")] 239 | ); 240 | 241 | // Create the signature of the transaction 242 | const signature = await addr1._signTypedData( 243 | domainType, 244 | { 245 | MetaTransaction: metaTransactionType, 246 | }, 247 | { 248 | nonce: nonce.toString(), 249 | from: addr1.address, 250 | functionSignature, 251 | } 252 | ); 253 | 254 | // Sign the transaction by the destiner user 255 | const { r, s, v } = ethers.utils.splitSignature(signature); 256 | 257 | await instanceERC20.executeMetaTransaction( 258 | addr1.address, 259 | functionSignature, 260 | r, 261 | s, 262 | v 263 | ); 264 | 265 | expect( 266 | await instanceERC20.allowance(addr1.address, addr2.address) 267 | ).to.equal(BigNumber.from("1000000000000000000000000000")); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /test/StartonERC20Mintable.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | import { BigNumber } from "ethers"; 5 | 6 | import { 7 | StartonERC20Mintable, 8 | StartonERC20Mintable__factory, // eslint-disable-line camelcase 9 | } from "../typechain-types"; 10 | 11 | let ERC20: StartonERC20Mintable__factory; // eslint-disable-line camelcase 12 | 13 | describe("StartonERC20Mintable", () => { 14 | let instanceERC20: StartonERC20Mintable; 15 | let owner: SignerWithAddress; 16 | let addr1: SignerWithAddress; 17 | let addr2: SignerWithAddress; 18 | 19 | before(async () => { 20 | // Get the Signers here 21 | [owner, addr1, addr2] = await ethers.getSigners(); 22 | 23 | // Create factory 24 | ERC20 = new StartonERC20Mintable__factory(owner); 25 | }); 26 | 27 | beforeEach(async () => { 28 | instanceERC20 = (await ERC20.deploy( 29 | "StartonToken", 30 | "ST", 31 | BigNumber.from("1000000000000000000000000000"), 32 | owner.address 33 | )) as StartonERC20Mintable; 34 | await instanceERC20.deployed(); 35 | }); 36 | 37 | describe("Deployment", () => { 38 | it("Should deploy", async () => {}); 39 | 40 | it("Should set admin role to owner", async () => { 41 | const adminRole = await instanceERC20.DEFAULT_ADMIN_ROLE(); 42 | 43 | expect(await instanceERC20.hasRole(adminRole, owner.address)).to.equal( 44 | true 45 | ); 46 | }); 47 | 48 | it("Should set default roles to owner", async () => { 49 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 50 | const lockerRole = await instanceERC20.LOCKER_ROLE(); 51 | const minterRole = await instanceERC20.MINTER_ROLE(); 52 | 53 | expect(await instanceERC20.hasRole(pauserRole, owner.address)).to.equal( 54 | true 55 | ); 56 | expect(await instanceERC20.hasRole(lockerRole, owner.address)).to.equal( 57 | true 58 | ); 59 | expect(await instanceERC20.hasRole(minterRole, owner.address)).to.equal( 60 | true 61 | ); 62 | }); 63 | 64 | it("Should set correctly the name", async () => { 65 | expect(await instanceERC20.name()).to.equal("StartonToken"); 66 | }); 67 | 68 | it("Should set correctly the symbol", async () => { 69 | expect(await instanceERC20.symbol()).to.equal("ST"); 70 | }); 71 | 72 | it("Should not be paused", async () => { 73 | expect(await instanceERC20.paused()).to.equal(false); 74 | }); 75 | 76 | it("Should set correctly the total supply", async () => { 77 | expect(await instanceERC20.totalSupply()).to.equal( 78 | BigNumber.from("1000000000000000000000000000") 79 | ); 80 | }); 81 | }); 82 | 83 | describe("Allowance", () => { 84 | it("Should return 0", async () => { 85 | expect( 86 | await instanceERC20.allowance(owner.address, addr1.address) 87 | ).to.equal(0); 88 | }); 89 | 90 | it("Should increase allowance", async () => { 91 | await instanceERC20.increaseAllowance( 92 | addr1.address, 93 | BigNumber.from("100") 94 | ); 95 | expect( 96 | await instanceERC20.allowance(owner.address, addr1.address) 97 | ).to.equal(BigNumber.from("100")); 98 | 99 | await instanceERC20.increaseAllowance( 100 | addr1.address, 101 | BigNumber.from("100") 102 | ); 103 | expect( 104 | await instanceERC20.allowance(owner.address, addr1.address) 105 | ).to.equal(BigNumber.from("200")); 106 | }); 107 | 108 | it("Should approve the correct amount", async () => { 109 | await instanceERC20.approve(addr1.address, BigNumber.from("100")); 110 | expect( 111 | await instanceERC20.allowance(owner.address, addr1.address) 112 | ).to.equal(BigNumber.from("100")); 113 | }); 114 | }); 115 | 116 | describe("Transfer", () => { 117 | it("Shouldn't transferFrom without enough allowance", async () => { 118 | await expect( 119 | instanceERC20 120 | .connect(addr2) 121 | .transferFrom( 122 | addr1.address, 123 | addr2.address, 124 | BigNumber.from("1000000000000000000000000000") 125 | ) 126 | ).to.be.revertedWith("ERC20: insufficient allowance"); 127 | }); 128 | 129 | it("Should transfer without approval while owner", async () => { 130 | await instanceERC20.transfer( 131 | addr2.address, 132 | BigNumber.from("1000000000000000000000000000") 133 | ); 134 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 135 | expect(await instanceERC20.balanceOf(addr2.address)).to.equal( 136 | BigNumber.from("1000000000000000000000000000") 137 | ); 138 | }); 139 | 140 | it("Should transfer with approval", async () => { 141 | await instanceERC20.approve( 142 | addr1.address, 143 | BigNumber.from("1000000000000000000000000000") 144 | ); 145 | await instanceERC20 146 | .connect(addr1) 147 | .transferFrom( 148 | owner.address, 149 | addr2.address, 150 | BigNumber.from("1000000000000000000000000000") 151 | ); 152 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 153 | expect(await instanceERC20.balanceOf(addr2.address)).to.equal( 154 | BigNumber.from("1000000000000000000000000000") 155 | ); 156 | }); 157 | }); 158 | 159 | describe("Minting", () => { 160 | it("Should mint token correctly", async () => { 161 | await instanceERC20.mint(addr1.address, "100000000000000"); 162 | expect(await instanceERC20.balanceOf(addr1.address)).to.equal( 163 | "100000000000000" 164 | ); 165 | }); 166 | }); 167 | 168 | describe("Pause", () => { 169 | it("Should pause correctly", async () => { 170 | await instanceERC20.pause(); 171 | expect(await instanceERC20.paused()).to.equal(true); 172 | }); 173 | 174 | it("Should unpause correctly", async () => { 175 | await instanceERC20.pause(); 176 | await instanceERC20.unpause(); 177 | 178 | expect(await instanceERC20.paused()).to.equal(false); 179 | }); 180 | }); 181 | 182 | describe("Lock", () => { 183 | it("Should lock the mint and not let anyone mint anymore", async () => { 184 | await instanceERC20.lockMint(); 185 | await expect( 186 | instanceERC20.mint(addr1.address, BigNumber.from("100")) 187 | ).to.be.revertedWith("Minting is locked"); 188 | }); 189 | }); 190 | 191 | describe("Roles", () => { 192 | it("Should assign roles accordingly", async () => { 193 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 194 | const lockerRole = await instanceERC20.LOCKER_ROLE(); 195 | const minterRole = await instanceERC20.MINTER_ROLE(); 196 | 197 | await instanceERC20.grantRole(pauserRole, addr1.address); 198 | expect(await instanceERC20.hasRole(pauserRole, addr1.address)).to.equal( 199 | true 200 | ); 201 | await instanceERC20.grantRole(lockerRole, addr1.address); 202 | expect(await instanceERC20.hasRole(lockerRole, addr1.address)).to.equal( 203 | true 204 | ); 205 | await instanceERC20.grantRole(minterRole, addr1.address); 206 | expect(await instanceERC20.hasRole(minterRole, addr1.address)).to.equal( 207 | true 208 | ); 209 | }); 210 | 211 | it("Should revoke roles accordingly", async () => { 212 | const pauserRole = await instanceERC20.PAUSER_ROLE(); 213 | 214 | await instanceERC20.grantRole(pauserRole, addr1.address); 215 | 216 | await instanceERC20.revokeRole(pauserRole, addr1.address); 217 | expect(await instanceERC20.hasRole(pauserRole, addr1.address)).to.equal( 218 | false 219 | ); 220 | }); 221 | 222 | it("Shouldn't let anyone without the pauser role to be able to pause or unpause the contract", async () => { 223 | await expect(instanceERC20.connect(addr1).pause()).to.be.reverted; 224 | await expect(instanceERC20.connect(addr1).unpause()).to.be.reverted; 225 | }); 226 | 227 | it("Shouldn't let anyone without the minter role to be able to mint new tokens", async () => { 228 | await expect( 229 | instanceERC20.connect(addr1).mint(addr1.address, BigNumber.from("100")) 230 | ).to.be.reverted; 231 | }); 232 | 233 | it("Shouldn't let anyone without the locker role to be able to lock", async () => { 234 | await expect(instanceERC20.connect(addr1).lockMint()).to.be.reverted; 235 | }); 236 | }); 237 | 238 | describe("Burn", () => { 239 | it("Should be able to burn tokens", async () => { 240 | await instanceERC20.burn(BigNumber.from("1000000000000000000000000000")); 241 | 242 | expect(await instanceERC20.balanceOf(owner.address)).to.equal(0); 243 | }); 244 | }); 245 | 246 | describe("Forwarder", () => { 247 | it("Should be able to send a forwarded transaction", async () => { 248 | const metaTransactionType = [ 249 | { 250 | name: "nonce", 251 | type: "uint256", 252 | }, 253 | { 254 | name: "from", 255 | type: "address", 256 | }, 257 | { 258 | name: "functionSignature", 259 | type: "bytes", 260 | }, 261 | ]; 262 | 263 | const name = await instanceERC20.name(); 264 | const nonce = await instanceERC20.getNonce(addr1.address); 265 | const { chainId } = await ethers.provider.getNetwork(); 266 | 267 | const domainType = { 268 | name, 269 | version: "1", 270 | verifyingContract: instanceERC20.address, 271 | salt: "0x" + chainId.toString(16).padStart(64, "0"), 272 | }; 273 | 274 | const functionSignature = instanceERC20.interface.encodeFunctionData( 275 | "approve", 276 | [addr2.address, BigNumber.from("1000000000000000000000000000")] 277 | ); 278 | 279 | // Create the signature of the transaction 280 | const signature = await addr1._signTypedData( 281 | domainType, 282 | { 283 | MetaTransaction: metaTransactionType, 284 | }, 285 | { 286 | nonce: nonce.toString(), 287 | from: addr1.address, 288 | functionSignature, 289 | } 290 | ); 291 | 292 | // Sign the transaction by the destiner user 293 | const { r, s, v } = ethers.utils.splitSignature(signature); 294 | 295 | await instanceERC20.executeMetaTransaction( 296 | addr1.address, 297 | functionSignature, 298 | r, 299 | s, 300 | v 301 | ); 302 | 303 | expect( 304 | await instanceERC20.allowance(addr1.address, addr2.address) 305 | ).to.equal(BigNumber.from("1000000000000000000000000000")); 306 | }); 307 | }); 308 | }); 309 | --------------------------------------------------------------------------------