diff --git a/index.html b/index.html index 4125bc1b..309531f8 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,54 @@ + - Core DAO Dapp tutorial + + + + Core DAO DApp Tutorial + + + -
+
+ +
+ diff --git a/package.json b/package.json index 27bfcb5f..202ab458 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "@vitejs/plugin-react": "^2.0.0", - "autoprefixer": "^10.4.7", + "autoprefixer": "^10.4.19", "babel-jest": "^28.1.3", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", @@ -38,9 +38,9 @@ "eslint-plugin-tailwindcss": "^3.6.0", "jest": "^28.1.3", "jest-environment-jsdom": "^28.1.3", - "postcss": "^8.4.14", + "postcss": "^8.4.39", "prettier": "2.7.1", - "tailwindcss": "^3.1.6", + "tailwindcss": "^3.4.4", "typescript": "^4.7.4", "vite": "^3.0.0", "vite-tsconfig-paths": "^3.5.0" diff --git a/src/WalletConnect.tsx b/src/WalletConnect.tsx new file mode 100644 index 00000000..b5bb0850 --- /dev/null +++ b/src/WalletConnect.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import { ethers } from 'ethers'; + +const WalletConnect: React.FC = () => { + const [account, setAccount] = useState(null); + const [provider, setProvider] = useState(null); // State to hold ethers provider + + const connectWallet = async () => { + const { ethereum } = window as any; + + if (ethereum) { + try { + const accounts: string[] = await ethereum.request({ method: 'eth_requestAccounts' }); + setAccount(accounts[0]); + const newProvider = new ethers.providers.Web3Provider(ethereum); + setProvider(newProvider); // Set ethers provider after connection + } catch (error) { + console.error("Error connecting to wallet:", error); + } + } else { + alert("Please install MetaMask or another wallet provider."); + } + }; + + useEffect(() => { + const { ethereum } = window as any; + + if (ethereum) { + const handleAccountsChanged = (accounts: string[]) => { + setAccount(accounts[0] || null); // Set the first account or null if disconnected + }; + + const handleChainChanged = (chainId: string) => { + console.log("Chain changed to:", chainId); + }; + + ethereum.on('accountsChanged', handleAccountsChanged); + ethereum.on('chainChanged', handleChainChanged); + + return () => { + ethereum.removeListener('accountsChanged', handleAccountsChanged); + ethereum.removeListener('chainChanged', handleChainChanged); + }; + } + }, []); + + return ( +
+ {account ? ( +

Connected account: {account}

+ ) : ( + + )} +
+ ); +}; + +export default WalletConnect; diff --git a/src/components/App.tsx b/src/components/App.tsx index ca76883d..30787a55 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,201 +1,185 @@ -/* eslint-disable tailwindcss/no-custom-classname */ -/* eslint-disable tailwindcss/classnames-order */ -/* eslint-disable react/no-unescaped-entities */ - -import { useEffect, useState } from 'react' -import { ethers } from 'ethers' -import storage from '../contract/Storage.json' +import React, { useEffect, useState } from 'react'; +import { ethers } from 'ethers'; +import storage from '../contract/Storage.json'; // Contract information -const contractAddress = '0xe007843F7d8e737A2816d7b9CaE9C10CB7548B55' -const abi = storage.abi +const contractAddress = '0x4E3F8ac2D3714f029bCa72854ECcd6bB72D78728'; +const abi = storage.abi; // Constants -const CORESCAN_BASE_URL = 'https://scan.test.btcs.network/address/' +const CORESCAN_BASE_URL = 'https://scan.test.btcs.network/address/'; + +const App: React.FC = () => { + const [currentAccount, setCurrentAccount] = useState(null); + const [storeNumber, setStoreNumber] = useState(''); + const [retrievedNumber, setRetrievedNumber] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); -function App() { - const [currentAccount, setCurrentAccount] = useState(null) - const [storeNumber, setStoreNumber] = useState('') - const [retrievedNumber, setRetrievedNumber] = useState('') const checkWalletIsConnected = async () => { - const { ethereum } = window + const { ethereum } = window as any; if (!ethereum) { - console.log('Make sure you have Metamask installed!') - return - } else { - console.log("Wallet exists! We're ready to go!") + alert('Please install Metamask!'); + return; } - const accounts = await ethereum.request({ method: 'eth_accounts' }) - - if (accounts.length !== 0) { - const account = accounts[0] - console.log('Found an authorized account: ', account) - setCurrentAccount(account) + const accounts = await ethereum.request({ method: 'eth_accounts' }); + if (accounts.length > 0) { + setCurrentAccount(accounts[0]); + console.log('Found an authorized account:', accounts[0]); } else { - console.log('No authorized account found') + console.log('No authorized account found'); } - } + }; const connectWalletHandler = async () => { - const { ethereum } = window + const { ethereum } = window as any; if (!ethereum) { - alert('Please install Metamask!') + alert('Please install Metamask!'); + return; } try { - const accounts = await ethereum.request({ method: 'eth_requestAccounts' }) - console.log(accounts[0]) - console.log('Found an account! Address: ', accounts[0]) - setCurrentAccount(accounts[0]) + const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); + setCurrentAccount(accounts[0]); + console.log('Connected account:', accounts[0]); } catch (err) { - console.log(err) + console.error('Error connecting wallet:', err); + alert('Connection failed. Please try again.'); } - } + }; const store = async () => { + setLoading(true); + setError(''); + try { - const { ethereum } = window - - if (ethereum) { - const provider = new ethers.providers.Web3Provider(ethereum) - const signer = provider.getSigner() - const storageContract = new ethers.Contract( - contractAddress, - abi, - signer - ) - - console.log('Write to contract') - const tx = await storageContract.store(storeNumber) - - console.log('Wait for the transaction to be confirmed') - await tx.wait() - - console.log( - `Transaction confirmed: https://scan.test.btcs.network/tx/${tx.hash}` - ) - } else { - console.log('Ethereum object does not exist') - } + const { ethereum } = window as any; + if (!ethereum) throw new Error('Ethereum object does not exist'); + + const provider = new ethers.providers.Web3Provider(ethereum); + const signer = provider.getSigner(); + const storageContract = new ethers.Contract(contractAddress, abi, signer); + + console.log('Writing to contract...'); + const tx = await storageContract.store(storeNumber); + console.log('Waiting for transaction confirmation...'); + await tx.wait(); + + console.log(`Transaction confirmed: https://scan.test.btcs.network/tx/${tx.hash}`); } catch (err) { - console.log(err) + console.error('Error storing number:', err); + setError('Failed to store number. Please try again.'); + } finally { + setLoading(false); } - } + }; + const retrieve = async () => { + setLoading(true); + setError(''); + try { - const { ethereum } = window - - if (ethereum) { - const provider = new ethers.providers.Web3Provider(ethereum) - const signer = provider.getSigner() - const storageContract = new ethers.Contract( - contractAddress, - abi, - signer - ) - - console.log('Read from contract') - const res = await storageContract.retrieve() - setRetrievedNumber(res.toString()) - } else { - console.log('Ethereum object does not exist') - } + const { ethereum } = window as any; + if (!ethereum) throw new Error('Ethereum object does not exist'); + + const provider = new ethers.providers.Web3Provider(ethereum); + const storageContract = new ethers.Contract(contractAddress, abi, provider); + + console.log('Reading from contract...'); + const res = await storageContract.retrieve(); + setRetrievedNumber(res.toString()); } catch (err) { - console.log(err) + console.error('Error retrieving number:', err); + setError('Failed to retrieve number. Please try again.'); + } finally { + setLoading(false); } - } - - const connectWalletButton = () => { - return ( - - ) - } - - const storageButton = () => { - return ( -
-

- Click "write" or "read" to call the smart contract -

-
-
- - setStoreNumber(e.target.value)} - className="rounded-l-none border-2 border-solid border-orange-500 caret-orange-500 focus:caret-indigo-500 py-1 px-2 h-10" - /> -
-
- - -
+ }; + + const renderConnectWalletButton = () => ( + + ); + + const renderStorageButtons = () => ( +
+

+ Click "write" or "read" to call the smart contract +

+
+
+ + setStoreNumber(e.target.value)} + className="rounded-l-none border-2 border-solid border-orange-500 py-1 px-2 h-10" + disabled={loading} + placeholder="Enter number" + />
-
- Contract address: - - {contractAddress} - +
+ +
- ) - } + {error &&

{error}

} +
+ Contract address: + + {contractAddress} + +
+
+ ); useEffect(() => { - checkWalletIsConnected() - }, []) + checkWalletIsConnected(); + }, []); return (
-
+
- + Logo

Core

- Dapp Starter + DApp Starter

- {currentAccount ? storageButton() : connectWalletButton()} + {currentAccount ? renderStorageButtons() : renderConnectWalletButton()}
- +
- ) -} + ); +}; -export default App +export default App; diff --git a/src/contract/Storage copy.json b/src/contract/Storage copy.json new file mode 100644 index 00000000..09afabca --- /dev/null +++ b/src/contract/Storage copy.json @@ -0,0 +1,37 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "Storage", + "sourceName": "contracts/Storage.sol", + "abi": [ + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec11460375780636057361d14604c575b600080fd5b60005460405190815260200160405180910390f35b605c6057366004605e565b600055565b005b600060208284031215606f57600080fd5b503591905056fea26469706673582212205bf847a0de0d9d42b6c46f829a7531ca62252925a5b8edd20fb8e03933afb9f664736f6c63430008130033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec11460375780636057361d14604c575b600080fd5b60005460405190815260200160405180910390f35b605c6057366004605e565b600055565b005b600060208284031215606f57600080fd5b503591905056fea26469706673582212205bf847a0de0d9d42b6c46f829a7531ca62252925a5b8edd20fb8e03933afb9f664736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/contract/Storage copy.sol b/src/contract/Storage copy.sol new file mode 100644 index 00000000..cfbed10a --- /dev/null +++ b/src/contract/Storage copy.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0 + + +pragma solidity >=0.7.0 <0.9.0; + + +/** + * @title Storage + * @dev Store & retrieve value in a variable + */ +contract Storage { + + + uint256 number; + + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + number = num; + } + + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 727b8511..8afa3c03 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,14 @@ -import { createRoot } from 'react-dom/client' -import 'tailwindcss/tailwind.css' -import './style/index.css' -import App from 'components/App' +// src/index.tsx -const container = document.getElementById('root') as HTMLDivElement -const root = createRoot(container) +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import 'tailwindcss/tailwind.css'; +import './style/index.css'; // Ensure you have a CSS file for custom styles +import App from './components/App'; -root.render() +// Ensure the root element is available +const container = document.getElementById('root') as HTMLElement; +const root = createRoot(container); + +// Render the App component +root.render(); diff --git a/src/public/service-worker.js b/src/public/service-worker.js new file mode 100644 index 00000000..15cb2d64 --- /dev/null +++ b/src/public/service-worker.js @@ -0,0 +1,46 @@ +// src/public/service-worker.js + +const CACHE_NAME = 'core-dao-dapp-cache-v1'; +const urlsToCache = [ + '/', + '/index.html', + '/src/style/index.css', + '/src/public/favicon.svg', + // Add any other assets you want to cache +]; + +self.addEventListener('install', event => { + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + return cache.addAll(urlsToCache); + }) + ); +}); + +self.addEventListener('fetch', event => { + event.respondWith( + caches.match(event.request) + .then(response => { + if (response) { + return response; + } + return fetch(event.request); + }) + ); +}); + +self.addEventListener('activate', event => { + const cacheWhitelist = [CACHE_NAME]; + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); diff --git a/src/style/index.css b/src/style/index.css index 08a33a80..592260a2 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -3,10 +3,25 @@ @tailwind utilities; @layer components { - :root{ + :root { + /* Define any custom CSS variables here, if needed */ + } + + body { + font-family: 'Roboto', sans-serif; + } - } .btn-primary { - @apply py-2 px-4 bg-orange-500 text-white font-semibold rounded-lg shadow-md hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75; + @apply py-2 px-4 bg-orange-500 text-white font-semibold rounded-lg shadow-md + hover:bg-orange-600 focus:outline-none focus:ring-2 + focus:ring-blue-400 focus:ring-opacity-75 transition-transform transform; + } + + .btn-primary:hover { + transform: scale(1.05); + } + + input { + @apply rounded-lg border-2 border-gray-300 py-2 px-4 focus:outline-none focus:ring-2 focus:ring-blue-400; } }