diff --git a/.npmrc 2 b/.npmrc 2 new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc 2 @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/.nvmrc 2 b/.nvmrc 2 new file mode 100644 index 0000000..d4c43fa --- /dev/null +++ b/.nvmrc 2 @@ -0,0 +1 @@ +12.14.1 \ No newline at end of file diff --git a/package.json b/package.json index 2b5e380..b19aa34 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "react-dev-utils": "10.0.0", "react-device-detect": "^1.11.14", "react-dom": "^16.12.0", + "react-modal": "^3.11.2", "react-toast-notifications": "^2.4.0", "react-tooltip": "^3.11.6", "resolve": "1.12.2", @@ -83,6 +84,7 @@ "ts-pnp": "1.1.5", "typescript": "^3.7.5", "url-loader": "2.3.0", + "userbase-js": "^1.4.1", "webpack": "4.41.2", "webpack-dev-server": "3.9.0", "webpack-manifest-plugin": "2.2.0", diff --git a/public/index-ERC20-Mainnet.html b/public/index-ERC20-Mainnet.html index 1e39a9a..461a47e 100644 --- a/public/index-ERC20-Mainnet.html +++ b/public/index-ERC20-Mainnet.html @@ -131,6 +131,22 @@

Wrapped Eth!

+ + + + + + + diff --git a/public/index-stanard.html b/public/index-stanard.html index 8cadef0..4a1da82 100644 --- a/public/index-stanard.html +++ b/public/index-stanard.html @@ -838,7 +838,7 @@

src="" type="text/javascript" id="dh-apiKey" - data-api="1581368358384x830713010847744000" + data-api="1592342853928x247583279535882240" > diff --git a/public/index-webflow-token-builder.html b/public/index-webflow-token-builder.html new file mode 100644 index 0000000..5ff173b --- /dev/null +++ b/public/index-webflow-token-builder.html @@ -0,0 +1,74 @@ + + + + + + + DappHero: Components + + + + +
+
+
+

Easy Token Builder

+
+
+
+
Transaction Hash
+
+
+
Success! Your new ERC20 Token address is:
+
0x0...
Check on Etherscan
+
+
+
+
+
Thank you! Your submission has been received!
+
+
+
Oops! Something went wrong while submitting the form.
+
+
+
+
+
+
+
Interested in learning how to create your own Decentralized Application on Ethereum?
Join our telegram or sign up!
+
+
+ + + + + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html index 8cadef0..6ea0a39 100644 --- a/public/index.html +++ b/public/index.html @@ -1,845 +1,145 @@ - - - + + Hello! - DappHero: Components - - - - - -
- - -

Network Features

- - -
Network: Get ID
-

NetworkID should be here

- - - - - -
Network: Get Name
-
Network ID should be here
- - - - - - - -
Network: Get Provider
-
Network provider should be here
- - - - - - - -
Network: Transfer - (unfinished)
- - - -
- - - - - - - -
Network: Get Provider
-
Network provider should be here
- - - - - - -
Network: Enable Button (unfinished)
- - - - - -

User Features

- - -
User: Account Adddress
-

User Address Should be Here

- - -
User: Account Adddress Short
-

User Address Should be Here

- - -
User: Get Balance
-

User Address Should be Here

- - -

3Box Features

- - -
3Box: UserName
-

3box name

- -
3Box: Website
-

3box website

- - - - - - - - - - - - - - - - - - -

Custom Contract

- -
Method: "hello"
-
Payable: false - StateMutability: "view"
-
-

Inputs

- - -

Outputs

- -
- -
- - -
- *Return value: -* -
-
- -
- - -
Method: "sendEthWithArgs"
-
Payable: true - StateMutability: "payable"
-
No ETH Sent
-
-

Inputs

- - -

Outputs

- -
- -
- - - - -
- *Return value: -* -
-
- -
- - - - -
Method: "sendEthWithArgs"
-
Payable: true - StateMutability: "payable"
-
ETH Value: HardCoded: .15ETH
-
-

Inputs

- - -

Outputs

- -
- -
- - - - -
- *Return value: -* -
-
- -
- - - - - -
Method: "sendEthWithArgs"
-
Payable: true - StateMutability: "payable"
-
ETH Value: User Input
-
-

Inputs

- - -

Outputs

- -
- -
This is the method
- - - - - - - -
- *Return value: -* -
- - -
- - - - - - - - -
Method: "viewMultipleArgsMultipleReturn"
-
Payable: false - StateMutability: "view"
-
-

Inputs

- - -

Outputs

- -
- -
- - - - - -
- *Return value: -* -
-
- - - - - -
Method: "sendVariableEthNoArgs"
-
Payable: true - StateMutability: "payable"
-
No eth is sent here.
-
-

Inputs

- - -

Outputs

- - > - -
- - -
- *Return value: -* -
-
- -
- - -
Method: "viewMultipleArgsSingleReturn"
-
Payable: false - StateMutability: "view"
-
-

Inputs

-
    -
  • fromAddress - address
  • -
  • amount - uint256
  • -
- -

Outputs

-
    -
  • longInteger - uint256 : (Should be: 8989898989)
  • -
-
- -
- - - - - - -
- *Return value: -* -
-
- -
- - -
Method: "viewNoArgsMultipleReturn"
-
Payable: false - StateMutability: "view"
-
-

Inputs

-
    -
  • none
  • -
- -

Outputs

-
    -
  • importantNumber - uint256 (should be zero)
  • -
  • hello - bytes32
  • -
-
- -
- --> - - All the args are returned in one, and in their respective output -
- A greeting here in bytes32 -
-
- A greeting here in bytes32 -
- -
Important Number here
- -
- *Return value: -* -
-
- *Return value: -* -
-
- -
- - - - - - - - - - - - - - - - - - - - -
ERC721 - Single NFT from an Owner
-
With Owner Account address and Token id
- -
-
-
- -
-

-
- $THIS_TokenID -
-
+ + -
+ + -
- - - -
ERC721 - List of specific NFTs from an Owner
-
With Owner Account address and Token ids
- -
+ + + +

Preview of DappHero Collectible Functionality for Alpha V2.

+ +

+ This page is powered via DappHero. We're using the nightly build of the + DappHero Script.
+ To build your own NFT project, copy this Glitch and replace it with your + own Script Tag from DappHero.io. +
Documentation can be found at + docs.dapphero.io.
+

+ + +
- -
- - - -
ERC721 - List of NFTs from an Owner
-
With Owner Account address
- -
+
- -
+ Next + + - -
ERC721 - List of NFTs from an Owner with Pagination
-
With Owner Account address
+ + +
- - - -
- -

-
-
- -
- - -
ERC721 - Single NFT from an Contract
-
With Asset contract address
- -
-
- -

-
-
- -
- - -
ERC721 - List of NFTs from an Contract
-
With Asset contract address
- -
-
- + + +
+

-
-
+ -
ERC721 - List of NFTs from an Contract
-
With Asset contract address
- -
-
- -

+ This is from the example on the Documentation, it was nonsensical and I have replaced it below. +
+ + +

+ +

+ +
--> + +
+ Token Id: +

+ Owner Address: +

+
+
-
- - -
-
-
- - + +
+ + + + + - diff --git a/src/Activator.tsx b/src/Activator.tsx index 160f3cf..8873dbd 100644 --- a/src/Activator.tsx +++ b/src/Activator.tsx @@ -26,6 +26,7 @@ type ActivatorProps = { timeStamp: any; contractElements: any; domElementsFilteredForContracts: any; + db: any; } export const Activator: React.FC = ({ @@ -37,6 +38,7 @@ export const Activator: React.FC = ({ supportedNetworks, contractElements, domElementsFilteredForContracts, + db, }: ActivatorProps) => { // Ethereum @@ -56,7 +58,7 @@ export const Activator: React.FC = ({ useEffect(() => { const dappHero = { debug: false, - enabled: true, + enabled: ethereum.isEnabled, highlightEnabled: false, domElements, configuration, @@ -66,6 +68,7 @@ export const Activator: React.FC = ({ retriggerEngine, projectId: consts.global.apiKey, provider: ethereum, + db, toggleHighlight(): void { dappHero.highlightEnabled = !dappHero.highlightEnabled highlightDomElements(dappHero.highlightEnabled, domElements) @@ -75,13 +78,20 @@ export const Activator: React.FC = ({ listenToTransactionStatusChange: (cb): void => listenToEvent(EVENT_NAMES.contract.statusChange, cb), listenToContractInvokeTriggerChange: (cb): void => listenToEvent(EVENT_NAMES.contract.invokeTrigger, cb), listenToSmartContractBlockchainEvent: (cb): void => listenToEvent(EVENT_NAMES.contract.contractEvent, cb), + listenToUserAddressChange: (cb): void => listenToEvent(EVENT_NAMES.user.addressStatusChange, cb), + listenToUserBalanceChange: (cb): void => listenToEvent(EVENT_NAMES.user.balanceStatusChange, cb), + listenToNFTLoadSingleToken: (cb): void => listenToEvent(EVENT_NAMES.nft.loadSingleToken, cb), + listenToNFTLoadMultipleToken: (cb): void => listenToEvent(EVENT_NAMES.nft.loadMultipleTokens, cb), + listenToNFTLoadAllToken: (cb): void => listenToEvent(EVENT_NAMES.nft.loadAllTokens, cb), + listenTo3BoxProfile: (cb): void => listenToEvent(EVENT_NAMES.threeBox.loadProfile, cb), + listenToEthTransfer: (cb): void => listenToEvent(EVENT_NAMES.ethTransfer.sendEther, cb), } Object.assign(window, { dappHero }) // Dispatch the event. const event = new CustomEvent('dappHeroConfigLoaded', { detail: dappHero }) document.dispatchEvent(event) - }, [ AppReady ]) + }, [ AppReady, ethereum.isEnabled ]) if (!AppReady || !domElementsFilteredForContracts) return null return ( @@ -90,6 +100,7 @@ export const Activator: React.FC = ({ && domElementsFilteredForContracts.map((domElement) => ( { // react hooks @@ -23,6 +26,18 @@ export const ProvidersWrapper: React.FC = () => { const ethereum = useWeb3Provider(consts.global.POLLING_INTERVAL) // This sets refresh speed of the whole app + // load serBase + const [ db, setDB ] = useState(null) + useEffect(() => { + + const makeDBConnection = async () => { + setDB(await new DB({ appId: process.env.REACT_APP_USERBASE_APP_ID, projectId: consts.global.apiKey })) + } + if (consts.global.apiKey) { + makeDBConnection() + } + }, []) + // load contracts effects only if not paused useEffect(() => { const getConfig = async () => { @@ -82,6 +97,7 @@ export const ProvidersWrapper: React.FC = () => { + {/* */} { supportedNetworks={supportedNetworks} domElementsFilteredForContracts={smartcontractElements.domElementsFilteredForContracts} contractElements={smartcontractElements.contractElements} + db={db} /> diff --git a/src/api/dappHero.ts b/src/api/dappHero.ts index 08a8fa5..29f1ab9 100644 --- a/src/api/dappHero.ts +++ b/src/api/dappHero.ts @@ -65,15 +65,15 @@ export const getContractsByProjectKeyDappHero = async (projectId) => { } } -const compareResponses = async (originalOutput, projectId) => { - const compareOutput = await getContractsByProjectKeyV2(projectId) - const isEqual = !!(JSON.stringify(originalOutput) === JSON.stringify(compareOutput)) - // logger.info(`Cache Check isEqual: ${isEqual.toString()}`) - if (!isEqual) { - logger.info('', compareOutput) - logger.info('', originalOutput) - } -} +// const compareResponses = async (originalOutput, projectId) => { +// const compareOutput = await getContractsByProjectKeyV2(projectId) +// const isEqual = !!(JSON.stringify(originalOutput) === JSON.stringify(compareOutput)) +// // logger.info(`Cache Check isEqual: ${isEqual.toString()}`) +// if (!isEqual) { +// logger.info('', compareOutput) +// logger.info('', originalOutput) +// } +// } export const getContractsByProjectKeyBubble = async (projectId) => { logger.log(`projectId: ${projectId}`) @@ -114,14 +114,15 @@ export const getContractsByProjectKey = async (projectId) => { // first try our cache server try { + return (await getContractsByProjectKeyBubble(projectId)) return (await getContractsByProjectKeyDappHero(projectId)) } catch (error) { // If the error fails, then try bubble - logger.log('(DH-CORE) Error in Global Cache Network, re-trying...', error) + logger.error('(DH-CORE) Error in Global Cache Network, re-trying...', error) try { return (await getContractsByProjectKeyBubble(projectId) ) } catch (error) { - logger.log('(DH-CORE) Failure in project cache backend', error) + logger.error('(DH-CORE) Failure in project cache backend', error) } } } diff --git a/src/api/database.ts b/src/api/database.ts new file mode 100644 index 0000000..de66f4c --- /dev/null +++ b/src/api/database.ts @@ -0,0 +1,43 @@ +import userbase from 'userbase-js' + +export class DB { + projectId: string + + constructor({ appId, projectId }) { + this.projectId = projectId + userbase.init({ appId }) + } + + async signUp(details): Promise { + return userbase.signUp(details) + } + + async signIn(details): Promise { + return userbase.signIn({ ...details }) + } + + async signOut(): Promise { + return userbase.signOut() + } + + async forgotPassword(username): Promise { + return userbase.forgotPassword(username) + } + + async openDatabase(changeFunc): Promise { + return userbase.openDatabase({ databaseName: this.projectId, changeHandler: changeFunc }) + } + + async insertItem(item): Promise { + return userbase.insertItem({ databaseName: this.projectId, item }) + } + + async updateItem(item, itemId): Promise { + return userbase.updateItem({ databaseName: this.projectId, item, itemId }) + } + + async deleteItem(itemId): Promise { + return userbase.deleteItem({ databaseName: this.projectId, itemId }) + } + +} diff --git a/src/components/userBase/UserBase.tsx b/src/components/userBase/UserBase.tsx new file mode 100644 index 0000000..c2cac4d --- /dev/null +++ b/src/components/userBase/UserBase.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react' +import Modal from 'react-modal' + +// Make sure to bind modal to your appElement (http://reactcommunity.org/react-modal/accessibility/) +// Modal.setAppElement('#yourAppElement') + +const customStyles = { + content: { + top: '50%', + left: '50%', + right: 'auto', + bottom: 'auto', + marginRight: '-50%', + transform: 'translate(-50%, -50%)', + }, +} + +export const UserBase = ({ db }) => { + let subtitle + + const [ modalIsOpen, setIsOpen ] = useState(false) + + const openModal = () => { + setIsOpen(true) + } + + const closeModal = () => { + setIsOpen(false) + } + + // useEffect(() => { + // if (window.dappHero) { + // // window.dappHero.db = db + // window.dappHero.openModal = openModal + // window.dappHero.closeModal = closeModal + // } + // }, [ window.dappHero, db ]) + + return ( +
+ + + + {/*

(subtitle = _subtitle)}>Hello

*/} + +
I am a modal
+
+ + + +
+
+
+ ) +} + diff --git a/src/consts/global.ts b/src/consts/global.ts index 07fafcf..31c8cc8 100644 --- a/src/consts/global.ts +++ b/src/consts/global.ts @@ -9,6 +9,7 @@ export const ethNetworkName = { 77: 'Sokol', 99: 'Core', 100: 'Xdai', + 80001: 'maticMumbaiTestnet', } const apiKeyElement = document.getElementById('dh-apiKey') export const apiKey = apiKeyElement.getAttribute('data-api') diff --git a/src/consts/provider.ts b/src/consts/provider.ts index 963cd10..17dc025 100644 --- a/src/consts/provider.ts +++ b/src/consts/provider.ts @@ -30,9 +30,13 @@ export const readProviders = { http: 'https://eth-goerli.alchemyapi.io/v2/mo2KeoBlZY6CAyc2o1i4BcBNioVN_wpJ', ws: 'wss://eth-goerli.ws.alchemyapi.io/v2/mo2KeoBlZY6CAyc2o1i4BcBNioVN_wpJ', }, - xDai: { + xdai: { http: 'https://dai.poa.network', ws: 'wss://dai-trace-ws.blockscout.com/ws', }, + maticmumbaitestnet: { + http: 'https://rpc-mumbai.matic.today', + ws: '', + }, } diff --git a/src/protocol/ethereum/customContract/Manager.tsx b/src/protocol/ethereum/customContract/Manager.tsx index b3a044f..edaf30c 100644 --- a/src/protocol/ethereum/customContract/Manager.tsx +++ b/src/protocol/ethereum/customContract/Manager.tsx @@ -22,19 +22,26 @@ export const Manager: React.FunctionComponent = ({ customContractE // TODO: [DEV-318] Create a function to add a new contract name and add a new configuration - for (const contractName of uniqueContractNames) { - - const newDomElements = getDomElements(configuration) - const contractElements = newDomElements.filter((element) => element.feature === 'customContract') - const methodsByContractAsElements = contractElements.filter((element) => element.contract.contractName === contractName) - const contract = configuration.contracts.filter((thisContract) => (thisContract.contractName === contractName))[0] - - return ( - - ) - } + // for (const contractName of uniqueContractNames) + return ( + <> + {Array.from(uniqueContractNames).map((contractName) => { + const newDomElements = getDomElements(configuration) + const contractElements = newDomElements.filter((element) => element.feature === 'customContract') + const methodsByContractAsElements = contractElements.filter((element) => element.contract.contractName === contractName) + const contract = configuration.contracts.filter((thisContract) => (thisContract.contractName === contractName))[0] + const contractNetworkId = configuration.contracts.filter((thisContract) => (thisContract.contractName === contractName))[0].networkId + + return ( + + ) + })} + + ) } diff --git a/src/protocol/ethereum/customContract/Reducer.tsx b/src/protocol/ethereum/customContract/Reducer.tsx index 54b0bcb..1913845 100644 --- a/src/protocol/ethereum/customContract/Reducer.tsx +++ b/src/protocol/ethereum/customContract/Reducer.tsx @@ -27,11 +27,13 @@ export type ReducerProps = { readEnabled: any; readChainId: any; writeEnabled: any; + writeChainId: number; timestamp: number; contractAbi: any; + contractNetworkId: number; } // Reducer Component -export const Reducer: React.FunctionComponent = ({ info, readContract, writeContract, readEnabled, readChainId, writeEnabled, timestamp, contractAbi }) => { +export const Reducer: React.FunctionComponent = ({ info, readContract, writeChainId, writeContract, readEnabled, readChainId, writeEnabled, timestamp, contractAbi, contractNetworkId }) => { const initialEffectiveConnectionType = '4g' const { effectiveConnectionType } = useNetworkStatus(initialEffectiveConnectionType) @@ -182,6 +184,8 @@ export const Reducer: React.FunctionComponent = ({ info, readContr // Send the user information that their write provider is not connected // We need to also check if the write provider is on the right network of the transaction + // console.log('writeEnabled', writeEnabled) + // console.log('isTransaction', isTransaction) if (writeEnabled && isTransaction && !doParamsContainUnformatedConstant) { emitToEvent( @@ -189,6 +193,13 @@ export const Reducer: React.FunctionComponent = ({ info, readContr { value: null, step: 'Triggering write transaction.', status: EVENT_STATUS.pending, methodNameKey }, ) + if (contractNetworkId !== writeChainId) { + const msg = `Please change your network to ${consts.global.ethNetworkName[contractNetworkId]} to use the deployed contract.` + addToast(msg, { appearance: 'info', autoDismiss: true, autoDismissTimeout: consts.global.REACT_TOAST_AUTODISMISS_INTERVAL }) + const msg2 = `You are on the wrong network. Confirming this transaction can be dangerous and lead to permanent loss of funds.` + addToast(msg2, { appearance: 'error', autoDismiss: true, autoDismissTimeout: consts.global.REACT_TOAST_AUTODISMISS_INTERVAL }) + } + try { const methodHash = await sendTx({ diff --git a/src/protocol/ethereum/customContract/Router.tsx b/src/protocol/ethereum/customContract/Router.tsx index ed83c7c..3730e78 100644 --- a/src/protocol/ethereum/customContract/Router.tsx +++ b/src/protocol/ethereum/customContract/Router.tsx @@ -4,6 +4,7 @@ import * as contexts from 'contexts' import * as consts from 'consts' import { ethers, logger } from 'ethers' import { useWeb3Provider } from 'hooks' +import { useToasts } from 'react-toast-notifications' import { EmitterContext } from 'providers/EmitterProvider/context' import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' @@ -22,10 +23,10 @@ type RouterProps = { listOfContractMethods: ListOfContractMethods; contract: Contract; timestamp: number; + contractNetworkId: number; } -export const Router: React.FunctionComponent = ({ listOfContractMethods, contract, timestamp }) => { - +export const Router: React.FunctionComponent = ({ listOfContractMethods, contract, timestamp, contractNetworkId }) => { const ethereum = useContext(contexts.EthereumContext) const { signer, isEnabled: writeEnabled, chainId: writeChainId, provider } = ethereum const { contractAddress, contractAbi, networkId } = contract @@ -34,6 +35,9 @@ export const Router: React.FunctionComponent = ({ listOfContractMet const { actions: { emitToEvent } } = useContext(EmitterContext) + // Toast Notifications + const { addToast } = useToasts() + // Get the Network for our Project const contractNetwork = consts.global.ethNetworkName[networkId].toLowerCase() @@ -47,16 +51,16 @@ export const Router: React.FunctionComponent = ({ listOfContractMet let readContractInstance = null // Make the contract instance from either the local provider or remote provider if (provider && contractNetwork === provider?._network?.name) { - console.log('Using local provider for contract reads.') readContractInstance = new ethers.Contract(contractAddress, contractAbi, provider) } else { - console.log('Using DH-backend provider for contract reads.') readContractInstance = new ethers.Contract(contractAddress, contractAbi, stableReadProvider) } - readContractInstance.on('*', (data) => emitToEvent( - EVENT_NAMES.contract.contractEvent, - { value: data, step: 'Contract has emitted a Contract Event', status: EVENT_STATUS.resolved, methodNameKey: null }, - )) + readContractInstance.on('*', (data) => { + emitToEvent( + EVENT_NAMES.contract.contractEvent, + { value: data, step: 'Contract has emitted a Contract Event', status: EVENT_STATUS.resolved, methodNameKey: null }, + ) + }) setReadContract(readContractInstance) } makeReadContract() @@ -69,7 +73,13 @@ export const Router: React.FunctionComponent = ({ listOfContractMet } // TODO: Check if we are on the same chainID as the Contract + // if (writeEnabled && writeChainId === contractNetworkId) makeWriteContract() + if (writeEnabled) makeWriteContract() + // if (writeChainId !== contractNetworkId) { + // const msg = `Please change your network to ${consts.global.ethNetworkName[contractNetworkId]} to use the deployed contract.` + // addToast(msg, { appearance: 'info', autoDismiss: true, autoDismissTimeout: consts.global.REACT_TOAST_AUTODISMISS_INTERVAL }) + // } // Else pop up information that we are not on the right network }, [ writeChainId, signer, writeEnabled ]) @@ -89,12 +99,14 @@ export const Router: React.FunctionComponent = ({ listOfContractMet readContract={readContract} readChainId={networkId} writeContract={writeContract} + writeChainId={writeChainId} readEnabled={Boolean(readContract)} writeEnabled={writeEnabled} info={contractMethodElement} key={contractMethodElement.id} timestamp={timestamp} contractAbi={contractAbi} + contractNetworkId={contractNetworkId} /> ))} diff --git a/src/protocol/ethereum/featureReducer.tsx b/src/protocol/ethereum/featureReducer.tsx index 71b0015..16d5131 100644 --- a/src/protocol/ethereum/featureReducer.tsx +++ b/src/protocol/ethereum/featureReducer.tsx @@ -16,6 +16,7 @@ export const FeatureReducer: React.FunctionComponent = ({ element, configuration, info, + domElements, customContractElements, retriggerEngine, }: FeatureReducerProps) => { @@ -64,7 +65,7 @@ export const FeatureReducer: React.FunctionComponent = ({ } case 'network': { - return + return } case 'threebox': { diff --git a/src/protocol/ethereum/network/EthEnable.tsx b/src/protocol/ethereum/network/EthEnable.tsx index 498a752..5c0078f 100644 --- a/src/protocol/ethereum/network/EthEnable.tsx +++ b/src/protocol/ethereum/network/EthEnable.tsx @@ -6,7 +6,7 @@ import * as consts from 'consts' import ReactTooltip from 'react-tooltip' interface EthEnableProps { - element: HTMLElement; + element: any; } /** * This function attaches a click handler to any element that a user wants to be responsbile for @@ -18,6 +18,9 @@ export const EthEnable: FunctionComponent = ({ element }) => { () => element.innerText , [], ) + + const memoizedInputValueForWebflow = useMemo(() => element.value, []) + const ethereum = useContext(contexts.EthereumContext) const { addToast } = useToasts() @@ -30,19 +33,26 @@ export const EthEnable: FunctionComponent = ({ element }) => { const [ buttonStatus, setButtonStatus ] = useState(element.innerText || 'Enable MetaMask') + const doEnable = (event) => { + event.preventDefault() + if (enable) { enable() } else { noWeb3Provider() } + + } useEffect(() => { if (isEnabled) { setButtonStatus('Connected') + element.value = 'Connected' } else { setButtonStatus(memoizedValue) + element.value = memoizedValue || memoizedInputValueForWebflow } }, [ isEnabled ]) useEffect(() => { try { - element.addEventListener('click', enable || noWeb3Provider, true) + element.addEventListener('click', doEnable, true) - return (() => element.removeEventListener('click', enable || noWeb3Provider, true)) + return (() => element.removeEventListener('click', doEnable, true)) } catch (e) { logger.log(e) } diff --git a/src/protocol/ethereum/network/EthTransfer.tsx b/src/protocol/ethereum/network/EthTransfer.tsx index d7f4430..872b575 100644 --- a/src/protocol/ethereum/network/EthTransfer.tsx +++ b/src/protocol/ethereum/network/EthTransfer.tsx @@ -4,6 +4,8 @@ import { logger } from 'logger/customLogger' import * as utils from 'utils' import * as contexts from 'contexts' import { ethers } from 'ethers' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' const apiKey = process.env.REACT_APP_BLOCKNATIVE_API interface EthTransferProps { @@ -17,6 +19,8 @@ interface EthTransferProps { export const EthTransfer: FunctionComponent = ({ element, amountObj, addressObj, outputObj, info }) => { const ethereum = useContext(contexts.EthereumContext) + const { actions: { emitToEvent } } = useContext(EmitterContext) + const { signer, provider, isEnabled } = ethereum useEffect(() => { @@ -43,20 +47,42 @@ export const EthTransfer: FunctionComponent = ({ element, amou const params = [ { from, to: addressObj.element.value, - value: ethers.utils.bigNumberify(convertedUnits).toHexString(), + // value: ethers.utils.bigNumberify(convertedUnits).toHexString(), + value: ethers.BigNumber.from(convertedUnits).toHexString(), // value: utils.convertUnits(inputUnits, 'wei', amountObj.element.value), } ] if (from && isEnabled) { // We will only attempt this if we actually got our address from the signer ourslves. + emitToEvent( + EVENT_NAMES.ethTransfer.sendEther, + { value: params, step: 'Send Ether', status: EVENT_STATUS.pending }, + ) provider.send('eth_sendTransaction', params) - .then(notify.hash) - .catch((err) => logger.info('There was an error sending ether with metaMask', err)) + .then((hash) => { + emitToEvent( + EVENT_NAMES.ethTransfer.sendEther, + { value: params, step: 'Send Ether Params', status: EVENT_STATUS.pending }, + ) + notify.hash(hash) + }) + .catch((err) => { + emitToEvent( + EVENT_NAMES.ethTransfer.sendEther, + { value: err, step: 'Send Ether Error', status: EVENT_STATUS.rejected }, + ) + + logger.info('There was an error sending ether with metaMask', err) + }) .finally(() => { amountObj.element.value = '' addressObj.element.value = '' }) } } catch (err) { + emitToEvent( + EVENT_NAMES.ethTransfer.sendEther, + { value: err, step: 'Send Ether Error', status: EVENT_STATUS.rejected }, + ) logger.warn('There was an error transfering ether', err) } } diff --git a/src/protocol/ethereum/network/Reducer.tsx b/src/protocol/ethereum/network/Reducer.tsx index 48be36f..3582d0d 100644 --- a/src/protocol/ethereum/network/Reducer.tsx +++ b/src/protocol/ethereum/network/Reducer.tsx @@ -7,10 +7,16 @@ import { EthEnable } from './EthEnable' import { EthNetworkInfo } from './EthNetworkInfo' import { EthTransfer } from './EthTransfer' -export const Reducer = ({ element, info }) => { - const domElements = hooks.useDomElements() +export type ReducerProps = { + element: HTMLElement; + info?: any; + domElements?: any; +} + +export const Reducer: React.FunctionComponent = ({ element, info, domElements }) => { + // const domElements = hooks.useDomElements() const ethereum = useContext(contexts.EthereumContext) - const { chainId, networkName, providerType, isEnabled } = ethereum + const { chainId, networkName, providerType } = ethereum switch (info?.properties[0]?.key) { case ('enable'): { // TODO: Drake- we need to settle on if we are going to use this style or not so we can be consistent diff --git a/src/protocol/ethereum/nft/useGetTokensForContractAddress.tsx b/src/protocol/ethereum/nft/useGetTokensForContractAddress.tsx index 2eb8680..39d7477 100644 --- a/src/protocol/ethereum/nft/useGetTokensForContractAddress.tsx +++ b/src/protocol/ethereum/nft/useGetTokensForContractAddress.tsx @@ -1,10 +1,14 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useContext } from 'react' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' import { openSeaApi } from './api' export const useGetTokensForContractAddress = ({ isSingleToken, isMultipleTokens, isAllTokens, parsedTokens, assetContractAddress, assetOwnerAddress, limit, offset, tokens }) => { const [ nfts, setNfts ] = useState(null) const [ error, setError ] = useState(null) + const { actions: { emitToEvent } } = useContext(EmitterContext) + useEffect(() => { if (assetOwnerAddress || !assetContractAddress) return @@ -15,8 +19,20 @@ export const useGetTokensForContractAddress = ({ isSingleToken, isMultipleTokens openSeaApi.contract .getSingleAsset({ assetContractAddress, token }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadSingleToken, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadSingleToken, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } if (isMultipleTokens) { @@ -25,8 +41,20 @@ export const useGetTokensForContractAddress = ({ isSingleToken, isMultipleTokens openSeaApi.contract .getMultipleAssets({ assetContractAddress, tokens, limit, offset }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadMultipleTokens, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadMultipleTokens, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } if (isAllTokens) { @@ -35,8 +63,20 @@ export const useGetTokensForContractAddress = ({ isSingleToken, isMultipleTokens openSeaApi.contract .getAllAssets({ assetContractAddress, limit, offset }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadAllTokens, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadAllTokens, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } }, [ assetContractAddress, offset ]) diff --git a/src/protocol/ethereum/nft/useGetTokensFromOwner.tsx b/src/protocol/ethereum/nft/useGetTokensFromOwner.tsx index cd5e17e..96a17b8 100644 --- a/src/protocol/ethereum/nft/useGetTokensFromOwner.tsx +++ b/src/protocol/ethereum/nft/useGetTokensFromOwner.tsx @@ -1,10 +1,14 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useContext } from 'react' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' import { openSeaApi } from './api' export const useGetTokensFromOwner = ({ assetOwnerAddress, isAllTokens, parsedTokens, isSingleToken, isMultipleTokens, assetContractAddress, tokens, limit, offset }) => { const [ nfts, setNfts ] = useState(null) const [ error, setError ] = useState(null) + const { actions: { emitToEvent } } = useContext(EmitterContext) + useEffect(() => { if (!assetOwnerAddress) return @@ -15,8 +19,20 @@ export const useGetTokensFromOwner = ({ assetOwnerAddress, isAllTokens, parsedTo openSeaApi.owner .getSingleAsset({ assetOwnerAddress, assetContractAddress, token }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadSingleToken, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadSingleToken, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } if (isMultipleTokens) { @@ -25,8 +41,20 @@ export const useGetTokensFromOwner = ({ assetOwnerAddress, isAllTokens, parsedTo openSeaApi.owner .getMultipleAssets({ assetOwnerAddress, assetContractAddress, tokens, limit, offset }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadMultipleTokens, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadMultipleTokens, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } if (isAllTokens) { @@ -35,8 +63,20 @@ export const useGetTokensFromOwner = ({ assetOwnerAddress, isAllTokens, parsedTo openSeaApi.owner .getAllAssets({ assetOwnerAddress, assetContractAddress, limit, offset }) - .then(setNfts) - .catch((error) => setError({ simpleErrorMessage, completeErrorMessage, error })) + .then((nfts) => { + emitToEvent( + EVENT_NAMES.nft.loadAllTokens, + { value: nfts, step: 'Load Single Token', status: EVENT_STATUS.resolved }, + ) + setNfts(nfts) + }) + .catch((error) => { + emitToEvent( + EVENT_NAMES.nft.loadAllTokens, + { value: error, step: 'Load Single Token', status: EVENT_STATUS.rejected }, + ) + setError({ simpleErrorMessage, completeErrorMessage, error }) + }) } }, [ assetOwnerAddress, offset ]) diff --git a/src/protocol/ethereum/nft/useRenderNfts.tsx b/src/protocol/ethereum/nft/useRenderNfts.tsx index 9018279..7400293 100644 --- a/src/protocol/ethereum/nft/useRenderNfts.tsx +++ b/src/protocol/ethereum/nft/useRenderNfts.tsx @@ -125,7 +125,7 @@ export const useRenderNfts = ({ nfts, item, element, getAssetElements }) => { 'data-dh-property-method-id', 'data-dh-property-asset-contract-address' ] // Replace the Inner text for any element type. - Array.from(item.root.children).forEach((element) => { + Array.from(item.root.children).forEach((element: HTMLElement) => { displayKeyValueElementInnerText(clonedItem, '$THIS_TokenID', nft?.token_id, element.nodeName) displayKeyValueElementInnerText(clonedItem, '$THIS_ContractAddress', nft?.asset_contract.address, element.nodeName) displayKeyValueElementInnerText(clonedItem, '$THIS_OwnerAddress', nft?.owner.address, element.nodeName) @@ -134,7 +134,7 @@ export const useRenderNfts = ({ nfts, item, element, getAssetElements }) => { // TODO: Add recursion to get inner children elements // Substitute the $THIS value on any attribute from array above, for any child element. - Array.from(item.root.children).forEach((element) => { + Array.from(item.root.children).forEach((element: HTMLElement) => { attributes.forEach((attribute) => { displayValueOnElementAttribute(clonedItem, '$THIS_TokenID', nft?.token_id, attribute) displayValueOnElementAttribute(clonedItem, '$THIS_ContractAddress', nft?.asset_contract.address, attribute) diff --git a/src/protocol/ethereum/threeBox/Reducer.tsx b/src/protocol/ethereum/threeBox/Reducer.tsx index 8fb529c..69558f9 100644 --- a/src/protocol/ethereum/threeBox/Reducer.tsx +++ b/src/protocol/ethereum/threeBox/Reducer.tsx @@ -3,6 +3,8 @@ import * as contexts from 'contexts' import { useWeb3React } from '@web3-react/core' import { logger } from 'logger/customLogger' import { getProfile } from '3box/lib/api' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' import { ThreeBoxProfileDataElement } from './ThreeBoxProfileDataElement' import { ThreeBoxProfileImgElement } from './ThreeBoxProfileImgElement' @@ -15,6 +17,9 @@ interface ReducerProps { } export const Reducer: FunctionComponent = ({ element, info }) => { + + const { actions: { emitToEvent } } = useContext(EmitterContext) + // const injectedContext = useWeb3React() // const { address } = injectedContext const defaultProfile = { @@ -46,9 +51,21 @@ export const Reducer: FunctionComponent = ({ element, info }) => { const fetchProfile = async () => { try { // TODO: [DEV-97] How to we check the status of a request? When no Profile this 404's + emitToEvent( + EVENT_NAMES.threeBox.loadProfile, + { value: null, step: 'Three Box Profile Load', status: EVENT_STATUS.pending }, + ) const profile = await get3boxProfile(address) + emitToEvent( + EVENT_NAMES.threeBox.loadProfile, + { value: profile, step: 'Three Box Profile Load', status: EVENT_STATUS.resolved }, + ) setThreeBoxProfile(profile) } catch (error) { + emitToEvent( + EVENT_NAMES.threeBox.loadProfile, + { value: error, step: 'Three Box Profile Load Error', status: EVENT_STATUS.rejected }, + ) logger.log('You have no profile. ', error) } } diff --git a/src/protocol/ethereum/types.ts b/src/protocol/ethereum/types.ts index 4e8a322..5ada502 100644 --- a/src/protocol/ethereum/types.ts +++ b/src/protocol/ethereum/types.ts @@ -25,13 +25,14 @@ export type Configuration = { export type FeatureReducerProps = { feature?: string; - element: HTMLElement | Element; + element: HTMLElement; configuration: Configuration; key?: string; index?: number; info?: any; customContractElements?: any; retriggerEngine?: any; + domElements?: any; timeStamp?: number; } diff --git a/src/protocol/ethereum/user/EthUserAddress.tsx b/src/protocol/ethereum/user/EthUserAddress.tsx index ca2ce8d..35ce3ce 100644 --- a/src/protocol/ethereum/user/EthUserAddress.tsx +++ b/src/protocol/ethereum/user/EthUserAddress.tsx @@ -2,6 +2,8 @@ import { useEffect, FunctionComponent, useContext, useMemo } from 'react' import * as contexts from 'contexts' import { logger } from 'logger/customLogger' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' interface EthUserAddressProps { element: HTMLElement; @@ -10,6 +12,8 @@ interface EthUserAddressProps { export const EthUserAddress: FunctionComponent = ({ element, displayFormat }) => { const ethereum = useContext(contexts.EthereumContext) + const { actions: { emitToEvent } } = useContext(EmitterContext) + const { address, isEnabled } = ethereum const memoizedValue = useMemo( @@ -17,6 +21,13 @@ export const EthUserAddress: FunctionComponent = ({ element , [], ) + useEffect(() => { + emitToEvent( + EVENT_NAMES.user.addressStatusChange, + { value: address, step: 'User address value change', status: EVENT_STATUS.resolved }, + ) + }, [ address, isEnabled ]) + useEffect(() => { try { if (address && isEnabled) { diff --git a/src/protocol/ethereum/user/EthUserBalance.tsx b/src/protocol/ethereum/user/EthUserBalance.tsx index 14f3375..c4343a4 100644 --- a/src/protocol/ethereum/user/EthUserBalance.tsx +++ b/src/protocol/ethereum/user/EthUserBalance.tsx @@ -1,7 +1,9 @@ import { logger } from 'logger/customLogger' -import { useEffect, useState, useContext, FunctionComponent, useMemo } from 'react' +import { useEffect, useState, useContext, FunctionComponent, useMemo, useRef } from 'react' import { useInterval } from 'beautiful-react-hooks' import { EthereumUnits } from 'types/types' +import { EmitterContext } from 'providers/EmitterProvider/context' +import { EVENT_NAMES, EVENT_STATUS } from 'providers/EmitterProvider/constants' import { useNetworkStatus } from 'react-adaptive-hooks/network' import * as utils from 'utils' import * as contexts from 'contexts' @@ -14,11 +16,34 @@ interface EthUserBalanceProps { } export const EthUserBalance: FunctionComponent = ({ element, units, decimals }) => { + + const { actions: { emitToEvent } } = useContext(EmitterContext) + const memoizedValue = useMemo( () => element.innerText , [], ) + // // Hook + const useMemoCompare = (value, compare) => { + // Ref for storing previous value + const previousRef = useRef() + const previous = previousRef.current + + // Pass previous and new value to compare function + const isEqual = compare(previous, value) + + // If not equal update previous to new value (for next render) + // and then return new new value below. + useEffect(() => { + if (!isEqual) { + previousRef.current = value + } + }) + + return isEqual ? previous : value + } + const initialEffectiveConnectionType = '4g' const { effectiveConnectionType } = useNetworkStatus(initialEffectiveConnectionType) units = units ?? 'wei' //eslint-disable-line @@ -29,17 +54,21 @@ export const EthUserBalance: FunctionComponent = ({ element const [ balance, setBalance ] = useState(null) + const emitBalanceValue = useMemoCompare(balance, (prev) => prev && prev._hex === balance._hex) + // TODO: [DEV-264] Feature: NotifyJS for UserBalance polling const poll = async () => { try { - const balance = await provider.getBalance(address) - setBalance(balance) + const newBalance = await provider.getBalance(address) + setBalance(newBalance) } catch (error) { logger.log(`Error getting balance: ${error}`) } } - if (address && isEnabled) poll() + useEffect(() => { + if (address && isEnabled) poll() + }, []) useInterval(() => { if (address && isEnabled) poll() @@ -58,5 +87,14 @@ export const EthUserBalance: FunctionComponent = ({ element if (address && isEnabled && balance) { getBalance() } else { element.innerHTML = memoizedValue } }, [ address, isEnabled, balance ]) + useEffect(() => { + + emitToEvent( + EVENT_NAMES.user.balanceStatusChange, + { value: balance?.toString(), step: 'User balance value change', status: EVENT_STATUS.resolved }, + ) + + }, [ emitBalanceValue ]) + return null } diff --git a/src/providers/EmitterProvider/constants.ts b/src/providers/EmitterProvider/constants.ts index 64cfc9b..8cfccaf 100644 --- a/src/providers/EmitterProvider/constants.ts +++ b/src/providers/EmitterProvider/constants.ts @@ -6,6 +6,17 @@ export const EVENT_NAMES = { invokeTrigger: 'contract:invokeTriggerChange', contractEvent: 'contract:contractEvent', }, + user: { + addressStatusChange: 'address:statusChange', + balanceStatusChange: 'balance:statusChange', + }, + nft: { + loadSingleToken: 'nft:loadSingleToken', + loadMultipleTokens: 'nft:loadMultipleTokens', + loadAllTokens: 'nft:loadAllTokens', + }, + threeBox: { loadProfile: 'threebox:loadProfile' }, + ethTransfer: { sendEther: 'ethTransfer:sendEther' }, } export const EVENT_STATUS = { pending: 'PENDING', resolved: 'RESOLVED', rejected: 'REJECTED' }