├── src ├── contract_access_object │ ├── constants.js │ ├── writerCao.js │ ├── cao.js │ └── readerCao.js ├── index.css ├── components │ ├── common │ │ ├── images │ │ │ └── catchon.png │ │ ├── PowerBy.js │ │ └── PowerBy.css │ ├── SignInModal │ │ ├── SignInModal.css │ │ ├── SignInModalSlice.js │ │ └── SignInModal.js │ ├── muiStyle.js │ ├── FunctionCallModal │ │ ├── FunctionCallSlice.js │ │ ├── FunctionCallView.css │ │ └── FunctionCallView.js │ ├── UserModal │ │ ├── UserModal.css │ │ ├── UserModal.js │ │ ├── UserModalSlice.js │ │ └── UserSection.js │ └── mint.css ├── wallet │ ├── web3Helper.js │ ├── utils.js │ ├── abiHelper.js │ └── wallet.js ├── app │ ├── store.js │ └── reducer.js ├── divCheck │ ├── buttonCheckTop.js │ ├── connectButtonCheck.js │ └── functionCheck.js ├── setupTests.js ├── reportWebVitals.js ├── __tests__ │ ├── App.test.js │ └── __snapshots__ │ │ └── App.test.js.snap ├── constants.js ├── App.css ├── publicPath.js ├── index.js ├── App.js ├── logo.svg ├── utils.js └── componentHelper.js ├── images └── logo.png ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── babel.config.js ├── .gitignore ├── craco.config.js ├── package.json └── README.md /src/contract_access_object/constants.js: -------------------------------------------------------------------------------- 1 | export const MOCK = false; 2 | 3 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nftblackmagic/web3cdn/HEAD/images/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nftblackmagic/web3cdn/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nftblackmagic/web3cdn/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nftblackmagic/web3cdn/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | code { 2 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 3 | monospace; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/common/images/catchon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nftblackmagic/web3cdn/HEAD/src/components/common/images/catchon.png -------------------------------------------------------------------------------- /src/wallet/web3Helper.js: -------------------------------------------------------------------------------- 1 | 2 | export const assert = (condition, msg) => { 3 | if (!condition) { 4 | throw msg || "assertion fail!"; 5 | } 6 | }; 7 | 8 | 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env"], 3 | env: { 4 | test: { 5 | plugins: ["@babel/plugin-transform-modules-commonjs"], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/SignInModal/SignInModal.css: -------------------------------------------------------------------------------- 1 | .blur { 2 | position: absolute; 3 | background-color: rgba(0, 0, 0, 0.61); 4 | backdrop-filter: blur(5px); 5 | -webkit-backdrop-filter: blur(5px); 6 | width: 100vw; 7 | height: 100vh; 8 | top: 0; 9 | left: 0; 10 | z-index: 100; 11 | } -------------------------------------------------------------------------------- /src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import rootReducer from "./reducer"; 3 | 4 | export const store = configureStore({ 5 | reducer: rootReducer, 6 | middleware: (getDefaultMiddleware) => 7 | getDefaultMiddleware({ 8 | serializableCheck: false, 9 | }), 10 | }); 11 | -------------------------------------------------------------------------------- /src/divCheck/buttonCheckTop.js: -------------------------------------------------------------------------------- 1 | // import { updateConnectButton, updateWalletStatus } from "./wallet.js"; 2 | import { updateConnectButton } from "./connectButtonCheck"; 3 | import { checkDivs } from "./functionCheck"; 4 | // import { blacklist } from "./blacklist"; 5 | 6 | export const initButtonCheck = async () => { 7 | checkDivs(); 8 | updateConnectButton(); 9 | } 10 | 11 | // init(); 12 | -------------------------------------------------------------------------------- /src/components/common/PowerBy.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logoImage from "./images/catchon.png"; 3 | 4 | import "./PowerBy.css"; 5 | 6 | export const PowerBy = () => { 7 | return ( 8 | 9 |

Powered by

10 | CATCHON 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /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"; 6 | import { configure } from "enzyme"; 7 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17"; 8 | 9 | configure({ adapter: new Adapter() }); 10 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .vercel 26 | -------------------------------------------------------------------------------- /src/components/muiStyle.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@mui/styles'; 2 | import { isMobile } from '../utils'; 3 | 4 | export const useStyles = makeStyles({ 5 | root: { 6 | "& .MuiOutlinedInput-notchedOutline": { 7 | borderRadius: "10px", 8 | } 9 | } 10 | }); 11 | 12 | export const titleClass = { width: "100%", padding: "0px" } 13 | 14 | export const cardClasses = isMobile() ? { minWidth: 275 } : { minWidth: 475 }; 15 | -------------------------------------------------------------------------------- /src/components/common/PowerBy.css: -------------------------------------------------------------------------------- 1 | .powerBy { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: center; 5 | align-items: center; 6 | gap: 5px; 7 | cursor: pointer; 8 | text-decoration: none; 9 | padding: 8px; 10 | } 11 | 12 | .powerBy h1 { 13 | color: gray; 14 | margin: 0; 15 | font-size: 14px; 16 | padding: 0; 17 | line-height: 120%; 18 | } 19 | 20 | .powerBy img { 21 | height: 14px; 22 | width: auto; 23 | margin: 0; 24 | padding: 0; 25 | } -------------------------------------------------------------------------------- /src/__tests__/App.test.js: -------------------------------------------------------------------------------- 1 | import App from "../App"; 2 | import { store } from "../app/store"; 3 | import { Provider } from "react-redux"; 4 | import { SnackbarProvider } from "notistack"; 5 | import { mount } from "enzyme"; 6 | 7 | test("renders learn react link", () => { 8 | const elem = ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | const wrapper = mount(elem); 18 | expect(wrapper.render()).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const GAS_INCREASE = 1.16; 2 | 3 | export const MINTED_CHECK_CAP = 5000; 4 | 5 | export const API_DB = "https://11xykht95a.execute-api.us-west-1.amazonaws.com/"; 6 | export const ENABLE_MOCK = false; 7 | 8 | export const SELF_WALLET_SYMBOL = "--self-wallet"; 9 | 10 | export const APP_TYPE_ECOMMERCE = "ecommerce"; 11 | export const APP_TYPE_BOXCAT = "boxcat"; 12 | export const APP_TYPE_BASE_NFT = "blanknft"; 13 | export const APP_TYPE_MEMBERSHIP = "membership"; 14 | 15 | export const APP_TYPES = [APP_TYPE_ECOMMERCE, APP_TYPE_BOXCAT, APP_TYPE_BASE_NFT, APP_TYPE_MEMBERSHIP]; -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/app/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | 3 | // common reducers 4 | import userModalReducer from "../components/UserModal/UserModalSlice"; 5 | import functionCallModalReducer from "../components/FunctionCallModal/FunctionCallSlice"; 6 | import signInModalReducer from "../components/SignInModal/SignInModalSlice"; 7 | 8 | const rootReducer = combineReducers({ 9 | // Define a top-level state field named `todos`, handled by `todosReducer` 10 | userModal: userModalReducer, 11 | functionCallModal: functionCallModalReducer, 12 | signInModal: signInModalReducer, 13 | }); 14 | 15 | export default rootReducer; 16 | -------------------------------------------------------------------------------- /src/wallet/utils.js: -------------------------------------------------------------------------------- 1 | 2 | export const normalizeURL = (u) => new URL(u).host.replace("www.", ""); 3 | 4 | export const isMobile = () => 5 | /Mobi/i.test(window.navigator.userAgent) || 6 | /iPhone|iPod|iPad/i.test(navigator.userAgent); 7 | 8 | export const objectMap = (object, mapFn) => { 9 | return Object.keys(object).reduce((result, key) => { 10 | result[key] = mapFn(object[key]); 11 | return result; 12 | }, {}); 13 | }; 14 | 15 | 16 | export const getChainID = () => { 17 | if (window.IS_TEST) { 18 | return 5; 19 | } 20 | else if (window.CHAINID) { 21 | return window.CHAINID; 22 | } 23 | } 24 | 25 | export const isValidAddress = (address) => { 26 | return /^(0x)?[0-9a-f]{40}$/i.test(address); 27 | } -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/publicPath.js: -------------------------------------------------------------------------------- 1 | /* global __webpack_public_path__:writable */ 2 | 3 | /* 4 | * Setting webpack's public path dynamically instead of statically at the compilation process. 5 | * This allows users of the library to be able to host JS main bundle and its chunks anywhere they 6 | * like as long as all js files are in the same directory. Setting path dynamically allows entry .html 7 | * file to live separately from JS bundle files which is required for CDN/CMS deployments of 8 | * Mapbuilder. 9 | * https://webpack.js.org/guides/public-path/ 10 | * 11 | * */ 12 | //@ts-ignore 13 | const url = new URL(document.currentScript.src); 14 | const widgetLink = url.href.substring(0, url.href.lastIndexOf('/') + 1); 15 | //@ts-ignore 16 | __webpack_public_path__ = widgetLink; 17 | console.log(__webpack_public_path__); -------------------------------------------------------------------------------- /src/components/FunctionCallModal/FunctionCallSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | functionCallModalOpen: false, 5 | functionCallInfo: {}, 6 | }; 7 | 8 | const functionCallModalSlice = createSlice({ 9 | name: "functionCallModal", 10 | initialState, 11 | reducers: { 12 | openFunctionCallModal: (state, action) => { 13 | state.functionCallInfo = action.payload; 14 | state.functionCallModalOpen = true 15 | }, 16 | closeFunctionCallModal: (state) => { 17 | state.functionCallModalOpen = false; 18 | }, 19 | }, 20 | }); 21 | 22 | export const { 23 | openFunctionCallModal, 24 | closeFunctionCallModal, 25 | } = functionCallModalSlice.actions; 26 | 27 | export default functionCallModalSlice.reducer; 28 | -------------------------------------------------------------------------------- /src/components/SignInModal/SignInModalSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | signInModal: false, 5 | orderSignData: "" 6 | }; 7 | 8 | const signInModalReducer = createSlice({ 9 | name: "signInModal", 10 | initialState, 11 | reducers: { 12 | openSignInModal: (state) => { 13 | console.log("openSignInModal"); 14 | state.signInModal = true 15 | }, 16 | closeSignInModal: (state) => { 17 | state.signInModal = false; 18 | }, 19 | updateOrderSignData: (state, action) => { 20 | state.orderSignData = action.payload; 21 | }, 22 | }, 23 | }); 24 | 25 | export const { 26 | openSignInModal, 27 | closeSignInModal, 28 | } = signInModalReducer.actions; 29 | 30 | export default signInModalReducer.reducer; 31 | -------------------------------------------------------------------------------- /src/components/UserModal/UserModal.css: -------------------------------------------------------------------------------- 1 | .user-profile-wrapper { 2 | position: absolute; 3 | right: 0; 4 | top: 0; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | width: 200px; 10 | height: 600px; 11 | padding: 20px; 12 | } 13 | 14 | .user-modal-account { 15 | display: flex; 16 | flex-direction: row; 17 | gap: 10px; 18 | } 19 | 20 | .user-modal-wrapper { 21 | display: flex; 22 | flex-direction: column; 23 | gap: 10px; 24 | } 25 | 26 | /* 27 | .user-modal-account button { 28 | background-color: transparent; 29 | color: #333; 30 | border: none; 31 | font-size: 14px; 32 | font-weight: 400; 33 | cursor: pointer; 34 | border-radius: 100px; 35 | padding: 4px 8px; 36 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px; 37 | } */ -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "./publicPath"; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import './index.css'; 5 | import App from './App'; 6 | import reportWebVitals from './reportWebVitals'; 7 | import { initButtonCheck } from './divCheck/buttonCheckTop'; 8 | import { Provider } from 'react-redux'; 9 | import { store } from './app/store'; 10 | import { SnackbarProvider } from "notistack"; 11 | 12 | const createDOMElement = () => { 13 | const body = document.getElementsByTagName('body')[0]; 14 | const div = Object.assign(document.createElement('div'), { 15 | id: "root", 16 | }); 17 | body.appendChild(div); 18 | return div; 19 | } 20 | 21 | export const renderAppContainer = () => { 22 | ReactDOM.render( 23 | <> 24 | 25 | 33 | 34 | 35 | 36 | , createDOMElement()); 37 | } 38 | 39 | document.addEventListener("DOMContentLoaded", () => { 40 | renderAppContainer(); 41 | initButtonCheck(); 42 | }); 43 | 44 | reportWebVitals(); 45 | -------------------------------------------------------------------------------- /src/wallet/abiHelper.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { API_DB } from "../constants"; 3 | import * as _ from "lodash"; 4 | /** 5 | * This file will be untimately replaced by Database calls. 6 | */ 7 | 8 | var abi = {}; 9 | 10 | // TODO: when converting this to db calls, remember to add "async" here and "await" at callers 11 | export const fetchAbiOfContractAt = async (contractAddr, chainId) => { 12 | if (contractAddr in abi) { 13 | console.log("ABI CACHE", contractAddr); 14 | return abi[contractAddr]; 15 | } 16 | else if (window.ABI) { 17 | return window.ABI; 18 | } 19 | else { 20 | return await fetch(API_DB + "abi/" + chainId + "/" + contractAddr, { 21 | method: "GET", 22 | }) 23 | .then((response) => response.json()) 24 | .then(async (response) => { 25 | const dataFromDb = _.get(response, ["Item", "Abi"]); 26 | if (dataFromDb) { 27 | console.log("ABI from data base"); 28 | // console.log("ABI", dataFromDb); 29 | abi[contractAddr] = JSON.parse(dataFromDb); 30 | return JSON.parse(dataFromDb); 31 | } 32 | else { 33 | alert("Cannot find correct abi!"); 34 | throw new Error("Cannot find abi"); 35 | } 36 | 37 | }) 38 | .catch((error) => { 39 | console.error(error); 40 | }); 41 | } 42 | }; -------------------------------------------------------------------------------- /src/contract_access_object/writerCao.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a collection of functions to write to contract via constructing and submitting a transaction 3 | */ 4 | 5 | 6 | import { callFunction } from "./cao"; 7 | 8 | /** 9 | * Create an ECOMERCE proxy owned by ownerAddr 10 | * @param {*} ownerAddr 11 | */ 12 | 13 | export const setProxyCurrentRound = async (proxyAddr, curRound) => { 14 | return await callFunction(proxyAddr, "setCurrentRound", [curRound], 0, true); 15 | }; 16 | 17 | export const setProxySetSupply = async (proxyAddr, tokenId, newSupply) => { 18 | return await callFunction(proxyAddr, "setSupply", [tokenId, newSupply], 0, true); 19 | }; 20 | 21 | export const setProxySetMintOn = async (proxyAddr, tokenId, status) => { 22 | return await callFunction(proxyAddr, "setMintOn", [tokenId, status], 0, true); 23 | }; 24 | 25 | export const setProxySetSaleInfo = async (proxyAddr, tokenId, price, maxSupply, isAllowlist, merkleTree) => { 26 | return await callFunction(proxyAddr, "setSaleInfo", [tokenId, price, maxSupply, isAllowlist, merkleTree], 0, true); 27 | }; 28 | 29 | export const setProxyMint = async (proxyAddr, tokenId, amount, price) => { 30 | // console.log("MINT", tokenId, amount); 31 | return await callFunction(proxyAddr, "mint", [tokenId, amount], price, true); 32 | }; 33 | 34 | export const setProxyBurn = async (proxyAddr, tokenId, amount) => { 35 | return await callFunction(proxyAddr, "forgeToken", [tokenId, amount], 0, true); 36 | }; -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { store } from "./app/store"; 2 | import '@rainbow-me/rainbowkit/styles.css'; 3 | import { 4 | getDefaultWallets, 5 | RainbowKitProvider, 6 | } from '@rainbow-me/rainbowkit'; 7 | import { configureChains, createClient, WagmiConfig } from 'wagmi'; 8 | import { mainnet, polygon, optimism, arbitrum, goerli } from 'wagmi/chains'; 9 | import { infuraProvider } from 'wagmi/providers/infura'; 10 | import { publicProvider } from 'wagmi/providers/public'; 11 | import { UserModal } from './components/UserModal/UserModal'; 12 | import { FunctionCallModal } from "./components/FunctionCallModal/FunctionCallView"; 13 | import { SignInModal } from "./components/SignInModal/SignInModal"; 14 | const { chains, provider } = configureChains( 15 | [mainnet, polygon, optimism, arbitrum, goerli], 16 | [ 17 | infuraProvider({ apiKey: process.env.INFURA_API_KEY }), 18 | publicProvider(), 19 | ] 20 | ); 21 | 22 | const { connectors } = getDefaultWallets({ 23 | appName: 'My RainbowKit App', 24 | chains 25 | }); 26 | 27 | const wagmiClient = createClient({ 28 | autoConnect: true, 29 | connectors, 30 | provider 31 | }) 32 | function App() { 33 | 34 | return ( 35 | <> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | 47 | export default App; 48 | -------------------------------------------------------------------------------- /src/divCheck/connectButtonCheck.js: -------------------------------------------------------------------------------- 1 | import { store } from "../app/store"; 2 | import { closeUserModal, openUserModal } from "../components/UserModal/UserModalSlice"; 3 | import { setButtonText } from "../utils"; 4 | 5 | const connectSymbol = "connect-button" 6 | 7 | 8 | export const updateConnectButton = async () => { 9 | console.log("updateConnectButton"); 10 | const connectButtons = [ 11 | ...document.querySelectorAll(`[${connectSymbol}]`) 12 | ] 13 | if (connectButtons) { 14 | console.log(connectButtons); 15 | connectButtons.forEach((connectButton) => { 16 | // get information from div attribute 17 | connectButton.href = "#" 18 | connectButton.onclick = async () => { 19 | console.log("CLICK the button") 20 | const initialBtnText = connectButton.textContent; 21 | setButtonText(connectButton, "Loading...") 22 | if (store.getState().userModal.userModalOpen) { 23 | store.dispatch(closeUserModal()); 24 | } 25 | else { 26 | store.dispatch(openUserModal()); 27 | } 28 | setButtonText(connectButton, initialBtnText) 29 | } 30 | }) 31 | } 32 | } 33 | 34 | export const updateConnectButtonText = (text) => { 35 | const connectButtons = [ 36 | ...document.querySelectorAll(`[${connectSymbol}]`) 37 | ] 38 | if (connectButtons) { 39 | // console.log(connectButtons); 40 | connectButtons.forEach((connectButton) => { 41 | // get information from div attribute 42 | setButtonText(connectButton, text) 43 | }) 44 | } 45 | } -------------------------------------------------------------------------------- /src/contract_access_object/cao.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract Access Object(CAO) 3 | */ 4 | 5 | import { fetchContractObjByAddr } from "../wallet/wallet"; 6 | import { assert } from "../wallet/web3Helper"; 7 | import { ethers } from "ethers"; 8 | 9 | export const GAS_INCREASE = 116; 10 | 11 | export const callViewFunction = async (contractAddr, methodName, args) => { 12 | const contractObj = fetchContractObjAt(contractAddr); 13 | console.log("callViewFunction", methodName); 14 | return getFunctionSignature(contractObj, methodName, args); 15 | }; 16 | 17 | const increaseGasLimit = (estimatedGasLimit) => { 18 | return estimatedGasLimit.mul(GAS_INCREASE).div(100) 19 | } 20 | 21 | /** 22 | * This is for non-view function call only. For view function call, use callViewFunction() instead. 23 | */ 24 | export const callFunction = async ( 25 | contractAddr, 26 | methodName, 27 | args, 28 | value = 0, 29 | requireGasOptimize = true 30 | ) => { 31 | const contractObj = await fetchContractObjAt(contractAddr); 32 | 33 | assert(contractObj, `contract obj is not defined: ${contractObj}`); 34 | const options = { value: ethers.utils.parseUnits(value.toString(), "wei") }; 35 | 36 | if (requireGasOptimize) { 37 | const estimation = (await contractObj.estimateGas[methodName](...args, options)); 38 | const optionsWithGas = { 39 | ...options, 40 | gasLimit: increaseGasLimit(estimation), 41 | } 42 | return await contractObj[methodName](...args, optionsWithGas); 43 | } else { 44 | return await contractObj[methodName](...args, options); 45 | } 46 | }; 47 | 48 | const getFunctionSignature = async (contractObj, functionName, args) => { 49 | if (args === undefined) { 50 | args = []; 51 | } 52 | const res = await contractObj[functionName](...args); 53 | return res; 54 | }; 55 | 56 | const fetchContractObjAt = (contractAddr) => { 57 | return fetchContractObjByAddr(contractAddr); 58 | }; 59 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | const rewireBabelLoader = require("craco-babel-loader"); 2 | const { realpathSync } = require("fs"); 3 | const { resolve } = require("path"); 4 | const Dotenv = require("dotenv-webpack"); 5 | 6 | function forceAbsolutePackage(relativePath) { 7 | const appDirectory = realpathSync(process.cwd()); 8 | return resolve(appDirectory, relativePath); 9 | } 10 | 11 | module.exports = { 12 | babel: { 13 | plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]], 14 | }, 15 | plugins: [ 16 | //This is a craco plugin: https://github.com/sharegate/craco/blob/master/packages/craco/README.md#configuration-overview 17 | { 18 | plugin: rewireBabelLoader, 19 | options: { 20 | includes: [ 21 | // On force un absolute path car c'est requis par babel-loader 22 | forceAbsolutePackage("node_modules/@wagmi/core/dist"), 23 | forceAbsolutePackage("node_modules/wagmi/dist"), 24 | ], 25 | }, 26 | }, 27 | ], 28 | webpack: { 29 | alias: {}, 30 | configure: (webpackConfig, { env, paths }) => { 31 | webpackConfig.plugins.push( 32 | new Dotenv({ systemvars: true, path: "./.env" }) 33 | ); 34 | // console.log("webpackConfig", env); 35 | const isEnvProduction = env === "production"; 36 | const isEnvDevelopment = env === "development"; 37 | webpackConfig.output.libraryTarget = "umd"; 38 | webpackConfig.output.library = "NFTComponents"; 39 | 40 | webpackConfig.output.publicPath = ""; 41 | 42 | webpackConfig.output.filename = isEnvProduction 43 | ? "[name].js" 44 | : isEnvDevelopment && "bundle.js"; 45 | // Turn off chunking 46 | webpackConfig.optimization = {}; 47 | 48 | const miniCssPlugin = webpackConfig.plugins.find( 49 | ({ constructor }) => constructor.name === "MiniCssExtractPlugin" 50 | ); 51 | if (miniCssPlugin) { 52 | miniCssPlugin.options.filename = "[name].css"; 53 | miniCssPlugin.options.chunkFilename = "[name].css"; 54 | } 55 | return webpackConfig; 56 | }, 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/UserModal/UserModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createTheme } from "@mui/material/styles"; 3 | import { ThemeProvider } from "@mui/material"; 4 | import Dialog from '@mui/material/Dialog'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import Slide from '@mui/material/Slide'; 7 | import { UserSection } from "./UserSection"; 8 | import { useDispatch, useSelector } from 'react-redux'; 9 | import { closeUserModal } from './UserModalSlice'; 10 | 11 | const Transition = React.forwardRef(function Transition(props, ref) { 12 | return ; 13 | }); 14 | 15 | const theme = createTheme({ 16 | components: { 17 | // Name of the component 18 | MuiDialog: { 19 | styleOverrides: { 20 | // Name of the slot 21 | root: { 22 | // Some CSS 23 | left: 'auto !important', 24 | bottom: 'auto !important', 25 | }, 26 | }, 27 | }, 28 | }, 29 | }); 30 | 31 | 32 | export const UserModal = () => { 33 | const userModalOpen = useSelector(state => state.userModal.userModalOpen); 34 | const dispatch = useDispatch(); 35 | const handleClose = () => { 36 | dispatch(closeUserModal()); 37 | }; 38 | 39 | return ( 40 | <> 41 | 42 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-widget", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^6.4.5", 7 | "@emotion/react": "^11.10.4", 8 | "@emotion/styled": "^11.10.4", 9 | "@mui/icons-material": "^5.10.6", 10 | "@mui/material": "^5.10.6", 11 | "@mui/styles": "^5.10.14", 12 | "@rainbow-me/rainbowkit": "^0.8.1", 13 | "@reduxjs/toolkit": "^1.9.0", 14 | "@testing-library/jest-dom": "^5.11.4", 15 | "@testing-library/react": "^11.1.0", 16 | "@testing-library/user-event": "^12.1.10", 17 | "@walletconnect/web3-provider": "^1.8.0", 18 | "craco-babel-loader": "^1.0.4", 19 | "ethers": "^5.7.2", 20 | "keccak256": "^1.0.6", 21 | "merkletreejs": "^0.2.32", 22 | "notistack": "^2.0.8", 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2", 25 | "react-icons": "^4.6.0", 26 | "react-redux": "^8.0.5", 27 | "react-scripts": "4.0.3", 28 | "redux": "^4.2.0", 29 | "use-debounce": "^9.0.2", 30 | "wagmi": "0.9.1", 31 | "web-vitals": "^1.0.1", 32 | "web3": "^1.8.0", 33 | "web3modal": "^1.9.9" 34 | }, 35 | "scripts": { 36 | "start": "craco start", 37 | "dev": "craco start", 38 | "build": "craco build", 39 | "test": "craco test", 40 | "eject": "react-scripts eject" 41 | }, 42 | "eslintConfig": { 43 | "extends": [ 44 | "react-app", 45 | "react-app/jest" 46 | ] 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | }, 60 | "devDependencies": { 61 | "@types/jest": "^29.2.4", 62 | "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", 63 | "dotenv-webpack": "^8.0.1", 64 | "enzyme": "^3.11.0", 65 | "html-webpack-plugin": "^4.5.2", 66 | "parcel-bundler": "^1.12.5", 67 | "react-test-renderer": "^17.0.2" 68 | }, 69 | "jest": { 70 | "transformIgnorePatterns": [ 71 | "node_modules/(?!<@rainbow-me>)/" 72 | ], 73 | "transform": { 74 | ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy", 75 | "^.+\\.(js|jsx)?$": "babel-jest" 76 | }, 77 | "testMatch": [ 78 | "/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))" 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /src/components/UserModal/UserModalSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { updateConnectButtonText } from "../../divCheck/connectButtonCheck"; 3 | import { isValidAddress } from "../../wallet/utils"; 4 | import { formatWalletAddress } from "../../utils"; 5 | 6 | const initialState = { 7 | userModalOpen: false, 8 | walletAddress: "", 9 | isInitializedContract: false, 10 | chainId: {}, 11 | provider: {}, 12 | signer: {}, 13 | orderSignData: "", 14 | readFunction: [], 15 | }; 16 | 17 | const userModalReducer = createSlice({ 18 | name: "userModal", 19 | initialState, 20 | reducers: { 21 | openUserModal: (state) => { 22 | state.userModalOpen = true 23 | }, 24 | closeUserModal: (state) => { 25 | state.userModalOpen = false; 26 | }, 27 | updateWalletAddress: (state, action) => { 28 | state.walletAddress = action.payload 29 | if (isValidAddress(state.walletAddress)) { 30 | updateConnectButtonText(formatWalletAddress(state.walletAddress)); 31 | } 32 | else { 33 | updateConnectButtonText("Connect Wallet"); 34 | } 35 | }, 36 | updateChainId: (state, action) => { 37 | state.chainId = action.payload 38 | }, 39 | updateProvider: (state, action) => { 40 | state.provider = action.payload 41 | }, 42 | updateSigner: (state, action) => { 43 | state.signer = action.payload 44 | }, 45 | updateOrderSignData: (state, action) => { 46 | state.orderSignData = action.payload 47 | }, 48 | updateReadFunction: (state, action) => { 49 | state.readFunction = action.payload 50 | }, 51 | appendReadFunction: (state, action) => { 52 | state.readFunction.push(action.payload) 53 | }, 54 | updateIsInitializedContract: (state, action) => { 55 | state.isInitializedContract = action.payload 56 | }, 57 | }, 58 | }); 59 | 60 | export const { 61 | openUserModal, 62 | closeUserModal, 63 | updateWalletAddress, 64 | updateChainId, 65 | updateProvider, 66 | updateSigner, 67 | updateOrderSignData, 68 | updateReadFunction, 69 | appendReadFunction, 70 | updateIsInitializedContract, 71 | } = userModalReducer.actions; 72 | 73 | export default userModalReducer.reducer; 74 | -------------------------------------------------------------------------------- /src/contract_access_object/readerCao.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a collection of functions to read from contract, i.e. Contract Access Object 3 | */ 4 | 5 | import { callViewFunction } from "./cao"; 6 | 7 | /** 8 | * A testing function to make sure we are able to call function on proxy 9 | * @param {*} proxyAddr 10 | */ 11 | export const getProxySymbol = async (proxyAddr) => { 12 | const res = await callViewFunction(proxyAddr, "symbol", []); 13 | return res; 14 | }; 15 | 16 | export const getProxyMintInfoOfRound = async (proxyAddr, round) => { 17 | const res = await callViewFunction(proxyAddr, "mintInfo", [round]); 18 | return res; 19 | }; 20 | 21 | export const getProxyIsMintOn = async (proxyAddr, round) => { 22 | const res = await callViewFunction(proxyAddr, "isMintOn", [round]); 23 | return res; 24 | }; 25 | 26 | // export const getProxyCurrentRoundByTokenID = async (proxyAddr, tokenId) => { 27 | // const res = await callViewFunction(proxyAddr, "currentRound", [tokenId]); 28 | // return res; 29 | // }; 30 | 31 | export const getProxyIsMintOnByTokenID = async (proxyAddr, tokenId) => { 32 | const res = await callViewFunction(proxyAddr, "isMintOn", [tokenId]); 33 | console.log("MINT", tokenId, res); 34 | return res; 35 | }; 36 | 37 | export const getProxyPriceByTokenID = async (proxyAddr, tokenId) => { 38 | const res = await callViewFunction(proxyAddr, "mintPrice", [tokenId]); 39 | return res; 40 | }; 41 | 42 | export const getProxyMintedByTokenID = async (proxyAddr, tokenId) => { 43 | const res = await callViewFunction(proxyAddr, "tokenMinted", [tokenId]); 44 | return res; 45 | }; 46 | 47 | export const getProxySupplyByTokenID = async (proxyAddr, tokenId) => { 48 | const res = await callViewFunction(proxyAddr, "supplyLimit", [tokenId]); 49 | return res; 50 | }; 51 | 52 | export const getProxyBalanceByTokenID = async (proxyAddr, user, tokenId) => { 53 | const res = await callViewFunction(proxyAddr, "balanceOf", [user, tokenId]); 54 | return res; 55 | }; 56 | 57 | export const getProxyName = async (proxyAddr) => { 58 | const res = await callViewFunction(proxyAddr, "name", []); 59 | return res; 60 | } 61 | 62 | export const getProxyMinted = async (proxyAddr) => { 63 | const res = await callViewFunction(proxyAddr, "totalSupply", []); 64 | return res; 65 | } 66 | 67 | /** 68 | * Returns proxy specific data. 69 | * TODO: fill XX with meaningful wording, e.g. getProxyPublicMintTime 70 | */ 71 | export const getProxyXX = async (proxyAddr) => { }; 72 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 | 33 | Name: 34 |

35 | Name 36 |

37 | balance Of self: 38 |

39 | balanceOf 40 |

41 | Total Supply: 42 |

43 | totalSupply 44 |

45 | saleConfig: 46 |

47 | saleConfig 48 |

49 | Mint: 50 | 53 | 56 | 59 | set sale info 60 | 63 | 68 |
69 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/App.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders learn react link 1`] = ` 4 | LoadedCheerio { 5 | "0":
8 | 11 |
, 12 | "_root": LoadedCheerio { 13 | "0": Document { 14 | "children": Array [ 15 | 16 | 17 | 18 | , 19 | ], 20 | "endIndex": null, 21 | "next": null, 22 | "parent": null, 23 | "prev": null, 24 | "startIndex": null, 25 | "type": "root", 26 | "x-mode": "quirks", 27 | }, 28 | "_root": [Circular], 29 | "length": 1, 30 | "options": Object { 31 | "decodeEntities": true, 32 | "xml": false, 33 | }, 34 | }, 35 | "length": 1, 36 | "options": Object { 37 | "decodeEntities": true, 38 | "xml": false, 39 | }, 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import * as Web3 from 'web3'; 2 | import { getChainIdFromWindow } from './wallet/wallet'; 3 | import { MerkleTree } from "merkletreejs"; 4 | import keccak256 from "keccak256"; 5 | 6 | export const isMobile = () => /Mobi/i.test(window.navigator.userAgent) 7 | || /iPhone|iPod|iPad/i.test(navigator.userAgent); 8 | 9 | export const objectMap = (object, mapFn) => { 10 | return Object.keys(object).reduce((result, key) => { 11 | result[key] = mapFn(object[key]); 12 | return result 13 | }, {}) 14 | } 15 | 16 | export const normalizeURL = (u) => ((new URL(u).host).replace('www.', '')) 17 | 18 | export const parseTxError = (error) => { 19 | try { 20 | return { 21 | code: error.code ?? JSON.parse(`{${error.message.split("{")[1]}`).code, 22 | message: error.message.split("{")[0].trim() 23 | }; 24 | } catch (parse_error) { 25 | console.log("Failed to parse error code and message") 26 | console.log("Original error:", error) 27 | return { 28 | code: undefined, message: error?.toString() 29 | } 30 | } 31 | } 32 | 33 | // Avoid big number errors without using external libraries 34 | export const formatValue = (v) => v.toLocaleString('fullwide', { 35 | useGrouping: false 36 | }); 37 | 38 | export const roundToDecimal = (n, d) => { 39 | return +n.toFixed(d) 40 | } 41 | 42 | export const setButtonText = (btn, text) => { 43 | if (btn.childElementCount > 0) { 44 | btn.children[0].textContent = text; 45 | } else { 46 | btn.textContent = text; 47 | } 48 | } 49 | 50 | export const formatWalletAddress = (address) => { 51 | return address && `${address.substring(0, 4)}...${address.substring(38)}`; 52 | }; 53 | 54 | export const convertWeiToETH = (wei) => { 55 | return Web3.utils.fromWei(wei, 'ether') 56 | } 57 | 58 | export const etherscanLink = async () => { 59 | const chainID = getChainIdFromWindow(); 60 | switch (chainID) { 61 | case 1: return "https://etherscan.io/tx/"; 62 | case 5: return "https://goerli.etherscan.io/tx/"; 63 | case 11155111: return "https://sepolia.etherscan.io/tx/"; 64 | default: break; 65 | } 66 | } 67 | 68 | export const getProof = (addresses, contract, address, round) => { 69 | console.log("getProof", contract, address, round); 70 | const leafNodes = addresses.map(address => Web3.utils.soliditySha3( 71 | { t: 'address', v: contract }, 72 | { t: 'address', v: address }, 73 | { t: 'uint256', v: round } 74 | )); 75 | const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true }); 76 | 77 | const leaf = Web3.utils.soliditySha3( 78 | { t: 'address', v: contract }, 79 | { t: 'address', v: address }, 80 | { t: 'uint256', v: round } 81 | ); 82 | const proof = merkleTree.getHexProof(leaf); 83 | console.log("PROOF", proof); 84 | return proof; 85 | } 86 | 87 | export const setAddr = (contractAddr, logicAddr) => { 88 | if (contractAddr && logicAddr) { 89 | return [contractAddr, logicAddr] 90 | } 91 | else if (contractAddr) { 92 | return [contractAddr, contractAddr] 93 | } 94 | else { 95 | return [window.CONTRACT_ADDRESS, window.LOGIC_ADDRESS] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/componentHelper.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import { fetchAbiOfContractAt } from "./wallet/abiHelper"; 3 | import { getChainID } from "./wallet/utils"; 4 | 5 | const web3Utils = new Web3(); 6 | 7 | const event721Transfer = 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, 12 | { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, 13 | { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } 14 | ], 15 | "name": "Transfer", 16 | "type": "event" 17 | } 18 | 19 | export function getTransactionReceiptMined(provider, txHash, interval) { 20 | const transactionReceiptAsync = function (resolve, reject) { 21 | // console.log("getTransactionReceiptMined", store.getState().userModal.provider); 22 | provider.getTransactionReceipt(txHash).then((receipt) => { 23 | if (receipt == null) { 24 | setTimeout( 25 | () => transactionReceiptAsync(resolve, reject), 26 | interval ? interval : 500); 27 | } else { 28 | resolve(receipt); 29 | } 30 | }).catch((error) => { 31 | reject(error); 32 | }); 33 | }; 34 | 35 | if (Array.isArray(txHash)) { 36 | return Promise.all(txHash.map( 37 | oneTxHash => getTransactionReceiptMined(provider, oneTxHash, interval))); 38 | } else if (typeof txHash === "string") { 39 | return new Promise(transactionReceiptAsync); 40 | } else { 41 | throw new Error("Invalid Type: " + txHash); 42 | } 43 | }; 44 | 45 | export const decodeLogs = async (logs, logicAddr) => { 46 | const abi = await fetchAbiOfContractAt(logicAddr, getChainID()); 47 | return logs.map(log => { 48 | const event = abi.find(e => { 49 | var signature = e.name + "(" + e.inputs.map(function (input) { return input.type; }).join(",") + ")"; 50 | var hash = web3Utils.utils.sha3(signature); 51 | return e.type === 'event' && hash === log.topics[0] 52 | }); 53 | if (!event) { 54 | return null; 55 | } 56 | const data = log.data; 57 | const values = web3Utils.eth.abi.decodeLog(event.inputs, data, log.topics.slice(1)); 58 | const result = {}; 59 | event.inputs.forEach((input, i) => { 60 | result[input.name] = values[i]; 61 | }); 62 | return result; 63 | }); 64 | }; 65 | 66 | export const decodeLogsBy = async (logs, logicAddr, eventName) => { 67 | const abi = await fetchAbiOfContractAt(logicAddr, getChainID()); 68 | return logs.map(log => { 69 | var event = abi.find(e => { 70 | return e.type === 'event' && e.name === eventName 71 | }); 72 | if (!event) { 73 | if (eventName === "Transfer") { 74 | event = event721Transfer; 75 | } 76 | else { 77 | return null; 78 | } 79 | } 80 | const data = log.data; 81 | try { 82 | const values = web3Utils.eth.abi.decodeLog(event.inputs, data, log.topics.slice(1)); 83 | const result = {}; 84 | event.inputs.forEach((input, i) => { 85 | result[input.name] = values[i]; 86 | }); 87 | return result; 88 | } 89 | catch { 90 | return null; 91 | } 92 | 93 | }); 94 | }; 95 | -------------------------------------------------------------------------------- /src/components/mint.css: -------------------------------------------------------------------------------- 1 | .cardWrapper { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | gap: 5px; 7 | padding: 0 10px; 8 | } 9 | 10 | .cardWrapper h1 { 11 | font-size: 18px; 12 | font-weight: 700; 13 | padding: 10px 16px; 14 | text-align: center; 15 | margin: 0; 16 | } 17 | 18 | .cardWrapper h2 { 19 | font-size: 18px; 20 | font-weight: 700; 21 | padding: 5px; 22 | text-align: center; 23 | margin: 0; 24 | } 25 | 26 | .ship { 27 | align-items: flex-start; 28 | gap: 10px; 29 | } 30 | 31 | .ship h1 { 32 | padding: 8px 0 0 0; 33 | } 34 | 35 | .ship h3 { 36 | font-size: 14px; 37 | font-weight: 400; 38 | color: gray; 39 | padding: 0; 40 | } 41 | 42 | .error-text { 43 | width: 100%; 44 | color: brown; 45 | } 46 | 47 | .select-wrapper { 48 | display: flex; 49 | flex-direction: row; 50 | justify-content: space-between; 51 | align-items: center; 52 | width: 100%; 53 | padding: 0 0 20px 0px; 54 | } 55 | 56 | .price-tag { 57 | width: 100%; 58 | display: flex; 59 | flex-direction: column; 60 | justify-content: left; 61 | align-items: flex-end; 62 | } 63 | 64 | .small { 65 | font-size: 14px; 66 | padding: 8px; 67 | } 68 | 69 | .bold { 70 | font-size: 18px; 71 | font-weight: bold; 72 | padding-right: 10px; 73 | } 74 | 75 | .common-button { 76 | position: relative; 77 | height: 40px; 78 | font-size: 18px; 79 | font-weight: 700; 80 | padding: 10px 16px; 81 | border-radius: 10px; 82 | transition-duration: 0.3s; 83 | background-color: #333; 84 | color: white; 85 | border: none; 86 | display: flex; 87 | flex-direction: row; 88 | align-items: center; 89 | justify-content: center; 90 | gap: 10px; 91 | width: auto; 92 | } 93 | 94 | .common-button:hover { 95 | cursor: pointer; 96 | background-color: gainsboro; 97 | color: #333; 98 | } 99 | 100 | .common-button:disabled, 101 | .common-button[disabled] { 102 | cursor: none; 103 | background-color: gainsboro; 104 | color: grey; 105 | } 106 | 107 | .common-button:active { 108 | background-color: grey; 109 | } 110 | 111 | .product-tag { 112 | display: flex; 113 | flex-direction: column; 114 | justify-content: center; 115 | align-items: flex-start; 116 | gap: 6px; 117 | } 118 | 119 | .product-tag h1 { 120 | margin: 0; 121 | padding: 0; 122 | font-size: 18px; 123 | font-weight: 700; 124 | line-height: 120%; 125 | } 126 | 127 | .product-tag h2 { 128 | margin: 0; 129 | padding: 0; 130 | font-size: 14px; 131 | font-weight: 400; 132 | line-height: 120%; 133 | } 134 | 135 | .link-button { 136 | text-decoration: none; 137 | display: flex; 138 | flex-direction: row; 139 | justify-content: center; 140 | align-items: center; 141 | gap: 5px; 142 | color: gray; 143 | } 144 | 145 | .link-button:hover { 146 | text-decoration: underline; 147 | } 148 | 149 | .confirm-button { 150 | align-self: center; 151 | margin: 20px 0 10px 0; 152 | } 153 | 154 | .cancel-button { 155 | color: black; 156 | background-color: white; 157 | border: 1px solid #333; 158 | } 159 | 160 | .login-button { 161 | color: #333; 162 | background-color: white; 163 | border: none; 164 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px; 165 | } 166 | 167 | .chain-button { 168 | color: #333; 169 | background-color: white; 170 | border: none; 171 | font-size: 14px; 172 | font-weight: 400; 173 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px; 174 | height: auto; 175 | } 176 | 177 | .addship-button { 178 | height: auto; 179 | font-size: 14px; 180 | } -------------------------------------------------------------------------------- /src/components/FunctionCallModal/FunctionCallView.css: -------------------------------------------------------------------------------- 1 | .function-call-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | gap: 20px; 7 | padding: 0 20px; 8 | } 9 | 10 | .function-call-wrapper button { 11 | position: relative; 12 | height: 40px; 13 | font-size: 18px; 14 | font-weight: 700; 15 | padding: 10px 16px; 16 | border-radius: 10px; 17 | transition-duration: 0.3s; 18 | background-color: #333; 19 | color: white; 20 | border: none; 21 | display: flex; 22 | flex-direction: row; 23 | align-items: center; 24 | justify-content: center; 25 | gap: 10px; 26 | width: auto; 27 | } 28 | 29 | .function-call-wrapper button:hover { 30 | cursor: pointer; 31 | background-color: gainsboro; 32 | color: #333; 33 | } 34 | 35 | .function-call-wrapper button:disabled, 36 | .function-call-wrapper button[disabled] { 37 | cursor: none; 38 | background-color: gainsboro; 39 | color: grey; 40 | } 41 | 42 | .function-call-wrapper button:active { 43 | background-color: grey; 44 | } 45 | 46 | .function-call-input { 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: center; 50 | align-items: center; 51 | gap: 10px; 52 | width: 100%; 53 | } 54 | 55 | .function-call-input a { 56 | font-weight: 700; 57 | font-size: 20px; 58 | border: 1px solid grey; 59 | border-radius: 5px; 60 | padding: 5px 10px; 61 | background-color: white; 62 | cursor: pointer; 63 | text-decoration: none; 64 | color: #333; 65 | } 66 | 67 | .function-input-wrapper { 68 | display: flex; 69 | flex-direction: row; 70 | justify-content: left; 71 | align-items: center; 72 | gap: 5px; 73 | width: 100%; 74 | } 75 | 76 | .function-input-wrapper input { 77 | width: 100%; 78 | height: 30px; 79 | border: 1px solid #e0e0e0; 80 | border-radius: 5px; 81 | padding: 5px; 82 | font-size: 14px; 83 | font-weight: 400; 84 | outline: none; 85 | } 86 | 87 | .function-input-wrapper input:focus { 88 | border: 1px solid grey; 89 | } 90 | 91 | .function-input-wrapper input::placeholder { 92 | color: #e0e0e0; 93 | } 94 | 95 | .function-input-wrapper input:disabled { 96 | background-color: gainsboro; 97 | } 98 | 99 | .function-input-wrapper label { 100 | font-size: 18px; 101 | font-weight: 400; 102 | color: grey; 103 | text-transform: capitalize; 104 | } 105 | 106 | .etherscan-wrapper { 107 | padding-top: 10px; 108 | color: grey; 109 | } 110 | 111 | .etherscan-wrapper a { 112 | color: grey; 113 | } 114 | 115 | .common-title { 116 | display: flex; 117 | align-items: center; 118 | justify-content: space-between; 119 | padding: 16px 0; 120 | width: 100%; 121 | } 122 | 123 | .common-title h1 { 124 | font-style: italic; 125 | font-weight: 700; 126 | font-size: 24px; 127 | padding: 0; 128 | margin: 0 24px; 129 | } 130 | 131 | .common-title button { 132 | padding: 0; 133 | margin: 0 24px; 134 | } 135 | 136 | .text-row { 137 | display: flex; 138 | flex-direction: row; 139 | justify-content: flex-start; 140 | align-items: center; 141 | width: 100%; 142 | gap: 5px; 143 | font-size: 18px; 144 | } 145 | 146 | .text-row a { 147 | font-size: 14px; 148 | border: none; 149 | text-decoration: underline; 150 | padding: 0; 151 | } 152 | 153 | .text-row button { 154 | font-size: 14px; 155 | border: none; 156 | background-color: transparent; 157 | text-decoration: underline; 158 | cursor: pointer; 159 | color: #333; 160 | width: auto; 161 | height: auto; 162 | padding: 0; 163 | } 164 | 165 | .text-row button:hover { 166 | color: grey; 167 | background-color: transparent; 168 | } -------------------------------------------------------------------------------- /src/components/UserModal/UserSection.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useEffect } from "react"; 3 | import "./UserModal.css"; 4 | import "../mint.css"; 5 | import { useAccountModal, useChainModal, useConnectModal } from "@rainbow-me/rainbowkit"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { updateChainId, updateIsInitializedContract, updateProvider, updateSigner, updateWalletAddress } from "./UserModalSlice"; 8 | import { useAccount, useEnsName, useNetwork, useSwitchNetwork, useProvider, useSigner } from "wagmi"; 9 | import { formatWalletAddress } from "../../utils"; 10 | import { connectWalletInit, getChainIdFromWindow, readViewCall } from "../../wallet/wallet"; 11 | import { useSnackbar } from "notistack"; 12 | import { ethers } from "ethers"; 13 | 14 | export const UserSection = () => { 15 | const dispatch = useDispatch(); 16 | const readFunction = useSelector(state => state.userModal.readFunction); 17 | const isInitializedContract = useSelector(state => state.userModal.isInitializedContract); 18 | const { openConnectModal } = useConnectModal(); 19 | const { openAccountModal } = useAccountModal(); 20 | const { openChainModal } = useChainModal(); 21 | const provider = useProvider(); 22 | const { data: signer } = useSigner() 23 | const { address, isConnected } = useAccount(); 24 | const { data, isError } = useEnsName({ 25 | address, 26 | }) 27 | const { chain } = useNetwork(); 28 | const { switchNetwork } = useSwitchNetwork(); 29 | 30 | const user = isError || (!data) ? formatWalletAddress(address) : data; 31 | 32 | const { enqueueSnackbar } = useSnackbar(); 33 | 34 | useEffect(() => { 35 | if (address && signer && provider && chain) { 36 | if (switchNetwork && (chain.id !== getChainIdFromWindow())) { 37 | switchNetwork(ethers.utils.hexlify(getChainIdFromWindow())); 38 | } 39 | else { 40 | dispatch(updateWalletAddress(address)); 41 | dispatch(updateProvider(provider)); 42 | dispatch(updateSigner(signer)); 43 | dispatch(updateChainId(chain)) 44 | try { 45 | connectWalletInit(signer); 46 | } 47 | catch (error) { 48 | enqueueSnackbar(error.message, { 49 | variant: "error", 50 | }); 51 | 52 | } 53 | } 54 | } 55 | if (!address) { 56 | dispatch(updateWalletAddress(null)); 57 | dispatch(updateProvider(null)); 58 | dispatch(updateSigner(null)); 59 | dispatch(updateChainId(null)) 60 | dispatch(updateIsInitializedContract(false)); 61 | } 62 | }, [address, provider, signer, chain]); 63 | 64 | useEffect(() => { 65 | if (signer && (readFunction.length > 0) && isInitializedContract) { 66 | readViewCall(signer, readFunction); 67 | } 68 | }, [signer, readFunction, isInitializedContract]); 69 | 70 | return ( 71 |
72 |
73 |
74 | {openConnectModal && ( 75 | 78 | )} 79 | 80 | {openAccountModal && ( 81 | 84 | )} 85 |
86 | {openChainModal && ( 87 | 90 | )} 91 |
92 |
93 | ) 94 | } -------------------------------------------------------------------------------- /src/wallet/wallet.js: -------------------------------------------------------------------------------- 1 | import { store } from "../app/store"; 2 | import { updateIsInitializedContract } from "../components/UserModal/UserModalSlice"; 3 | import { ENABLE_MOCK, SELF_WALLET_SYMBOL } from "../constants"; 4 | import { callViewFunction } from "../contract_access_object/cao"; 5 | import { updateDivText } from "../divCheck/functionCheck"; 6 | import { 7 | fetchAbiOfContractAt, 8 | } from "./abiHelper"; 9 | 10 | import { 11 | assert, 12 | } from "./web3Helper"; 13 | 14 | import { ethers } from "ethers"; 15 | 16 | let initializedContracts = {}; // initialized Contract objects 17 | 18 | export const fetchContractObjByAddr = (addr) => { 19 | assert( 20 | initializedContracts[addr], 21 | `No contract object initialized for address ${addr}!` 22 | ); 23 | return initializedContracts[addr]; 24 | }; 25 | 26 | export const initProxyContract = async ( 27 | proxyAddr, 28 | logicAddr, 29 | signer, 30 | ) => { 31 | console.log("Initializing proxy contract...", proxyAddr); 32 | await initContract(proxyAddr, logicAddr, signer); 33 | }; 34 | 35 | export const connectWalletInit = async (signer) => { 36 | console.log("Connecting to wallet..."); 37 | if (window.CONTRACT_ADDRESS && window.LOGIC_ADDRESS) { 38 | await initProxyContract(window.CONTRACT_ADDRESS, window.LOGIC_ADDRESS, signer); 39 | store.dispatch(updateIsInitializedContract(true)); 40 | } 41 | }; 42 | 43 | const convertOutput = (result, outputType) => { 44 | if (outputType === "time") { 45 | return new Date(result * 1000).toLocaleString(); 46 | } 47 | else { 48 | return result; 49 | } 50 | } 51 | 52 | export const readViewCall = async (signer, readFunction) => { 53 | if (readFunction.length > 0) { 54 | console.log("Read function for CDN", readFunction); 55 | readFunction.forEach(async (func) => { 56 | try { 57 | const args = func.inputs.map((input) => { 58 | const nameOfInput = input.name; 59 | if (nameOfInput in func.args) { 60 | if (func.args[nameOfInput] === SELF_WALLET_SYMBOL) { 61 | return signer.getAddress(); 62 | } 63 | else { 64 | return func.args[nameOfInput]; 65 | } 66 | } 67 | else { 68 | throw new Error(`Input ${nameOfInput} not found in args`); 69 | } 70 | }); 71 | const res = await callViewFunction(window.CONTRACT_ADDRESS, func.name, args); 72 | var result = "" 73 | 74 | if (typeof res === "object") { 75 | if (res._isBigNumber) { 76 | result = res.toNumber(); 77 | } 78 | else { 79 | result = JSON.stringify(res); 80 | } 81 | } 82 | else { 83 | result = res; 84 | } 85 | if (func.outputs && (func.outputs.length > 0)) { 86 | result = ""; 87 | func.outputs.forEach((output) => { 88 | if (output.name in res) { 89 | result += res[output.name]; 90 | } 91 | }) 92 | } 93 | 94 | result = convertOutput(result, func.outputType); 95 | updateDivText(func.name, result); 96 | } catch (e) { 97 | console.log("Error in reading function", e.message); 98 | } 99 | }) 100 | } 101 | } 102 | 103 | const initContract = async ( 104 | contractAddr, 105 | abiAddr, 106 | signer, 107 | ) => { 108 | if (ENABLE_MOCK) { 109 | return; 110 | } 111 | if (!signer) { 112 | throw new Error("signer is undefined"); 113 | } 114 | 115 | if (contractAddr in initializedContracts) { 116 | return; 117 | } 118 | const contract = await initContractGlobalObject(contractAddr, abiAddr); 119 | initializedContracts[contractAddr] = await initEthers(contract, signer); 120 | // console.log(`Initialized contract ${contractAddr}`); 121 | }; 122 | 123 | const initEthers = async (contract, signer) => { 124 | const address = contract.address[contract.allowedNetworks[0]]; 125 | const abi = contract.abi; 126 | return new ethers.Contract(address, abi, signer); 127 | }; 128 | 129 | const initContractGlobalObject = async (addr, abiAddr) => { 130 | const chainID = getChainIdFromWindow(); 131 | return { 132 | address: { 133 | [chainID]: addr, 134 | }, 135 | abi: await fetchAbiOfContractAt(abiAddr, chainID), 136 | allowedNetworks: [chainID], 137 | }; 138 | }; 139 | 140 | export const getChainIdFromWindow = () => { 141 | return window.IS_TEST ? 5 : window.CHAINID ? window.CHAINID : 5; 142 | } 143 | -------------------------------------------------------------------------------- /src/components/SignInModal/SignInModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import "../FunctionCallModal/FunctionCallView.css"; 4 | import "./SignInModal.css"; 5 | 6 | import Dialog from '@mui/material/Dialog'; 7 | import DialogContent from '@mui/material/DialogContent'; 8 | import DialogTitle from '@mui/material/DialogTitle'; 9 | import { useAccount, useSignMessage } from 'wagmi' 10 | 11 | import { closeSignInModal, openSignInModal } from "./SignInModalSlice"; 12 | import { cardClasses, titleClass } from "../muiStyle"; 13 | import { updateOrderSignData } from "../UserModal/UserModalSlice"; 14 | import { useSnackbar } from "notistack"; 15 | import { callViewFunction } from "../../contract_access_object/cao"; 16 | import { formatWalletAddress } from "../../utils"; 17 | import { UserSection } from "../UserModal/UserSection"; 18 | 19 | 20 | export const SignInModal = () => { 21 | const dispatch = useDispatch(); 22 | const signInModal = useSelector(state => state.signInModal.signInModal); 23 | const orderSignData = useSelector(state => state.signInModal.orderSignData); 24 | const walletAddress = useSelector(state => state.userModal.walletAddress); 25 | const isInitializedContract = useSelector(state => state.userModal.isInitializedContract); 26 | const { enqueueSnackbar } = useSnackbar(); 27 | const [ownerOf, setOwnerOf] = React.useState(""); 28 | const [step, setStep] = React.useState(0); 29 | const [closeBlur, setCloseBlur] = React.useState(false); 30 | const buyPassLink = window.BUY_PASS_LINK ? window.BUY_PASS_LINK : "/"; 31 | 32 | const { data, isError, isLoading, isSuccess, signMessage } = useSignMessage({ 33 | message: 'Login to see the page details', 34 | }) 35 | 36 | const handleReturn = () => { 37 | dispatch(closeSignInModal()); 38 | window.history.back(); 39 | }; 40 | 41 | const handleClose = () => { 42 | dispatch(closeSignInModal()); 43 | }; 44 | 45 | const handleSignMessage = () => { 46 | if (!ownerOf) { 47 | setStep(2); 48 | } 49 | else { 50 | if (!orderSignData) { 51 | signMessage(); 52 | } 53 | } 54 | } 55 | 56 | const readOwnerOf = async (user) => { 57 | const res = await callViewFunction(window.CONTRACT_ADDRESS, "balanceOf", [user]); 58 | console.log("readOwnerOf", parseInt(res)); 59 | if (parseInt(res)) { 60 | setOwnerOf(res); 61 | } 62 | } 63 | 64 | useEffect(() => { 65 | if (window.SIGN_PAGE_PATH) { 66 | setCloseBlur(false); 67 | const href = window.location.href; 68 | const pathName = window.location.pathname; 69 | console.log("checkPath", href, pathName); 70 | if (pathName === window.SIGN_PAGE_PATH) { 71 | console.log("SIGN PAGE"); 72 | dispatch(openSignInModal()); 73 | if (walletAddress) { 74 | setStep(1); 75 | } 76 | } 77 | } 78 | }, [walletAddress, window.location.pathname]) 79 | 80 | useEffect(() => { 81 | if (!(parseInt(ownerOf) > 0) && isInitializedContract) { 82 | readOwnerOf(walletAddress); 83 | } 84 | }, [ownerOf, walletAddress, isInitializedContract]) 85 | 86 | useEffect(() => { 87 | if (isSuccess) { 88 | dispatch(updateOrderSignData(data)); 89 | handleClose(); 90 | setCloseBlur(true); 91 | } 92 | if (isError) { 93 | enqueueSnackbar('Sign Message Error', { variant: 'error' }) 94 | } 95 | }, [isSuccess, data, isError]) 96 | 97 | return ( 98 | <> 99 | 106 | 107 |
108 |

109 | {walletAddress ? formatWalletAddress(walletAddress) : "Connect wallet"} 110 |

111 |
112 |
113 | 114 | {step === 0 && 115 |
116 |
117 | 118 |
119 |
120 | } 121 | {step === 1 && 122 |
123 |
124 | 127 |
128 |
129 | } 130 | {step === 2 && 131 |
132 |
133 | You don't have any pass to access this page. 134 | Buy pass to access 135 |
136 |
137 | 138 | } 139 |
140 |
141 | {!closeBlur && signInModal &&
} 142 | 143 | ) 144 | } -------------------------------------------------------------------------------- /src/divCheck/functionCheck.js: -------------------------------------------------------------------------------- 1 | import { store } from "../app/store"; 2 | import { openFunctionCallModal } from "../components/FunctionCallModal/FunctionCallSlice"; 3 | import { appendReadFunction } from "../components/UserModal/UserModalSlice"; 4 | import { setButtonText } from "../utils"; 5 | import { fetchAbiOfContractAt } from "../wallet/abiHelper"; 6 | 7 | const functionSymbol = "function-name" 8 | 9 | const getDivAttrInfo = (div, symbol) => { 10 | const name = div.getAttribute(symbol); 11 | var args = {}; 12 | try { 13 | args = JSON.parse(div.getAttribute(`${symbol}-args`)); 14 | } catch (e) { 15 | console.log("Error in parsing args", e.message); 16 | if (typeof div.getAttribute(`${symbol}-args`) === "object") { 17 | args = div.getAttribute(`${symbol}-args`); 18 | } 19 | } 20 | var price = div.getAttribute(`${symbol}-value-in-eth`); 21 | console.log("getDivAttrInfo info check", name, args, typeof args); 22 | if (!(args && Object.keys(args).length)) { 23 | args = {}; 24 | } 25 | if (price) { 26 | return { 27 | name: name.trim(), 28 | args, 29 | price: price.trim() 30 | } 31 | } 32 | else { 33 | return { 34 | name: name.trim(), 35 | args, 36 | } 37 | } 38 | } 39 | 40 | const getFunctionArgsFromAttr = (div, symbol) => { 41 | const argName = div.getAttribute(symbol); 42 | const argValue = div.textContent; 43 | if (argName && argValue) { 44 | return [ 45 | argName.trim(), 46 | argValue.trim() 47 | ] 48 | } 49 | else { 50 | return [null, null] 51 | } 52 | } 53 | 54 | const getDivInfoFromLoader = async (div, info) => { 55 | const abi = await fetchAbiOfContractAt(window.LOGIC_ADDRESS, window.CHAINID); 56 | const event = abi.find(e => { 57 | return e.type === 'function' && e.name === info.name 58 | }); 59 | if (event) { 60 | info.contractAddress = window.CONTRACT_ADDRESS; 61 | info.type = event.stateMutability; 62 | const returnValue = div.getAttribute(`${functionSymbol}-return`); 63 | if (returnValue) { 64 | const outputsTmp = event.outputs.filter((output) => { 65 | return output.name === returnValue.trim(); 66 | }); 67 | info.outputs = outputsTmp; 68 | } 69 | const outputType = div.getAttribute(`${functionSymbol}-output-type`); 70 | if (outputType) { 71 | info.outputType = outputType; 72 | } 73 | info.event = event; 74 | info.inputs = event.inputs; 75 | var args = info.args; 76 | for (let i = 0; i < event.inputs.length; i++) { 77 | const name = event.inputs[i].name.trim(); 78 | const value = div.getAttribute(`${functionSymbol}-args-${name}`); 79 | if (value) { 80 | args = { 81 | ...args, 82 | [name]: value 83 | } 84 | } 85 | } 86 | info.args = args; 87 | console.log("getDivInfoFromLoader info check", info); 88 | } 89 | else { 90 | alert(`Function not found in ABI ${info.name}`); 91 | } 92 | return info; 93 | } 94 | 95 | const getInputInfoFromDiv = (info) => { 96 | const querySymbol = `${functionSymbol}-${info.name}`; 97 | const functionArgDivs = [ 98 | ...document.querySelectorAll(`[${querySymbol}]`) 99 | ] 100 | var inputArgs = info.args; 101 | if (functionArgDivs && functionArgDivs.length) { 102 | functionArgDivs.forEach((functionArgDiv) => { 103 | const [name, value] = getFunctionArgsFromAttr(functionArgDiv, querySymbol); 104 | if (name && value) { 105 | if (name === "value-in-eth") { 106 | info = { 107 | ...info, 108 | price: value 109 | } 110 | } 111 | else { 112 | inputArgs = { 113 | ...inputArgs, 114 | [name]: value 115 | } 116 | } 117 | } 118 | }) 119 | } 120 | info = { 121 | ...info, 122 | inputArgs 123 | } 124 | return info; 125 | } 126 | 127 | export const checkDivs = async () => { 128 | console.log("checkDivs"); 129 | const functionDivs = [ 130 | ...document.querySelectorAll(`[${functionSymbol}]`) 131 | ] 132 | if (functionDivs) { 133 | console.log(functionDivs); 134 | functionDivs.forEach(async (functionDiv) => { 135 | // get information from div attribute 136 | functionDiv.href = "#" 137 | // get information from div attribute 138 | const infoFromAttr = getDivAttrInfo(functionDiv, functionSymbol); 139 | const divInfo = await getDivInfoFromLoader(functionDiv, infoFromAttr); 140 | if (divInfo.type === "view") { 141 | store.dispatch(appendReadFunction(divInfo)); 142 | } 143 | else { 144 | functionDiv.onclick = async () => { 145 | console.log("CLICK the button") 146 | const initialBtnText = functionDiv.textContent; 147 | setButtonText(functionDiv, "Loading...") 148 | const finalInfo = getInputInfoFromDiv(divInfo); 149 | console.log("finalInfo before open write modal", finalInfo); 150 | store.dispatch(openFunctionCallModal(finalInfo)); 151 | setButtonText(functionDiv, initialBtnText) 152 | } 153 | } 154 | }) 155 | } 156 | } 157 | 158 | export const updateDivText = (functionName, text) => { 159 | const functionDivs = [ 160 | ...document.querySelectorAll(`[${functionSymbol}=${functionName}]`) 161 | ] 162 | if (functionDivs) { 163 | // console.log(connectButtons); 164 | functionDivs.forEach((functionDiv) => { 165 | // get information from div attribute 166 | setButtonText(functionDiv, text) 167 | }) 168 | } 169 | } -------------------------------------------------------------------------------- /src/components/FunctionCallModal/FunctionCallView.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { ethers } from "ethers"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { 5 | usePrepareContractWrite, 6 | useContractWrite, 7 | useWaitForTransaction, 8 | } from 'wagmi' 9 | 10 | import Dialog from '@mui/material/Dialog'; 11 | import DialogContent from '@mui/material/DialogContent'; 12 | import DialogTitle from '@mui/material/DialogTitle'; 13 | import { closeFunctionCallModal } from "./FunctionCallSlice"; 14 | 15 | import "./FunctionCallView.css"; 16 | import { cardClasses, titleClass } from "../muiStyle"; 17 | import { AiOutlineCloseCircle } from "react-icons/ai"; 18 | import { useSnackbar } from "notistack"; 19 | import { CircularProgress, IconButton, Tooltip } from "@mui/material"; 20 | import { AiOutlineQuestionCircle } from "react-icons/ai"; 21 | import * as _ from "lodash"; 22 | import { SELF_WALLET_SYMBOL } from "../../constants"; 23 | import { formatWalletAddress } from "../../utils"; 24 | 25 | export const FunctionCallModal = () => { 26 | const dispatch = useDispatch(); 27 | const functionCallModalOpen = useSelector(state => state.functionCallModal.functionCallModalOpen); 28 | const functionCallInfo = useSelector(state => state.functionCallModal.functionCallInfo); 29 | const walletAddress = useSelector(state => state.userModal.walletAddress); 30 | const chain = useSelector(state => state.userModal.chainId); 31 | const [args, setArgs] = React.useState({}); 32 | const [argsValues, setArgsValues] = React.useState([]); 33 | const [price, setPrice] = React.useState(); 34 | const { enqueueSnackbar } = useSnackbar(); 35 | 36 | const isPayable = functionCallInfo.type === "payable"; 37 | 38 | const { 39 | config, 40 | error: prepareError, 41 | isError: isPrepareError, 42 | } = usePrepareContractWrite({ 43 | address: functionCallInfo.contractAddress, 44 | abi: [ 45 | functionCallInfo.event, 46 | ], 47 | functionName: functionCallInfo.name, 48 | args: argsValues, 49 | chainId: chain?.id, 50 | overrides: { 51 | value: Number(price) ? ethers.utils.parseEther(price) : "0", 52 | }, 53 | enabled: ((argsValues.length > 0) && (chain)), 54 | }); 55 | const { data, error, isError, write } = useContractWrite(config); 56 | 57 | const { isLoading, isSuccess } = useWaitForTransaction({ 58 | hash: data?.hash, 59 | }) 60 | 61 | const handleSetArgs = (name, value) => { 62 | setArgs({ ...args, [name]: value }) 63 | } 64 | 65 | const handleClose = () => { 66 | dispatch(closeFunctionCallModal()); 67 | setArgs({}); 68 | setArgsValues([]); 69 | setPrice(); 70 | }; 71 | 72 | const reportError = (e) => { 73 | const msg = e.message; 74 | const regex = /reason="(.*?)",/; 75 | const found = msg.match(regex); 76 | if (found) { 77 | enqueueSnackbar(found[1], { 78 | variant: "error", 79 | }); 80 | } 81 | else { 82 | enqueueSnackbar(msg.split("(")[0], { 83 | variant: "error", 84 | }); 85 | } 86 | } 87 | 88 | const handleSubmit = () => { 89 | console.log("submit", config, args, argsValues); 90 | if (isPrepareError) { 91 | reportError(prepareError); 92 | return 93 | } 94 | write?.(); 95 | } 96 | 97 | const handleInputDisabled = (input) => { 98 | if (functionCallInfo.inputArgs) { 99 | if (input.name in functionCallInfo.inputArgs) { 100 | return true 101 | } 102 | else { 103 | return false 104 | } 105 | } 106 | else { 107 | return false 108 | } 109 | } 110 | 111 | useEffect(() => { 112 | var argsValuesTmp = []; 113 | if (functionCallInfo.inputs) { 114 | for (var input of functionCallInfo.inputs) { 115 | if (args[input.name]) { 116 | if (input.type.includes("[]")) { 117 | try { 118 | argsValuesTmp.push(JSON.parse(args[input.name])); 119 | } 120 | catch { 121 | argsValuesTmp.push(args[input.name]); 122 | } 123 | } 124 | else { 125 | argsValuesTmp.push(args[input.name]); 126 | } 127 | 128 | } 129 | } 130 | } 131 | setArgsValues(argsValuesTmp); 132 | }, [args, functionCallInfo.inputs]) 133 | 134 | useEffect(() => { 135 | if (functionCallInfo.price && functionCallModalOpen) { 136 | setPrice(functionCallInfo.price); 137 | } 138 | }, [functionCallInfo.price, functionCallModalOpen]) 139 | 140 | useEffect(() => { 141 | if (isError) { 142 | reportError(error); 143 | } 144 | }, [error, isError, enqueueSnackbar]) 145 | 146 | useEffect(() => { 147 | if (functionCallInfo.inputs && functionCallInfo.inputArgs && functionCallModalOpen) { 148 | var argsTmp = {}; 149 | for (var input of functionCallInfo.inputs) { 150 | if (input.name in functionCallInfo.inputArgs) { 151 | if (functionCallInfo.inputArgs[input.name] === SELF_WALLET_SYMBOL) { 152 | argsTmp[input.name] = walletAddress; 153 | continue; 154 | } 155 | argsTmp[input.name] = functionCallInfo.inputArgs[input.name]; 156 | } 157 | } 158 | setArgs(argsTmp); 159 | } 160 | }, [functionCallInfo.inputs, functionCallInfo.inputArgs, functionCallModalOpen]) 161 | 162 | return ( 163 | <> 164 | 170 | 171 |
172 |

173 | Review my transaction 174 |

175 | 178 |
179 |
180 | 181 |
182 |
183 |
184 | 185 | {`${formatWalletAddress(functionCallInfo.contractAddress)}:`} 186 | 187 | {functionCallInfo.name} 188 | 189 | 192 | 193 |
194 | { 195 | functionCallInfo.inputs && functionCallInfo.inputs.map((input, index) => { 196 | return ( 197 |
198 | 199 | handleSetArgs(input.name, e.target.value)} 203 | placeholder={input.type} 204 | value={args[input.name] || ""} 205 | /> 206 |
207 | ) 208 | }) 209 | } 210 | {isPayable && 211 |
212 | 213 | setPrice(e.target.value)} 216 | placeholder="0.05" 217 | value={price || ""} 218 | /> 219 | 220 |
221 | } 222 |
223 | 236 |
237 | {isSuccess && ( 238 |
239 | {"Transcation success! View on "} 240 | {_.get(chain, ["blockExplorers", "default", "name"])} 241 |
242 | )} 243 | 244 |
245 |
246 | 247 | ) 248 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 21 | 27 | 28 | 29 | 30 | 31 |
32 |
33 | 34 | Logo 35 | 36 | 37 |

Catchon WEB3 CDN

38 | 39 |

40 | An awesome no code solution for web3 development! 41 |
42 | Explore the docs » 43 |
44 |
45 | View Demo 46 | · 47 | Report Bug 48 | · 49 | Request Feature 50 |

51 |
52 | 53 | 54 | 55 | 56 |
57 | Table of Contents 58 |
    59 |
  1. 60 | About The Project 61 |
  2. 62 |
  3. 63 | Getting Started 64 | 68 |
  4. 69 |
  5. Usage
  6. 70 |
  7. Roadmap
  8. 71 |
  9. Contributing
  10. 72 |
  11. License
  12. 73 |
  13. Contact
  14. 74 |
  15. Acknowledgments
  16. 75 |
76 |
77 | 78 | 79 | 80 | 81 | ## About The Project 82 | 83 | 84 | 85 | "What is the easiest way to add web3 capabilities, such as connecting a wallet and interacting with contracts, to my website?" 86 | 87 | Catchon CDN is a useful tool for: 88 | 89 | * Smart contract developers who don't want to build a website from scratch 90 | * UI-based web development tool users, such as those using Webflow, WIX, or WordPress, who want to add web3 functionality to their websites 91 | * Those who want to easily call a smart contract function from their website without having to deal with complicated web3 settings. 92 | 93 | Catchon CDN is a web3 tool that enables low-code development for frontend engineers, smart contract engineers, and designers. It simplifies the process of interacting with blockchain technologies, allowing users to easily connect with the blockchain without needing to understand the detailed implementation of wallet and contract interactions. With a quick setup, Catchon CDN enables users to easily communicate with the blockchain. 94 | 95 | 96 | 97 | 98 |

(back to top)

99 | 100 | 101 | 102 | ### Live example 103 | 104 | * [![React][React.js]][React-url] 105 | * [Webflow][Webflow-url] 106 | * [Static html][Static-url] 107 | 108 |

(back to top)

109 | 110 | 111 | 112 | 113 | ## Getting Started 114 | 115 | This is an example of how you may give instructions on setting up your project locally. 116 | To get a local copy up and running follow these simple example steps. 117 | 118 | ### Prerequisites 119 | 120 | #### Get your website ready. 121 | 122 | Your website can be developed using any of the following methods: 123 | 1. Coding (e.g. static HTML, React) PS: we only support React 17, react 18 will be supported in later version 124 | 2. No-code visual website builder (e.g. Webflow, Wix, SquareSpace, WordPress) 125 | 126 | 127 | #### Get the contract ready 128 | 1. Smart Contract address (get the implemetation address if it's a proxy smart contract) 129 | 2. Smart Contract abi 130 | 3. Chain ID of the smart contract 131 | 132 | ### Intergration 133 | 134 | Copy and paste the following script into your website html 135 | 136 | ``` 137 | 142 | 143 | 144 | ``` 145 | 146 | For example, the website wants to interact with smart contract 0xCA127e07Ce57c78eF0C739F268A437e76c66e0F1. It's not a proxy contract. And it's on etherum goerli testnet. 147 | 148 | PS: In this example, we get ABI from an API call. But not all abi can be found from API call. Add ABI=[... put your own abi here] to make sure script work 149 | ``` 150 | 154 | 155 | 156 | ``` 157 | 158 | If you want to have some membership page, you can use SIGN_PAGE_PATH. 159 | For example, you have a page "/blog", it shuold be a membership only page. Only NFT holder have access to view it. You can add following section. 160 | It required the wallet has NFT of 0xCA127e07Ce57c78eF0C739F268A437e76c66e0F1 to view the page. Help people create their own valuable informations. 161 | 162 | ``` 163 |