Add environmental values for interacting with contracts

pull/5474/head
Doris Benda 4 weeks ago
parent 0a1fdb6c9a
commit 2459405964
  1. 4
      apps/contract-interaction/src/app/AppContext.tsx
  2. 31
      apps/contract-interaction/src/app/actions/index.ts
  3. 3
      apps/contract-interaction/src/app/components/NavMenu.tsx
  4. 86
      apps/contract-interaction/src/app/reducers/state.ts
  5. 15
      apps/contract-interaction/src/app/routes.tsx
  6. 20
      apps/contract-interaction/src/app/views/GetABIView.tsx
  7. 132
      apps/contract-interaction/src/app/views/InteractView.tsx
  8. 5
      apps/contract-interaction/src/app/views/index.ts
  9. 5
      libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx

@ -1,7 +1,7 @@
import React from 'react'
import type { ThemeType, Chain, ContractInteractionSettings } from './types'
import { ContractInteractionPluginClient } from './ContractInteractionPluginClient'
import { State } from './reducers/state'
import { appInitialState, State } from './reducers/state'
// Define the type for the context
type AppContextType = {
@ -20,7 +20,7 @@ const defaultContextValue: AppContextType = {
// setThemeType: (themeType: ThemeType) => { },
plugin: {} as ContractInteractionPluginClient,
settings: { chains: {} },
appState: { loading: { screen: true }, contractInstances: [] },
appState: appInitialState,
setSettings: () => { },
chains: [],
}

@ -1,7 +1,7 @@
import { PluginClient } from '@remixproject/plugin';
import ContractInteractionPluginClient from '../ContractInteractionPluginClient';
import { CLEAR_INSTANCES, PIN_INSTANCE, REMOVE_INSTANCE, SET_INSTANCE, UNPIN_INSTANCE } from "../reducers/state"
import { CLEAR_INSTANCES, PIN_INSTANCE, REMOVE_INSTANCE, SET_GAS_LIMIT, SET_INSTANCE, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, UNPIN_INSTANCE } from "../reducers/state"
import { Chain, ContractInstance } from '../types/AbiProviderTypes';
let dispatch: React.Dispatch<any>
@ -96,3 +96,32 @@ export const clearInstancesAction = () => {
type: CLEAR_INSTANCES
})
}
export const setSelectedAccountAction = (selectedAccount: string) => {
dispatch({
type: SET_SELECTED_ACCOUNT,
payload: selectedAccount
})
}
export const setSendValue = (sendValue: string) => {
dispatch({
type: SET_SEND_VALUE,
payload: sendValue
})
}
export const setSendUnit = (sendUnit: string) => {
dispatch({
type: SET_SEND_UNIT,
payload: sendUnit
})
}
export const setGasLimit = (gasLimit: number) => {
dispatch({
type: SET_GAS_LIMIT,
payload: gasLimit
})
}

@ -21,7 +21,8 @@ const NavItem: React.FC<NavItemProps> = ({ to, icon, title }) => {
export const NavMenu = () => {
return (
<nav className="d-flex flex-row justify-start w-100">
<NavItem to="/" icon={<i className="fas fa-search"></i>} title="LookupABI" />
<NavItem to="/" icon={<i className="fas fa-search"></i>} title="GetABI" />
<NavItem to="/interact" icon={<i className="fas fa-paper-plane"></i>} title="Interact" />
<NavItem to="/settings" icon={<i className="fas fa-cog"></i>} title="Settings" />
<div className="flex-grow-1"></div>
</nav>

@ -2,29 +2,54 @@ import { ContractInstance } from "../types";
export const appInitialState: State = {
loading: { screen: true },
contractInstances: []
// settings: {
// sendValue: '0',
// sendUnit: 'wei',
// gasLimit: 3000000,
// networkName: 'Goerli',
// loadedAccounts: {},
// isRequesting: false,
// isSuccessful: false,
// error: null,
// selectedAccount: '',s
// provider: window.ethereum ? 'metamask' : 'walletconnect',
// theme: 'Dark',
// },
contractInstances: [],
interaction_environment: {
sendValue: '0',
sendUnit: 'wei',
gasLimit: 3000000,
proxyAddress: '',
error: '',
selectedAccount: '',
// TODO
accounts: {
loadedAccounts: {},
isRequesting: false,
isSuccessful: false,
error: null,
selectedAccount: ''
},
},
// isRequesting: false,
// isSuccessful: false,
// terminal: { journalBlocks: [], hidden: false, height: 250 },
};
export interface State {
loading: { screen: boolean },
contractInstances: ContractInstance[]
interaction_environment: InteractionEnvironment
};
export type ActionType = 'SET_LOADING' | 'SET_INSTANCE' | 'SET_SETTINGS' | 'SET_TERMINAL' | 'PIN_INSTANCE' | 'UNPIN_INSTANCE' | 'REMOVE_INSTANCE' | 'CLEAR_INSTANCES';
export interface InteractionEnvironment {
sendValue: string,
sendUnit: string,
gasLimit: number,
proxyAddress: string,
error: string | null,
selectedAccount: string,
// TODO
accounts: {
loadedAccounts: {},
isRequesting: boolean,
isSuccessful: boolean,
error: string | null,
selectedAccount: string
},
// isRequesting: boolean,
// isSuccessful: boolean,
}
export type ActionType = 'SET_LOADING' | 'SET_INSTANCE' | 'SET_SETTINGS' | 'SET_TERMINAL' | 'PIN_INSTANCE' | 'UNPIN_INSTANCE' | 'REMOVE_INSTANCE' | 'CLEAR_INSTANCES' | 'SET_SELECTED_ACCOUNT' | 'SET_SEND_VALUE' | 'SET_SEND_UNIT' | 'SET_GAS_LIMIT';
export const SET_LOADING: ActionType = 'SET_LOADING';
export const SET_INSTANCE: ActionType = 'SET_INSTANCE';
@ -34,6 +59,10 @@ export const PIN_INSTANCE: ActionType = 'PIN_INSTANCE';
export const UNPIN_INSTANCE: ActionType = 'UNPIN_INSTANCE';
export const REMOVE_INSTANCE: ActionType = 'REMOVE_INSTANCE';
export const CLEAR_INSTANCES: ActionType = 'CLEAR_INSTANCES';
export const SET_SELECTED_ACCOUNT: ActionType = 'SET_SELECTED_ACCOUNT';
export const SET_SEND_VALUE: ActionType = 'SET_SEND_VALUE';
export const SET_SEND_UNIT: ActionType = 'SET_SEND_UNIT';
export const SET_GAS_LIMIT: ActionType = 'SET_GAS_LIMIT';
export type Action =
{
@ -93,9 +122,32 @@ export const appReducer = (state = appInitialState, action: Action): State => {
}
}
case SET_SELECTED_ACCOUNT:
return {
...state,
interaction_environment: { ...state.interaction_environment, selectedAccount: action.payload },
};
case SET_SEND_VALUE:
return {
...state,
interaction_environment: { ...state.interaction_environment, sendValue: action.payload },
};
case SET_SEND_UNIT:
return {
...state,
interaction_environment: { ...state.interaction_environment, sendUnit: action.payload }
};
case SET_GAS_LIMIT:
return {
...state,
interaction_environment: { ...state.interaction_environment, gasLimit: action.payload },
};
// case UPDATE_INSTANCES_BALANCE: {
// const payload: Array<{ contractData: ContractData, address: string, balance: number, name: string, abi?: any, decodedResponse?: Record<number, any> }> = action.payload
// return {
// ...state,
// instances: {
@ -105,8 +157,6 @@ export const appReducer = (state = appInitialState, action: Action): State => {
// }
// }
// case 'SET_SETTINGS':
// return {
// ...state,

@ -1,6 +1,6 @@
import { HashRouter as Router, Route, Routes } from 'react-router-dom'
import { LookupABIView, SettingsView } from './views'
import { GetABIView, InteractView, SettingsView } from './views'
import { DefaultLayout } from './layouts'
const DisplayRoutes = () => (
@ -10,8 +10,17 @@ const DisplayRoutes = () => (
<Route
path="/"
element={
<DefaultLayout from="/" title="LookupABI" description="Search for verified contracts and download the ABI to Remix">
<LookupABIView />
<DefaultLayout from="/" title="GetABI" description="Lookup or decode the smart contract ABI">
<GetABIView />
</DefaultLayout>
}
/>
<Route
path="/interact"
element={
<DefaultLayout from="/" title="Interact" description="Interact with smart contracts on-chain">
<InteractView />
</DefaultLayout>
}
/>

@ -13,7 +13,7 @@ import { InstanceContainerUI } from '../components/InstanceContainerUI'
import { clearInstancesAction, loadPinnedContractsAction, setInstanceAction } from '../actions'
import { FormattedMessage } from 'react-intl'
export const LookupABIView = () => {
export const GetABIView = () => {
const { appState, settings, plugin } = useContext(AppContext);
const contractInstances = appState.contractInstances;
@ -27,7 +27,6 @@ export const LookupABIView = () => {
const navigate = useNavigate()
const chainSettings = useMemo(() => (selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined), [selectedChain, settings])
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [deriveFromContractAddress, setDeriveFromContractAddress] = useState<boolean>(true)
const sourcifySupported = useSourcifySupported(selectedChain, chainSettings)
@ -223,10 +222,6 @@ export const LookupABIView = () => {
}
}
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return (
<>
<form>
@ -395,19 +390,6 @@ export const LookupABIView = () => {
})}
</div>
<div className="d-flex px-4 remixui_compilerConfigSection justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex">
<label className="mt-1 remixui_compilerConfigSection">
<FormattedMessage id="contractInteraction.environment" defaultMessage="Environment" />
</label>
</div>
<div>
<span data-id="scConfigExpander" onClick={toggleConfigurations}>
<i className={!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div>
</div>
{contractInstances.length > 0 && (
<InstanceContainerUI
// TODO

@ -0,0 +1,132 @@
import { useContext, useState } from 'react'
import { SearchableChainDropdown } from '../components'
import type { Chain } from '../types'
import { AppContext } from '../AppContext'
import { InteractionFormContext } from '../InteractionFormContext'
import { InstanceContainerUI } from '../components/InstanceContainerUI'
import { clearInstancesAction, loadPinnedContractsAction, setSelectedAccountAction, setSendValue, setSendUnit, setGasLimit } from '../actions'
import { Modal } from 'libs/remix-ui/run-tab/src/lib/types'
import { GasLimitUI } from 'libs/remix-ui/run-tab/src/lib/components/gasLimit'
import { ValueUI } from 'libs/remix-ui/run-tab/src/lib/components/value'
import { AccountUI } from 'libs/remix-ui/run-tab/src/lib/components/account'
import { NetworkUI } from 'libs/remix-ui/run-tab/src/lib/components/network'
export const InteractView = () => {
const { appState, plugin } = useContext(AppContext);
const contractInstances = appState.contractInstances;
const environment = appState.interaction_environment;
const { selectedChain, setSelectedChain } = useContext(InteractionFormContext)
const handleSelectedChain = async (newSelectedChain: Chain) => {
setSelectedChain(newSelectedChain)
// Clear all contract interfaces for the old chain.
await clearInstancesAction();
// Load pinned contracts for the new chain.
await loadPinnedContractsAction(plugin, newSelectedChain);
}
const [modals, setModals] = useState<Modal[]>([])
const modal = (
title: string,
message: string | JSX.Element,
okLabel: string,
okFn: () => void,
cancelLabel?: string,
cancelFn?: () => void,
okBtnClass?: string,
cancelBtnClass?: string
) => {
setModals((modals) => {
modals.push({
message,
title,
okLabel,
okFn,
cancelLabel,
cancelFn,
okBtnClass,
cancelBtnClass
})
return [...modals]
})
}
const [toasters, setToasters] = useState<string[]>([])
const toast = (toasterMsg: string) => {
setToasters((messages) => {
messages.push(toasterMsg)
return [...messages]
})
}
return (
<>
{/* INTERACTION_ENVIRONMENT */}
<SearchableChainDropdown label="Chain" id="network-dropdown" selectedChain={selectedChain} setSelectedChain={handleSelectedChain} />
<NetworkUI networkName={selectedChain.name ? selectedChain.name : ''} />
<AccountUI
addFile={() => { console.error('not supported in this plugin') }}
personalMode={false}
selectExEnv={'blockchain'}
accounts={environment.accounts}
setAccount={setSelectedAccountAction}
createNewBlockchainAccount={() => { console.error('not supported in this plugin') }}
setPassphrase={() => { console.error('not supported in this plugin') }}
setMatchPassphrase={() => { console.error('not supported in this plugin') }}
tooltip={toast}
modal={modal}
signMessageWithAddress={() => { console.error('not supported in this plugin') }}
passphrase={''}
/>
<GasLimitUI gasLimit={environment.gasLimit} setGasFee={setGasLimit} />
<ValueUI sendUnit={environment.sendUnit} setUnit={setSendUnit} sendValue={environment.sendValue} setSendValue={setSendValue} />
{/* INSTANCES */}
{contractInstances.length > 0 && (
<InstanceContainerUI
// TODO
// clearInstances={() => { }}
// unpinInstance={(index) => { }}
// pinInstance={(index, pinnedAt) => { }}
// removeInstance={(index) => { }}
// "TODO: runTransactions + sendValue"
// runTransactions={executeTransactions}
// sendValue={runTab.sendValue}
// gasEstimationPrompt={gasEstimationPrompt}
// passphrasePrompt={passphrasePrompt}
// mainnetPrompt={mainnetPrompt}
// solcVersion={solcVersion}
// getVersion={getVersion}
// getFuncABIInputs={getFuncABIValues}
// TODO
solcVersion={{
version: '',
canReceive: false
}}
evmCheckComplete={true}
exEnvironment={"TODO: environment"}
chain={selectedChain}
editInstance={(instance) => {
// TODO
// const {metadata, abi, object} = instance.contractData
// plugin.call('quick-dapp', 'edit', {
// address: instance.address,
// abi: abi,
// name: instance.name,
// network: runTab.networkName,
// devdoc: object.devdoc,
// methodIdentifiers: object.evm.methodIdentifiers,
// solcVersion: JSON.parse(metadata).compiler.version,
// })
}}
/>
)}
</>
)
}

@ -1,2 +1,3 @@
export { SettingsView } from './SettingsView'
export { LookupABIView } from './LookupABIView'
export { GetABIView } from './GetABIView'
export { InteractView } from './InteractView'
export { SettingsView } from './SettingsView'

@ -7,6 +7,11 @@ import { AccountUI } from './account'
import { GasLimitUI } from './gasLimit'
import { ValueUI } from './value'
// Re-exporting components to be re-used.
export { NetworkUI } from './network'
export { ValueUI } from './value'
export { GasLimitUI } from './gasLimit'
export function SettingsUI(props: SettingsProps) {
// this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this))

Loading…
Cancel
Save