diff --git a/.env.example b/.env.example index 45dbcaa191e0..2151b913c4f6 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ ETH1_ADDRESS_RESOLVER_ADDRESS=0xa32cf2433ba24595d3aCE5cc9A7079d3f1CC5E0c TEST_PRIVATE_KEY_1= TEST_PRIVATE_KEY_2= TARGET_GAS_LIMIT=9000000000 -CHAIN_ID=420 +CHAIN_ID=28 # Local NODE_ENV=local @@ -16,4 +16,4 @@ ETH1_ADDRESS_RESOLVER_ADDRESS=0x3e4CFaa8730092552d9425575E49bB542e329981 TEST_PRIVATE_KEY_1=0x754fde3f5e60ef2c7649061e06957c29017fe21032a8017132c0078e37f6193a TEST_PRIVATE_KEY_2=0x23d9aeeaa08ab710a57972eb56fc711d9ab13afdecc92c89586e0150bfa380a6 TARGET_GAS_LIMIT=9000000000 -CHAIN_ID=420 \ No newline at end of file +CHAIN_ID=28 \ No newline at end of file diff --git a/README.md b/README.md index 008c83e833f6..58c29aee74c1 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,10 @@ Create a `.env` file in the root directory of this project. Add environment-spec ### 1.1 Using Rinkeby Testnet -If you just want to work on the wallet, you can use the stable testnet on Rinkeby and AWS. To test on Rinkeby (ChainID 4), you will need an Infura key and two accounts with Rinkeby ETH in them. The test wallets must contain enough ETH to cover the tests. The `.env` parameters are +If you just want to work on the wallet, you can use the stable testnet on Rinkeby (ChainID 4) and AWS. To test on Rinkeby, you will need an Infura key and two accounts with Rinkeby ETH in them. The test wallets must contain enough ETH to cover the tests. The `.env` parameters are ```bash + NODE_ENV=local L1_NODE_WEB3_URL=https://rinkeby.infura.io/v3/KEY L2_NODE_WEB3_URL=http://54.161.5.63:8545 @@ -35,36 +36,40 @@ ETH1_ADDRESS_RESOLVER_ADDRESS=0xa32cf2433ba24595d3aCE5cc9A7079d3f1CC5E0c TEST_PRIVATE_KEY_1=0xPRIVATE KEY OF THE FIRST TEST WALLET TEST_PRIVATE_KEY_2=0xPRIVATE KEY OF THE SECOND TEST WALLET TARGET_GAS_LIMIT=9000000000 -CHAIN_ID=420 +CHAIN_ID=28 + ``` -### 1.2 Using Local Net +If you do not know what these values are, you can get them from the values written to the terminal as your local OMGX spins up. + +### 1.2 Using a Local Testnet If would like to change the wallet-associated smart contracts and/or work on other aspects of the system, you should run a local development system. You can do this by ```bash -# Git clone with submodules + $ git clone git@github.com:enyalabs/optimism-integration.git $ cd optimism-integration $ docker-compose pull $ yarn install $ ./up_local.sh -``` - -If you do not know what these values are, you can get them from the values written to the terminal as your local OMGX spins up. +``` As the system boots, you'll see several things zip by in the terminal that you will need to correctly configure your `.env`, namely the values for the ```bash + ETH1_ADDRESS_RESOLVER_ADDRESS=0x______ TEST_PRIVATE_KEY_1=0x_____ TEST_PRIVATE_KEY_2=0x______ + ``` -For the test private keys, we normally use the ones generated by hardhat as it spins up the local L1. Each of those is funded with 1000 ETH. You can get the test private keys from the hardhat (`l1_chain`) Docker terminal or from your main terminal. You will also need the `ADDRESS_RESOLVER_ADDRESS`, which will zip by as your local system deploys (see the `deployer` Docker terminal output or your main terminal window). Fill these values into your `.env`. Your final `.env` should look something like this +For the test private keys, we normally use the ones generated by hardhat as it sets up the local L1. Each of those is funded with 1000 ETH. You can get the test private keys from the hardhat (`l1_chain`) Docker terminal or from your main terminal. You will also need the `ADDRESS_RESOLVER_ADDRESS`, which will zip by as your local system deploys (see the `deployer` Docker terminal output or your main terminal window). Fill these values into your `.env`. Your final `.env` should look something like this ```bash + NODE_ENV=local L1_NODE_WEB3_URL=http://localhost:9545 L2_NODE_WEB3_URL=http://localhost:8545 @@ -72,15 +77,18 @@ ETH1_ADDRESS_RESOLVER_ADDRESS=0x3e4CFaa8730092552d9425575E49bB542e329981 TEST_PRIVATE_KEY_1=0x754fde3f5e60ef2c7649061e06957c29017fe21032a8017132c0078e37f6193a TEST_PRIVATE_KEY_2=0x23d9aeeaa08ab710a57972eb56fc711d9ab13afdecc92c89586e0150bfa380a6 TARGET_GAS_LIMIT=9000000000 -CHAIN_ID=420 +CHAIN_ID=28 + ``` ## 2. Running the Integration Tests ```bash + $ yarn install $ yarn build #builds the contracts $ yarn deploy #if needed - this will test and deploy the contracts, and write their addresses to /deployments/addresses.json + ``` The information generated during the deploy (e.g the `/deployments/addresses.json`) is used by the web wallet front end to set things up correctly. **The full test suite includes some very slow transactions such as withdrawals, which can take 300 seconds each. Please be patient.** @@ -140,22 +148,22 @@ If you are working on a local testnet, please deploy **L2LiquidityPool.sol** fir ```javascript - L2LiquidityPool = await Factory__L2LiquidityPool.deploy( - env.watcher.l2.messengerAddress, - ) - await L2LiquidityPool.deployTransaction.wait() - - L1LiquidityPool = await Factory__L1LiquidityPool.deploy( - L2LiquidityPool.address, - env.watcher.l1.messengerAddress, - env.L2ETHGateway.address, - 3 - ) - await L1LiquidityPool.deployTransaction.wait() - - const L2LiquidityPoolTX = await L2LiquidityPool.init(L1LiquidityPool.address, "3") - await L2LiquidityPoolTX.wait() - console.log(' L2 LP initialized:',L2LiquidityPoolTX.hash); + L2LiquidityPool = await Factory__L2LiquidityPool.deploy( + env.watcher.l2.messengerAddress, + ) + await L2LiquidityPool.deployTransaction.wait() + + L1LiquidityPool = await Factory__L1LiquidityPool.deploy( + L2LiquidityPool.address, + env.watcher.l1.messengerAddress, + env.L2ETHGateway.address, + 3 + ) + await L1LiquidityPool.deployTransaction.wait() + + const L2LiquidityPoolTX = await L2LiquidityPool.init(L1LiquidityPool.address, "3") + await L2LiquidityPoolTX.wait() + console.log(' L2 LP initialized:',L2LiquidityPoolTX.hash); ``` @@ -170,21 +178,27 @@ Used to swap ERC20 tokens. * `expire` sets the status of the swap to be **EXPIRED**. * `check` returns the **Swap** construct -## 4. Running the web wallet +## 4. Running the Web Wallet ```bash + $ cd /wallet $ yarn install $ yarn start + ``` You will need to set up MetaMask to know about the two accounts you are using for local testing. You will need to point MetaMask at your local chains (at :9545 and :8545) and add the account used for testing, which is typically whatever you used a `TEST_PRIVATE_KEY_2` aka `Alice`: ```bash + TEST_PRIVATE_KEY_2=0x23d9aeeaa08ab710a57972eb56fc711d9ab13afdecc92c89586e0150bfa380a6 + ``` -You also need to add networks to your MetaMask. +### Metamask Settings + +You also need to add networks to your MetaMask: * Local L1 @@ -197,31 +211,27 @@ You also need to add networks to your MetaMask. ``` URL = http://localhost:8545 - ChainID = 420 + ChainID = 28 ``` -> You have to reset the MetaMask when you re-run the local networks! The reset button is in **Settings > Advanced > Reset Account**. +> You might have to reset MetaMask when you re-start the local network. The reset button is in **Settings > Advanced > Reset Account**. -* Rinkeby +* Rinkeby L1 - Select Rinkeby Test Network + Select the dewfault Rinkeby Test Network -* AWS L2 +* OMGX L2 ``` URL = http://18.208.138.49:8545 - ChainID = 420 + ChainID = 28 ``` -Here's a step-by-step process on how to do that while in your browser: - -1. Open MetaMask the browser extension and click on your currently connected network at the top pane, next to your jazzicon. - -2. You'll see a modal open up with list of networks. Click on the "Custom RPC" button at the very bottom. (See example image below.) +### Wallet Use and Supported Functions - ![Custom RPC button](https://community.optimism.io/assets/img/custom-metamask-network-1.d6908890.png) +1. Open the MetaMask browser extension and click on your currently connected network at the top pane, next to your jazzicon. -3. Next, you'll enter in the network parameters and save it: +2. You'll see a modal open up with list of networks. If you are working on a local system, click on the "Custom RPC" button at the very bottom. Enter/select your L1 and L2 network parameters. - ![image](https://user-images.githubusercontent.com/46272347/116638580-7c53a600-a91b-11eb-86c5-75242eb0507b.png) +3. Account Page. here you can see your balances, and move tokens from L1 to L2, and back. diff --git a/contracts/Address.sol b/contracts/Address.sol deleted file mode 100644 index 42a9dc1ebe9a..000000000000 --- a/contracts/Address.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.2 <0.8.0; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize, which returns 0 for contracts in - // construction, since the code is only stored at the end of the - // constructor execution. - - uint256 size; - // solhint-disable-next-line no-inline-assembly - assembly { size := extcodesize(account) } - return size > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - // solhint-disable-next-line avoid-low-level-calls, avoid-call-value - (bool success, ) = recipient.call{ value: amount }(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain`call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target), "Address: call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.call{ value: value }(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.staticcall(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.delegatecall(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - - // solhint-disable-next-line no-inline-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} diff --git a/contracts/ERC721Mock.sol b/contracts/ERC721Mock.sol new file mode 100644 index 000000000000..a924bf4809cc --- /dev/null +++ b/contracts/ERC721Mock.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.6; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +/** + * @title ERC721Mock + * This mock just provides a public safeMint, mint, and burn functions for testing purposes + */ +contract ERC721Mock is ERC721 { + + uint256 tID; + + constructor (string memory name, string memory symbol, uint256 tID_start) + ERC721(name, symbol) { + tID = tID_start; + } + + function mintNFT(address recipient, string memory tokenURI) public returns (uint256) + { + safeMint(recipient, tID); + setTokenURI(tID, tokenURI); + tID += 1; + return tID; + } + + function getLastTID() public view returns(uint256) { + return tID; + } + + function baseURI() public view override returns (string memory) { + return baseURI(); + } + + function setBaseURI(string calldata newBaseURI) public { + _setBaseURI(newBaseURI); + } + + function setTokenURI(uint256 tokenId, string memory _tokenURI) public { + _setTokenURI(tokenId, _tokenURI); + } + + //for a specific tokenId, get the associated NFT + function getTokenURI(uint256 tokenId) public view returns (string memory) { + return tokenURI(tokenId); + } + + function getName() public view returns (string memory) { + return name(); + } + + function getSymbol() public view returns (string memory) { + return symbol(); + } + + function exists(uint256 tokenId) public view returns (bool) { + return _exists(tokenId); + } + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function safeMint(address to, uint256 tokenId) public { + _safeMint(to, tokenId); + } + + function safeMint(address to, uint256 tokenId, bytes memory _data) public { + _safeMint(to, tokenId, _data); + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } +} diff --git a/contracts/Initializable.sol b/contracts/Initializable.sol deleted file mode 100644 index d8d620126b4d..000000000000 --- a/contracts/Initializable.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT - -// solhint-disable-next-line compiler-version -pragma solidity >=0.4.24 <0.8.0; - -import "./Address.sol"; - -/** - * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed - * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an - * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer - * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. - * - * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as - * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. - * - * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure - * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. - */ -abstract contract Initializable { - - /** - * @dev Indicates that the contract has been initialized. - */ - bool private _initialized; - - /** - * @dev Indicates that the contract is in the process of being initialized. - */ - bool private _initializing; - - /** - * @dev Modifier to protect an initializer function from being invoked twice. - */ - modifier initializer() { - require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); - - bool isTopLevelCall = !_initializing; - if (isTopLevelCall) { - _initializing = true; - _initialized = true; - } - - _; - - if (isTopLevelCall) { - _initializing = false; - } - } - - /// @dev Returns true if and only if the function is running in the constructor - function _isConstructor() private view returns (bool) { - return !Address.isContract(address(this)); - } -} diff --git a/contracts/Proxy.sol b/contracts/Proxy.sol deleted file mode 100644 index 7f51c61d3e46..000000000000 --- a/contracts/Proxy.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.0 <0.8.0; - -/** - * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM - * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to - * be specified by overriding the virtual {_implementation} function. - * - * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a - * different contract through the {_delegate} function. - * - * The success and return data of the delegated call will be returned back to the caller of the proxy. - */ -abstract contract Proxy { - /** - * @dev Delegates the current call to `implementation`. - * - * This function does not return to its internall call site, it will return directly to the external caller. - */ - function _delegate(address implementation) internal virtual { - // solhint-disable-next-line no-inline-assembly - assembly { - // Copy msg.data. We take full control of memory in this inline assembly - // block because it will not return to Solidity code. We overwrite the - // Solidity scratch pad at memory position 0. - calldatacopy(0, 0, calldatasize()) - - // Call the implementation. - // out and outsize are 0 because we don't know the size yet. - let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - - // Copy the returned data. - returndatacopy(0, 0, returndatasize()) - - switch result - // delegatecall returns 0 on error. - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } - - /** - * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function - * and {_fallback} should delegate. - */ - function _implementation() internal view virtual returns (address); - - /** - * @dev Delegates the current call to the address returned by `_implementation()`. - * - * This function does not return to its internall call site, it will return directly to the external caller. - */ - function _fallback() internal virtual { - _beforeFallback(); - _delegate(_implementation()); - } - - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other - * function in the contract matches the call data. - */ - fallback () external payable virtual { - _fallback(); - } - - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data - * is empty. - */ - receive () external payable virtual { - _fallback(); - } - - /** - * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` - * call, or as part of the Solidity `fallback` or `receive` functions. - * - * If overriden should call `super._beforeFallback()`. - */ - function _beforeFallback() internal virtual { - } -} diff --git a/contracts/UpgradeableProxy.sol b/contracts/UpgradeableProxy.sol deleted file mode 100644 index 40f24c879645..000000000000 --- a/contracts/UpgradeableProxy.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.0 <0.8.0; - -import "./Proxy.sol"; -import "./Address.sol"; - -/** - * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an - * implementation address that can be changed. This address is stored in storage in the location specified by - * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the - * implementation behind the proxy. - * - * Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see - * {TransparentUpgradeableProxy}. - */ -contract UpgradeableProxy is Proxy { - /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. - * - * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded - * function call, and allows initializating the storage of the proxy like a Solidity constructor. - */ - constructor(address _logic, bytes memory _data) public payable { - assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); - _setImplementation(_logic); - if(_data.length > 0) { - Address.functionDelegateCall(_logic, _data); - } - } - - /** - * @dev Emitted when the implementation is upgraded. - */ - event Upgraded(address indexed implementation); - - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is - * validated in the constructor. - */ - bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /** - * @dev Returns the current implementation address. - */ - function _implementation() internal view virtual override returns (address impl) { - bytes32 slot = _IMPLEMENTATION_SLOT; - // solhint-disable-next-line no-inline-assembly - assembly { - impl := sload(slot) - } - } - - /** - * @dev Upgrades the proxy to a new implementation. - * - * Emits an {Upgraded} event. - */ - function _upgradeTo(address newImplementation) internal virtual { - _setImplementation(newImplementation); - emit Upgraded(newImplementation); - } - - /** - * @dev Stores a new address in the EIP1967 implementation slot. - */ - function _setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "UpgradeableProxy: new implementation is not a contract"); - - bytes32 slot = _IMPLEMENTATION_SLOT; - - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(slot, newImplementation) - } - } -} diff --git a/deployment/local/addresses.json b/deployment/local/addresses.json index 233b03ced44b..8d34dd26ca12 100644 --- a/deployment/local/addresses.json +++ b/deployment/local/addresses.json @@ -1,10 +1,10 @@ { - "L1LiquidityPool": "0x2673a37B287b9896fbc9fB8E29Ed1d899BD4281E", - "L2LiquidityPool": "0x16Af4Db6548234c6463Ad6F0cf355260E96E741b", - "L1ERC20": "0x858B8623Ca51F089E7a51Dc81F7630901E6CCF55", - "L2DepositedERC20": "0x5736b4030Dc0A2aEC72c42C5f1b937E8CAFe46CE", - "L1ERC20Gateway": "0xE85308bB7378debbAC14ae15B41dDeaA2939bab9", + "L1LiquidityPool": "0x8d5cC5C837EE7898133cCf56c71D64d0e78e16DF", + "L2LiquidityPool": "0xBF3013B96cBc021BC27D8e07ee6dfDD1bEc10bcE", + "L1ERC20": "0x2673a37B287b9896fbc9fB8E29Ed1d899BD4281E", + "L2DepositedERC20": "0x63531d3472ff8d012274AE2c0A9Ddf30ACC54Cc8", + "L1ERC20Gateway": "0x858B8623Ca51F089E7a51Dc81F7630901E6CCF55", "l1ETHGatewayAddress": "0x4F53A01556Dc6f120db9a1b4caE786D5f0b5792C", "l1MessengerAddress": "0xA6404B184Ad3f6F41b6472f02ba45f25C6A66d49", - "L2ERC721": "0xbCbF2cEBa7bFb6F25B3f9fC5A37e8b07Fa43700B" + "L2ERC721": "0xeB4cbbCE5B52ca1b167898dac418da2C68ca9c01" } \ No newline at end of file diff --git a/package.json b/package.json index e0d819541a98..6b281ab40857 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "build": "./scripts/build.sh", "build:contracts": "hardhat compile", "build:contracts:ovm": "hardhat compile --network omgx", - "deploy": "./scripts/build.sh && hardhat --network omgx test && ./scripts/copy.sh" + "deploy": "./scripts/build.sh && hardhat --network omgx test && ./scripts/copy.sh", + "postinstall": "patch-package" }, "devDependencies": { "@eth-optimism/hardhat-ovm": "^0.0.2", diff --git a/patches/@openzeppelin+contracts+3.4.1.patch b/patches/@openzeppelin+contracts+3.4.1.patch new file mode 100644 index 000000000000..4a74a382bda8 --- /dev/null +++ b/patches/@openzeppelin+contracts+3.4.1.patch @@ -0,0 +1,66 @@ +diff --git a/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol b/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol +index 7958fdf..20b8972 100644 +--- a/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol ++++ b/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol +@@ -19,6 +19,7 @@ import "../../utils/Strings.sol"; + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ + contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable { ++ + using SafeMath for uint256; + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; +@@ -136,18 +137,19 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory _tokenURI = _tokenURIs[tokenId]; +- string memory base = baseURI(); +- +- // If there is no base URI, return the token URI. +- if (bytes(base).length == 0) { +- return _tokenURI; +- } +- // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). +- if (bytes(_tokenURI).length > 0) { +- return string(abi.encodePacked(base, _tokenURI)); +- } +- // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. +- return string(abi.encodePacked(base, tokenId.toString())); ++ //string memory base = baseURI(); ++ ++ // // If there is no base URI, return the token URI. ++ // if (bytes(base).length == 0) { ++ return _tokenURI; ++ // } ++ // // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). ++ // if (bytes(_tokenURI).length > 0) { ++ // return string(abi.encodePacked(base, _tokenURI)); ++ // } ++ // // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. ++ // return string(abi.encodePacked(base, tokenId.toString())); ++ //return "TestTestTest"; + } + + /** +diff --git a/node_modules/@openzeppelin/contracts/utils/Address.sol b/node_modules/@openzeppelin/contracts/utils/Address.sol +index 42a9dc1..bb610c2 100644 +--- a/node_modules/@openzeppelin/contracts/utils/Address.sol ++++ b/node_modules/@openzeppelin/contracts/utils/Address.sol +@@ -51,7 +51,7 @@ library Address { + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { +- require(address(this).balance >= amount, "Address: insufficient balance"); ++ //require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); +@@ -112,7 +112,7 @@ library Address { + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { +- require(address(this).balance >= value, "Address: insufficient balance for call"); ++ //require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls diff --git a/test/setup_test.spec.ts b/test/a_setup.spec.ts similarity index 87% rename from test/setup_test.spec.ts rename to test/a_setup.spec.ts index 35aa8a27c762..25715a041d11 100644 --- a/test/setup_test.spec.ts +++ b/test/a_setup.spec.ts @@ -1,47 +1,40 @@ import { expect } from 'chai' - import { Contract, ContractFactory, BigNumber, Wallet, utils, providers } from 'ethers' import { Direction } from './shared/watcher-utils' -import L1LiquidityPoolJson from '../artifacts/contracts/L1LiquidityPool.sol/L1LiquidityPool.json' import L1ERC20Json from '../artifacts/contracts/ERC20.sol/ERC20.json' import L1ERC20GatewayJson from '../artifacts/contracts/L1ERC20Gateway.sol/L1ERC20Gateway.json' -import L2LiquidityPoolJson from '../artifacts-ovm/contracts/L2LiquidityPool.sol/L2LiquidityPool.json' import L2DepositedERC20Json from '../artifacts-ovm/contracts/L2DepositedERC20.sol/L2DepositedERC20.json' -import L2ERC721Json from '../artifacts-ovm/contracts/NFT/ERC721.sol/ERC721.json' + +import L1LiquidityPoolJson from '../artifacts/contracts/L1LiquidityPool.sol/L1LiquidityPool.json' +import L2LiquidityPoolJson from '../artifacts-ovm/contracts/L2LiquidityPool.sol/L2LiquidityPool.json' import { OptimismEnv } from './shared/env' import * as fs from 'fs' -describe('Token, Bridge, and Swap Pool Setup and Test', async () => { +describe('System setup', async () => { let Factory__L1LiquidityPool: ContractFactory let Factory__L2LiquidityPool: ContractFactory let Factory__L1ERC20: ContractFactory let Factory__L2DepositedERC20: ContractFactory let Factory__L1ERC20Gateway: ContractFactory - let Factory__L2ERC721: ContractFactory let L1LiquidityPool: Contract let L2LiquidityPool: Contract let L1ERC20: Contract let L2DepositedERC20: Contract let L1ERC20Gateway: Contract - let L2ERC721: Contract let env: OptimismEnv //Test ERC20 const initialAmount = utils.parseEther("10000000000") - const tokenName = 'JLKN Test' + const tokenName = 'OMGX Test' const tokenDecimals = 18 - const tokenSymbol = 'JLKN' - - //Test Marc's BioBase NFT system - const nftName = 'BioBase' - const nftSymbol = 'BEE' //Bioeconomy Explodes + const tokenSymbol = 'OMGX' const getBalances = async ( _address: string, @@ -78,7 +71,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { } } - /************* BOB owns all the pools, and ALICE Mints a new token ***********/ + /************* BOB owns all the pools, and ALICE mints a new token ***********/ before(async () => { env = await OptimismEnv.new() @@ -113,17 +106,11 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { env.bobl1Wallet ) - Factory__L2ERC721 = new ContractFactory( - L2ERC721Json.abi, - L2ERC721Json.bytecode, - env.bobl1Wallet - ) - }) before(async () => { - //Who? sets up the L2LP + //Set up the L2LP L2LiquidityPool = await Factory__L2LiquidityPool.deploy( env.watcher.l2.messengerAddress, ) @@ -144,8 +131,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { await L2LiquidityPoolTX.wait() console.log('L2 LP initialized with the L1LiquidityPool.address:',L2LiquidityPoolTX.hash); - //Who? mints a new token and sets up the L1 and L2 infrastructure - // Mint a new token on L1 + //Mint a new token on L1 and set up the L1 and L2 infrastructure // [initialSupply, name, decimals, symbol] // this is owned by bobl1Wallet L1ERC20 = await Factory__L1ERC20.deploy( @@ -157,7 +143,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { await L1ERC20.deployTransaction.wait() console.log("L1ERC20 deployed to:", L1ERC20.address) - // Who? sets up things on L2 for this new token + //Set up things on L2 for this new token // [l2MessengerAddress, name, symbol] L2DepositedERC20 = await Factory__L2DepositedERC20.deploy( env.watcher.l2.messengerAddress, @@ -167,7 +153,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { await L2DepositedERC20.deployTransaction.wait() console.log("L2DepositedERC20 deployed to:", L2DepositedERC20.address) - // Who? deploys a gateway for this new token + //Deploy a gateway for the new token // [L1_ERC20.address, OVM_L2DepositedERC20.address, l1MessengerAddress] L1ERC20Gateway = await Factory__L1ERC20Gateway.deploy( L1ERC20.address, @@ -177,24 +163,14 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { await L1ERC20Gateway.deployTransaction.wait() console.log("L1ERC20Gateway deployed to:", L1ERC20Gateway.address) - // Who initializes the contracts for the new token + //Initialize the contracts for the new token const initL2 = await L2DepositedERC20.init(L1ERC20Gateway.address); await initL2.wait(); console.log('L2 ERC20 initialized:',initL2.hash); - // register erc20 token addresses on both Liquidity pools + //Register Erc20 token addresses in both Liquidity pools await L1LiquidityPool.registerTokenAddress(L1ERC20.address, L2DepositedERC20.address); await L2LiquidityPool.registerTokenAddress(L1ERC20.address, L2DepositedERC20.address); - - // Mint a new NFT on L2 - // [initialSupply, name, decimals, symbol] - // this is owned by bobl1Wallet - L2ERC721 = await Factory__L2ERC721.deploy( - nftSymbol, - nftName - ) - await L2ERC721.deployTransaction.wait() - console.log("Marc's BioBase NFT L2ERC721 deployed to:", L2ERC721.address) }) @@ -209,8 +185,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { L2DepositedERC20: L2DepositedERC20.address, L1ERC20Gateway: L1ERC20Gateway.address, l1ETHGatewayAddress: env.L1ETHGateway.address, - l1MessengerAddress: env.l1MessengerAddress, - L2ERC721: L2ERC721.address + l1MessengerAddress: env.l1MessengerAddress } console.log(JSON.stringify(addresses, null, 2)) @@ -245,26 +220,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { preERC20Balances.add(transferAmount) ) }) -/* - it('should mint a new ERC721 and transfer it from Bob to Alice', async () => { - const transferAmount = utils.parseEther("500") - - //L1ERC20 is owned by Bob - const preERC20Balances = await L1ERC20.balanceOf(env.alicel1Wallet.address); - const transferERC20TX = await L1ERC20.transfer( - env.alicel1Wallet.address, - transferAmount, - ) - await transferERC20TX.wait() - - const postERC20Balance = await L1ERC20.balanceOf(env.alicel1Wallet.address); - - expect(postERC20Balance).to.deep.eq( - preERC20Balances.add(transferAmount) - ) - }) -*/ it('should add initial ETH and ERC20 to the L1 Liquidity Pool', async () => { // ************************************************** @@ -274,6 +230,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { const addERC20Amount = utils.parseEther("500") const l1ProviderLL = providers.getDefaultProvider(process.env.L1_NODE_WEB3_URL) + // Add ETH const preETHBalances = await getBalances("0x0000000000000000000000000000000000000000") const preL1LPETHBalance = await l1ProviderLL.getBalance(L1LiquidityPool.address) @@ -374,7 +331,8 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { ) }) - it('should add initial oWETH and ERC20 to the L2 Liquidity Pool', async () => { + it('should add initial oETH and ERC20 to the L2 Liquidity Pool', async () => { + const depositL2oWETHAmount = utils.parseEther("5.1") const addoWETHAmount = utils.parseEther("5") const depositL2ERC20Amount = utils.parseEther("510") @@ -440,7 +398,7 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { it('should move ETH from L1 LP to L2', async () => { - const swapAmount = utils.parseEther("0.05") + const swapAmount = utils.parseEther("0.50") const preBalances = await getBalances("0x0000000000000000000000000000000000000000") //this triggers the receive @@ -463,8 +421,9 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { ) }) - it('should swap wETH from L2 LP to ETH in L1 user wallet', async () => { + it('should swap oETH from L2 LP to ETH in L1 user wallet', async () => { + //basically, the swap-exit const swapAmount = utils.parseEther("0.05") const preBalances = await getBalances(env.L2ETHGateway.address) @@ -491,4 +450,5 @@ describe('Token, Bridge, and Swap Pool Setup and Test', async () => { preBalances.L2LPFeeBalance.add(swapAmount.mul(3).div(100)) ) }) + }) \ No newline at end of file diff --git a/test/b_nft.spec.ts b/test/b_nft.spec.ts new file mode 100644 index 000000000000..2816b152b81a --- /dev/null +++ b/test/b_nft.spec.ts @@ -0,0 +1,154 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +chai.use(chaiAsPromised) +const expect = chai.expect; + +import { Contract, ContractFactory, BigNumber, Wallet, utils, providers } from 'ethers' +import { Direction } from './shared/watcher-utils' +import L2ERC721Json from '../artifacts-ovm/contracts/ERC721Mock.sol/ERC721Mock.json' +import { OptimismEnv } from './shared/env' + +import * as fs from 'fs' + +describe('NFT Test', async () => { + + let Factory__L2ERC721: ContractFactory + let L2ERC721: Contract + + let env: OptimismEnv + + //Test Marc's BioBase NFT system + const nftName = 'BioBase' + const nftSymbol = 'BEE' //BioEconomy Explodes + + const getBalances = async ( + _address: string, + _env=env + ) => { + + const aliceL1Balance = await _env.alicel1Wallet.getBalance() + const aliceL2Balance = await _env.alicel2Wallet.getBalance() + + const bobL1Balance = await _env.bobl1Wallet.getBalance() + const bobL2Balance = await _env.bobl2Wallet.getBalance() + + console.log("\nbobL1Balance:", bobL1Balance.toString()) + console.log("bobL2Balance:", bobL2Balance.toString()) + console.log("aliceL1Balance:", aliceL1Balance.toString()) + console.log("aliceL2Balance:", aliceL2Balance.toString()) + + return { + aliceL1Balance, + aliceL2Balance, + bobL1Balance, + bobL2Balance, + } + } + + /************* BOB owns all the pools, and ALICE Mints a new token ***********/ + before(async () => { + + env = await OptimismEnv.new() + + Factory__L2ERC721 = new ContractFactory( + L2ERC721Json.abi, + L2ERC721Json.bytecode, + env.bobl2Wallet + ) + + }) + + before(async () => { + + // Mint a new NFT on L2 + // [nftSymbol, nftName] + // this is owned by bobl1Wallet + L2ERC721 = await Factory__L2ERC721.deploy( + nftSymbol, + nftName, + BigNumber.from(String(0)) //starting index for the tokenIDs + ) + await L2ERC721.deployTransaction.wait() + console.log("Marc's BioBase NFT L2ERC721 deployed to:", L2ERC721.address) + + }) + + before(async () => { + + fs.readFile('./deployment/local/addresses.json', 'utf8' , (err, data) => { + + if (err) { + console.error(err) + return + } + + const addressArray = JSON.parse(data); + + //this will either update or overwrite, depending, but either is fine + addressArray['L2ERC721'] = L2ERC721.address; + + fs.writeFile('./deployment/local/addresses.json', JSON.stringify(addressArray, null, 2), err => { + if (err) { + console.log('Error adding NFT address to file:', err) + } else { + console.log('Successfully added NFT address to file') + } + }) + }) + + }) + + it('should mint a new ERC721 and transfer it from Bob to Alice', async () => { + + const owner = env.bobl2Wallet.address; + const recipient = env.alicel2Wallet.address; + + const ownerName = "Henrietta Lacks" + + //for some strange reason need a string here + //no idea why that matters + const tokenID = BigNumber.from(String(50)); + + let meta = ownerName + "#" + Date.now().toString() + '#https://www.atcc.org/products/all/CCL-2.aspx'; + console.log("meta:",meta) + + //mint one NFT + let nft = await L2ERC721.mintNFT(recipient,meta) + await nft.wait() + //console.log("ERC721:",nft) + + const balanceOwner = await L2ERC721.balanceOf(owner) + const balanceRecipient = await L2ERC721.balanceOf(recipient) + + console.log("balanceOwner:",balanceOwner.toString()) + console.log("balanceRecipient:",balanceRecipient.toString()) + + //Get the URL + let nftURL = await L2ERC721.getTokenURI(BigNumber.from(String(0))) + console.log("nftURL:",nftURL) + + //Should be 1 + let TID = await L2ERC721.getLastTID() + console.log("TID:",TID.toString()) + + //mint a second NFT + meta = ownerName + "#" + Date.now().toString() + '#https://www.atcc.org/products/all/CCL-185.aspx'; + nft = await L2ERC721.mintNFT(recipient,meta) + await nft.wait() + + //Should be 2 + TID = await L2ERC721.getLastTID() + console.log("TID:",TID.toString()) + + //it('returns the amount of tokens owned by the given address', async function () { + expect(await L2ERC721.balanceOf(owner)).to.deep.eq(BigNumber.from(String(0))); + //}); + + // Token 1 should be owned by recipient + expect(await L2ERC721.ownerOf(BigNumber.from(String(1)))).to.deep.eq(recipient); + + //And Token 50 should not exist (at this point) + expect(L2ERC721.ownerOf(BigNumber.from(String(50)))).to.be.eventually.rejectedWith("ERC721: owner query for nonexistent token"); + }) + +}) \ No newline at end of file diff --git a/test/shared/env.ts b/test/shared/env.ts index 0cc3013eb615..f7cbeeda5b74 100644 --- a/test/shared/env.ts +++ b/test/shared/env.ts @@ -34,6 +34,8 @@ export class OptimismEnv { l1MessengerAddress: String ctc: Contract + l2Provider + // L2 Contracts L2ETHGateway: Contract l2Messenger: Contract @@ -60,6 +62,7 @@ export class OptimismEnv { this.bobl2Wallet = args.bobl2Wallet this.alicel1Wallet = args.alicel1Wallet this.alicel2Wallet = args.alicel2Wallet + this.l2Provider = args.l2Provider this.ctc = args.ctc } @@ -100,7 +103,8 @@ export class OptimismEnv { bobl1Wallet, bobl2Wallet, alicel1Wallet, - alicel2Wallet + alicel2Wallet, + l2Provider }) } diff --git a/wallet/package.json b/wallet/package.json index 87f6f36105f3..28ad7f396b86 100644 --- a/wallet/package.json +++ b/wallet/package.json @@ -18,8 +18,6 @@ "bn.js": "^5.1.3", "buffer": "^6.0.3", "dotenv": "^8.2.0", - "elliptic": "^6.5.4", - "enyafhe": "git+https://github.com/enyalabs/EnyaFHE.git#4d0b48e", "eth-sig-util": "^3.0.1", "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^7.0.5", diff --git a/wallet/src/actions/nftAction.js b/wallet/src/actions/nftAction.js new file mode 100644 index 000000000000..1ff0285555a8 --- /dev/null +++ b/wallet/src/actions/nftAction.js @@ -0,0 +1,55 @@ +/* +Copyright 2019-present OmiseGO Pte Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +import store from 'store'; + +export async function getNFTs () { + const state = store.getState() + return state.nftList; +} + +/* +Get the token info from networkService.web3.eth.Contract +*/ +export async function addNFT ( NFTproperties ) { + + const state = store.getState(); + const UUID = NFTproperties.UUID; + + //if we already have looked it up, no need to look up again. + if (state.nftList[UUID]) { + return state.nftList[UUID]; + } + + const nftInfo = { + UUID: NFTproperties.UUID, + owner: NFTproperties.owner, + url: NFTproperties.url, + mintedTime: NFTproperties.mintedTime, + decimals: 0, + name: NFTproperties.name, + symbol: NFTproperties.symbol, + }; + + //console.log("nftInfo0:",nftInfo) + + store.dispatch({ + type: 'NFT/GET/SUCCESS', + payload: nftInfo + }) + + return nftInfo; + +} \ No newline at end of file diff --git a/wallet/src/components/nft/NftCard.js b/wallet/src/components/nft/NftCard.js new file mode 100644 index 000000000000..a63006184353 --- /dev/null +++ b/wallet/src/components/nft/NftCard.js @@ -0,0 +1,57 @@ +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardActionArea from '@material-ui/core/CardActionArea'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import CardMedia from '@material-ui/core/CardMedia'; +import Button from '@material-ui/core/Button'; +import Typography from '@material-ui/core/Typography'; + +import * as styles from './NftCard.module.scss'; + +import cells from 'images/hela.jpg'; + +function NFTCard({ UUID, owner, URL, time, name, symbol }) { + + return ( + + + + + + {name} + + + {symbol} + + + NFT ID: {UUID}
+ Owner: {owner}
+ Time Minted: {time}
+
+ + + LINK to datasheet + + +
+
+ + + + +
+ ); +} + +export default NFTCard; \ No newline at end of file diff --git a/wallet/src/components/nft/NftCard.module.scss b/wallet/src/components/nft/NftCard.module.scss new file mode 100644 index 000000000000..a88c2fba36db --- /dev/null +++ b/wallet/src/components/nft/NftCard.module.scss @@ -0,0 +1,42 @@ +@import 'index.scss'; + +.container { + padding-right: 20px; + + .boxContainer{ + display: inline-block; + max-width: 500px; + padding: 20px; + padding-top: 0px; + vertical-align: top; + + .button { + max-width: 500px; + margin-top: 10px; + margin-bottom: 10px; + } + .select { + flex: 1 1; + background-color: #d2d2d2; + color: #565656; + font-size: 1em; + border: none; + outline: none; + border: 1px solid #d2d2d2; + background-color: #F8F8F8; + padding: 10px; + width: 100%; + max-width: 500px; + } + } +} + +.NFTroot { + flexGrow: 1; + padding: 10px; + maxWidth: 345px; +} + +.NFTmedia { + height: 140px; +} \ No newline at end of file diff --git a/wallet/src/components/walletpicker/WalletPicker.js b/wallet/src/components/walletpicker/WalletPicker.js index 9e10f8bdc65a..0245071cc869 100644 --- a/wallet/src/components/walletpicker/WalletPicker.js +++ b/wallet/src/components/walletpicker/WalletPicker.js @@ -24,7 +24,6 @@ import { selectModalState } from 'selectors/uiSelector'; import { selectWalletMethod, selectNetwork, - //selectLayer, } from 'selectors/setupSelector'; import { openModal } from 'actions/uiAction'; @@ -48,21 +47,16 @@ function WalletPicker ({ onEnable }) { const walletMethod = useSelector(selectWalletMethod()) const networkName = useSelector(selectNetwork()) - //const netLayer = useSelector(selectLayer()) - - // console.log("walletMethod:",walletMethod) - console.log("networkName:",networkName) - //console.log("netLayer:",netLayer) const wrongNetworkModalState = useSelector(selectModalState('wrongNetworkModal')); const dispatchSetWalletMethod = useCallback((methodName) => { - console.log("dispatchSetWalletMethod:",methodName) + //console.log("dispatchSetWalletMethod:",methodName) dispatch(setWalletMethod(methodName)); }, [ dispatch ]) const dispatchSetNetwork = useCallback((network) => { - console.log("dispatchSetNetwork:",network) + //console.log("dispatchSetNetwork:",network) setShowAllNetworks(false); dispatch(setNetwork(network)); }, [ dispatch ]) @@ -74,10 +68,10 @@ function WalletPicker ({ onEnable }) { } async function enableBrowserWallet () { - console.log("enableBrowserWallet() for",networkName) + //console.log("enableBrowserWallet() for",networkName) const selectedNetwork = networkName ? networkName : "local"; const walletEnabled = await networkService.enableBrowserWallet(selectedNetwork); - console.log("walletEnabled:",walletEnabled) + //console.log("walletEnabled:",walletEnabled) return walletEnabled ? setWalletEnabled(true) : dispatchSetWalletMethod(null); @@ -89,7 +83,7 @@ function WalletPicker ({ onEnable }) { async function initializeAccounts () { - console.log("initializeAccounts() for:",networkName) + //console.log("initializeAccounts() for:",networkName) const initialized = await networkService.initializeAccounts(networkName); diff --git a/wallet/src/containers/home/Home.js b/wallet/src/containers/home/Home.js index c115ab82271c..243202adebe8 100644 --- a/wallet/src/containers/home/Home.js +++ b/wallet/src/containers/home/Home.js @@ -44,6 +44,8 @@ import Status from 'containers/status/Status'; import Account from 'containers/account/Account'; import Transactions from 'containers/transactions/Transactions'; +//NFT Example Page +import NFT from 'containers/nft/Nft'; import MobileHeader from 'components/mobileheader/MobileHeader'; import MobileMenu from 'components/mobilemenu/MobileMenu'; @@ -147,6 +149,12 @@ function Home () { > Wallet +

{handleSetPage("NFT")}} + > + NFT +

{pageDisplay === "AccountNow" && <> @@ -154,6 +162,9 @@ function Home () { } + {pageDisplay === "NFT" && + + } diff --git a/wallet/src/containers/nft/Nft.js b/wallet/src/containers/nft/Nft.js new file mode 100644 index 000000000000..af1a1a3752e1 --- /dev/null +++ b/wallet/src/containers/nft/Nft.js @@ -0,0 +1,143 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Input from 'components/input/Input'; +import NFTCard from 'components/nft/NftCard'; +import Button from 'components/button/Button'; +import { openError, openAlert } from 'actions/uiAction'; +import networkService from 'services/networkService'; +import { Grid } from '@material-ui/core/' + +import * as styles from './Nft.module.scss'; + +class Nft extends React.Component { + + constructor(props) { + + super(props); + + const { nftList } = this.props; + + this.state = { + NFTs: nftList, + loading: false, + receiverAddress: '', + tokenID: 1, + tokenURI: '' + } + } + + componentDidMount() { + //ToDo + } + + componentDidUpdate(prevState) { + //ToDo + } + + async handleMintAndSend() { + + const { receiverAddress, tokenID, tokenURI } = this.state; + + const networkStatus = await this.props.dispatch(networkService.checkNetwork('L2')); + + if (!networkStatus) { + this.props.dispatch(openError('Please use L2 network.')); + return; + } + + this.setState({ loading: true }); + + const mintTX = await networkService.mintAndSendNFT( + receiverAddress, + tokenID, + tokenURI + ); + + if (mintTX) { + this.props.dispatch(openAlert(`You minted a new NFT for ${receiverAddress}`)); + } else { + this.props.dispatch(openError('NFT minting error')); + } + + this.setState({ loading: false }) + } + + render() { + + const { + loading, + receiverAddress, + tokenID, + tokenURI, + NFTs, + } = this.state; + + return ( + +
+
+ +

Minter/Owner Functions

+ +

Mint and Send (Contract Owner Only)

+ + {this.setState({receiverAddress: i.target.value})}} + /> + {this.setState({tokenID: i.target.value})}} + /> + {this.setState({tokenURI: i.target.value})}} + /> + + +

My NFTs

+ +
+ + {Object.keys(NFTs).map(elem => ( + + + + + ))} + +
+ +
+ +
+ ) + } +} + +const mapStateToProps = state => ({ + nftList: state.nftList +}); + +export default connect(mapStateToProps)(Nft); \ No newline at end of file diff --git a/wallet/src/containers/pool/Pool.module.scss b/wallet/src/containers/nft/Nft.module.scss similarity index 86% rename from wallet/src/containers/pool/Pool.module.scss rename to wallet/src/containers/nft/Nft.module.scss index ba2926115baf..56a2751e723d 100644 --- a/wallet/src/containers/pool/Pool.module.scss +++ b/wallet/src/containers/nft/Nft.module.scss @@ -29,5 +29,14 @@ max-width: 500px; } } +} +.NFTroot { + flexGrow: 1; + padding: 10px; + maxWidth: 345; +} + +.NFTmedia { + height: 140; } \ No newline at end of file diff --git a/wallet/src/containers/pool/Pool.js b/wallet/src/containers/pool/Pool.js deleted file mode 100644 index b0dc9739650d..000000000000 --- a/wallet/src/containers/pool/Pool.js +++ /dev/null @@ -1,579 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { isEqual } from 'lodash'; - -import InputSelect from 'components/inputselect/InputSelect'; -import Input from 'components/input/Input'; -import Button from 'components/button/Button'; - -import { openError, openAlert } from 'actions/uiAction'; - -import { logAmount } from 'util/amountConvert'; - -import networkService from 'services/networkService'; - -import * as styles from './Pool.module.scss'; - -class Pool extends React.Component { - constructor(props) { - super(props); - - const { balance } = this.props; - - this.state = { - balance, - - initialL1Value: '', - initialL1Currency: '', - L1Value: '', - L1Currency: '', - LPL1SearchToken: '', - LPL1Balance: '', - LPL1FeeSearchToken: '', - LPL1FeeBalance: '', - L1FeeWithdrawToken: '', - L1FeeReceiverAddress: '', - L1FeeWithdrawAmount: '', - - initialL2Value: '', - initialL2Currency: '', - L2Value: '', - L2Currency: '', - LPL2SearchToken: '', - LPL2Balance: '', - LPL2FeeSearchToken: '', - LPL2FeeBalance: '', - L2FeeWithdrawToken: '', - L2FeeReceiverAddress: '', - L2FeeWithdrawAmount: '', - - loading: false, - } - } - - componentDidMount() { - const { balance } = this.props; - if (balance.rootchain.length && balance.childchain.length) { - const L1Token = balance.rootchain.filter(i => i.symbol !== 'WETH')[0]; - const L2Token = balance.childchain.filter(i => i.symbol !== 'ETH')[0]; - this.setState({ - initialL1Currency: L1Token.currency, - initialL2Currency: L2Token.currency, - L1Currency : L1Token.currency, - L2Currency : L2Token.currency, - LPL1SearchToken : L1Token, - LPL2SearchToken : L2Token, - LPL1FeeSearchToken : L1Token, - LPL2FeeSearchToken : L2Token, - L1FeeWithdrawToken : L1Token, - L2FeeWithdrawToken : L2Token - }); - } - } - - componentDidUpdate(prevState) { - const { balance } = this.props; - const { - initialL1Currency, initialL2Currency, - L1Currency, L2Currency, - LPL1SearchToken, LPL2SearchToken, - LPL1FeeSearchToken, LPL2FeeSearchToken, - L1FeeWithdrawToken, L2FeeWithdrawToken, - } = this.state; - - const L1Token = balance.rootchain.filter(i => i.symbol !== 'WETH')[0]; - const L2Token = balance.childchain.filter(i => i.symbol !== 'ETH')[0]; - if (!isEqual(prevState.balance, balance)) { - this.setState({ balance }); - if (!initialL1Currency) this.setState({initialL1Currency : L1Token.currency}); - if (!initialL2Currency) this.setState({initialL2Currency : L2Token.currency}); - if (!L1Currency) this.setState({L1Currency : L1Token.currency}); - if (!L2Currency) this.setState({L2Currency : L2Token.currency}); - if (!LPL1SearchToken) this.setState({LPL1SearchToken : L1Token}); - if (!LPL2SearchToken) this.setState({LPL2SearchToken : L2Token}); - if (!LPL1FeeSearchToken) this.setState({LPL1FeeSearchToken : L1Token}); - if (!LPL2FeeSearchToken) this.setState({LPL2FeeSearchToken : L2Token}); - if (!L1FeeWithdrawToken) this.setState({L1FeeWithdrawToken : L1Token}); - if (!L2FeeWithdrawToken) this.setState({L2FeeWithdrawToken : L2Token}); - } - } - - async handleInitialDepositL1() { - const { initialL1Value, initialL1Currency } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L1')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L1 network.')); - return; - } - - this.setState({ loading: true }); - const depositTX = await networkService.initialDepositL1LP(initialL1Currency, initialL1Value); - this.setState({ loading: false }); - if (depositTX) { - this.props.dispatch(openAlert(`Deposited ${initialL1Value} ${initialL1Currency} into L1 Liquidity Pool.`)) - } else { - this.props.dispatch(openError('Unknown error')); - } - } - - async handleInitialDepositL2() { - const { initialL2Value, initialL2Currency } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L2')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L2 network.')); - return; - } - - this.setState({ loading: true }); - const depositTX = await networkService.initialDepositL2LP(initialL2Currency, initialL2Value); - this.setState({ loading: false }); - if (depositTX) { - this.props.dispatch(openAlert(`Deposited ${initialL2Value} ${initialL2Currency} into L2 Liquidity Pool.`)) - } else { - this.props.dispatch(openError('Unknown error')); - } - } - - async handleDepositL1() { - const { L1Value, L1Currency } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L1')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L1 network.')); - return; - } - - this.setState({ loading: true }); - const depositTX = await networkService.depositL1LP(L1Currency, L1Value); - this.setState({ loading: false }); - if (depositTX) { - this.props.dispatch(openAlert(`Deposited ${L1Value} ${L1Currency} into L1 Liquidity Pool.`)) - } else { - this.props.dispatch(openError('L2 Liquidity Pool doesn\'t have enough balance to cover this.')); - } - } - - async handleDepositL2() { - const { L2Value, L2Currency } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L2')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L2 network.')); - return; - } - - this.setState({ loading: true }); - const depositTX = await networkService.depositL2LP(L2Currency, L2Value); - if (depositTX) { - this.setState({ loading: false }); - } - } - - async handleBalanceL1() { - const { LPL1SearchToken } = this.state; - const balanceTX = await networkService.L1LPBalance(LPL1SearchToken.currency); - if (balanceTX) { - this.setState({ LPL1Balance: balanceTX }); - } - } - - async handleBalanceL2() { - const { LPL2SearchToken } = this.state; - const balanceTX = await networkService.L2LPBalance(LPL2SearchToken.currency); - console.log({ balanceTX }); - if (balanceTX) { - this.setState({ LPL2Balance: balanceTX }); - } - } - - async handleFeeBalanceL1() { - const { LPL1FeeSearchToken } = this.state; - const balanceTX = await networkService.L1LPFeeBalance(LPL1FeeSearchToken.currency); - if (balanceTX) { - this.setState({ LPL1FeeBalance: balanceTX }); - } - } - - async handleFeeBalanceL2() { - const { LPL2FeeSearchToken } = this.state; - const balanceTX = await networkService.L2LPFeeBalance(LPL2FeeSearchToken.currency); - if (balanceTX) { - this.setState({ LPL2FeeBalance: balanceTX }); - } - } - - async handleWithdrawFeeL1() { - const { L1FeeWithdrawToken, L1FeeReceiverAddress, L1FeeWithdrawAmount } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L1')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L1 network.')); - return; - } - - this.setState({ loading: true }); - const withdrawTX = await networkService.L1LPWithdrawFee( - L1FeeWithdrawToken.currency, - L1FeeReceiverAddress, - L1FeeWithdrawAmount - ); - - if (withdrawTX) { - this.props.dispatch(openAlert(`You withdraw ${L1FeeWithdrawAmount} ${L1FeeWithdrawToken.symbol}.`)); - } else { - this.props.dispatch(openError('You don\'t have enough fee amount to be withdrew')); - } - this.setState({ loading: false }) - } - - async handleWithdrawFeeL2() { - const { L2FeeWithdrawToken, L2FeeReceiverAddress, L2FeeWithdrawAmount } = this.state; - - const networkStatus = await this.props.dispatch(networkService.checkNetwork('L2')); - if (!networkStatus) { - this.props.dispatch(openError('Please use L2 network.')); - return; - } - - this.setState({ loading: true }); - const withdrawTX = await networkService.L2LPWithdrawFee( - L2FeeWithdrawToken.currency, - L2FeeReceiverAddress, - L2FeeWithdrawAmount - ); - - if (withdrawTX) { - this.props.dispatch(openAlert(`You withdraw ${L2FeeWithdrawAmount} ${L2FeeWithdrawToken.symbol}.`)); - } else { - this.props.dispatch(openError('You don\'t have enough fee amount to be withdrew')); - } - this.setState({ loading: false }) - } - - render() { - const { - balance, - - initialL1Value, initialL1Currency, - L1Value, L1Currency, - LPL1SearchToken, LPL1Balance, - LPL1FeeSearchToken, LPL1FeeBalance, - L1FeeReceiverAddress, L1FeeWithdrawAmount, - - initialL2Value, initialL2Currency, - L2Value, L2Currency, - LPL2SearchToken, LPL2Balance, - LPL2FeeSearchToken, LPL2FeeBalance, - L2FeeReceiverAddress, L2FeeWithdrawAmount, - - loading - } = this.state; - - const rootChainBalance = balance.rootchain; - const selectL1Options = rootChainBalance.map(i => ({ - title: i.symbol, - value: i.currency, - subTitle: `Balance: ${logAmount(i.amount, i.decimals)}` - })).filter(i => i.title !== 'WETH'); - - const childChainBalance = balance.childchain; - const selectL2Options = childChainBalance.map(i => ({ - title: i.symbol, - value: i.currency, - subTitle: `Balance: ${logAmount(i.amount, i.decimals)}` - })).filter(i => i.title !== 'ETH'); - - return ( -
-
-

Layer 1 Liquidity Pool

-

Initial Deposit L1

- {this.setState({initialL1Value: i.target.value})}} - selectOptions={selectL1Options} - onSelect={i => {this.setState({initialL1Currency: i.target.value})}} - selectValue={initialL1Currency} - /> - - -

Deposit L1

- {this.setState({L1Value: i.target.value})}} - selectOptions={selectL1Options} - onSelect={i => {this.setState({L1Currency: i.target.value})}} - selectValue={L1Currency} - /> - - - -

L1 Liquidity Pool Balance

- - - - - {LPL1Balance && -

- {`The L1 liquidity pool has ${LPL1Balance} ${LPL1SearchToken.symbol}.`} -

- } - -

L1 Liquidity Pool Fee Balance

- - - - - {LPL1FeeBalance && -

- {`The L1 liquidity pool has ${LPL1FeeBalance} ${LPL1FeeSearchToken.symbol}.`} -

- } - -

Withdraw L1 Liquidity Pool Fee

-

Fee

- -

Receiver

- {this.setState({ L1FeeReceiverAddress: i.target.value })}} - /> -

Amount

- {this.setState({ L1FeeWithdrawAmount: i.target.value })}} - /> - - -
- -
-

Layer 2 Liquidity Pool

-

Initial Deposit L2

- {this.setState({initialL2Value: i.target.value})}} - selectOptions={selectL2Options} - onSelect={i => {this.setState({initialL2Currency: i.target.value})}} - selectValue={initialL2Currency} - /> - - -

Deposit L2

- {this.setState({L2Value: i.target.value})}} - selectOptions={selectL2Options} - onSelect={i => {this.setState({L2Currency: i.target.value})}} - selectValue={L2Currency} - /> - - - -

L2 Liquidity Pool Balance

- - - - - {LPL2Balance && -

- {`The L2 liquidity pool has ${LPL2Balance} ${LPL2SearchToken.symbol}.`} -

- } - -

L2 Liquidity Pool Fee Balance

- - - - - {LPL2FeeBalance && -

- {`The L2 liquidity pool has ${LPL2FeeBalance} ${LPL2FeeSearchToken.symbol}.`} -

- } - -

Withdraw L2 Liquidity Pool Fee

-

Fee

- -

Receiver

- {this.setState({ L2FeeReceiverAddress: i.target.value })}} - /> -

Amount

- {this.setState({ L2FeeWithdrawAmount: i.target.value })}} - /> - -
- -
- ) - } -} - -const mapStateToProps = state => ({ - login: state.login, - sell: state.sell, - sellTask: state.sellTask, - balance: state.balance, - transaction: state.transaction, - tokenList: state.tokenList -}); - -export default connect(mapStateToProps)(Pool); \ No newline at end of file diff --git a/wallet/src/images/hela.jpg b/wallet/src/images/hela.jpg new file mode 100644 index 000000000000..03f6cf182a3c Binary files /dev/null and b/wallet/src/images/hela.jpg differ diff --git a/wallet/src/reducers/index.js b/wallet/src/reducers/index.js index 76afa5a3c3c5..73b98fbd84c9 100644 --- a/wallet/src/reducers/index.js +++ b/wallet/src/reducers/index.js @@ -23,6 +23,7 @@ import balanceReducer from './balanceReducer'; import exitReducer from './exitReducer'; import queueReducer from './queueReducer'; import tokenReducer from './tokenReducer'; +import nftReducer from './nftReducer'; import feeReducer from './feeReducer'; import gasReducer from './gasReducer'; import uiReducer from './uiReducer'; @@ -38,6 +39,7 @@ const rootReducer = combineReducers({ exit: exitReducer, queue: queueReducer, tokenList: tokenReducer, + nftList: nftReducer, fees: feeReducer, gas: gasReducer, ui: uiReducer, diff --git a/wallet/src/reducers/nftReducer.js b/wallet/src/reducers/nftReducer.js new file mode 100644 index 000000000000..71b43cbaa76f --- /dev/null +++ b/wallet/src/reducers/nftReducer.js @@ -0,0 +1,31 @@ +/* +Copyright 2019-present OmiseGO Pte Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +const initialState = { +}; + +function nftReducer (state = initialState, action) { + switch (action.type) { + case 'NFT/GET/SUCCESS': + return { + ...state, + [action.payload.UUID]: action.payload, + }; + default: + return state; + } +} + +export default nftReducer; diff --git a/wallet/src/services/networkService.js b/wallet/src/services/networkService.js index 7f62209f53d4..416247e06d94 100644 --- a/wallet/src/services/networkService.js +++ b/wallet/src/services/networkService.js @@ -19,7 +19,7 @@ import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"; import { hexlify } from "@ethersproject/bytes"; import { parseUnits, parseEther } from "@ethersproject/units"; import { Watcher } from "@eth-optimism/watcher"; -import { ethers } from "ethers"; +import { ethers, BigNumber } from "ethers"; import Web3Modal from "web3modal"; @@ -30,6 +30,7 @@ import BN from 'bn.js'; import Web3 from 'web3'; import { getToken } from 'actions/tokenAction'; +import { getNFTs, addNFT } from 'actions/nftAction'; import { openAlert, openError } from 'actions/uiAction'; import { WebWalletError } from 'services/errorService'; @@ -38,6 +39,7 @@ import L2LPJson from '../deployment/artifacts-ovm/contracts/L2LiquidityPool.sol/ import L1ERC20Json from '../deployment/artifacts/contracts/ERC20.sol/ERC20.json' import L2DepositedERC20Json from '../deployment/artifacts-ovm/contracts/L2DepositedERC20.sol/L2DepositedERC20.json' import L1ERC20GatewayJson from '../deployment/artifacts/contracts/L1ERC20Gateway.sol/L1ERC20Gateway.json' +import ERC721Json from '../deployment/artifacts-ovm/contracts/ERC721Mock.sol/ERC721Mock.json' import { powAmount, logAmount } from 'util/amountConvert'; import { getAllNetworks } from 'util/networkName'; @@ -81,6 +83,8 @@ class NetworkService { this.ERC20L1Contract = null; this.ERC20L2Contract = null; + this.ERC721Contract = null; + // L1 or L2 this.L1orL2 = null; this.networkName = null; @@ -90,6 +94,7 @@ class NetworkService { // addresses this.ERC20Address = null; + this.ERC721Address = null; this.l1ETHGatewayAddress = null; this.L1ERC20GatewayAddress = null; this.L2DepositedERC20Address = null; @@ -138,6 +143,7 @@ class NetworkService { async initializeAccounts ( networkName ) { console.log("NS: initializeAccounts() for",networkName) + try { let addresses; if (networkName === 'local') addresses = localAddresses; @@ -150,9 +156,9 @@ class NetworkService { this.chainID = network.chainId; this.networkName = networkName; - console.log("NS: networkName:",this.networkName) - console.log("NS: account:",this.account) - console.log("NS: network:",network) + //console.log("NS: networkName:",this.networkName) + //console.log("NS: account:",this.account) + //console.log("NS: network:",network) //there are numerous possible chains we could be on //either local, rinkeby etc @@ -199,6 +205,7 @@ class NetworkService { this.l1MessengerAddress = addresses.l1MessengerAddress; this.L1LPAddress = addresses.L1LiquidityPool; this.L2LPAddress = addresses.L2LiquidityPool; + this.ERC721Address = addresses.L2ERC721; this.L1ETHGatewayContract = new ethers.Contract( this.l1ETHGatewayAddress, @@ -248,6 +255,12 @@ class NetworkService { this.web3Provider.getSigner(), ); + this.ERC721Contract = new ethers.Contract( + this.ERC721Address, + ERC721Json.abi, + this.web3Provider.getSigner(), + ); + //Fire up the new watcher //const addressManager = getAddressManager(bobl1Wallet) //const watcher = await initWatcher(l1Provider, this.l2Provider, addressManager) @@ -350,6 +363,65 @@ class NetworkService { const childChainBalance = await this.l2Provider.getBalance(this.account); const ERC20L2Balance = await this.ERC20L2Contract.methods.balanceOf(this.account).call({from: this.account}); + //const ERC721L2Balance = 0; //await this.ERC721Contract.balanceOf(this.account); + + // //how many NFTs do I own? + const ERC721L2Balance = await this.ERC721Contract.balanceOf(this.account) + + //console.log("ERC721L2Balance",ERC721L2Balance) + //console.log("this.account",this.account) + //console.log(this.ERC721Contract) + + //let see if we already know about them + const myNFTS = await getNFTs() + const numberOfNFTS = Object.keys(myNFTS).length; + //console.log(myNFTS) + + //console.log(ERC721L2Balance.toString()) + //console.log(numberOfNFTS.toString()) + + if(ERC721L2Balance.toNumber() !== numberOfNFTS) { + + //oh - something just changed - either got one, or sent one + console.log("NFT change detected!") + + //we need to do something + //get the first one + + let tokenID = null + let nftTokenIDs = null + let nftMeta = null + let meta = null + + //always the same, no need to have in the loop + let nftName = await this.ERC721Contract.getName() + let nftSymbol = await this.ERC721Contract.getSymbol() + + for (var i = 0; i < ERC721L2Balance.toNumber(); i++) { + + tokenID = BigNumber.from(i) + nftTokenIDs = await this.ERC721Contract.tokenOfOwnerByIndex(this.account, tokenID) + nftMeta = await this.ERC721Contract.getTokenURI(tokenID) + meta = nftMeta.split("#") + + const time = new Date(parseInt(meta[1])); + + addNFT({ + UUID: this.ERC721Address.substring(1, 6) + "_" + nftTokenIDs.toString() + "_" + this.account.substring(1, 6), + owner: meta[0], + mintedTime: String(time.toLocaleString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })), + url: meta[2], + tokenID: tokenID, + name: nftName, + symbol: nftSymbol + }) + } + + } else { + //console.log("No NFT changes") + //all set - do nothing + } + const ethToken = await getToken(OmgUtil.transaction.ETH_CURRENCY); let testToken = null;