├── .gitignore ├── README.md ├── artwork.json ├── package.json ├── packages ├── hardhat │ ├── .eslintrc.js │ ├── contracts │ │ └── YourCollectible.sol │ ├── hardhat.config.js │ ├── package.json │ └── scripts │ │ ├── deploy.js │ │ ├── publish.js │ │ ├── upload.js │ │ └── watch.js └── react-app │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .prettierrc │ ├── .sample.env │ ├── gulpfile.js │ ├── package.json │ ├── public │ ├── dark-theme.css │ ├── favicon.ico │ ├── index.html │ ├── light-theme.css │ ├── manifest.json │ └── robots.txt │ ├── scripts │ ├── ipfs.js │ ├── s3.js │ └── watch.js │ └── src │ ├── App.css │ ├── App.jsx │ ├── App.test.js │ ├── components │ ├── Account.jsx │ ├── Address.jsx │ ├── AddressInput.jsx │ ├── Balance.jsx │ ├── Blockie.jsx │ ├── BytesStringInput.jsx │ ├── Contract │ │ ├── DisplayVariable.jsx │ │ ├── FunctionForm.jsx │ │ ├── index.jsx │ │ └── utils.js │ ├── Faucet.jsx │ ├── GasGauge.jsx │ ├── Header.jsx │ ├── MaticInput.jsx │ ├── Provider.jsx │ ├── Ramp.jsx │ ├── ThemeSwitch.jsx │ ├── TokenBalance.jsx │ ├── Wallet.jsx │ └── index.js │ ├── constants.js │ ├── helpers │ ├── Transactor.js │ └── index.js │ ├── hooks │ ├── Balance.js │ ├── ContractExistsAtAddress.js │ ├── ContractLoader.js │ ├── ContractReader.js │ ├── CustomContractLoader.js │ ├── Debounce.js │ ├── EventListener.js │ ├── ExchangePrice.js │ ├── ExternalContractLoader.js │ ├── GasPrice.js │ ├── LocalStorage.js │ ├── LookupAddress.js │ ├── Nonce.js │ ├── Poller.js │ ├── ResolveName.js │ ├── TokenList.js │ ├── UserProvider.js │ └── index.js │ ├── index.css │ ├── index.jsx │ ├── setupTests.js │ └── themes │ ├── dark-theme.less │ └── light-theme.less └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | packages/hardhat/uploaded.json 2 | packages/react-app/src/assets.js 3 | packages/subgraph/subgraph.yaml 4 | packages/subgraph/generated 5 | packages/subgraph/abis 6 | packages/hardhat/*.txt 7 | **/aws.json 8 | 9 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 10 | **/node_modules 11 | packages/hardhat/artifacts 12 | packages/hardhat/deployments 13 | packages/react-app/src/contracts 14 | packages/hardhat/cache 15 | 16 | docker/**/data 17 | 18 | packages/subgraph/config/config.json 19 | tenderly.yaml 20 | 21 | # dependencies 22 | /node_modules 23 | /.pnp 24 | .pnp.js 25 | 26 | # testing 27 | coverage 28 | 29 | # production 30 | build 31 | 32 | # misc 33 | .DS_Store 34 | .env* 35 | 36 | # debug 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | .idea 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NFT's strength 2 | 3 | > Use Chainlink VRF to get a random strength for each NFT as it is minted. 4 | 5 | ## Quick Start 6 | 7 | required: [Node](https://nodejs.org/dist/latest-v12.x/) plus [Yarn](https://classic.yarnpkg.com/en/docs/install/) and [Git](https://git-scm.com/downloads) 8 | 9 | - Clone this repository 10 | 11 | ```bash 12 | 13 | yarn install 14 | 15 | ``` 16 | 17 | ```bash 18 | 19 | yarn start 20 | 21 | ``` 22 | 23 | > in a second terminal window: 24 | 25 | ```bash 26 | yarn chain 27 | 28 | ``` 29 | 30 | --- 31 | 32 | > Edit the artwork manifest `artwork.js` with all of your art, then upload it to IPFS: 33 | 34 | > in a third terminal window: 35 | 36 | 37 | ```bash 38 | yarn upload 39 | 40 | yarn deploy 41 | 42 | ``` 43 | 44 | Open http://localhost:3000 to see the app 45 | 46 | --- 47 | 48 | Your artwork from `artwork.json` (if uploaded and deployed correctly) should show a gallery of possible NFTS to mint: 49 | 50 | ![image](https://user-images.githubusercontent.com/2653167/110538535-5fe87980-80e1-11eb-83aa-fe2b53f9c277.png) 51 | 52 | 53 | Use the faucet wallet icon in the bottom left of the frontend to give your address **$1000** in testnet MATIC. 54 | 55 | --- 56 | 57 | 58 | 59 | This repo uses Chainlink's VRF on Mumbai. 60 | 61 | > First call `getRandomNumber()` from the `debug contracts` tab: 62 | 63 | ![image](https://user-images.githubusercontent.com/2653167/111365232-d93f1980-8657-11eb-933f-e4e408e2c3ab.png) 64 | 65 | > Wait for the `randomResult` to get set: 66 | 67 | ![image](https://user-images.githubusercontent.com/2653167/111365297-f247ca80-8657-11eb-9aed-d3867e489996.png) 68 | 69 | 70 | > Finally, mint from the `gallery` tab and your NFT will have a `tokenStrength`: 71 | 72 | ![image](https://user-images.githubusercontent.com/2653167/111365450-1e634b80-8658-11eb-938c-a2523586dfd4.png) 73 | 74 | 75 | --- 76 | 77 | 78 | Try to "Mint" an NFT: 79 | 80 | ![image](https://user-images.githubusercontent.com/2653167/110538992-ec933780-80e1-11eb-9d15-aaa7efea698d.png) 81 | 82 | 83 | Open an *incognito* window and navigate to http://localhost:3000 (You'll notice it has a new wallet address). 84 | 85 | Grab some gas for each account using the faucet: 86 | 87 | ![image](https://user-images.githubusercontent.com/2653167/109543971-35b10f00-7a84-11eb-832e-36d6b66afbe7.png) 88 | 89 | Send an NFT to the *incognito* window just to make sure it works. 90 | 91 | ## Deploy NFT smart contract! 92 | 93 | > Change the `defaultNetwork` in `packages/hardhat/hardhat.config.js` to `mumbai` 94 | 95 | Generate a deploy account with `yarn generate` 96 | 97 | 98 | View your deployer address using `yarn account` (You'll need to fund this account. Hint: use an [instant wallet](https://instantwallet.io) to fund your account via QR code) 99 | 100 | Thoroughly check your `artwork.json` file and run: 101 | 102 | ```bash 103 | 104 | yarn upload 105 | 106 | ``` 107 | 108 | Deploy your NFT smart contract: 109 | 110 | ```bash 111 | 112 | yarn deploy 113 | 114 | ``` 115 | --- 116 | --- 117 | 118 | > ✏️ Edit your frontend `App.jsx` in `packages/react-app/src` to change the `targetNetwork` to `mumbai` 119 | 120 | 121 | You should see the correct network in the frontend: 122 | 123 | ## Ship the app! 124 | 125 | > build and upload your frontend and share the url in the ETB's discord. 126 | 127 | ```bash 128 | 129 | # build it: 130 | 131 | yarn build 132 | 133 | # upload it: 134 | 135 | yarn surge 136 | 137 | yarn s3 138 | 139 | yarn ipfs 140 | ``` 141 | 142 | ### Original Credits 143 | - [Austin griffith's Scaffold ETH](https://github.com/scaffold-eth/scaffold-eth/tree/chainlink-vrf-nft) 144 | - [YT Video tutorial](https://youtu.be/63sXEPIEh-k?t=1773) -------------------------------------------------------------------------------- /artwork.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Buffalo", 4 | "description": "It's actually a bison?", 5 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=buffalo", 6 | "image": "https://austingriffith.com/images/paintings/buffalo.jpg", 7 | "attributes": [ 8 | { 9 | "trait_type": "BackgroundColor", 10 | "value": "green" 11 | }, 12 | { 13 | "trait_type": "Eyes", 14 | "value": "googly" 15 | }, 16 | { 17 | "trait_type": "Stamina", 18 | "value": 42 19 | } 20 | ] 21 | }, 22 | { 23 | "name": "Zebra", 24 | "description": "What is it so worried about?", 25 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=zebra", 26 | "image": "https://austingriffith.com/images/paintings/zebra.jpg", 27 | "attributes": [ 28 | { 29 | "trait_type": "BackgroundColor", 30 | "value": "blue" 31 | }, 32 | { 33 | "trait_type": "Eyes", 34 | "value": "googly" 35 | }, 36 | { 37 | "trait_type": "Stamina", 38 | "value": 38 39 | } 40 | ] 41 | }, 42 | { 43 | "name": "Rhino", 44 | "description": "What a horn!", 45 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=rhino", 46 | "image": "https://austingriffith.com/images/paintings/rhino.jpg", 47 | "attributes": [ 48 | { 49 | "trait_type": "BackgroundColor", 50 | "value": "pink" 51 | }, 52 | { 53 | "trait_type": "Eyes", 54 | "value": "googly" 55 | }, 56 | { 57 | "trait_type": "Stamina", 58 | "value": 22 59 | } 60 | ] 61 | }, 62 | { 63 | "name": "Fish", 64 | "description": "Is that an underbyte?", 65 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=fish", 66 | "image": "https://austingriffith.com/images/paintings/fish.jpg", 67 | "attributes": [ 68 | { 69 | "trait_type": "BackgroundColor", 70 | "value": "blue" 71 | }, 72 | { 73 | "trait_type": "Eyes", 74 | "value": "googly" 75 | }, 76 | { 77 | "trait_type": "Stamina", 78 | "value": 15 79 | } 80 | ] 81 | }, 82 | { 83 | "name": "Flamingo", 84 | "description": "So delicate.", 85 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=flamingo", 86 | "image": "https://austingriffith.com/images/paintings/flamingo.jpg", 87 | "attributes": [ 88 | { 89 | "trait_type": "BackgroundColor", 90 | "value": "black" 91 | }, 92 | { 93 | "trait_type": "Eyes", 94 | "value": "googly" 95 | }, 96 | { 97 | "trait_type": "Stamina", 98 | "value": 6 99 | } 100 | ] 101 | }, 102 | { 103 | "name": "Godzilla", 104 | "description": "Raaaar!", 105 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=godzilla", 106 | "image": "https://austingriffith.com/images/paintings/godzilla.jpg", 107 | "attributes": [ 108 | { 109 | "trait_type": "BackgroundColor", 110 | "value": "orange" 111 | }, 112 | { 113 | "trait_type": "Eyes", 114 | "value": "googly" 115 | }, 116 | { 117 | "trait_type": "Stamina", 118 | "value": 99 119 | } 120 | ] 121 | }, 122 | { 123 | "name": "Antelope", 124 | "description": "Speed goat.", 125 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=antelope", 126 | "image": "https://austingriffith.com/images/paintings/antelope.jpg", 127 | "attributes": [ 128 | { 129 | "trait_type": "BackgroundColor", 130 | "value": "purple" 131 | }, 132 | { 133 | "trait_type": "Eyes", 134 | "value": "googly" 135 | }, 136 | { 137 | "trait_type": "Stamina", 138 | "value": 86 139 | } 140 | ] 141 | }, 142 | { 143 | "name": "Bear", 144 | "description": "Snack time!", 145 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=bear", 146 | "image": "https://austingriffith.com/images/paintings/bear.jpg", 147 | "attributes": [ 148 | { 149 | "trait_type": "BackgroundColor", 150 | "value": "green" 151 | }, 152 | { 153 | "trait_type": "Eyes", 154 | "value": "googly" 155 | }, 156 | { 157 | "trait_type": "Stamina", 158 | "value": 47 159 | } 160 | ] 161 | }, 162 | { 163 | "name": "Elephant", 164 | "description": "Junk in the trunk!", 165 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=elephant", 166 | "image": "https://austingriffith.com/images/paintings/elephant.jpg", 167 | "attributes": [ 168 | { 169 | "trait_type": "BackgroundColor", 170 | "value": "orange" 171 | }, 172 | { 173 | "trait_type": "Eyes", 174 | "value": "googly" 175 | }, 176 | { 177 | "trait_type": "Stamina", 178 | "value": 38 179 | } 180 | ] 181 | }, 182 | { 183 | "name": "Hippo", 184 | "description": "Unsure.", 185 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=hippo", 186 | "image": "https://austingriffith.com/images/paintings/hippo.jpg", 187 | "attributes": [ 188 | { 189 | "trait_type": "BackgroundColor", 190 | "value": "green" 191 | }, 192 | { 193 | "trait_type": "Eyes", 194 | "value": "googly" 195 | }, 196 | { 197 | "trait_type": "Stamina", 198 | "value": 39 199 | } 200 | ] 201 | }, 202 | { 203 | "name": "Lobster", 204 | "description": "Rock lobster!", 205 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=lobster", 206 | "image": "https://austingriffith.com/images/paintings/lobster.jpg", 207 | "attributes": [ 208 | { 209 | "trait_type": "BackgroundColor", 210 | "value": "grey" 211 | }, 212 | { 213 | "trait_type": "Eyes", 214 | "value": "googly" 215 | }, 216 | { 217 | "trait_type": "Stamina", 218 | "value": 12 219 | } 220 | ] 221 | }, 222 | { 223 | "name": "Mountain Goat", 224 | "description": "Goatse!", 225 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=mountaingoat", 226 | "image": "https://austingriffith.com/images/paintings/mountaingoat.jpg", 227 | "attributes": [ 228 | { 229 | "trait_type": "BackgroundColor", 230 | "value": "blue" 231 | }, 232 | { 233 | "trait_type": "Eyes", 234 | "value": "googly" 235 | }, 236 | { 237 | "trait_type": "Stamina", 238 | "value": 70 239 | } 240 | ] 241 | }, 242 | { 243 | "name": "Octopus", 244 | "description": "Dope 'stach.", 245 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=octopus", 246 | "image": "https://austingriffith.com/images/paintings/octopus.jpg", 247 | "attributes": [ 248 | { 249 | "trait_type": "BackgroundColor", 250 | "value": "purple" 251 | }, 252 | { 253 | "trait_type": "Eyes", 254 | "value": "googly" 255 | }, 256 | { 257 | "trait_type": "Stamina", 258 | "value": 41 259 | } 260 | ] 261 | }, 262 | { 263 | "name": "Ox", 264 | "description": "The ringer.", 265 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=ox", 266 | "image": "https://austingriffith.com/images/paintings/ox.jpg", 267 | "attributes": [ 268 | { 269 | "trait_type": "BackgroundColor", 270 | "value": "blue" 271 | }, 272 | { 273 | "trait_type": "Eyes", 274 | "value": "googly" 275 | }, 276 | { 277 | "trait_type": "Stamina", 278 | "value": 59 279 | } 280 | ] 281 | }, 282 | { 283 | "name": "Penguin", 284 | "description": "It's cold out here.", 285 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=penguin", 286 | "image": "https://austingriffith.com/images/paintings/penguin.jpg", 287 | "attributes": [ 288 | { 289 | "trait_type": "BackgroundColor", 290 | "value": "orange" 291 | }, 292 | { 293 | "trait_type": "Eyes", 294 | "value": "googly" 295 | }, 296 | { 297 | "trait_type": "Stamina", 298 | "value": 23 299 | } 300 | ] 301 | }, 302 | { 303 | "name": "Walrus", 304 | "description": "Seems nice.", 305 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=walrus", 306 | "image": "https://austingriffith.com/images/paintings/walrus.jpg", 307 | "attributes": [ 308 | { 309 | "trait_type": "BackgroundColor", 310 | "value": "orange" 311 | }, 312 | { 313 | "trait_type": "Eyes", 314 | "value": "googly" 315 | }, 316 | { 317 | "trait_type": "Stamina", 318 | "value": 23 319 | } 320 | ] 321 | }, 322 | { 323 | "name": "Killer Whale", 324 | "description": "Killers.", 325 | "external_url": "https://austingriffith.com/portfolio/paintings/?id=killerwhale", 326 | "image": "https://austingriffith.com/images/paintings/killerwhale.jpg", 327 | "attributes": [ 328 | { 329 | "trait_type": "BackgroundColor", 330 | "value": "green" 331 | }, 332 | { 333 | "trait_type": "Eyes", 334 | "value": "googly" 335 | }, 336 | { 337 | "trait_type": "Stamina", 338 | "value": 87 339 | } 340 | ] 341 | } 342 | ] 343 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft-strength", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "polygon", 6 | "react", 7 | "uniswap", 8 | "workspaces", 9 | "yarn" 10 | ], 11 | "private": true, 12 | "scripts": { 13 | "react-app:build": "yarn workspace nft-strength-app build --max-old-space-size=12288", 14 | "react-app:eject": "yarn workspace nft-strength-app eject", 15 | "react-app:start": "yarn workspace nft-strength-app start", 16 | "react-app:test": "yarn workspace nft-strength-app test", 17 | "build": "yarn workspace nft-strength-app build --max-old-space-size=12288", 18 | "chain": "yarn workspace nft-strength-contracts chain", 19 | "node": "yarn workspace nft-strength-contracts chain", 20 | "test": "yarn workspace nft-strength-contracts test", 21 | "start": "yarn workspace nft-strength-app start", 22 | "compile": "yarn workspace nft-strength-contracts compile", 23 | "deploy": "yarn workspace nft-strength-contracts deploy", 24 | "watch": "yarn workspace nft-strength-contracts watch", 25 | "accounts": "yarn workspace nft-strength-contracts accounts", 26 | "balance": "yarn workspace nft-strength-contracts balance", 27 | "send": "yarn workspace nft-strength-contracts send", 28 | "ipfs": "yarn workspace nft-strength-app ipfs", 29 | "surge": "yarn workspace nft-strength-app surge", 30 | "s3": "yarn workspace nft-strength-app s3", 31 | "ship": "yarn workspace nft-strength-app ship", 32 | "generate": "yarn workspace nft-strength-contracts generate", 33 | "account": "yarn workspace nft-strength-contracts account", 34 | "mineContractAddress": "cd packages/hardhat && npx hardhat mineContractAddress", 35 | "wallet": "cd packages/hardhat && npx hardhat wallet", 36 | "fundedwallet": "cd packages/hardhat && npx hardhat fundedwallet", 37 | "flatten": "cd packages/hardhat && npx hardhat flatten", 38 | "upload": "yarn workspace nft-strength-contracts upload", 39 | "mint": "yarn workspace nft-strength-contracts mint", 40 | "theme": "yarn workspace nft-strength-app theme", 41 | "watch-theme": "yarn workspace nft-strength-app watch" 42 | }, 43 | "workspaces": { 44 | "packages": [ 45 | "packages/*" 46 | ], 47 | "nohoist": [ 48 | "**/hardhat", 49 | "**/hardhat/**" 50 | ] 51 | }, 52 | "dependencies": {} 53 | } 54 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | ts: "never", 15 | }, 16 | ], 17 | "import/prefer-default-export": "off", 18 | "prefer-destructuring": "off", 19 | "prefer-template": "off", 20 | "no-console": "off", 21 | "func-names": "off", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/YourCollectible.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.0 <0.7.0; 2 | //SPDX-License-Identifier: MIT 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/utils/Counters.sol"; 6 | import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol"; 7 | 8 | // GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two 9 | 10 | contract YourCollectible is ERC721, VRFConsumerBase { 11 | 12 | bytes32 internal keyHash; 13 | uint256 internal fee; 14 | 15 | uint256 public randomResult; 16 | 17 | using Counters for Counters.Counter; 18 | Counters.Counter private _tokenIds; 19 | 20 | constructor(bytes32[] memory assetsForSale) public 21 | VRFConsumerBase( 22 | 0x8C7382F9D8f56b33781fE506E897a4F1e2d17255, // VRF Coordinator on Mumbai 23 | 0x326C977E6efc84E512bB9C30f76E30c160eD06FB // LINK Token o Mumbai 24 | ) ERC721("YourCollectible", "YCB") { 25 | _setBaseURI("https://ipfs.io/ipfs/"); 26 | for(uint256 i=0;i bool) public forSale; 35 | //this lets you look up a token by the uri (assuming there is only one of each uri for now) 36 | mapping (bytes32 => uint256) public uriToTokenId; 37 | 38 | 39 | //strong NFTs 40 | mapping (bytes32 => uint8) public tokenStrength; 41 | 42 | 43 | function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) { 44 | require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet"); 45 | return requestRandomness(keyHash, fee, userProvidedSeed); 46 | } 47 | 48 | function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { 49 | randomResult = randomness; 50 | } 51 | 52 | 53 | function mintItem(string memory tokenURI) 54 | public 55 | returns (uint256) 56 | { 57 | bytes32 uriHash = keccak256(abi.encodePacked(tokenURI)); 58 | 59 | //make sure they are only minting something that is marked "forsale" 60 | require(forSale[uriHash],"NOT FOR SALE"); 61 | forSale[uriHash]=false; 62 | 63 | tokenStrength[uriHash] = uint8( (randomResult % 100)+1 ); 64 | randomResult=0; 65 | 66 | _tokenIds.increment(); 67 | 68 | uint256 id = _tokenIds.current(); 69 | _mint(msg.sender, id); 70 | _setTokenURI(id, tokenURI); 71 | 72 | uriToTokenId[uriHash] = id; 73 | 74 | return id; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/hardhat/hardhat.config.js: -------------------------------------------------------------------------------- 1 | const { utils } = require("ethers"); 2 | const fs = require("fs"); 3 | const chalk = require("chalk"); 4 | 5 | require("@nomiclabs/hardhat-waffle"); 6 | 7 | const { isAddress, getAddress, formatUnits, parseUnits } = utils; 8 | 9 | const defaultNetwork = "mumbai"; 10 | 11 | function mnemonic() { 12 | try { 13 | return fs.readFileSync("./mnemonic.txt").toString().trim(); 14 | } catch (e) { 15 | if (defaultNetwork !== "localhost") { 16 | console.log( 17 | "☢️ WARNING: No mnemonic file created for a deploy account. Try `yarn run generate` and then `yarn run account`." 18 | ); 19 | } 20 | } 21 | return ""; 22 | } 23 | 24 | module.exports = { 25 | defaultNetwork, 26 | 27 | networks: { 28 | localhost: { 29 | url: "http://localhost:8545", 30 | }, 31 | matic: { 32 | url: "https://rpc-mainnet.maticvigil.com/", 33 | gasPrice: 1000000000, 34 | accounts: { 35 | mnemonic: mnemonic(), 36 | }, 37 | }, 38 | mumbai: { 39 | url: "https://rpc-mumbai.maticvigil.com", 40 | gasPrice: 1000000000, 41 | accounts: { 42 | mnemonic: mnemonic(), 43 | }, 44 | }, 45 | }, 46 | solidity: { 47 | compilers: [ 48 | { 49 | version: "0.7.6", 50 | settings: { 51 | optimizer: { 52 | enabled: true, 53 | runs: 200, 54 | }, 55 | }, 56 | }, 57 | { 58 | version: "0.6.7", 59 | settings: { 60 | optimizer: { 61 | enabled: true, 62 | runs: 200, 63 | }, 64 | }, 65 | }, 66 | ], 67 | }, 68 | }; 69 | 70 | const DEBUG = false; 71 | 72 | function debug(text) { 73 | if (DEBUG) { 74 | console.log(text); 75 | } 76 | } 77 | 78 | task("wallet", "Create a wallet (pk) link", async (_, { ethers }) => { 79 | const randomWallet = ethers.Wallet.createRandom(); 80 | const privateKey = randomWallet._signingKey().privateKey; 81 | console.log("WALLET Generated as " + randomWallet.address + ""); 82 | console.log("http://localhost:3000/pk#" + privateKey); 83 | }); 84 | 85 | task("fundedwallet", "Create a wallet (pk) link and fund it with deployer?") 86 | .addOptionalParam( 87 | "amount", 88 | "Amount of MATIC to send to wallet after generating" 89 | ) 90 | .addOptionalParam("url", "URL to add pk to") 91 | .setAction(async (taskArgs, { network, ethers }) => { 92 | const randomWallet = ethers.Wallet.createRandom(); 93 | const privateKey = randomWallet._signingKey().privateKey; 94 | console.log("WALLET Generated as " + randomWallet.address + ""); 95 | let url = taskArgs.url ? taskArgs.url : "http://localhost:3000"; 96 | 97 | let localDeployerMnemonic; 98 | try { 99 | localDeployerMnemonic = fs.readFileSync("./mnemonic.txt"); 100 | localDeployerMnemonic = localDeployerMnemonic.toString().trim(); 101 | } catch (e) { 102 | /* do nothing - this file isn't always there */ 103 | } 104 | 105 | let amount = taskArgs.amount ? taskArgs.amount : "0.01"; 106 | const tx = { 107 | to: randomWallet.address, 108 | value: ethers.utils.parseEther(amount), 109 | }; 110 | 111 | //SEND USING LOCAL DEPLOYER MNEMONIC IF THERE IS ONE 112 | // IF NOT SEND USING LOCAL HARDHAT NODE: 113 | if (localDeployerMnemonic) { 114 | let deployerWallet = new ethers.Wallet.fromMnemonic( 115 | localDeployerMnemonic 116 | ); 117 | deployerWallet = deployerWallet.connect(ethers.provider); 118 | console.log( 119 | "Sending " + 120 | amount + 121 | " MATIC to " + 122 | randomWallet.address + 123 | " using deployer account" 124 | ); 125 | let sendresult = await deployerWallet.sendTransaction(tx); 126 | console.log("\n" + url + "/pk#" + privateKey + "\n"); 127 | return; 128 | } else { 129 | console.log( 130 | "Sending " + 131 | amount + 132 | " MATIC to " + 133 | randomWallet.address + 134 | " using local node" 135 | ); 136 | console.log("\n" + url + "/pk#" + privateKey + "\n"); 137 | return send(ethers.provider.getSigner(), tx); 138 | } 139 | }); 140 | 141 | task( 142 | "generate", 143 | "Create a mnemonic for builder deploys", 144 | async (_, { ethers }) => { 145 | const bip39 = require("bip39"); 146 | const hdkey = require("ethereumjs-wallet/hdkey"); 147 | const mnemonic = bip39.generateMnemonic(); 148 | if (DEBUG) console.log("mnemonic", mnemonic); 149 | const seed = await bip39.mnemonicToSeed(mnemonic); 150 | if (DEBUG) console.log("seed", seed); 151 | const hdwallet = hdkey.fromMasterSeed(seed); 152 | const wallet_hdpath = "m/44'/60'/0'/0/"; 153 | const account_index = 0; 154 | let fullPath = wallet_hdpath + account_index; 155 | if (DEBUG) console.log("fullPath", fullPath); 156 | const wallet = hdwallet.derivePath(fullPath).getWallet(); 157 | const privateKey = "0x" + wallet._privKey.toString("hex"); 158 | if (DEBUG) console.log("privateKey", privateKey); 159 | var EthUtil = require("ethereumjs-util"); 160 | const address = 161 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex"); 162 | console.log( 163 | "Account Generated as " + 164 | address + 165 | " and set as mnemonic in packages/hardhat" 166 | ); 167 | console.log( 168 | "Use 'yarn run account' to get more information about the deployment account." 169 | ); 170 | 171 | fs.writeFileSync("./" + address + ".txt", mnemonic.toString()); 172 | fs.writeFileSync("./mnemonic.txt", mnemonic.toString()); 173 | } 174 | ); 175 | 176 | task( 177 | "mineContractAddress", 178 | "Looks for a deployer account that will give leading zeros" 179 | ) 180 | .addParam("searchFor", "String to search for") 181 | .setAction(async (taskArgs, { network, ethers }) => { 182 | let contract_address = ""; 183 | let address; 184 | 185 | const bip39 = require("bip39"); 186 | const hdkey = require("ethereumjs-wallet/hdkey"); 187 | 188 | let mnemonic = ""; 189 | while (contract_address.indexOf(taskArgs.searchFor) != 0) { 190 | mnemonic = bip39.generateMnemonic(); 191 | if (DEBUG) console.log("mnemonic", mnemonic); 192 | const seed = await bip39.mnemonicToSeed(mnemonic); 193 | if (DEBUG) console.log("seed", seed); 194 | const hdwallet = hdkey.fromMasterSeed(seed); 195 | const wallet_hdpath = "m/44'/60'/0'/0/"; 196 | const account_index = 0; 197 | let fullPath = wallet_hdpath + account_index; 198 | if (DEBUG) console.log("fullPath", fullPath); 199 | const wallet = hdwallet.derivePath(fullPath).getWallet(); 200 | const privateKey = "0x" + wallet._privKey.toString("hex"); 201 | if (DEBUG) console.log("privateKey", privateKey); 202 | var EthUtil = require("ethereumjs-util"); 203 | address = 204 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex"); 205 | 206 | const rlp = require("rlp"); 207 | const keccak = require("keccak"); 208 | 209 | let nonce = 0x00; //The nonce must be a hex literal! 210 | let sender = address; 211 | 212 | let input_arr = [sender, nonce]; 213 | let rlp_encoded = rlp.encode(input_arr); 214 | 215 | let contract_address_long = keccak("keccak256") 216 | .update(rlp_encoded) 217 | .digest("hex"); 218 | 219 | contract_address = contract_address_long.substring(24); //Trim the first 24 characters. 220 | } 221 | 222 | console.log( 223 | "Account Mined as " + address + " and set as mnemonic in packages/hardhat" 224 | ); 225 | console.log( 226 | "This will create the first contract: " + 227 | chalk.magenta("0x" + contract_address) 228 | ); 229 | console.log( 230 | "Use 'yarn run account' to get more information about the deployment account." 231 | ); 232 | 233 | fs.writeFileSync( 234 | "./" + address + "_produces" + contract_address + ".txt", 235 | mnemonic.toString() 236 | ); 237 | fs.writeFileSync("./mnemonic.txt", mnemonic.toString()); 238 | }); 239 | 240 | task( 241 | "account", 242 | "Get balance informations for the deployment account.", 243 | async (_, { ethers }) => { 244 | const hdkey = require("ethereumjs-wallet/hdkey"); 245 | const bip39 = require("bip39"); 246 | let mnemonic = fs.readFileSync("./mnemonic.txt").toString().trim(); 247 | if (DEBUG) console.log("mnemonic", mnemonic); 248 | const seed = await bip39.mnemonicToSeed(mnemonic); 249 | if (DEBUG) console.log("seed", seed); 250 | const hdwallet = hdkey.fromMasterSeed(seed); 251 | const wallet_hdpath = "m/44'/60'/0'/0/"; 252 | const account_index = 0; 253 | let fullPath = wallet_hdpath + account_index; 254 | if (DEBUG) console.log("fullPath", fullPath); 255 | const wallet = hdwallet.derivePath(fullPath).getWallet(); 256 | const privateKey = "0x" + wallet._privKey.toString("hex"); 257 | if (DEBUG) console.log("privateKey", privateKey); 258 | var EthUtil = require("ethereumjs-util"); 259 | const address = 260 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex"); 261 | 262 | var qrcode = require("qrcode-terminal"); 263 | qrcode.generate(address); 264 | console.log("Deployer Account is " + address); 265 | for (let n in config.networks) { 266 | //console.log(config.networks[n],n) 267 | try { 268 | let provider = new ethers.providers.JsonRpcProvider( 269 | config.networks[n].url 270 | ); 271 | let balance = await provider.getBalance(address); 272 | console.log(" -- " + n + " -- -- -- 📡 "); 273 | console.log(" balance: " + ethers.utils.formatEther(balance)); 274 | console.log( 275 | " nonce: " + (await provider.getTransactionCount(address)) 276 | ); 277 | } catch (e) { 278 | if (DEBUG) { 279 | console.log(e); 280 | } 281 | } 282 | } 283 | } 284 | ); 285 | 286 | async function addr(ethers, addr) { 287 | if (isAddress(addr)) { 288 | return getAddress(addr); 289 | } 290 | const accounts = await ethers.provider.listAccounts(); 291 | if (accounts[addr] !== undefined) { 292 | return accounts[addr]; 293 | } 294 | throw `Could not normalize address: ${addr}`; 295 | } 296 | 297 | task("accounts", "Prints the list of accounts", async (_, { ethers }) => { 298 | const accounts = await ethers.provider.listAccounts(); 299 | accounts.forEach((account) => console.log(account)); 300 | }); 301 | 302 | task("blockNumber", "Prints the block number", async (_, { ethers }) => { 303 | const blockNumber = await ethers.provider.getBlockNumber(); 304 | console.log(blockNumber); 305 | }); 306 | 307 | task("balance", "Prints an account's balance") 308 | .addPositionalParam("account", "The account's address") 309 | .setAction(async (taskArgs, { ethers }) => { 310 | const balance = await ethers.provider.getBalance( 311 | await addr(ethers, taskArgs.account) 312 | ); 313 | console.log(formatUnits(balance, "ether"), "MATIC"); 314 | }); 315 | 316 | function send(signer, txparams) { 317 | return signer.sendTransaction(txparams, (error, transactionHash) => { 318 | if (error) { 319 | debug(`Error: ${error}`); 320 | } 321 | debug(`transactionHash: ${transactionHash}`); 322 | // checkForReceipt(2, params, transactionHash, resolve) 323 | }); 324 | } 325 | 326 | task("send", "Send MATIC") 327 | .addParam("from", "From address or account index") 328 | .addOptionalParam("to", "To address or account index") 329 | .addOptionalParam("amount", "Amount to send in MATIC") 330 | .addOptionalParam("data", "Data included in transaction") 331 | .addOptionalParam("gasPrice", "Price you are willing to pay in gwei") 332 | .addOptionalParam("gasLimit", "Limit of how much gas to spend") 333 | 334 | .setAction(async (taskArgs, { network, ethers }) => { 335 | const from = await addr(ethers, taskArgs.from); 336 | debug(`Normalized from address: ${from}`); 337 | const fromSigner = await ethers.provider.getSigner(from); 338 | 339 | let to; 340 | if (taskArgs.to) { 341 | to = await addr(ethers, taskArgs.to); 342 | debug(`Normalized to address: ${to}`); 343 | } 344 | 345 | const txRequest = { 346 | from: await fromSigner.getAddress(), 347 | to, 348 | value: parseUnits( 349 | taskArgs.amount ? taskArgs.amount : "0", 350 | "ether" 351 | ).toHexString(), 352 | nonce: await fromSigner.getTransactionCount(), 353 | gasPrice: parseUnits( 354 | taskArgs.gasPrice ? taskArgs.gasPrice : "1.001", 355 | "gwei" 356 | ).toHexString(), 357 | gasLimit: taskArgs.gasLimit ? taskArgs.gasLimit : 24000, 358 | chainId: network.config.chainId, 359 | }; 360 | 361 | if (taskArgs.data !== undefined) { 362 | txRequest.data = taskArgs.data; 363 | debug(`Adding data to payload: ${txRequest.data}`); 364 | } 365 | debug(txRequest.gasPrice / 1000000000 + " gwei"); 366 | debug(JSON.stringify(txRequest, null, 2)); 367 | 368 | return send(fromSigner, txRequest); 369 | }); 370 | -------------------------------------------------------------------------------- /packages/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft-strength-contracts", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "eslint": "^7.5.0", 8 | "eslint-config-airbnb": "^18.2.0", 9 | "eslint-config-prettier": "^6.11.0", 10 | "eslint-plugin-babel": "^5.3.1", 11 | "eslint-plugin-prettier": "^3.1.4" 12 | }, 13 | "dependencies": { 14 | "@chainlink/contracts": "^0.1.6", 15 | "@nomiclabs/hardhat-ethers": "^2.0.0", 16 | "@nomiclabs/hardhat-waffle": "^2.0.0", 17 | "@openzeppelin/contracts": "^3.2.0", 18 | "chai": "^4.2.0", 19 | "chalk": "^4.1.0", 20 | "ethereum-waffle": "^3.1.1", 21 | "ethers": "^5.0.17", 22 | "hardhat": "^2.0.11", 23 | "node-watch": "^0.7.0", 24 | "qrcode-terminal": "^0.12.0", 25 | "ramda": "^0.27.1" 26 | }, 27 | "scripts": { 28 | "chain": "hardhat node", 29 | "test": "hardhat test --network hardhat", 30 | "compile": "hardhat compile", 31 | "deploy": "hardhat run scripts/deploy.js", 32 | "postdeploy": "hardhat run scripts/publish.js", 33 | "watch": "node scripts/watch.js", 34 | "accounts": "hardhat accounts", 35 | "balance": "hardhat balance", 36 | "send": "hardhat send", 37 | "generate": "hardhat generate", 38 | "account": "hardhat account", 39 | "upload": "hardhat run scripts/upload.js" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "warn" */ 2 | const fs = require("fs"); 3 | const chalk = require("chalk"); 4 | const { config, ethers } = require("hardhat"); 5 | const { utils } = require("ethers"); 6 | const R = require("ramda"); 7 | 8 | 9 | const main = async () => { 10 | 11 | console.log("\n\n 📡 Deploying...\n"); 12 | 13 | // read in all the assets to get their IPFS hash... 14 | let uploadedAssets = JSON.parse(fs.readFileSync("./uploaded.json")) 15 | let bytes32Array = [] 16 | for(let a in uploadedAssets){ 17 | console.log(" IPFS:",a) 18 | let bytes32 = utils.id(a) 19 | console.log(" #️hashed:",bytes32) 20 | bytes32Array.push(bytes32) 21 | } 22 | console.log(" \n") 23 | 24 | const yourCollectible = await deploy("YourCollectible",[ bytes32Array ]) // <-- add in constructor args like line 19 vvvv 25 | 26 | console.log( 27 | " Artifacts (address, abi, and args) saved to: ", 28 | chalk.blue("packages/hardhat/artifacts/"), 29 | "\n\n" 30 | ); 31 | }; 32 | 33 | const deploy = async (contractName, _args = [], overrides = {}, libraries = {}) => { 34 | console.log(` Deploying: ${contractName}`); 35 | 36 | const contractArgs = _args || []; 37 | const contractArtifacts = await ethers.getContractFactory(contractName,{libraries: libraries}); 38 | const deployed = await contractArtifacts.deploy(...contractArgs, overrides); 39 | const encoded = abiEncodeArgs(deployed, contractArgs); 40 | fs.writeFileSync(`artifacts/${contractName}.address`, deployed.address); 41 | 42 | let extraGasInfo = "" 43 | if(deployed&&deployed.deployTransaction){ 44 | const gasUsed = deployed.deployTransaction.gasLimit.mul(deployed.deployTransaction.gasPrice) 45 | extraGasInfo = `${utils.formatEther(gasUsed)} MATIC, tx hash ${deployed.deployTransaction.hash}` 46 | } 47 | 48 | console.log( 49 | chalk.cyan(contractName), 50 | "deployed to:", 51 | chalk.magenta(deployed.address) 52 | ); 53 | console.log( 54 | chalk.grey(extraGasInfo) 55 | ); 56 | 57 | if (!encoded || encoded.length <= 2) return deployed; 58 | fs.writeFileSync(`artifacts/${contractName}.args`, encoded.slice(2)); 59 | 60 | return deployed; 61 | }; 62 | 63 | 64 | const abiEncodeArgs = (deployed, contractArgs) => { 65 | if ( 66 | !contractArgs || 67 | !deployed || 68 | !R.hasPath(["interface", "deploy"], deployed) 69 | ) { 70 | return ""; 71 | } 72 | const encoded = utils.defaultAbiCoder.encode( 73 | deployed.interface.deploy.inputs, 74 | contractArgs 75 | ); 76 | return encoded; 77 | }; 78 | 79 | // checks if it is a Solidity file 80 | const isSolidity = (fileName) => 81 | fileName.indexOf(".sol") >= 0 && fileName.indexOf(".swp") < 0 && fileName.indexOf(".swap") < 0; 82 | 83 | const readArgsFile = (contractName) => { 84 | let args = []; 85 | try { 86 | const argsFile = `./contracts/${contractName}.args`; 87 | if (!fs.existsSync(argsFile)) return args; 88 | args = JSON.parse(fs.readFileSync(argsFile)); 89 | } catch (e) { 90 | console.log(e); 91 | } 92 | return args; 93 | }; 94 | 95 | function sleep(ms) { 96 | return new Promise(resolve => setTimeout(resolve, ms)); 97 | } 98 | 99 | // If you want to verify on https://tenderly.co/ 100 | const tenderlyVerify = async ({contractName, contractAddress}) => { 101 | 102 | let tenderlyNetworks = ["kovan","goerli","mainnet","rinkeby","ropsten","matic","mumbai","xDai","POA"] 103 | let targetNetwork = process.env.HARDHAT_NETWORK || config.defaultNetwork 104 | 105 | if(tenderlyNetworks.includes(targetNetwork)) { 106 | console.log(chalk.blue(` 📁 Attempting tenderly verification of ${contractName} on ${targetNetwork}`)) 107 | 108 | await tenderly.persistArtifacts({ 109 | name: contractName, 110 | address: contractAddress 111 | }); 112 | 113 | let verification = await tenderly.verify({ 114 | name: contractName, 115 | address: contractAddress, 116 | network: targetNetwork 117 | }) 118 | 119 | return verification 120 | } else { 121 | console.log(chalk.grey(`Contract verification not supported on ${targetNetwork}`)) 122 | } 123 | } 124 | 125 | main() 126 | .then(() => process.exit(0)) 127 | .catch((error) => { 128 | console.error(error); 129 | process.exit(1); 130 | }); 131 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | const bre = require("hardhat"); 4 | 5 | const publishDir = "../react-app/src/contracts"; 6 | 7 | function publishContract(contractName) { 8 | console.log( 9 | " Publishing", 10 | chalk.cyan(contractName), 11 | "to", 12 | chalk.gray(publishDir) 13 | ); 14 | try { 15 | let contract = fs 16 | .readFileSync(`${bre.config.paths.artifacts}/contracts/${contractName}.sol/${contractName}.json`) 17 | .toString(); 18 | const address = fs 19 | .readFileSync(`${bre.config.paths.artifacts}/${contractName}.address`) 20 | .toString(); 21 | contract = JSON.parse(contract); 22 | 23 | fs.writeFileSync( 24 | `${publishDir}/${contractName}.address.js`, 25 | `module.exports = "${address}";` 26 | ); 27 | fs.writeFileSync( 28 | `${publishDir}/${contractName}.abi.js`, 29 | `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 30 | ); 31 | fs.writeFileSync( 32 | `${publishDir}/${contractName}.bytecode.js`, 33 | `module.exports = "${contract.bytecode}";` 34 | ); 35 | 36 | console.log("Published "+chalk.green(contractName)+" to the frontend.") 37 | 38 | return true; 39 | } catch (e) { 40 | if(e.toString().indexOf("no such file or directory")>=0){ 41 | console.log(chalk.yellow(" ⚠️ Can't publish "+contractName+" yet (make sure it getting deployed).")) 42 | }else{ 43 | console.log(e); 44 | return false; 45 | } 46 | } 47 | } 48 | 49 | async function main() { 50 | if (!fs.existsSync(publishDir)) { 51 | fs.mkdirSync(publishDir); 52 | } 53 | const finalContractList = []; 54 | fs.readdirSync(bre.config.paths.sources).forEach((file) => { 55 | if (file.indexOf(".sol") >= 0) { 56 | const contractName = file.replace(".sol", ""); 57 | // Add contract to list if publishing is successful 58 | if (publishContract(contractName)) { 59 | finalContractList.push(contractName); 60 | } 61 | } 62 | }); 63 | fs.writeFileSync( 64 | `${publishDir}/contracts.js`, 65 | `module.exports = ${JSON.stringify(finalContractList)};` 66 | ); 67 | } 68 | main() 69 | .then(() => process.exit(0)) 70 | .catch((error) => { 71 | console.error(error); 72 | process.exit(1); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/upload.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "warn" */ 2 | const fs = require("fs"); 3 | const ipfsAPI = require('ipfs-http-client'); 4 | const ipfs = ipfsAPI({host: 'ipfs.infura.io', port: '5001', protocol: 'https' }) 5 | 6 | const main = async () => { 7 | 8 | let allAssets = {} 9 | 10 | console.log("\n\n Loading artwork.json...\n"); 11 | const artwork = JSON.parse(fs.readFileSync("../../artwork.json").toString()) 12 | 13 | for(let a in artwork){ 14 | console.log(" Uploading "+artwork[a].name+"...") 15 | const stringJSON = JSON.stringify(artwork[a]) 16 | const uploaded = await ipfs.add(stringJSON) 17 | console.log(" "+artwork[a].name+" ipfs:",uploaded.path) 18 | allAssets[uploaded.path] = artwork[a] 19 | } 20 | 21 | console.log("\n Injecting assets into the frontend...") 22 | const finalAssetFile = "export default "+JSON.stringify(allAssets)+"" 23 | fs.writeFileSync("../react-app/src/assets.js",finalAssetFile) 24 | fs.writeFileSync("./uploaded.json",JSON.stringify(allAssets)) 25 | 26 | }; 27 | 28 | main() 29 | .then(() => process.exit(0)) 30 | .catch((error) => { 31 | console.error(error); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/react-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | build/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/react-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | }, 5 | extends: ["airbnb", 'plugin:prettier/recommended', 'prettier/react'], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | "js": "never", 14 | "jsx": "never", 15 | "ts": "never", 16 | "tsx": "never" 17 | } 18 | ], 19 | "import/prefer-default-export": "off", 20 | "prefer-destructuring": "off", 21 | "prefer-template": "off", 22 | "react/prop-types": "off", 23 | "react/destructuring-assignment": "off", 24 | "no-console": "off", 25 | "jsx-a11y/accessible-emoji": ["off"] 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/react-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/.sample.env: -------------------------------------------------------------------------------- 1 | REACT_APP_PROVIDER=https://rinkeby.infura.io/v3/2717afb6bf164045b5d5468031b93f87 2 | -------------------------------------------------------------------------------- /packages/react-app/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const gulpless = require('gulp-less') 3 | const postcss = require('gulp-postcss') 4 | const debug = require('gulp-debug') 5 | var csso = require('gulp-csso') 6 | const autoprefixer = require('autoprefixer') 7 | const NpmImportPlugin = require('less-plugin-npm-import') 8 | 9 | gulp.task('less', function () { 10 | const plugins = [autoprefixer()] 11 | 12 | return gulp 13 | .src('src/themes/*-theme.less') 14 | .pipe(debug({title: 'Less files:'})) 15 | .pipe( 16 | gulpless({ 17 | javascriptEnabled: true, 18 | plugins: [new NpmImportPlugin({prefix: '~'})], 19 | }), 20 | ) 21 | .pipe(postcss(plugins)) 22 | .pipe( 23 | csso({ 24 | debug: true, 25 | }), 26 | ) 27 | .pipe(gulp.dest('./public')) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft-strength-app", 3 | "version": "1.0.0", 4 | "homepage": ".", 5 | "browserslist": { 6 | "production": [ 7 | ">0.2%", 8 | "not dead", 9 | "not op_mini all" 10 | ], 11 | "development": [ 12 | "last 1 chrome version", 13 | "last 1 firefox version", 14 | "last 1 safari version" 15 | ] 16 | }, 17 | "dependencies": { 18 | "@ant-design/icons": "^4.2.2", 19 | "@ethersproject/address": "^5.0.5", 20 | "@ethersproject/bignumber": "^5.0.8", 21 | "@ethersproject/bytes": "^5.0.5", 22 | "@ethersproject/contracts": "^5.0.5", 23 | "@ethersproject/providers": "^5.0.12", 24 | "@ethersproject/solidity": "^5.0.9", 25 | "@ethersproject/units": "^5.0.6", 26 | "@ramp-network/ramp-instant-sdk": "^2.2.0", 27 | "@testing-library/jest-dom": "^5.11.4", 28 | "@testing-library/react": "^11.1.0", 29 | "@testing-library/user-event": "^12.1.8", 30 | "@uniswap/sdk": "^3.0.3", 31 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 32 | "@walletconnect/web3-provider": "^1.3.1", 33 | "antd": "^4.7.0", 34 | "axios": "^0.20.0", 35 | "bnc-notify": "^1.5.0", 36 | "burner-provider": "^1.0.38", 37 | "dotenv": "^8.2.0", 38 | "eth-hooks": "^1.1.2", 39 | "ethers": "^5.0.31", 40 | "isomorphic-fetch": "^3.0.0", 41 | "node-watch": "^0.7.1", 42 | "postcss": "^8.2.6", 43 | "qrcode.react": "^1.0.0", 44 | "react": "^16.14.0", 45 | "react-blockies": "^1.4.1", 46 | "react-css-theme-switcher": "^0.2.2", 47 | "react-dom": "^16.14.0", 48 | "react-json-view": "^1.21.1", 49 | "react-qr-reader": "^2.2.1", 50 | "react-router-dom": "^5.2.0", 51 | "react-scripts": "^3.4.3", 52 | "react-stack-grid": "^0.7.1", 53 | "web3modal": "^1.9.1" 54 | }, 55 | "devDependencies": { 56 | "@testing-library/dom": "^6.12.2", 57 | "@types/react": "^16.9.19", 58 | "autoprefixer": "^10.2.4", 59 | "chalk": "^4.1.0", 60 | "eslint": "^7.5.0", 61 | "eslint-config-airbnb": "^18.2.0", 62 | "eslint-config-prettier": "^6.11.0", 63 | "eslint-plugin-babel": "^5.3.1", 64 | "eslint-plugin-import": "^2.22.1", 65 | "eslint-plugin-jsx-a11y": "^6.4.1", 66 | "eslint-plugin-prettier": "^3.1.4", 67 | "eslint-plugin-react": "^7.22.0", 68 | "eslint-plugin-react-hooks": "^4.2.0", 69 | "gulp": "^4.0.2", 70 | "gulp-csso": "^4.0.1", 71 | "gulp-debug": "^4.0.0", 72 | "gulp-less": "^4.0.1", 73 | "gulp-postcss": "^9.0.0", 74 | "ipfs-http-client": "^45.0.0", 75 | "less-plugin-npm-import": "^2.1.0", 76 | "prettier": "^2.0.5", 77 | "s3-folder-upload": "^2.3.1", 78 | "surge": "^0.21.5" 79 | }, 80 | "eslintConfig": { 81 | "extends": "react-app" 82 | }, 83 | "scripts": { 84 | "build": "react-scripts build", 85 | "eject": "react-scripts eject", 86 | "start": "react-scripts start", 87 | "test": "react-scripts test", 88 | "lint": "eslint --config ./.eslintrc.js --ignore-path ./.eslintignore ./src/**/*", 89 | "ipfs": "node ./scripts/ipfs.js", 90 | "surge": "surge ./build", 91 | "s3": "node ./scripts/s3.js", 92 | "ship": "yarn surge", 93 | "theme": "npx gulp less", 94 | "watch": "node ./scripts/watch.js" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EatTheBlocks/polygon-development/8335f47f950627bbec0f7a881bf92404cfb09601/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Polygon App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Polygon App", 3 | "start_url": ".", 4 | "name": "Polygon App", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | } 11 | ], 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /packages/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const ipfsAPI = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const { globSource } = ipfsAPI; 6 | 7 | const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 8 | 9 | const ipfs = ipfsAPI(infura); 10 | 11 | const ipfsGateway = "https://ipfs.io/ipfs/"; 12 | 13 | const addOptions = { 14 | pin: true, 15 | }; 16 | 17 | const pushDirectoryToIPFS = async path => { 18 | try { 19 | const response = await ipfs.add(globSource(path, { recursive: true }), addOptions); 20 | return response; 21 | } catch (e) { 22 | return {}; 23 | } 24 | }; 25 | 26 | const publishHashToIPNS = async ipfsHash => { 27 | try { 28 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 29 | return response; 30 | } catch (e) { 31 | return {}; 32 | } 33 | }; 34 | 35 | const nodeMayAllowPublish = ipfsClient => { 36 | // You must have your own IPFS node in order to publish an IPNS name 37 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 38 | const nonPublishingNodes = ["ipfs.infura.io"]; 39 | const { host } = ipfsClient.getEndpointConfig(); 40 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 41 | }; 42 | 43 | const deploy = async () => { 44 | console.log("🛰 Sending to IPFS..."); 45 | const { cid } = await pushDirectoryToIPFS("./build"); 46 | if (!cid) { 47 | console.log(`📡 App deployment failed`); 48 | return false; 49 | } 50 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 51 | 52 | console.log(); 53 | 54 | let ipnsName = ""; 55 | if (nodeMayAllowPublish(ipfs)) { 56 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 57 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 58 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 59 | clearLine(process.stdout, 0); 60 | if (!ipnsName) { 61 | console.log(" Publishing IPNS name on node failed."); 62 | } 63 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 64 | console.log(); 65 | } 66 | 67 | console.log("🚀 Deployment to IPFS complete!"); 68 | console.log(); 69 | 70 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 71 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 72 | if (ipnsName) { 73 | console.log(` IPNS: ${chalk.cyan(`${ipfsGateway}${ipnsName}`)}`); 74 | console.log(); 75 | console.log( 76 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 77 | ); 78 | console.log( 79 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 80 | ); 81 | } 82 | console.log(); 83 | return true; 84 | }; 85 | 86 | deploy(); 87 | -------------------------------------------------------------------------------- /packages/react-app/scripts/s3.js: -------------------------------------------------------------------------------- 1 | const s3FolderUpload = require("s3-folder-upload"); 2 | const fs = require("fs"); 3 | 4 | const directoryName = "build"; 5 | 6 | const BUCKETNAME = ""; // <<---- SET YOUR BUCKET NAME AND CREATE aws.json ** see below vvvvvvvvvv 7 | 8 | if (!BUCKETNAME) { 9 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 10 | process.exit(1); 11 | } 12 | 13 | let credentials = {}; 14 | try { 15 | credentials = JSON.parse(fs.readFileSync("aws.json")); 16 | } catch (e) { 17 | console.log(e); 18 | console.log( 19 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 20 | ); 21 | process.exit(1); 22 | } 23 | console.log("credentials", credentials); 24 | 25 | credentials.bucket = BUCKETNAME; 26 | 27 | // optional options to be passed as parameter to the method 28 | const options = { 29 | useFoldersForFileTypes: false, 30 | useIAMRoleCredentials: false, 31 | }; 32 | 33 | // optional cloudfront invalidation rule 34 | // const invalidation = { 35 | // awsDistributionId: "", 36 | // awsInvalidationPath: "/*" 37 | // } 38 | 39 | s3FolderUpload(directoryName, credentials, options /* , invalidation */); 40 | -------------------------------------------------------------------------------- /packages/react-app/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("Compiling & Generating..."); 6 | exec("npx gulp less", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Themes..."); 14 | watch("./src/themes", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-app/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from "react"; 2 | import { BrowserRouter, Switch, Route, Link } from "react-router-dom"; 3 | import "antd/dist/antd.css"; 4 | import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"; 5 | import { LinkOutlined } from "@ant-design/icons"; 6 | import "./App.css"; 7 | import { Row, Col, Button, Menu, Alert, Input, List, Card, Switch as SwitchD } from "antd"; 8 | import Web3Modal from "web3modal"; 9 | import WalletConnectProvider from "@walletconnect/web3-provider"; 10 | import { useUserAddress } from "eth-hooks"; 11 | import { 12 | useExchangePrice, 13 | useGasPrice, 14 | useUserProvider, 15 | useContractLoader, 16 | useContractReader, 17 | useEventListener, 18 | useBalance, 19 | } from "./hooks"; 20 | import { Header, Account, Faucet, Ramp, Contract, GasGauge, Address, AddressInput, ThemeSwitch } from "./components"; 21 | import { Transactor } from "./helpers"; 22 | import { formatEther, parseEther } from "@ethersproject/units"; 23 | import { utils } from "ethers"; 24 | import { INFURA_ID, NETWORK, NETWORKS } from "./constants"; 25 | import StackGrid from "react-stack-grid"; 26 | import ReactJson from "react-json-view"; 27 | import assets from "./assets.js"; 28 | 29 | const { BufferList } = require("bl"); 30 | // https://www.npmjs.com/package/ipfs-http-client 31 | const ipfsAPI = require("ipfs-http-client"); 32 | const ipfs = ipfsAPI({ host: "ipfs.infura.io", port: "5001", protocol: "https" }); 33 | 34 | console.log("📦 Assets: ", assets); 35 | 36 | const targetNetwork = NETWORKS["mumbai"]; 37 | 38 | // 😬 Sorry for all the console logging 39 | const DEBUG = true; 40 | 41 | //EXAMPLE STARTING JSON: 42 | const STARTING_JSON = { 43 | description: "It's actually a bison?", 44 | external_url: "https://austingriffith.com/portfolio/paintings/", // <-- this can link to a page for the specific file too 45 | image: "https://austingriffith.com/images/paintings/buffalo.jpg", 46 | name: "Buffalo", 47 | attributes: [ 48 | { 49 | trait_type: "BackgroundColor", 50 | value: "green", 51 | }, 52 | { 53 | trait_type: "Eyes", 54 | value: "googly", 55 | }, 56 | ], 57 | }; 58 | 59 | //helper function to "Get" from IPFS 60 | // you usually go content.toString() after this... 61 | const getFromIPFS = async hashToGet => { 62 | for await (const file of ipfs.get(hashToGet)) { 63 | console.log(file.path); 64 | if (!file.content) continue; 65 | const content = new BufferList(); 66 | for await (const chunk of file.content) { 67 | content.append(chunk); 68 | } 69 | console.log(content); 70 | return content; 71 | } 72 | }; 73 | 74 | // 🛰 providers 75 | if (DEBUG) console.log("📡 Connecting to Mumbai"); 76 | const mainnetInfura = new JsonRpcProvider("https://mainnet.infura.io/v3/" + INFURA_ID); 77 | const localProviderUrl = targetNetwork.rpcUrl; 78 | const localProviderUrlFromEnv = process.env.REACT_APP_PROVIDER ? process.env.REACT_APP_PROVIDER : localProviderUrl; 79 | if (DEBUG) console.log("🏠 Connecting to provider:", localProviderUrlFromEnv); 80 | const localProvider = new JsonRpcProvider(localProviderUrlFromEnv); 81 | 82 | const blockExplorer = targetNetwork.blockExplorer; 83 | 84 | function App(props) { 85 | const mainnetProvider = mainnetInfura; 86 | if (DEBUG) console.log("🌎 mainnetProvider", mainnetProvider); 87 | 88 | const [injectedProvider, setInjectedProvider] = useState(); 89 | /* 💵 This hook will get the price of MATIC from 🦄 Uniswap: */ 90 | const price = useExchangePrice(targetNetwork, mainnetProvider); 91 | 92 | const gasPrice = useGasPrice(targetNetwork, "fast"); 93 | // Use your injected provider from 🦊 Metamask or if you don't have it then instantly generate a 🔥 burner wallet. 94 | const userProvider = useUserProvider(injectedProvider, localProvider); 95 | const address = useUserAddress(userProvider); 96 | if (DEBUG) console.log("👩‍💼 selected address:", address); 97 | 98 | // You can warn the user if you would like them to be on a specific network 99 | let localChainId = localProvider && localProvider._network && localProvider._network.chainId; 100 | if (DEBUG) console.log("🏠 localChainId", localChainId); 101 | 102 | let selectedChainId = userProvider && userProvider._network && userProvider._network.chainId; 103 | if (DEBUG) console.log("🕵🏻‍♂️ selectedChainId:", selectedChainId); 104 | 105 | // For more hooks, check out 🔗eth-hooks at: https://www.npmjs.com/package/eth-hooks 106 | 107 | // The transactor wraps transactions and provides notificiations 108 | const tx = Transactor(userProvider, gasPrice); 109 | 110 | // Faucet Tx can be used to send funds from the faucet 111 | const faucetTx = Transactor(localProvider, gasPrice); 112 | 113 | const yourLocalBalance = useBalance(localProvider, address); 114 | if (DEBUG) console.log("💵 yourLocalBalance", yourLocalBalance ? formatEther(yourLocalBalance) : "..."); 115 | 116 | // Just plug in different 🛰 providers to get your balance on different chains: 117 | const yourMainnetBalance = useBalance(mainnetProvider, address); 118 | if (DEBUG) console.log("💵 yourMainnetBalance", yourMainnetBalance ? formatEther(yourMainnetBalance) : "..."); 119 | 120 | // Load in your local 📝 contract and read a value from it: 121 | const readContracts = useContractLoader(localProvider); 122 | if (DEBUG) console.log("📝 readContracts", readContracts); 123 | 124 | // If you want to make 🔐 write transactions to your contracts, use the userProvider: 125 | const writeContracts = useContractLoader(userProvider); 126 | if (DEBUG) console.log("🔐 writeContracts", writeContracts); 127 | 128 | // keep track of a variable from the contract in the local React state: 129 | const balance = useContractReader(readContracts, "YourCollectible", "balanceOf", [address]); 130 | console.log("🤗 balance:", balance); 131 | 132 | //📟 Listen for broadcast events 133 | const transferEvents = useEventListener(readContracts, "YourCollectible", "Transfer", localProvider, 1); 134 | console.log("📟 Transfer events:", transferEvents); 135 | 136 | // 137 | // 🧠 This effect will update yourCollectibles by polling when your balance changes 138 | // 139 | const yourBalance = balance && balance.toNumber && balance.toNumber(); 140 | const [yourCollectibles, setYourCollectibles] = useState(); 141 | 142 | useEffect(() => { 143 | const updateYourCollectibles = async () => { 144 | let collectibleUpdate = []; 145 | for (let tokenIndex = 0; tokenIndex < balance; tokenIndex++) { 146 | try { 147 | console.log("GEtting token index", tokenIndex); 148 | const tokenId = await readContracts.YourCollectible.tokenOfOwnerByIndex(address, tokenIndex); 149 | console.log("tokenId", tokenId); 150 | const tokenURI = await readContracts.YourCollectible.tokenURI(tokenId); 151 | console.log("tokenURI", tokenURI); 152 | 153 | const strength = await readContracts.YourCollectible.tokenStrength( 154 | utils.id(tokenURI.replace("https://ipfs.io/ipfs/", "")), 155 | ); 156 | 157 | const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", ""); 158 | console.log("ipfsHash", ipfsHash); 159 | 160 | const jsonManifestBuffer = await getFromIPFS(ipfsHash); 161 | 162 | try { 163 | const jsonManifest = JSON.parse(jsonManifestBuffer.toString()); 164 | console.log("jsonManifest", jsonManifest); 165 | collectibleUpdate.push({ id: tokenId, uri: tokenURI, owner: address, ...jsonManifest, strength: strength }); 166 | } catch (e) { 167 | console.log(e); 168 | } 169 | } catch (e) { 170 | console.log(e); 171 | } 172 | } 173 | setYourCollectibles(collectibleUpdate); 174 | }; 175 | updateYourCollectibles(); 176 | }, [address, yourBalance]); 177 | 178 | let networkDisplay = ""; 179 | if (localChainId && selectedChainId && localChainId != selectedChainId) { 180 | networkDisplay = ( 181 |
182 | 186 | You have {NETWORK(selectedChainId).name} selected and you need to be on{" "} 187 | {NETWORK(localChainId).name}. 188 |
189 | } 190 | type="error" 191 | closable={false} 192 | /> 193 | 194 | ); 195 | } else { 196 | networkDisplay = ( 197 |
198 | {targetNetwork.name} 199 |
200 | ); 201 | } 202 | 203 | const loadWeb3Modal = useCallback(async () => { 204 | const provider = await web3Modal.connect(); 205 | setInjectedProvider(new Web3Provider(provider)); 206 | }, [setInjectedProvider]); 207 | 208 | useEffect(() => { 209 | if (web3Modal.cachedProvider) { 210 | loadWeb3Modal(); 211 | } 212 | }, [loadWeb3Modal]); 213 | 214 | const [route, setRoute] = useState(); 215 | useEffect(() => { 216 | setRoute(window.location.pathname); 217 | }, [setRoute]); 218 | 219 | let faucetHint = ""; 220 | const faucetAvailable = 221 | localProvider && 222 | localProvider.connection && 223 | localProvider.connection.url && 224 | localProvider.connection.url.indexOf(window.location.hostname) >= 0 && 225 | !process.env.REACT_APP_PROVIDER && 226 | price > 1; 227 | 228 | const [faucetClicked, setFaucetClicked] = useState(false); 229 | if ( 230 | !faucetClicked && 231 | localProvider && 232 | localProvider._network && 233 | localProvider._network.chainId == 31337 && 234 | yourLocalBalance && 235 | formatEther(yourLocalBalance) <= 0 236 | ) { 237 | faucetHint = ( 238 |
239 | 251 |
252 | ); 253 | } 254 | 255 | const [yourJSON, setYourJSON] = useState(STARTING_JSON); 256 | const [sending, setSending] = useState(); 257 | const [ipfsHash, setIpfsHash] = useState(); 258 | const [ipfsDownHash, setIpfsDownHash] = useState(); 259 | 260 | const [downloading, setDownloading] = useState(); 261 | const [ipfsContent, setIpfsContent] = useState(); 262 | 263 | const [transferToAddresses, setTransferToAddresses] = useState({}); 264 | 265 | const [loadedAssets, setLoadedAssets] = useState(); 266 | useEffect(() => { 267 | const updateYourCollectibles = async () => { 268 | let assetUpdate = []; 269 | for (let a in assets) { 270 | try { 271 | const forSale = await readContracts.YourCollectible.forSale(utils.id(a)); 272 | let owner; 273 | let strength = 0; 274 | if (!forSale) { 275 | strength = await readContracts.YourCollectible.tokenStrength(utils.id(a)); 276 | const tokenId = await readContracts.YourCollectible.uriToTokenId(utils.id(a)); 277 | owner = await readContracts.YourCollectible.ownerOf(tokenId); 278 | } 279 | assetUpdate.push({ id: a, ...assets[a], forSale: forSale, owner: owner, strength }); 280 | } catch (e) { 281 | console.log(e); 282 | } 283 | } 284 | setLoadedAssets(assetUpdate); 285 | }; 286 | if (readContracts && readContracts.YourCollectible) updateYourCollectibles(); 287 | }, [assets, readContracts, transferEvents]); 288 | 289 | let galleryList = []; 290 | for (let a in loadedAssets) { 291 | console.log("loadedAssets", a, loadedAssets[a]); 292 | 293 | let cardActions = []; 294 | if (loadedAssets[a].forSale) { 295 | cardActions.push( 296 |
297 | 305 |
, 306 | ); 307 | } else { 308 | cardActions.push( 309 |
310 | owned by:{" "} 311 |
317 |
str:{loadedAssets[a].strength}
318 |
, 319 | ); 320 | } 321 | 322 | galleryList.push( 323 | 329 | {loadedAssets[a].name}{" "} 330 | 331 | 332 | 333 | 334 | } 335 | > 336 | 337 |
{loadedAssets[a].description}
338 |
, 339 | ); 340 | } 341 | 342 | return ( 343 |
344 | {/* ✏️ Edit the header and change the title to your project name */} 345 |
346 | {networkDisplay} 347 | 348 | 349 | 350 | 351 | { 353 | setRoute("/"); 354 | }} 355 | to="/" 356 | > 357 | Gallery 358 | 359 | 360 | 361 | { 363 | setRoute("/yourcollectibles"); 364 | }} 365 | to="/yourcollectibles" 366 | > 367 | YourCollectibles 368 | 369 | 370 | 371 | { 373 | setRoute("/transfers"); 374 | }} 375 | to="/transfers" 376 | > 377 | Transfers 378 | 379 | 380 | 381 | { 383 | setRoute("/ipfsup"); 384 | }} 385 | to="/ipfsup" 386 | > 387 | IPFS Upload 388 | 389 | 390 | 391 | { 393 | setRoute("/ipfsdown"); 394 | }} 395 | to="/ipfsdown" 396 | > 397 | IPFS Download 398 | 399 | 400 | 401 | { 403 | setRoute("/debugcontracts"); 404 | }} 405 | to="/debugcontracts" 406 | > 407 | Debug Contracts 408 | 409 | 410 | 411 | 412 | 413 | 414 |
415 | 416 | {galleryList} 417 | 418 |
419 |
420 | 421 | 422 |
423 | { 427 | console.log("ITEM", item); 428 | const id = item.id.toNumber(); 429 | return ( 430 | 431 | 434 | #{id} {item.name} 435 |
436 | } 437 | > 438 |
439 | 440 |
441 |
{item.description}
442 |
strength: {item.strength}
443 | 444 | 445 |
446 | owner:{" "} 447 |
453 | { 458 | let update = {}; 459 | update[id] = newValue; 460 | setTransferToAddresses({ ...transferToAddresses, ...update }); 461 | }} 462 | /> 463 | 471 |
472 | 473 | ); 474 | }} 475 | /> 476 |
477 | 478 | 479 | 480 |
481 | { 485 | return ( 486 | 487 | #{item[2].toNumber()} 488 |
=> 489 |
490 | 491 | ); 492 | }} 493 | /> 494 |
495 |
496 | 497 | 498 |
499 | { 505 | setYourJSON(edit.updated_src); 506 | }} 507 | onAdd={(add, a) => { 508 | setYourJSON(add.updated_src); 509 | }} 510 | onDelete={(del, a) => { 511 | setYourJSON(del.updated_src); 512 | }} 513 | /> 514 |
515 | 516 | 536 | 537 |
{ipfsHash}
538 |
539 | 540 |
541 | { 545 | setIpfsDownHash(e.target.value); 546 | }} 547 | /> 548 |
549 | 568 | 569 |
{ipfsContent}
570 |
571 | 572 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | {/* 👨‍💼 Your account is in the top right with a wallet at connect options */} 586 |
587 | 598 | {faucetHint} 599 |
600 | 601 |
602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 614 | 615 | 616 | 617 | 618 | 619 | { 620 | /* if the local provider has a signer, let's show the faucet: */ 621 | faucetAvailable ? ( 622 | 623 | ) : ( 624 | "" 625 | ) 626 | } 627 | 628 | 629 |
630 | 631 | ); 632 | } 633 | 634 | /* 635 | Web3 modal helps us "connect" external wallets: 636 | */ 637 | const web3Modal = new Web3Modal({ 638 | // network: "mainnet", // optional 639 | cacheProvider: true, // optional 640 | providerOptions: { 641 | walletconnect: { 642 | package: WalletConnectProvider, // required 643 | options: { 644 | infuraId: INFURA_ID, 645 | }, 646 | }, 647 | }, 648 | }); 649 | 650 | const logoutOfWeb3Modal = async () => { 651 | await web3Modal.clearCachedProvider(); 652 | setTimeout(() => { 653 | window.location.reload(); 654 | }, 1); 655 | }; 656 | 657 | window.ethereum && 658 | window.ethereum.on("chainChanged", chainId => { 659 | setTimeout(() => { 660 | window.location.reload(); 661 | }, 1); 662 | }); 663 | 664 | export default App; 665 | -------------------------------------------------------------------------------- /packages/react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import App from "./App"; 4 | 5 | test("renders learn react link", () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Account.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "antd"; 3 | import Address from "./Address"; 4 | import Balance from "./Balance"; 5 | import Wallet from "./Wallet"; 6 | export default function Account({ 7 | address, 8 | userProvider, 9 | localProvider, 10 | mainnetProvider, 11 | price, 12 | minimized, 13 | web3Modal, 14 | loadWeb3Modal, 15 | logoutOfWeb3Modal, 16 | blockExplorer, 17 | }) { 18 | const modalButtons = []; 19 | if (web3Modal) { 20 | if (web3Modal.cachedProvider) { 21 | modalButtons.push( 22 | , 31 | ); 32 | } else { 33 | modalButtons.push( 34 | , 44 | ); 45 | } 46 | } 47 | 48 | const display = minimized ? ( 49 | "" 50 | ) : ( 51 | 52 | {address ?
: "Connecting..."} 53 | 54 | 55 | 56 | ); 57 | 58 | return ( 59 |
60 | {display} 61 | {modalButtons} 62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Address.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blockies from "react-blockies"; 3 | import { Typography, Skeleton } from "antd"; 4 | import { useLookupAddress } from "../hooks"; 5 | 6 | // changed value={address} to address={address} 7 | 8 | 9 | const { Text } = Typography; 10 | 11 | const blockExplorerLink = (address, blockExplorer) => `${blockExplorer || "https://polygonscan.com/"}${"address/"}${address}`; 12 | 13 | export default function Address(props) { 14 | 15 | const address = props.value || props.address; 16 | 17 | const ens = useLookupAddress(props.ensProvider, address); 18 | 19 | if (!address) { 20 | return ( 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | let displayAddress = address.substr(0, 6); 28 | 29 | if (ens && ens.indexOf("0x")<0) { 30 | displayAddress = ens; 31 | } else if (props.size === "short") { 32 | displayAddress += "..." + address.substr(-4); 33 | } else if (props.size === "long") { 34 | displayAddress = address; 35 | } 36 | 37 | const polygonscanLink = blockExplorerLink(address, props.blockExplorer); 38 | if (props.minimized) { 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | } 47 | 48 | let text; 49 | if (props.onChange) { 50 | text = ( 51 | 52 | 53 | {displayAddress} 54 | 55 | 56 | ); 57 | } else { 58 | text = ( 59 | 60 | 61 | {displayAddress} 62 | 63 | 64 | ); 65 | } 66 | 67 | return ( 68 | 69 | 70 | 71 | 72 | {text} 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /packages/react-app/src/components/AddressInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from "react"; 2 | import QrReader from "react-qr-reader"; 3 | import { CameraOutlined, QrcodeOutlined } from "@ant-design/icons"; 4 | import { Input, Badge } from "antd"; 5 | import { useLookupAddress } from "eth-hooks"; 6 | import Blockie from "./Blockie"; 7 | 8 | // probably we need to change value={toAddress} to address={toAddress} 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Displays an address input with QR scan option 14 | 15 | ~ How can I use? ~ 16 | 17 | 24 | 25 | ~ Features ~ 26 | 27 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 28 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 29 | - Provide placeholder="Enter address" value for the input 30 | - Value of the address input is stored in value={toAddress} 31 | - Control input change by onChange={setToAddress} 32 | or onChange={address => { setToAddress(address);}} 33 | */ 34 | 35 | export default function AddressInput(props) { 36 | const [value, setValue] = useState(props.value); 37 | const [scan, setScan] = useState(false); 38 | 39 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 40 | const ens = useLookupAddress(props.ensProvider, currentValue); 41 | 42 | const scannerButton = ( 43 |
{ 46 | setScan(!scan); 47 | }} 48 | > 49 | }> 50 | 51 | {" "} 52 | Scan 53 |
54 | ); 55 | 56 | const {ensProvider, onChange} = props; 57 | const updateAddress = useCallback( 58 | async newValue => { 59 | if (typeof newValue !== "undefined") { 60 | let address = newValue; 61 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 62 | try { 63 | const possibleAddress = await ensProvider.resolveName(address); 64 | if (possibleAddress) { 65 | address = possibleAddress; 66 | } 67 | // eslint-disable-next-line no-empty 68 | } catch (e) {} 69 | } 70 | setValue(address); 71 | if (typeof onChange === "function") { 72 | onChange(address); 73 | } 74 | } 75 | }, 76 | [ensProvider, onChange], 77 | ); 78 | 79 | const scanner = scan ? ( 80 |
{ 89 | setScan(false); 90 | }} 91 | > 92 | { 96 | console.log("SCAN ERROR", e); 97 | setScan(false); 98 | }} 99 | onScan={newValue => { 100 | if (newValue) { 101 | console.log("SCAN VALUE", newValue); 102 | let possibleNewValue = newValue; 103 | if (possibleNewValue.indexOf("/") >= 0) { 104 | possibleNewValue = possibleNewValue.substr(possibleNewValue.lastIndexOf("0x")); 105 | console.log("CLEANED VALUE", possibleNewValue); 106 | } 107 | setScan(false); 108 | updateAddress(possibleNewValue); 109 | } 110 | }} 111 | style={{ width: "100%" }} 112 | /> 113 |
114 | ) : ( 115 | "" 116 | ); 117 | 118 | return ( 119 |
120 | {scanner} 121 | } 128 | value={ens || currentValue} 129 | addonAfter={scannerButton} 130 | onChange={e => { 131 | updateAddress(e.target.value); 132 | }} 133 | /> 134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Balance.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { formatEther } from "@ethersproject/units"; 3 | import { usePoller } from "eth-hooks"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Displays a balance of given address in ether & dollar 9 | 10 | ~ How can I use? ~ 11 | 12 | 17 | 18 | ~ If you already have the balance as a bignumber ~ 19 | 23 | 24 | ~ Features ~ 25 | 26 | - Provide address={address} and get balance corresponding to given address 27 | - Provide provider={mainnetProvider} to access balance on mainnet or any other network (ex. localProvider) 28 | - Provide price={price} of ether and get your balance converted to dollars 29 | */ 30 | 31 | 32 | export default function Balance(props) { 33 | const [dollarMode, setDollarMode] = useState(true); 34 | const [balance, setBalance] = useState(); 35 | 36 | const getBalance = async () => { 37 | if (props.address && props.provider) { 38 | try { 39 | const newBalance = await props.provider.getBalance(props.address); 40 | setBalance(newBalance); 41 | } catch (e) { 42 | console.log(e); 43 | } 44 | } 45 | }; 46 | 47 | usePoller( 48 | () => { 49 | getBalance(); 50 | }, 51 | props.pollTime ? props.pollTime : 1999, 52 | ); 53 | 54 | let floatBalance = parseFloat("0.00"); 55 | 56 | let usingBalance = balance; 57 | 58 | if (typeof props.balance !== "undefined") { 59 | usingBalance = props.balance; 60 | } 61 | if (typeof props.value !== "undefined") { 62 | usingBalance = props.value; 63 | } 64 | 65 | if (usingBalance) { 66 | const etherBalance = formatEther(usingBalance); 67 | parseFloat(etherBalance).toFixed(2); 68 | floatBalance = parseFloat(etherBalance); 69 | } 70 | 71 | let displayBalance = floatBalance.toFixed(4); 72 | 73 | const price = props.price || props.dollarMultiplier 74 | 75 | if (price && dollarMode) { 76 | displayBalance = "$" + (floatBalance * price).toFixed(2); 77 | } 78 | 79 | return ( 80 | { 88 | setDollarMode(!dollarMode); 89 | }} 90 | > 91 | {displayBalance} 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Blockie.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blockies from "react-blockies"; 3 | 4 | // provides a blockie image for the address using "react-blockies" library 5 | 6 | export default function Blockie(props) { 7 | if (!props.address || typeof props.address.toLowerCase !== "function") { 8 | return ; 9 | } 10 | // eslint-disable-next-line react/jsx-props-no-spreading 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-app/src/components/BytesStringInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Input } from "antd"; 3 | const { utils, constants } = require("ethers"); 4 | 5 | 6 | export default function BytesStringInput(props) { 7 | const [mode, setMode] = useState("STRING"); 8 | const [display, setDisplay] = useState(); 9 | const [value, setValue] = useState(constants.HashZero); 10 | 11 | // current value is the value in bytes32 12 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 13 | 14 | const option = title => { 15 | 16 | return ( 17 |
{ 20 | if (mode === "STRING") { 21 | setMode("BYTES32"); 22 | if (!utils.isHexString(currentValue)) { 23 | /* in case user enters invalid bytes32 number, 24 | it considers it as string and converts to bytes32 */ 25 | const changedValue = utils.formatBytes32String(currentValue); 26 | setDisplay(changedValue); 27 | } 28 | else { 29 | setDisplay(currentValue); 30 | } 31 | 32 | 33 | } 34 | else { 35 | setMode("STRING"); 36 | if (currentValue && utils.isHexString(currentValue)) { 37 | setDisplay(utils.parseBytes32String(currentValue)); 38 | } 39 | else { 40 | setDisplay(currentValue); 41 | } 42 | } 43 | }} 44 | > 45 | {title} 46 |
47 | ); 48 | }; 49 | 50 | let addonAfter; 51 | if (mode === "STRING") { 52 | addonAfter = option("STRING 🔀"); 53 | } else { 54 | addonAfter = option("BYTES32 🔀"); 55 | } 56 | 57 | useEffect( 58 | ()=>{ 59 | if(!currentValue){ 60 | setDisplay(""); 61 | } 62 | } 63 | ,[ currentValue ]) 64 | 65 | return ( 66 | { 72 | const newValue = e.target.value; 73 | if (mode === "STRING") { 74 | if (typeof props.onChange === "function") { 75 | props.onChange(utils.formatBytes32String(newValue)); 76 | } 77 | setValue(utils.formatBytes32String(newValue)); 78 | setDisplay(newValue); 79 | 80 | } else { 81 | if (typeof props.onChange === "function") { 82 | props.onChange(newValue); 83 | } 84 | setValue(newValue); 85 | setDisplay(newValue); 86 | } 87 | }} 88 | /> 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/DisplayVariable.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | /* eslint-disable jsx-a11y/anchor-is-valid */ 3 | import React, { useState, useEffect, useCallback } from "react"; 4 | import { Row, Col, Divider } from "antd"; 5 | import tryToDisplay from "./utils"; 6 | 7 | const DisplayVariable = ({ contractFunction, functionInfo, refreshRequired, triggerRefresh}) => { 8 | const [variable, setVariable] = useState(""); 9 | 10 | const refresh = useCallback(async () => { 11 | try { 12 | const funcResponse = await contractFunction(); 13 | setVariable(funcResponse); 14 | triggerRefresh(false); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | }, [setVariable, contractFunction, triggerRefresh]); 19 | 20 | useEffect(() => { 21 | refresh(); 22 | }, [refresh, refreshRequired, contractFunction]); 23 | 24 | return ( 25 |
26 | 27 | 36 | {functionInfo.name} 37 | 38 | 39 |

{tryToDisplay(variable)}

40 | 41 | 42 |

43 | 44 | 🔄 45 | 46 |

47 | 48 |
49 | 50 |
51 | ); 52 | }; 53 | 54 | export default DisplayVariable; 55 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/FunctionForm.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 2 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 3 | /* eslint-disable jsx-a11y/accessible-emoji */ 4 | import React, { useState } from "react"; 5 | import { BigNumber } from "@ethersproject/bignumber"; 6 | import { hexlify } from "@ethersproject/bytes"; 7 | import { Row, Col, Input, Divider, Tooltip, Button } from "antd"; 8 | import { Transactor } from "../../helpers"; 9 | import tryToDisplay from "./utils"; 10 | import Blockies from "react-blockies"; 11 | const { utils } = require("ethers"); 12 | 13 | 14 | export default function FunctionForm({ contractFunction, functionInfo, provider, gasPrice, triggerRefresh }) { 15 | const [form, setForm] = useState({}); 16 | const [txValue, setTxValue] = useState(); 17 | const [returnValue, setReturnValue] = useState(); 18 | 19 | const tx = Transactor(provider, gasPrice); 20 | 21 | let inputIndex = 0; 22 | const inputs = functionInfo.inputs.map(input => { 23 | 24 | const key = functionInfo.name + "_" + input.name + "_" + input.type + "_" + inputIndex++ 25 | 26 | let buttons = "" 27 | if (input.type === "bytes32") { 28 | buttons = ( 29 | 30 |
{ 34 | if (utils.isHexString(form[key])) { 35 | const formUpdate = { ...form }; 36 | formUpdate[key] = utils.parseBytes32String(form[key]); 37 | setForm(formUpdate); 38 | } else { 39 | const formUpdate = { ...form }; 40 | formUpdate[key] = utils.formatBytes32String(form[key]); 41 | setForm(formUpdate); 42 | } 43 | }} 44 | > 45 | #️⃣ 46 |
47 |
48 | ) 49 | } else if (input.type === "bytes") { 50 | buttons = ( 51 | 52 |
{ 56 | if (utils.isHexString(form[key])) { 57 | const formUpdate = { ...form }; 58 | formUpdate[key] = utils.toUtf8String(form[key]); 59 | setForm(formUpdate); 60 | } else { 61 | const formUpdate = { ...form }; 62 | formUpdate[key] = utils.hexlify(utils.toUtf8Bytes(form[key])) 63 | setForm(formUpdate); 64 | } 65 | }} 66 | > 67 | #️⃣ 68 |
69 |
70 | ) 71 | } else if (input.type == "uint256") { 72 | buttons = ( 73 | 74 |
{ 78 | const formUpdate = { ...form }; 79 | formUpdate[key] = utils.parseEther(form[key]) 80 | setForm(formUpdate); 81 | }} 82 | > 83 | ✴️ 84 |
85 |
86 | ) 87 | } else if (input.type == "address") { 88 | const possibleAddress = form[key]&&form[key].toLowerCase&&form[key].toLowerCase().trim() 89 | if(possibleAddress && possibleAddress.length==42){ 90 | buttons = ( 91 | 92 | 93 | 94 | ) 95 | } 96 | } 97 | 98 | 99 | 100 | 101 | return ( 102 |
103 | { 110 | const formUpdate = { ...form }; 111 | formUpdate[event.target.name] = event.target.value; 112 | setForm(formUpdate); 113 | }} 114 | suffix={buttons} 115 | /> 116 |
117 | ) 118 | }); 119 | 120 | const txValueInput = ( 121 |
122 | setTxValue(e.target.value)} 125 | value={txValue} 126 | addonAfter={ 127 |
128 | 129 | 130 | 131 |
{ 135 | let floatValue = parseFloat(txValue) 136 | if(floatValue) setTxValue("" + floatValue * 10 ** 18); 137 | }} 138 | > 139 | ✳️ 140 |
141 |
142 | 143 | 144 | 145 |
{ 149 | setTxValue(BigNumber.from(txValue).toHexString()); 150 | }} 151 | > 152 | #️⃣ 153 |
154 |
155 | 156 |
157 |
158 | } 159 | /> 160 |
161 | ); 162 | 163 | if (functionInfo.payable) { 164 | inputs.push(txValueInput); 165 | } 166 | 167 | const buttonIcon = functionInfo.type === "call" ? : ; 168 | inputs.push( 169 |
170 | setReturnValue(e.target.value)} 172 | defaultValue="" 173 | bordered={false} 174 | disabled={true} 175 | value={returnValue} 176 | suffix={ 177 |
{ 181 | let innerIndex = 0 182 | const args = functionInfo.inputs.map((input) => { 183 | const key = functionInfo.name + "_" + input.name + "_" + input.type + "_" + innerIndex++ 184 | let value = form[key] 185 | if(input.baseType=="array"){ 186 | value = JSON.parse(value) 187 | } else if(input.type === "bool"){ 188 | if(value==='true' || value==='1' || value ==="0x1"|| value ==="0x01"|| value ==="0x0001"){ 189 | value = 1; 190 | }else{ 191 | value = 0; 192 | } 193 | } 194 | return value 195 | }); 196 | 197 | let result 198 | if(functionInfo.stateMutability === "view"||functionInfo.stateMutability === "pure"){ 199 | const returned = await contractFunction(...args) 200 | result = tryToDisplay(returned); 201 | }else{ 202 | const overrides = {}; 203 | if (txValue) { 204 | overrides.value = txValue; // ethers.utils.parseEther() 205 | } 206 | // Uncomment this if you want to skip the gas estimation for each transaction 207 | // overrides.gasLimit = hexlify(1200000); 208 | 209 | 210 | // console.log("Running with extras",extras) 211 | const returned = await tx(contractFunction(...args, overrides)); 212 | result = tryToDisplay(returned); 213 | } 214 | 215 | 216 | console.log("SETTING RESULT:", result); 217 | setReturnValue(result); 218 | triggerRefresh(true); 219 | }} 220 | > 221 | {buttonIcon} 222 |
223 | } 224 | /> 225 |
, 226 | ); 227 | 228 | return ( 229 |
230 | 231 | 240 | {functionInfo.name} 241 | 242 | {inputs} 243 | 244 | 245 |
246 | ); 247 | } 248 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState } from "react"; 2 | import { Card } from "antd"; 3 | import { useContractLoader, useContractExistsAtAddress } from "../../hooks"; 4 | import Account from "../Account"; 5 | import DisplayVariable from "./DisplayVariable"; 6 | import FunctionForm from "./FunctionForm"; 7 | 8 | const noContractDisplay = ( 9 |
10 | Loading...{" "} 11 |
12 | You need to run{" "} 13 | 14 | yarn run chain 15 | {" "} 16 | and{" "} 17 | 18 | yarn run deploy 19 | {" "} 20 | to see your contract here. 21 |
22 |
23 | 24 | ☢️ 25 | 26 | Warning: You might need to run 27 | 28 | yarn run deploy 29 | {" "} 30 | again after the frontend comes up! 31 |
32 |
33 | ); 34 | 35 | const isQueryable = fn => (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; 36 | 37 | export default function Contract({ customContract, account, gasPrice, signer, provider, name, show, price, blockExplorer }) { 38 | 39 | const contracts = useContractLoader(provider); 40 | let contract 41 | if(!customContract){ 42 | contract = contracts ? contracts[name] : ""; 43 | }else{ 44 | contract = customContract 45 | } 46 | 47 | const address = contract ? contract.address : ""; 48 | const contractIsDeployed = useContractExistsAtAddress(provider, address); 49 | 50 | const displayedContractFunctions = useMemo( 51 | () => 52 | contract 53 | ? Object.values(contract.interface.functions).filter( 54 | fn => fn.type === "function" && !(show && show.indexOf(fn.name) < 0), 55 | ) 56 | : [], 57 | [contract, show], 58 | ); 59 | 60 | const [refreshRequired, triggerRefresh] = useState(false) 61 | const contractDisplay = displayedContractFunctions.map(fn => { 62 | if (isQueryable(fn)) { 63 | // If there are no inputs, just display return value 64 | return ; 65 | } 66 | // If there are inputs, display a form to allow users to provide these 67 | return ( 68 | 76 | ); 77 | }); 78 | 79 | return ( 80 |
81 | 84 | {name} 85 |
86 | 94 | {account} 95 |
96 |
97 | } 98 | size="large" 99 | style={{ marginTop: 25, width: "100%" }} 100 | loading={contractDisplay && contractDisplay.length <= 0} 101 | > 102 | {contractIsDeployed ? contractDisplay : noContractDisplay} 103 | 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { formatUnits } from "@ethersproject/units"; 3 | import { isAddress } from "@ethersproject/address"; 4 | import { Address } from "../../components"; 5 | 6 | const tryToDisplay = thing => { 7 | if (thing && thing.toNumber) { 8 | try { 9 | return thing.toNumber(); 10 | } catch (e) { 11 | return "MATIC"+formatUnits(thing, "ether"); 12 | } 13 | } 14 | if(thing && thing.indexOf && thing.indexOf("0x")==0 && thing.length == 42){ 15 | return ( 16 |
20 | ) 21 | } 22 | return JSON.stringify(thing); 23 | }; 24 | 25 | export default tryToDisplay; 26 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Faucet.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from "react"; 2 | import { Input, Button, Tooltip } from "antd"; 3 | import Blockies from "react-blockies"; 4 | import { SendOutlined } from "@ant-design/icons"; 5 | import { parseEther } from "@ethersproject/units"; 6 | import { Transactor } from "../helpers"; 7 | import Wallet from "./Wallet"; 8 | import { useLookupAddress } from "eth-hooks"; 9 | 10 | export default function Faucet(props) { 11 | const [address, setAddress] = useState(); 12 | 13 | let blockie; 14 | if (address && typeof address.toLowerCase === "function") { 15 | blockie = ; 16 | } else { 17 | blockie =
; 18 | } 19 | 20 | const ens = useLookupAddress(props.ensProvider, address); 21 | 22 | const updateAddress = useCallback( 23 | async newValue => { 24 | if (typeof newValue !== "undefined") { 25 | let address = newValue; 26 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 27 | try { 28 | const possibleAddress = await props.ensProvider.resolveName(address); 29 | if (possibleAddress) { 30 | address = possibleAddress; 31 | } 32 | // eslint-disable-next-line no-empty 33 | } catch (e) {} 34 | } 35 | setAddress(address); 36 | } 37 | }, 38 | [props.ensProvider, props.onChange], 39 | ); 40 | 41 | const tx = Transactor(props.localProvider); 42 | 43 | return ( 44 | 45 | { 52 | //setAddress(e.target.value); 53 | updateAddress(e.target.value); 54 | }} 55 | suffix={ 56 | 57 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PageHeader } from "antd"; 3 | 4 | // displays a page header 5 | 6 | export default function Header() { 7 | return ( 8 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-app/src/components/MaticInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Input } from "antd"; 3 | 4 | export default function MaticInput(props) { 5 | const [mode, setMode] = useState(props.price ? "USD" : "MATIC"); 6 | const [display, setDisplay] = useState(); 7 | const [value, setValue] = useState(); 8 | 9 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 10 | 11 | const option = title => { 12 | if (!props.price) return ""; 13 | return ( 14 |
{ 17 | if (mode === "USD") { 18 | setMode("MATIC"); 19 | setDisplay(currentValue); 20 | } else { 21 | setMode("USD"); 22 | if (currentValue) { 23 | const usdValue = "" + (parseFloat(currentValue) * props.price).toFixed(2); 24 | setDisplay(usdValue); 25 | } else { 26 | setDisplay(currentValue); 27 | } 28 | } 29 | }} 30 | > 31 | {title} 32 |
33 | ); 34 | }; 35 | 36 | let prefix; 37 | let addonAfter; 38 | if (mode === "USD") { 39 | prefix = "$"; 40 | addonAfter = option("USD 🔀"); 41 | } else { 42 | prefix = "Ξ"; 43 | addonAfter = option("MATIC 🔀"); 44 | } 45 | 46 | useEffect( 47 | ()=>{ 48 | if(!currentValue){ 49 | setDisplay(""); 50 | } 51 | } 52 | ,[ currentValue ]) 53 | 54 | return ( 55 | { 62 | const newValue = e.target.value; 63 | if (mode === "USD") { 64 | const possibleNewValue = parseFloat(newValue) 65 | if(possibleNewValue){ 66 | const maticValue = possibleNewValue / props.price; 67 | setValue(maticValue); 68 | if (typeof props.onChange === "function") { 69 | props.onChange(maticValue); 70 | } 71 | setDisplay(newValue); 72 | }else{ 73 | setDisplay(newValue); 74 | } 75 | } else { 76 | setValue(newValue); 77 | if (typeof props.onChange === "function") { 78 | props.onChange(newValue); 79 | } 80 | setDisplay(newValue); 81 | } 82 | }} 83 | /> 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Provider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Badge } from "antd"; 3 | import { usePoller, useBlockNumber } from "eth-hooks"; 4 | // import { WalletOutlined } from '@ant-design/icons'; 5 | 6 | import Address from "./Address"; 7 | 8 | export default function Provider(props) { 9 | const [showMore, setShowMore] = useState(false); 10 | const [status, setStatus] = useState("processing"); 11 | const [network, setNetwork] = useState(); 12 | const [signer, setSigner] = useState(); 13 | const [address, setAddress] = useState(); 14 | 15 | const blockNumber = useBlockNumber(props.provider); 16 | 17 | usePoller(async () => { 18 | if (props.provider && typeof props.provider.getNetwork === "function") { 19 | try { 20 | const newNetwork = await props.provider.getNetwork(); 21 | setNetwork(newNetwork); 22 | if (newNetwork.chainId > 0) { 23 | setStatus("success"); 24 | } else { 25 | setStatus("warning"); 26 | } 27 | } catch (e) { 28 | console.log(e); 29 | setStatus("processing"); 30 | } 31 | try { 32 | const newSigner = await props.provider.getSigner(); 33 | setSigner(newSigner); 34 | const newAddress = await newSigner.getAddress(); 35 | setAddress(newAddress); 36 | // eslint-disable-next-line no-empty 37 | } catch (e) {} 38 | } 39 | }, 1377); 40 | 41 | if ( 42 | typeof props.provider === "undefined" || 43 | typeof props.provider.getNetwork !== "function" || 44 | !network || 45 | !network.chainId 46 | ) { 47 | return ( 48 | 57 | ); 58 | } 59 | 60 | let showExtra = ""; 61 | if (showMore) { 62 | showExtra = ( 63 | 64 | 65 | id: 66 | {network ? network.chainId : ""} 67 | 68 | 69 | name: 70 | {network ? network.name : ""} 71 | 72 | 73 | ); 74 | } 75 | 76 | let showWallet = ""; 77 | if (typeof signer !== "undefined" && address) { 78 | showWallet = ( 79 | 80 | 81 |
82 | 83 | 84 | ); 85 | } 86 | 87 | return ( 88 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Ramp.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Modal, Divider } from "antd"; 3 | import { DollarCircleOutlined } from "@ant-design/icons"; 4 | import { RampInstantSDK } from "@ramp-network/ramp-instant-sdk"; 5 | 6 | 7 | export default function Ramp(props) { 8 | const [modalUp, setModalUp] = useState("down"); 9 | 10 | const type = "default"; 11 | 12 | let allFaucets = [] 13 | for(let n in props.networks){ 14 | if(props.networks[n].chainId!=31337&&props.networks[n].chainId!=1){ 15 | allFaucets.push( 16 |

17 | 28 |

29 | ) 30 | } 31 | } 32 | 33 | return ( 34 |
35 | 44 | { 48 | setModalUp("down"); 49 | }} 50 | footer={[ 51 | , 59 | ]} 60 | > 61 |

62 | 75 |

76 |

77 | {" "} 78 | 99 |

100 | 101 |

102 | 115 |

116 | 117 | 118 | 119 |

Mumbai MATIC

120 | 121 | {allFaucets} 122 | 123 |
124 |
125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /packages/react-app/src/components/ThemeSwitch.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Switch } from "antd"; 3 | import { useThemeSwitcher } from "react-css-theme-switcher"; 4 | 5 | export default function ThemeSwitcher() { 6 | 7 | const theme = window.localStorage.getItem("theme"); 8 | const [isDarkMode, setIsDarkMode] = useState(!theme || theme == "light" ? false : true); 9 | const { switcher, currentTheme, status, themes } = useThemeSwitcher(); 10 | 11 | useEffect(() => { 12 | window.localStorage.setItem("theme", currentTheme); 13 | }, [currentTheme]); 14 | 15 | const toggleTheme = (isChecked) => { 16 | setIsDarkMode(isChecked); 17 | switcher({ theme: isChecked ? themes.dark : themes.light }); 18 | }; 19 | 20 | // Avoid theme change flicker 21 | // if (status === "loading") { 22 | // return null; 23 | // } 24 | 25 | return ( 26 |
27 | {currentTheme=="light" ? "☀️" : "🌜"} 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-app/src/components/TokenBalance.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { formatEther } from "@ethersproject/units"; 3 | import { useTokenBalance } from "eth-hooks"; 4 | 5 | export default function TokenBalance(props) { 6 | const [dollarMode, setDollarMode] = useState(true); 7 | 8 | const tokenContract = props.contracts && props.contracts[props.name]; 9 | const balance = useTokenBalance(tokenContract, props.address, 1777); 10 | 11 | let floatBalance = parseFloat("0.00"); 12 | 13 | let usingBalance = balance; 14 | 15 | if (typeof props.balance !== "undefined") { 16 | usingBalance = props.balance; 17 | } 18 | 19 | if (usingBalance) { 20 | const etherBalance = formatEther(usingBalance); 21 | parseFloat(etherBalance).toFixed(2); 22 | floatBalance = parseFloat(etherBalance); 23 | } 24 | 25 | let displayBalance = floatBalance.toFixed(4); 26 | 27 | if (props.dollarMultiplier && dollarMode) { 28 | displayBalance = "$" + (floatBalance * props.dollarMultiplier).toFixed(2); 29 | } 30 | 31 | return ( 32 | { 40 | setDollarMode(!dollarMode); 41 | }} 42 | > 43 | {props.img} {displayBalance} 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Wallet.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { WalletOutlined, QrcodeOutlined, SendOutlined, KeyOutlined } from "@ant-design/icons"; 3 | import { Tooltip, Spin, Modal, Button, Typography } from "antd"; 4 | import QR from "qrcode.react"; 5 | import { parseEther } from "@ethersproject/units"; 6 | import { useUserAddress } from "eth-hooks"; 7 | import { Transactor } from "../helpers"; 8 | import Address from "./Address"; 9 | import Balance from "./Balance"; 10 | import AddressInput from "./AddressInput"; 11 | import MaticInput from "./MaticInput"; 12 | import { ethers } from "ethers"; 13 | const { Text, Paragraph } = Typography; 14 | export default function Wallet(props) { 15 | const signerAddress = useUserAddress(props.provider); 16 | const selectedAddress = props.address || signerAddress; 17 | 18 | const [open, setOpen] = useState(); 19 | const [qr, setQr] = useState(); 20 | const [amount, setAmount] = useState(); 21 | const [toAddress, setToAddress] = useState(); 22 | const [pk, setPK] = useState() 23 | 24 | const providerSend = props.provider ? ( 25 | 26 | { 28 | setOpen(!open); 29 | }} 30 | rotate={-90} 31 | style={{ 32 | padding: 7, 33 | color: props.color ? props.color : "", 34 | cursor: "pointer", 35 | fontSize: 28, 36 | verticalAlign: "middle", 37 | }} 38 | /> 39 | 40 | ) : ( 41 | "" 42 | ); 43 | 44 | let display; 45 | let receiveButton; 46 | let privateKeyButton 47 | if (qr) { 48 | display = ( 49 |
50 |
51 | {selectedAddress} 52 |
53 | 61 |
62 | ); 63 | receiveButton = ( 64 | 72 | ); 73 | privateKeyButton = ( 74 | 77 | ) 78 | }else if(pk){ 79 | 80 | let pk = localStorage.getItem("metaPrivateKey") 81 | let wallet = new ethers.Wallet(pk) 82 | 83 | if(wallet.address!==selectedAddress){ 84 | display = ( 85 |
86 | *injected account*, private key unknown 87 |
88 | ) 89 | }else{ 90 | 91 | let extraPkDisplayAdded = {} 92 | let extraPkDisplay = [] 93 | extraPkDisplayAdded[wallet.address] = true 94 | extraPkDisplay.push( 95 | 100 | ) 101 | for (var key in localStorage){ 102 | if(key.indexOf("metaPrivateKey_backup")>=0){ 103 | console.log(key) 104 | let pastpk = localStorage.getItem(key) 105 | let pastwallet = new ethers.Wallet(pastpk) 106 | if(!extraPkDisplayAdded[pastwallet.address] /*&& selectedAddress!=pastwallet.address*/){ 107 | extraPkDisplayAdded[pastwallet.address] = true 108 | extraPkDisplay.push( 109 | 114 | ) 115 | } 116 | } 117 | } 118 | 119 | 120 | display = ( 121 |
122 | Private Key: 123 | 124 |
125 | {pk} 126 |
127 | 128 |
129 | 130 | Point your camera phone at qr code to open in 131 | burner wallet: 132 | 133 | 134 | 135 | {"https://xdai.io/"+pk} 136 | 137 | {extraPkDisplay?( 138 |
139 |

140 | Known Private Keys: 141 |

142 | {extraPkDisplay} 143 | 155 |
156 | ):""} 157 | 158 |
159 | ) 160 | } 161 | 162 | receiveButton = ( 163 | 166 | ) 167 | privateKeyButton = ( 168 | 171 | ) 172 | } else { 173 | const inputStyle = { 174 | padding: 10, 175 | }; 176 | 177 | display = ( 178 |
179 |
180 | 187 |
188 |
189 | { 193 | setAmount(value); 194 | }} 195 | /> 196 |
197 |
198 | ); 199 | receiveButton = ( 200 | 209 | ); 210 | privateKeyButton = ( 211 | 214 | ); 215 | } 216 | 217 | return ( 218 | 219 | {providerSend} 220 | 224 | {selectedAddress ?
: } 225 |
226 | 227 |
228 |
229 | } 230 | onOk={() => { 231 | setQr(); 232 | setPK(); 233 | setOpen(!open); 234 | }} 235 | onCancel={() => { 236 | setQr(); 237 | setPK(); 238 | setOpen(!open); 239 | }} 240 | footer={[ 241 | privateKeyButton, receiveButton, 242 | , 268 | ]} 269 | > 270 | {display} 271 | 272 | 273 | ); 274 | } 275 | -------------------------------------------------------------------------------- /packages/react-app/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Account } from "./Account"; 2 | export { default as Contract } from "./Contract"; 3 | export { default as Address } from "./Address"; 4 | export { default as AddressInput } from "./AddressInput"; 5 | export { default as MaticInput } from "./MaticInput"; 6 | export { default as Balance } from "./Balance"; 7 | export { default as TokenBalance } from "./TokenBalance"; 8 | export { default as Provider } from "./Provider"; 9 | export { default as Ramp } from "./Ramp"; 10 | export { default as Faucet } from "./Faucet"; 11 | export { default as Wallet } from "./Wallet"; 12 | export { default as Blockie } from "./Blockie"; 13 | export { default as Header } from "./Header"; 14 | export { default as GasGauge } from "./GasGauge"; 15 | export { default as BytesStringInput } from "./BytesStringInput"; 16 | export { default as ThemeSwitch } from "./ThemeSwitch"; -------------------------------------------------------------------------------- /packages/react-app/src/constants.js: -------------------------------------------------------------------------------- 1 | export const INFURA_ID = "f0c9f4449331457db6be5fcef7db0208"; 2 | 3 | //BLOCKNATIVE ID FOR Notify.js: 4 | export const BLOCKNATIVE_DAPPID = "0b58206a-f3c0-4701-a62f-73c7243e8c77"; 5 | 6 | // EXTERNAL CONTRACTS 7 | 8 | export const NETWORK = chainId => { 9 | for (let n in NETWORKS) { 10 | if (NETWORKS[n].chainId == chainId) { 11 | return NETWORKS[n]; 12 | } 13 | } 14 | }; 15 | 16 | export const NETWORKS = { 17 | localhost: { 18 | name: "localhost", 19 | color: "#666666", 20 | chainId: 31337, 21 | blockExplorer: "", 22 | rpcUrl: "http://" + window.location.hostname + ":8545", 23 | }, 24 | matic: { 25 | name: "matic", 26 | color: "#2bbdf7", 27 | chainId: 137, 28 | price: 1, 29 | gasPrice: 1000000000, 30 | rpcUrl: "https://rpc-mainnet.maticvigil.com", 31 | faucet: "https://faucet.matic.network/", 32 | blockExplorer: "https://explorer-mainnet.maticvigil.com//", 33 | }, 34 | mumbai: { 35 | name: "mumbai", 36 | color: "#92D9FA", 37 | chainId: 80001, 38 | price: 1, 39 | gasPrice: 1000000000, 40 | rpcUrl: "https://rpc-mumbai.maticvigil.com", 41 | faucet: "https://faucet.matic.network/", 42 | blockExplorer: "https://mumbai-explorer.matic.today/", 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/Transactor.js: -------------------------------------------------------------------------------- 1 | import { hexlify } from "@ethersproject/bytes"; 2 | import { parseUnits } from "@ethersproject/units"; 3 | import { notification } from "antd"; 4 | import { BLOCKNATIVE_DAPPID, } from "../constants"; 5 | 6 | import Notify from "bnc-notify"; 7 | 8 | export default function Transactor(provider, gasPrice, polygonscan) { 9 | if (typeof provider !== "undefined") { 10 | // eslint-disable-next-line consistent-return 11 | return async tx => { 12 | const signer = provider.getSigner(); 13 | const network = await provider.getNetwork(); 14 | console.log("network", network); 15 | const options = { 16 | dappId: BLOCKNATIVE_DAPPID, // GET YOUR OWN KEY AT https://account.blocknative.com 17 | system: "polygon", 18 | networkId: network.chainId, 19 | // darkMode: Boolean, // (default: false) 20 | transactionHandler: txInformation => { 21 | console.log("HANDLE TX", txInformation); 22 | }, 23 | }; 24 | const notify = Notify(options); 25 | 26 | let polygonscanNetwork = ""; 27 | if (network.name && network.chainId > 1) { 28 | polygonscanNetwork = network.name + "."; 29 | } 30 | 31 | let polygonscanTxUrl = "https://" + polygonscanNetwork + "polygonscan.io/tx/"; 32 | if (network.chainId === 100) { 33 | polygonscanTxUrl = "https://blockscout.com/poa/xdai/tx/"; 34 | } 35 | 36 | try { 37 | let result; 38 | if (tx instanceof Promise) { 39 | console.log("AWAITING TX", tx); 40 | result = await tx; 41 | } else { 42 | if (!tx.gasPrice) { 43 | tx.gasPrice = gasPrice || parseUnits("4.1", "gwei"); 44 | } 45 | if (!tx.gasLimit) { 46 | tx.gasLimit = hexlify(120000); 47 | } 48 | console.log("RUNNING TX", tx); 49 | result = await signer.sendTransaction(tx); 50 | } 51 | console.log("RESULT:", result); 52 | // console.log("Notify", notify); 53 | 54 | // if it is a valid Notify.js network, use that, if not, just send a default notification 55 | if ([1, 3, 4, 5, 42, 100].indexOf(network.chainId) >= 0) { 56 | const { emitter } = notify.hash(result.hash); 57 | emitter.on("all", transaction => { 58 | return { 59 | onclick: () => window.open((polygonscan || polygonscanTxUrl) + transaction.hash), 60 | }; 61 | }); 62 | } else { 63 | notification.info({ 64 | message: "Local Transaction Sent", 65 | description: result.hash, 66 | placement: "bottomRight", 67 | }); 68 | } 69 | 70 | return result; 71 | } catch (e) { 72 | console.log(e); 73 | console.log("Transaction Error:", e.message); 74 | notification.error({ 75 | message: "Transaction Error", 76 | description: e.message, 77 | }); 78 | } 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Transactor } from "./Transactor"; 2 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Balance.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import usePoller from "./Poller"; 3 | 4 | export default function useBalance(provider, address, pollTime) { 5 | const [balance, setBalance] = useState(); 6 | const pollBalance = async () => { 7 | if (address && provider) { 8 | const newBalance = await provider.getBalance(address); 9 | if (newBalance !== balance) { 10 | // console.log("NEW BALANCE:",newBalance,"Current balance",balance) 11 | setBalance(newBalance); 12 | } 13 | } 14 | }; 15 | usePoller(pollBalance, 27777, address && provider ); 16 | 17 | return balance; 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractExistsAtAddress.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { isAddress } from "@ethersproject/address"; 3 | 4 | /* 5 | ~ What it does? ~ 6 | 7 | Checks whether a contract exists on the blockchain, returns true if it exists, otherwise false 8 | 9 | ~ How can I use? ~ 10 | 11 | const contractIsDeployed = useContractExistsAtAddress(localProvider, contractAddress); 12 | 13 | ~ Features ~ 14 | 15 | - Provide contractAddress to check if the contract is deployed 16 | - Change provider to check contract address on different chains (ex. mainnetProvider) 17 | */ 18 | 19 | const useContractExistsAtAddress = (provider, contractAddress) => { 20 | const [contractIsDeployed, setContractIsDeployed] = useState(false); 21 | 22 | // We can look at the blockchain and see what's stored at `contractAddress` 23 | // If we find code then we know that a contract exists there. 24 | // If we find nothing (0x0) then there is no contract deployed to that address 25 | useEffect(() => { 26 | // eslint-disable-next-line consistent-return 27 | const checkDeployment = async () => { 28 | if (!isAddress(contractAddress)) return false; 29 | const bytecode = await provider.getCode(contractAddress); 30 | setContractIsDeployed(bytecode !== "0x0"); 31 | }; 32 | if (provider) checkDeployment(); 33 | }, [provider, contractAddress]); 34 | 35 | return contractIsDeployed; 36 | }; 37 | 38 | export default useContractExistsAtAddress; 39 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useState, useEffect } from "react"; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Loads your local contracts and gives options to read values from contracts 10 | or write transactions into them 11 | 12 | ~ How can I use? ~ 13 | 14 | const readContracts = useContractLoader(localProvider) // or 15 | const writeContracts = useContractLoader(userProvider) 16 | 17 | ~ Features ~ 18 | 19 | - localProvider enables reading values from contracts 20 | - userProvider enables writing transactions into contracts 21 | - Example of keeping track of "purpose" variable by loading contracts into readContracts 22 | and using ContractReader.js hook: 23 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 24 | - Example of using setPurpose function from our contract and writing transactions by Transactor.js helper: 25 | tx( writeContracts.YourContract.setPurpose(newPurpose) ) 26 | */ 27 | 28 | const loadContract = (contractName, signer) => { 29 | const newContract = new Contract( 30 | require(`../contracts/${contractName}.address.js`), 31 | require(`../contracts/${contractName}.abi.js`), 32 | signer, 33 | ); 34 | try { 35 | newContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 36 | } catch (e) { 37 | console.log(e); 38 | } 39 | return newContract; 40 | }; 41 | 42 | export default function useContractLoader(providerOrSigner) { 43 | const [contracts, setContracts] = useState(); 44 | useEffect(() => { 45 | async function loadContracts() { 46 | if (typeof providerOrSigner !== "undefined") { 47 | try { 48 | // we need to check to see if this providerOrSigner has a signer or not 49 | let signer; 50 | let accounts; 51 | if (providerOrSigner && typeof providerOrSigner.listAccounts === "function") { 52 | accounts = await providerOrSigner.listAccounts(); 53 | } 54 | 55 | if (accounts && accounts.length > 0) { 56 | signer = providerOrSigner.getSigner(); 57 | } else { 58 | signer = providerOrSigner; 59 | } 60 | 61 | const contractList = require("../contracts/contracts.js"); 62 | 63 | const newContracts = contractList.reduce((accumulator, contractName) => { 64 | accumulator[contractName] = loadContract(contractName, signer); 65 | return accumulator; 66 | }, {}); 67 | setContracts(newContracts); 68 | } catch (e) { 69 | console.log("ERROR LOADING CONTRACTS!!", e); 70 | } 71 | } 72 | } 73 | loadContracts(); 74 | }, [providerOrSigner]); 75 | return contracts; 76 | } 77 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractReader.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import usePoller from "./Poller"; 3 | 4 | const DEBUG = false; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Enables you to read values from contracts and keep track of them in the local React states 10 | 11 | ~ How can I use? ~ 12 | 13 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 14 | 15 | ~ Features ~ 16 | 17 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 18 | - Specify the name of the contract, in this case it is "YourContract" 19 | - Specify the name of the variable in the contract, in this case we keep track of "purpose" variable 20 | */ 21 | 22 | export default function useContractReader(contracts, contractName, functionName, args, pollTime, formatter, onChange) { 23 | let adjustPollTime = 1777; 24 | if (pollTime) { 25 | adjustPollTime = pollTime; 26 | } else if (!pollTime && typeof args === "number") { 27 | // it's okay to pass poll time as last argument without args for the call 28 | adjustPollTime = args; 29 | } 30 | 31 | const [value, setValue] = useState(); 32 | useEffect(() => { 33 | if (typeof onChange === "function") { 34 | setTimeout(onChange.bind(this, value), 1); 35 | } 36 | }, [value, onChange]); 37 | 38 | usePoller(async () => { 39 | if (contracts && contracts[contractName]) { 40 | try { 41 | let newValue; 42 | if (DEBUG) console.log("CALLING ", contractName, functionName, "with args", args); 43 | if (args && args.length > 0) { 44 | newValue = await contracts[contractName][functionName](...args); 45 | if (DEBUG) 46 | console.log("contractName", contractName, "functionName", functionName, "args", args, "RESULT:", newValue); 47 | } else { 48 | newValue = await contracts[contractName][functionName](); 49 | } 50 | if (formatter && typeof formatter === "function") { 51 | newValue = formatter(newValue); 52 | } 53 | // console.log("GOT VALUE",newValue) 54 | if (newValue !== value) { 55 | setValue(newValue); 56 | } 57 | } catch (e) { 58 | console.log(e); 59 | } 60 | } 61 | }, adjustPollTime, contracts && contracts[contractName]); 62 | 63 | return value; 64 | } 65 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/CustomContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useState, useEffect } from "react"; 5 | 6 | /* 7 | when you want to load a local contract's abi but supply a custom address 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load a local contract with custom address 14 | 15 | ~ How can I use? ~ 16 | 17 | const customContract = useCustomContractLoader(localProvider, "YourContract", customAddress) 18 | 19 | ~ Features ~ 20 | 21 | - Specify the localProvider 22 | - Specify the name of the contract, in this case it is "YourContract" 23 | - Specify the customAddress of your contract 24 | */ 25 | 26 | export default function useCustomContractLoader(provider, contractName, address) { 27 | const [contract, setContract] = useState(); 28 | useEffect(() => { 29 | async function loadContract() { 30 | if (typeof provider !== "undefined" && contractName && address) { 31 | try { 32 | // we need to check to see if this provider has a signer or not 33 | let signer; 34 | const accounts = await provider.listAccounts(); 35 | if (accounts && accounts.length > 0) { 36 | signer = provider.getSigner(); 37 | } else { 38 | signer = provider; 39 | } 40 | 41 | const customContract = new Contract(address, require(`../contracts/${contractName}.abi.js`), signer); 42 | try { 43 | customContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 44 | } catch (e) { 45 | console.log(e); 46 | } 47 | 48 | setContract(customContract); 49 | } catch (e) { 50 | console.log("ERROR LOADING CONTRACTS!!", e); 51 | } 52 | } 53 | } 54 | loadContract(); 55 | }, [provider, contractName, address]); 56 | return contract; 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Debounce.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export default function useDebounce(value, delay) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/EventListener.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Enables you to keep track of events 7 | 8 | ~ How can I use? ~ 9 | 10 | const setPurposeEvents = useEventListener(readContracts, "YourContract", "SetPurpose", localProvider, 1); 11 | 12 | ~ Features ~ 13 | 14 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 15 | - Specify the name of the contract, in this case it is "YourContract" 16 | - Specify the name of the event in the contract, in this case we keep track of "SetPurpose" event 17 | - Specify the provider 18 | */ 19 | 20 | export default function useEventListener(contracts, contractName, eventName, provider, startBlock, args) { 21 | const [updates, setUpdates] = useState([]); 22 | 23 | useEffect(() => { 24 | if (typeof provider !== "undefined" && typeof startBlock !== "undefined") { 25 | // if you want to read _all_ events from your contracts, set this to the block number it is deployed 26 | provider.resetEventsBlock(startBlock); 27 | } 28 | if (contracts && contractName && contracts[contractName]) { 29 | try { 30 | contracts[contractName].on(eventName, (...args) => { 31 | let blockNumber = args[args.length-1].blockNumber 32 | setUpdates(messages => [Object.assign({blockNumber},args.pop().args), ...messages]); 33 | }); 34 | return () => { 35 | contracts[contractName].removeListener(eventName); 36 | }; 37 | } catch (e) { 38 | console.log(e); 39 | } 40 | } 41 | }, [provider, startBlock, contracts, contractName, eventName]); 42 | 43 | return updates; 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExchangePrice.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Token, WETH, Fetcher, Route } from "@uniswap/sdk"; 3 | import { usePoller } from "eth-hooks"; 4 | 5 | export default function useExchangePrice(targetNetwork, mainnetProvider, pollTime) { 6 | const [price, setPrice] = useState(0); 7 | 8 | const pollPrice = () => { 9 | async function getPrice() { 10 | if(targetNetwork.price){ 11 | setPrice(targetNetwork.price) 12 | }else{ 13 | const DAI = new Token( 14 | mainnetProvider.network ? mainnetProvider.network.chainId : 1, 15 | "0x6B175474E89094C44Da98b954EedeAC495271d0F", 16 | 18, 17 | ); 18 | const pair = await Fetcher.fetchPairData(DAI, WETH[DAI.chainId], mainnetProvider); 19 | const route = new Route([pair], WETH[DAI.chainId]); 20 | setPrice(parseFloat(route.midPrice.toSignificant(6))); 21 | } 22 | } 23 | getPrice(); 24 | }; 25 | usePoller(pollPrice, pollTime || 9777); 26 | 27 | return price; 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExternalContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useState, useEffect } from "react"; 5 | 6 | /* 7 | when you want to load an existing contract using just the provider, address, and ABI 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load an existing mainnet DAI contract using the provider, address and abi 14 | 15 | ~ How can I use? ~ 16 | 17 | const mainnetDAIContract = useExternalContractLoader(mainnetProvider, DAI_ADDRESS, DAI_ABI) 18 | 19 | ~ Features ~ 20 | 21 | - Specify mainnetProvider 22 | - Specify DAI_ADDRESS and DAI_ABI, you can load/write them using constants.js 23 | */ 24 | export default function useExternalContractLoader(provider, address, ABI, optionalBytecode) { 25 | const [contract, setContract] = useState(); 26 | useEffect(() => { 27 | async function loadContract() { 28 | if (typeof provider !== "undefined" && address && ABI) { 29 | try { 30 | // we need to check to see if this provider has a signer or not 31 | let signer; 32 | const accounts = await provider.listAccounts(); 33 | if (accounts && accounts.length > 0) { 34 | signer = provider.getSigner(); 35 | } else { 36 | signer = provider; 37 | } 38 | 39 | const customContract = new Contract(address, ABI, signer); 40 | if(optionalBytecode) customContract.bytecode = optionalBytecode 41 | 42 | setContract(customContract); 43 | } catch (e) { 44 | console.log("ERROR LOADING EXTERNAL CONTRACT AT "+address+" (check provider, address, and ABI)!!", e); 45 | } 46 | } 47 | } 48 | loadContract(); 49 | }, [provider, address, ABI, optionalBytecode]); 50 | return contract; 51 | } 52 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/GasPrice.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { usePoller } from "eth-hooks"; 3 | import axios from "axios"; 4 | 5 | export default function useGasPrice(targetNetwork,speed) { 6 | const [gasPrice, setGasPrice] = useState(); 7 | const loadGasPrice = async () => { 8 | if(targetNetwork.gasPrice){ 9 | setGasPrice(targetNetwork.gasPrice); 10 | }else{ 11 | axios 12 | .get("https://gasstation-mainnet.matic.network/") 13 | .then(response => { 14 | const newGasPrice = response.data[speed || "fast"] * 100000000; 15 | if (newGasPrice !== gasPrice) { 16 | setGasPrice(newGasPrice); 17 | } 18 | }) 19 | .catch(error => console.log(error)); 20 | } 21 | }; 22 | 23 | usePoller(loadGasPrice, 39999); 24 | return gasPrice; 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | // Hook from useHooks! (https://usehooks.com/useLocalStorage/) 3 | export default function useLocalStorage(key, initialValue) { 4 | // State to store our value 5 | // Pass initial state function to useState so logic is only executed once 6 | const [storedValue, setStoredValue] = useState(() => { 7 | try { 8 | // Get from local storage by key 9 | const item = window.localStorage.getItem(key); 10 | // Parse stored json or if none return initialValue 11 | return item ? JSON.parse(item) : initialValue; 12 | } catch (error) { 13 | // If error also return initialValue 14 | console.log(error); 15 | return initialValue; 16 | } 17 | }); 18 | 19 | // Return a wrapped version of useState's setter function that ... 20 | // ... persists the new value to localStorage. 21 | const setValue = value => { 22 | try { 23 | // Allow value to be a function so we have same API as useState 24 | const valueToStore = 25 | value instanceof Function ? value(storedValue) : value; 26 | // Save state 27 | setStoredValue(valueToStore); 28 | // Save to local storage 29 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 30 | } catch (error) { 31 | // A more advanced implementation would handle the error case 32 | console.log(error); 33 | } 34 | }; 35 | 36 | return [storedValue, setValue]; 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LookupAddress.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { getAddress } from "@ethersproject/address"; 3 | import { useLocalStorage } from "." 4 | 5 | // resolved if(name){} to not save "" into cache 6 | 7 | /* 8 | ~ What it does? ~ 9 | 10 | Gets ENS name from given address and provider 11 | 12 | ~ How can I use? ~ 13 | 14 | const ensName = useLookupAddress(mainnetProvider, address); 15 | 16 | ~ Features ~ 17 | 18 | - Provide address and get ENS name corresponding to given address 19 | */ 20 | 21 | const lookupAddress = async (provider, address) => { 22 | try { 23 | // Accuracy of reverse resolution is not enforced. 24 | // We then manually ensure that the reported ens name resolves to address 25 | const reportedName = await provider.lookupAddress(address); 26 | 27 | const resolvedAddress = await provider.resolveName(reportedName); 28 | 29 | if (getAddress(address) === getAddress(resolvedAddress)) { 30 | return reportedName; 31 | } 32 | } catch (e) { 33 | // Do nothing 34 | } 35 | return 0; 36 | }; 37 | 38 | const useLookupAddress = (provider, address) => { 39 | const [ensName, setEnsName] = useState(address); 40 | const [ensCache, setEnsCache] = useLocalStorage('ensCache_'+address); 41 | 42 | useEffect(() => { 43 | if( ensCache && ensCache.timestamp>Date.now()){ 44 | setEnsName(ensCache.name) 45 | }else{ 46 | if (provider) { 47 | lookupAddress(provider, address).then((name) => { 48 | if (name) { 49 | setEnsName(name); 50 | setEnsCache({ 51 | timestamp:Date.now()+360000, 52 | name:name 53 | }) 54 | } 55 | }); 56 | } 57 | } 58 | }, [ensCache, provider, address, setEnsName, setEnsCache]); 59 | 60 | return ensName; 61 | }; 62 | 63 | export default useLookupAddress; 64 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Nonce.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useNonce(mainnetProvider, address) { 4 | const [nonce, setNonce] = useState(0); 5 | 6 | const Nonce = () => { 7 | async function getNonce() { 8 | setNonce(await mainnetProvider.getTransactionCount(address)); 9 | } 10 | if(address) getNonce(); 11 | }; 12 | Nonce(); 13 | return nonce; 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Poller.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | // helper hook to call a function regularly in time intervals 4 | 5 | export default function usePoller(fn, delay, extraWatch) { 6 | const savedCallback = useRef(); 7 | // Remember the latest fn. 8 | useEffect(() => { 9 | savedCallback.current = fn; 10 | }, [fn]); 11 | // Set up the interval. 12 | // eslint-disable-next-line consistent-return 13 | useEffect(() => { 14 | console.log("tick") 15 | function tick() { 16 | savedCallback.current(); 17 | } 18 | if (delay !== null) { 19 | const id = setInterval(tick, delay); 20 | return () => clearInterval(id); 21 | } 22 | }, [delay]); 23 | // run at start too 24 | useEffect(() => { 25 | fn(); 26 | },[ extraWatch ]); 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ResolveName.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { AddressZero } from "@ethersproject/constants"; 3 | 4 | const useResolveName = (provider, ensName) => { 5 | const [address, setAddress] = useState(AddressZero); 6 | 7 | useEffect(() => { 8 | if (provider) { 9 | provider.resolveName(ensName).then((resolvedAddress) => setAddress(resolvedAddress)); 10 | } 11 | }, [provider, ensName]); 12 | 13 | return address; 14 | }; 15 | 16 | export default useResolveName; 17 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/TokenList.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Gets a tokenlist (see more at https://tokenlists.org/), returning the .tokens only 7 | 8 | ~ How can I use? ~ 9 | 10 | const tokenList = useTokenList(); <- default returns the Unsiwap tokens 11 | const tokenList = useTokenList("https://gateway.ipfs.io/ipns/tokens.uniswap.org"); 12 | 13 | ~ Features ~ 14 | 15 | - Optional - specify chainId to filter by chainId 16 | */ 17 | 18 | const useTokenList = (tokenListUri, chainId) => { 19 | const [tokenList, setTokenList] = useState([]); 20 | 21 | let _tokenListUri = tokenListUri || "https://gateway.ipfs.io/ipns/tokens.uniswap.org" 22 | 23 | useEffect(() => { 24 | 25 | const getTokenList = async () => { 26 | try { 27 | let tokenList = await fetch(_tokenListUri) 28 | let tokenListJson = await tokenList.json() 29 | let _tokenList 30 | 31 | if(chainId) { 32 | _tokenList = tokenListJson.tokens.filter(function (t) { 33 | return t.chainId === chainId 34 | }) 35 | } else { 36 | _tokenList = tokenListJson 37 | } 38 | 39 | setTokenList(_tokenList.tokens) 40 | 41 | } catch (e) { 42 | console.log(e) 43 | } 44 | } 45 | getTokenList() 46 | },[tokenListUri]) 47 | 48 | return tokenList; 49 | }; 50 | 51 | export default useTokenList; 52 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/UserProvider.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { Web3Provider } from "@ethersproject/providers"; 3 | import BurnerProvider from "burner-provider"; 4 | import { INFURA_ID } from "../constants"; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Gets user provider 10 | 11 | ~ How can I use? ~ 12 | 13 | const userProvider = useUserProvider(injectedProvider, localProvider); 14 | 15 | ~ Features ~ 16 | 17 | - Specify the injected provider from Metamask 18 | - Specify the local provider 19 | - Usage examples: 20 | const address = useUserAddress(userProvider); 21 | const tx = Transactor(userProvider, gasPrice) 22 | */ 23 | 24 | const useUserProvider = (injectedProvider, localProvider) => 25 | useMemo(() => { 26 | if (injectedProvider) { 27 | console.log("🦊 Using injected provider"); 28 | return injectedProvider; 29 | } 30 | if (!localProvider) return undefined; 31 | 32 | let burnerConfig = {} 33 | 34 | if(window.location.pathname){ 35 | if(window.location.pathname.indexOf("/pk")>=0){ 36 | let incomingPK = window.location.hash.replace("#","") 37 | let rawPK 38 | if(incomingPK.length===64||incomingPK.length===66){ 39 | console.log("🔑 Incoming Private Key..."); 40 | rawPK=incomingPK 41 | burnerConfig.privateKey = rawPK 42 | window.history.pushState({},"", "/"); 43 | let currentPrivateKey = window.localStorage.getItem("metaPrivateKey"); 44 | if(currentPrivateKey && currentPrivateKey!==rawPK){ 45 | window.localStorage.setItem("metaPrivateKey_backup"+Date.now(),currentPrivateKey); 46 | } 47 | window.localStorage.setItem("metaPrivateKey",rawPK); 48 | } 49 | } 50 | } 51 | 52 | console.log("🔥 Using burner provider",burnerConfig); 53 | if (localProvider.connection && localProvider.connection.url) { 54 | burnerConfig.rpcUrl = localProvider.connection.url 55 | return new Web3Provider(new BurnerProvider(burnerConfig)); 56 | }else{ 57 | // eslint-disable-next-line no-underscore-dangle 58 | const networkName = localProvider._network && localProvider._network.name; 59 | burnerConfig.rpcUrl = `https://${networkName || "mainnet"}.infura.io/v3/${INFURA_ID}` 60 | return new Web3Provider(new BurnerProvider(burnerConfig)); 61 | } 62 | }, [injectedProvider, localProvider]); 63 | 64 | export default useUserProvider; 65 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useContractLoader } from "./ContractLoader"; 2 | export { default as useCustomContractLoader } from "./CustomContractLoader"; 3 | export { default as useExternalContractLoader } from "./ExternalContractLoader"; 4 | export { default as useContractExistsAtAddress } from "./ContractExistsAtAddress"; 5 | export { default as useExchangePrice } from "./ExchangePrice"; 6 | export { default as useGasPrice } from "./GasPrice"; 7 | export { default as useUserProvider } from "./UserProvider"; 8 | export { default as useContractReader } from "./ContractReader"; 9 | export { default as usePoller } from "./Poller"; 10 | export { default as useBalance } from "./Balance"; 11 | export { default as useEventListener } from "./EventListener"; 12 | export { default as useLocalStorage } from "./LocalStorage"; 13 | export { default as useLookupAddress } from "./LookupAddress"; 14 | export { default as useResolveName } from "./ResolveName"; 15 | export { default as useNonce } from "./Nonce"; 16 | export { default as useTokenList } from "./TokenList"; 17 | export { default as useDebounce } from "./Debounce"; 18 | -------------------------------------------------------------------------------- /packages/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | code { 6 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 7 | monospace; 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | import { ThemeSwitcherProvider } from "react-css-theme-switcher"; 7 | 8 | const themes = { 9 | dark: `${process.env.PUBLIC_URL}/dark-theme.css`, 10 | light: `${process.env.PUBLIC_URL}/light-theme.css`, 11 | }; 12 | 13 | const prevTheme = window.localStorage.getItem("theme"); 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById("root"), 20 | ); 21 | -------------------------------------------------------------------------------- /packages/react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom/extend-expect"; 6 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/dark-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/dark.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | @primary-color: #2caad9; 7 | @border-radius-base: 4px; 8 | 9 | @component-background: #212121; 10 | @body-background: #212121; 11 | @popover-background: #212121; 12 | @border-color-base: #6f6c6c; 13 | @border-color-split: #424242; 14 | @table-header-sort-active-bg: #424242; 15 | @card-skeleton-bg: #424242; 16 | @skeleton-color: #424242; 17 | @table-header-sort-active-bg: #424242; 18 | 19 | @link-color: @text-color; 20 | @icon-color: @primary-color; 21 | 22 | .highlight { 23 | background-color: #3f3f3f; 24 | } 25 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/light-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/default.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | // @primary-color: #00adb5; 7 | @border-radius-base: 4px; 8 | 9 | @link-color: @text-color; 10 | @icon-color: @primary-color; 11 | 12 | .highlight { 13 | background-color: #f9f9f9; 14 | } 15 | --------------------------------------------------------------------------------