diff --git a/contracts/erc/ERC165.sol b/contracts/erc/ERC165.sol new file mode 100644 index 0000000..ad0a049 --- /dev/null +++ b/contracts/erc/ERC165.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.5.0; + +interface IERC165 { + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract ERC165 is IERC165 { + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + _registerInterface(_INTERFACE_ID_ERC165); + } + + function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} diff --git a/contracts/erc/ERC721.sol b/contracts/erc/ERC721.sol new file mode 100644 index 0000000..d0a02b4 --- /dev/null +++ b/contracts/erc/ERC721.sol @@ -0,0 +1,134 @@ +pragma solidity ^0.5.0; + +import "mchplus-contracts/libraries/Uint256.sol"; +import "mchplus-contracts/libraries/Address.sol"; + +import "mchplus-contracts/interfaces/IERC721.sol"; +import "mchplus-contracts/interfaces/IERC721TokenReceiver.sol"; +import "mchplus-contracts/erc/ERC165.sol"; + +contract ERC721 is IERC721, ERC165 { + using Uint256 for uint256; + using Address for address; + + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + bytes4 private constant _InterfaceId_ERC721 = 0x80ac58cd; + + mapping (uint256 => address) private _tokenOwner; + mapping (address => uint256) private _balance; + mapping (uint256 => address) private _tokenApproved; + mapping (address => mapping (address => bool)) private _operatorApprovals; + + constructor () public { + _registerInterface(_InterfaceId_ERC721); + } + + function balanceOf(address _owner) public view returns (uint256) { + return _balance[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address) { + require(_exist(_tokenId), + "`_tokenId` is not a valid NFT."); + return _tokenOwner[_tokenId]; + } + + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public payable { + require(_data.length == 0, "data is not implemented"); + safeTransferFrom(_from, _to, _tokenId); + } + + function safeTransferFrom(address _from, address _to, uint256 _tokenId) public payable { + require(_checkOnERC721Received(_from, _to, _tokenId, ""), + "`_to` is a smart contract and onERC721Received is invalid"); + transferFrom(_from, _to, _tokenId); + } + + function transferFrom(address _from, address _to, uint256 _tokenId) public payable { + require(_transferable(_from, _tokenId), + "Unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT."); + require(ownerOf(_tokenId) == _from, + "`_from` is not the current owner."); + require(_to != address(0), + "`_to` is the zero address."); + require(_exist(_tokenId), + "`_tokenId` is not a valid NFT."); + _transfer(_from, _to, _tokenId); + } + + function approve(address _approved, uint256 _tokenId) public payable { + address owner = ownerOf(_tokenId); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender), + "Unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner."); + + _tokenApproved[_tokenId] = _approved; + emit Approval(msg.sender, _approved, _tokenId); + } + + function setApprovalForAll(address _operator, bool _approved) public { + _setApprovalForAll(msg.sender, _operator, _approved); + } + + function _setApprovalForAll(address _owner, address _operator, bool _approved) internal { + _operatorApprovals[_owner][_operator] = _approved; + emit ApprovalForAll(_owner, _operator, _approved); + } + + function getApproved(uint256 _tokenId) public view returns (address) { + require(_exist(_tokenId), + "`_tokenId` is not a valid NFT."); + return _tokenApproved[_tokenId]; + } + + function isApprovedForAll(address _owner, address _operator) public view returns (bool) { + return _operatorApprovals[_owner][_operator]; + } + + function _transferable(address _spender, uint256 _tokenId) internal view returns (bool){ + address owner = ownerOf(_tokenId); + return (_spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender)); + } + + function _transfer(address _from, address _to, uint256 _tokenId) internal { + _clearApproval(_tokenId); + _tokenOwner[_tokenId] = _to; + _balance[_from] = _balance[_from].sub(1); + _balance[_to] = _balance[_to].add(1); + emit Transfer(_from, _to, _tokenId); + } + + function _mint(address _to, uint256 _tokenId) internal { + require(!_exist(_tokenId), "mint token already exists"); + _tokenOwner[_tokenId] = _to; + _balance[_to] = _balance[_to].add(1); + emit Transfer(address(0), _to, _tokenId); + } + + function _burn(uint256 _tokenId) internal { + require(_exist(_tokenId), "burn token does not already exists"); + address owner = ownerOf(_tokenId); + _clearApproval(_tokenId); + _tokenOwner[_tokenId] = address(0); + _balance[owner] = _balance[owner].sub(1); + emit Transfer(owner, address(0), _tokenId); + } + + function _exist(uint256 _tokenId) internal view returns (bool) { + address owner = _tokenOwner[_tokenId]; + return owner != address(0); + } + + function _checkOnERC721Received(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns (bool) { + if (!_to.isContract()) { + return true; + } + bytes4 retval = IERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data); + return (retval == _ERC721_RECEIVED); + } + + function _clearApproval(uint256 tokenId) internal { + if (_tokenApproved[tokenId] != address(0)) { + _tokenApproved[tokenId] = address(0); + } + } +} diff --git a/contracts/erc/ERC721Mintable.sol b/contracts/erc/ERC721Mintable.sol new file mode 100644 index 0000000..22417ff --- /dev/null +++ b/contracts/erc/ERC721Mintable.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.5.0; + +import "mchplus-contracts/erc/ERC721.sol"; +import "mchplus-contracts/roles/Operatable.sol"; + +interface IERC721Mintable { + event MinterAdded(address indexed account); + event MinterRemoved(address indexed account); + function mint(address _to, uint256 _tokenId) external; + function addMinter(address account) external; + function removeMinter(address account) external; +} + +contract ERC721Mintable is ERC721, IERC721Mintable, Operatable { + using Roles for Roles.Role; + Roles.Role private minters; + + constructor() public { + addMinter(msg.sender); + } + + modifier onlyMinter() { + require(isMinter(msg.sender), "Must be minter"); + _; + } + + function isMinter(address account) public view returns (bool) { + return minters.has(account); + } + + function addMinter(address account) public onlyOperator() { + minters.add(account); + emit MinterAdded(account); + } + + function removeMinter(address account) public onlyOperator() { + minters.remove(account); + emit MinterRemoved(account); + } + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } +} \ No newline at end of file diff --git a/contracts/interfaces/IERC721.sol b/contracts/interfaces/IERC721.sol new file mode 100644 index 0000000..88feda6 --- /dev/null +++ b/contracts/interfaces/IERC721.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.5.0; + +/// @title ERC-721 Non-Fungible Token Standard +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. +interface IERC721 /* is ERC165 */ { + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + function balanceOf(address _owner) external view returns (uint256); + function ownerOf(uint256 _tokenId) external view returns (address); + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable; + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + function approve(address _approved, uint256 _tokenId) external payable; + function setApprovalForAll(address _operator, bool _approved) external; + function getApproved(uint256 _tokenId) external view returns (address); + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} diff --git a/contracts/interfaces/IERC721Metadata.sol b/contracts/interfaces/IERC721Metadata.sol new file mode 100644 index 0000000..6ffb32d --- /dev/null +++ b/contracts/interfaces/IERC721Metadata.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.5.0; + +/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x5b5e139f. +interface IERC721Metadata /* is ERC721 */ { + /// @notice A descriptive name for a collection of NFTs in this contract + function name() external view returns (string memory _name); + + /// @notice An abbreviated name for NFTs in this contract + function symbol() external view returns (string memory _symbol); + + /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. + /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC + /// 3986. The URI may point to a JSON file that conforms to the "ERC721 + /// Metadata JSON Schema". + function tokenURI(uint256 _tokenId) external view returns (string memory); +} diff --git a/contracts/interfaces/IERC721TokenReceiver.sol b/contracts/interfaces/IERC721TokenReceiver.sol new file mode 100644 index 0000000..1ddba0f --- /dev/null +++ b/contracts/interfaces/IERC721TokenReceiver.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.5.0; + +/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. +interface IERC721TokenReceiver { + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _operator The address which called `safeTransferFrom` function + /// @param _from The address which previously owned the token + /// @param _tokenId The NFT identifier which is being transferred + /// @param _data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + /// unless throwing + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4); +} diff --git a/contracts/libraries/Address.sol b/contracts/libraries/Address.sol index 580aae5..ed3d956 100644 --- a/contracts/libraries/Address.sol +++ b/contracts/libraries/Address.sol @@ -8,6 +8,10 @@ library Address { return size > 0; } + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + function toHex(address account) internal pure returns (string memory) { bytes32 value = bytes32(uint256(account)); bytes memory alphabet = "0123456789abcdef"; diff --git a/contracts/roles/Operatable.sol b/contracts/roles/Operatable.sol new file mode 100644 index 0000000..88e558d --- /dev/null +++ b/contracts/roles/Operatable.sol @@ -0,0 +1,69 @@ +pragma solidity ^0.5.0; + +import "mchplus-contracts/roles/Roles.sol"; + +contract Operatable { + using Roles for Roles.Role; + + event OperatorAdded(address indexed account); + event OperatorRemoved(address indexed account); + + event Paused(address account); + event Unpaused(address account); + + bool private _paused; + Roles.Role private operators; + + constructor() public { + operators.add(msg.sender); + _paused = false; + } + + modifier onlyOperator() { + require(isOperator(msg.sender), "Must be operator"); + _; + } + + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + function isOperator(address account) public view returns (bool) { + return operators.has(account); + } + + function addOperator(address account) public onlyOperator() { + operators.add(account); + emit OperatorAdded(account); + } + + function removeOperator(address account) public onlyOperator() { + operators.remove(account); + emit OperatorRemoved(account); + } + + function paused() public view returns (bool) { + return _paused; + } + + function pause() public onlyOperator() whenNotPaused() { + _paused = true; + emit Paused(msg.sender); + } + + function unpause() public onlyOperator() whenPaused() { + _paused = false; + emit Unpaused(msg.sender); + } + + function withdrawEther() public onlyOperator() { + msg.sender.transfer(address(this).balance); + } + +} diff --git a/contracts/roles/OperatorRole.sol b/contracts/roles/OperatorRole.sol deleted file mode 100644 index 1929565..0000000 --- a/contracts/roles/OperatorRole.sol +++ /dev/null @@ -1,66 +0,0 @@ -pragma solidity ^0.5.0; - -import "./Roles.sol"; - -contract OperatorRole { - using Roles for Roles.Role; - - event OperatorAdded(address indexed account); - event OperatorRemoved(address indexed account); - - event Paused(address account); - event Unpaused(address account); - - bool private _paused; - - Roles.Role private operators; - - constructor() public { - operators.add(msg.sender); - _paused = false; - } - - modifier onlyOperator() { - require(isOperator(msg.sender)); - _; - } - - modifier whenNotPaused() { - require(!_paused, "Pausable: paused"); - _; - } - - modifier whenPaused() { - require(_paused, "Pausable: not paused"); - _; - } - - function isOperator(address account) public view returns (bool) { - return operators.has(account); - } - - function addOperator(address account) public onlyOperator() { - operators.add(account); - emit OperatorAdded(account); - } - - function removeOperator(address account) public onlyOperator() { - operators.remove(account); - emit OperatorRemoved(account); - } - - function paused() public view returns (bool) { - return _paused; - } - - function pause() public onlyOperator() whenNotPaused() { - _paused = true; - emit Paused(msg.sender); - } - - function unpause() public onlyOperator() whenPaused() { - _paused = false; - emit Unpaused(msg.sender); - } - -} diff --git a/package-lock.json b/package-lock.json index 418f336..1ba2dc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,8 @@ { - "requires": true, + "name": "mchplus-contracts", + "version": "0.0.1", "lockfileVersion": 1, + "requires": true, "dependencies": { "@improved/node": { "version": "1.1.1", @@ -1264,6 +1266,7 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "optional": true, "requires": { "nan": "^2.12.1", "node-pre-gyp": "^0.12.0" @@ -1271,19 +1274,23 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true + "bundled": true, + "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "optional": true }, "are-we-there-yet": { "version": "1.1.5", "bundled": true, + "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1291,11 +1298,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1303,57 +1312,69 @@ }, "chownr": { "version": "1.1.1", - "bundled": true + "bundled": true, + "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "debug": { "version": "4.1.1", "bundled": true, + "optional": true, "requires": { "ms": "^2.1.1" } }, "deep-extend": { "version": "0.6.0", - "bundled": true + "bundled": true, + "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true + "bundled": true, + "optional": true }, "fs-minipass": { "version": "1.2.5", "bundled": true, + "optional": true, "requires": { "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "gauge": { "version": "2.7.4", "bundled": true, + "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -1368,6 +1389,7 @@ "glob": { "version": "7.1.3", "bundled": true, + "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1379,11 +1401,13 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true + "bundled": true, + "optional": true }, "iconv-lite": { "version": "0.4.24", "bundled": true, + "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -1391,6 +1415,7 @@ "ignore-walk": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "minimatch": "^3.0.4" } @@ -1398,6 +1423,7 @@ "inflight": { "version": "1.0.6", "bundled": true, + "optional": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -1405,37 +1431,44 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", - "bundled": true + "bundled": true, + "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1444,6 +1477,7 @@ "minizlib": { "version": "1.2.1", "bundled": true, + "optional": true, "requires": { "minipass": "^2.2.1" } @@ -1451,17 +1485,20 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "needle": { "version": "2.3.0", "bundled": true, + "optional": true, "requires": { "debug": "^4.1.0", "iconv-lite": "^0.4.4", @@ -1471,6 +1508,7 @@ "node-pre-gyp": { "version": "0.12.0", "bundled": true, + "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -1487,6 +1525,7 @@ "nopt": { "version": "4.0.1", "bundled": true, + "optional": true, "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -1494,11 +1533,13 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true + "bundled": true, + "optional": true }, "npm-packlist": { "version": "1.4.1", "bundled": true, + "optional": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -1507,6 +1548,7 @@ "npmlog": { "version": "4.1.2", "bundled": true, + "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -1516,30 +1558,36 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true + "bundled": true, + "optional": true }, "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "osenv": { "version": "0.1.5", "bundled": true, + "optional": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -1547,15 +1595,18 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true + "bundled": true, + "optional": true }, "rc": { "version": "1.2.8", "bundled": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -1565,13 +1616,15 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true + "bundled": true, + "optional": true } } }, "readable-stream": { "version": "2.3.6", "bundled": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1585,37 +1638,45 @@ "rimraf": { "version": "2.6.3", "bundled": true, + "optional": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "bundled": true, + "optional": true }, "sax": { "version": "1.2.4", - "bundled": true + "bundled": true, + "optional": true }, "semver": { "version": "5.7.0", - "bundled": true + "bundled": true, + "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true + "bundled": true, + "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true + "bundled": true, + "optional": true }, "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1625,6 +1686,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1632,17 +1694,20 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true + "bundled": true, + "optional": true }, "tar": { "version": "4.4.8", "bundled": true, + "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -1655,22 +1720,26 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "wide-align": { "version": "1.1.3", "bundled": true, + "optional": true, "requires": { "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -3963,6 +4032,7 @@ "version": "3.6.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.4.tgz", "integrity": "sha512-9Yc2i881pF4BPGhjteCXQNaXx1DCwm3dtOyBaG2hitHjLWOczw/ki8vD1bqyT3u6K0Ms/FpCShkmfg+FtlOfYA==", + "optional": true, "requires": { "commander": "~2.20.3", "source-map": "~0.6.1" @@ -3971,7 +4041,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true } } },