Skip to content

Commit

Permalink
Merge pull request #44 from gnosis/direct-exec
Browse files Browse the repository at this point in the history
Support direct execution without mod
  • Loading branch information
jfschwarz authored Dec 19, 2022
2 parents da4cb34 + 7207945 commit b810e95
Show file tree
Hide file tree
Showing 46 changed files with 1,209 additions and 474 deletions.
10 changes: 6 additions & 4 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
"fix:lint": "eslint src --ext .ts --fix"
},
"devDependencies": {
"@gnosis.pm/safe-ethers-lib": "^1.6.0",
"@gnosis.pm/safe-service-client": "^1.3.0",
"@gnosis.pm/zodiac": "^2.0.1",
"@safe-global/safe-core-sdk": "^3.2.0",
"@safe-global/safe-core-sdk-types": "^1.7.0",
"@safe-global/safe-ethers-lib": "^1.7.0",
"@safe-global/safe-service-client": "^1.4.0",
"@shazow/whatsabi": "^0.2.1",
"@testing-library/jest-dom": "^5.16.1",
"@typechain/ethers-v5": "^10.0.0",
Expand All @@ -53,7 +55,7 @@
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"ethereum-blockies-base64": "^1.0.2",
"ethers": "^5.5.1",
"ethers": "^5.7.2",
"ethers-proxies": "^1.0.0",
"events": "^3.3.0",
"ganache": "^7.0.0-beta.2",
Expand All @@ -66,7 +68,7 @@
"react-dom": "^18.2.0",
"react-icons": "^4.3.1",
"react-modal": "^3.15.1",
"react-multisend": "^1.1.0",
"react-multisend": "^1.2.0",
"react-select": "^5.2.1",
"react-toastify": "^9.0.8",
"rimraf": "^3.0.2",
Expand Down
38 changes: 27 additions & 11 deletions extension/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,45 @@ import ZodiacToastContainer from './components/Toast'
import { pushLocation } from './location'
import { ProvideMetaMask } from './providers'
import { useMatchSettingsRoute, usePushSettingsRoute } from './routing'
import Settings, { ProvideConnections } from './settings'
import { useConnection } from './settings'
import Settings, { ProvideConnections, useConnection } from './settings'
import { validateAddress } from './utils'

const Routes: React.FC = () => {
const settingsRouteMatch = useMatchSettingsRoute()
const { connection, connected } = useConnection()
const pushSettingsRoute = usePushSettingsRoute()
const { connection, connected } = useConnection()

const isSettingsRoute = !!settingsRouteMatch
const settingsRequired =
!connection ||
!connection.avatarAddress ||
!connection.moduleAddress ||
!connected
!validateAddress(connection.avatarAddress) ||
!validateAddress(connection.pilotAddress)

const waitForWallet = !isSettingsRoute && !settingsRequired && !connected

// redirect to settings page if more settings are required
useEffect(() => {
if (!settingsRouteMatch && settingsRequired) {
if (!isSettingsRoute && settingsRequired) {
pushSettingsRoute()
}
}, [pushSettingsRoute, settingsRouteMatch, settingsRequired])
if (!settingsRouteMatch && settingsRequired) return null
}, [isSettingsRoute, pushSettingsRoute, settingsRequired])

// redirect to settings page if wallet is not connected, but only after a small delay to give the wallet time to connect when initially loading the page
useEffect(() => {
let timeout: number
if (waitForWallet) {
timeout = window.setTimeout(() => {
pushSettingsRoute()
}, 100)
}
return () => {
window.clearTimeout(timeout)
}
}, [waitForWallet, connected, pushSettingsRoute])

if (!isSettingsRoute && settingsRequired) return null
if (!isSettingsRoute && waitForWallet) return null

if (settingsRouteMatch) {
if (isSettingsRoute) {
return (
<Settings
url={settingsRouteMatch.url}
Expand Down
2 changes: 1 addition & 1 deletion extension/src/browser/Drawer/CallContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const CallContract: React.FC<Props> = ({ value }) => {
onChange: () => {
/*nothing here*/
},
network: chainId ? (chainId.toString() as NetworkId) : '1',
network: chainId.toString() as NetworkId,
blockExplorerApiKey: process.env.ETHERSCAN_API_KEY,
})

Expand Down
8 changes: 5 additions & 3 deletions extension/src/browser/Drawer/ContractAddress/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy from 'copy-to-clipboard'
import makeBlockie from 'ethereum-blockies-base64'
import { getAddress } from 'ethers/lib/utils'
import React, { useEffect, useMemo, useState } from 'react'
import { RiExternalLinkLine, RiFileCopyLine } from 'react-icons/ri'

Expand Down Expand Up @@ -37,8 +38,9 @@ const ContractAddress: React.FC<Props> = ({

const blockie = useMemo(() => address && makeBlockie(address), [address])

const start = address.substring(0, VISIBLE_START + 2)
const end = address.substring(42 - VISIBLE_END, 42)
const checksumAddress = address && getAddress(address)
const start = checksumAddress.substring(0, VISIBLE_START + 2)
const end = checksumAddress.substring(42 - VISIBLE_END, 42)
const displayAddress = `${start}...${end}`

useEffect(() => {
Expand All @@ -64,7 +66,7 @@ const ContractAddress: React.FC<Props> = ({
<Flex
gap={2}
alignItems="center"
justifyContent="space-between"
justifyContent="start"
className={className}
>
<Box p={1} rounded className={classes.blockies}>
Expand Down
2 changes: 1 addition & 1 deletion extension/src/browser/Drawer/RolePermissionCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const RolePermissionCheck: React.FC<{
if (!canceled) setError(false)
})
.catch((e: JsonRpcError) => {
const decodedError = decodeRolesError(e.data.message || e.message)
const decodedError = decodeRolesError(e)
if (!canceled) {
setError(isPermissionsError(decodedError) ? decodedError : false)
}
Expand Down
127 changes: 92 additions & 35 deletions extension/src/browser/Drawer/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'

import { Button, IconButton } from '../../components'
import toastClasses from '../../components/Toast/Toast.module.css'
import { EXPLORER_URL, NETWORK_PREFIX } from '../../networks'
import { ChainId, EXPLORER_URL, NETWORK_PREFIX } from '../../networks'
import { waitForMultisigExecution } from '../../providers'
import { useConnection } from '../../settings'
import { JsonRpcError, ProviderType } from '../../types'
Expand All @@ -19,7 +19,13 @@ import classes from './style.module.css'
const Submit: React.FC = () => {
const {
provider,
connection: { chainId, pilotAddress, providerType },
connection: {
chainId,
avatarAddress,
pilotAddress,
moduleAddress,
providerType,
},
} = useConnection()
const dispatch = useDispatch()

Expand All @@ -34,13 +40,14 @@ const Submit: React.FC = () => {
try {
batchTransactionHash = await submitTransactions()
} catch (e) {
console.warn(e)
setSignaturePending(false)
const err = e as JsonRpcError
toast.error(
<>
<p>Submitting the transaction batch failed:</p>
<br />
<code>{decodeRolesError(err.data.message || err.message)}</code>
<code>{decodeRolesError(err)}</code>
</>,
{ className: toastClasses.toastError }
)
Expand Down Expand Up @@ -93,46 +100,96 @@ const Submit: React.FC = () => {
>
Submit
</Button>
{signaturePending && (
<Modal
{signaturePending && moduleAddress && (
<AwaitingSignatureModal
isOpen={signaturePending}
style={modalStyle}
contentLabel="Sign the batch transaction"
>
<IconButton
className={classes.modalClose}
title="Cancel"
onClick={() => {
setSignaturePending(false)
}}
>
<RiCloseLine />
</IconButton>
<p>Awaiting your signature ...</p>
{providerType === ProviderType.WalletConnect && (
<>
<br />
<p>
<a
className={classes.safeAppLink}
href={`https://gnosis-safe.io/app/${NETWORK_PREFIX[chainId]}:${pilotAddress}/apps?appUrl=https://apps.gnosis-safe.io/wallet-connect`}
target="_blank"
rel="noreferrer"
>
<RiExternalLinkLine />
WalletConnect Safe app
</a>
</p>
</>
)}
</Modal>
onClose={() => setSignaturePending(false)}
usesWalletConnectApp={providerType === ProviderType.WalletConnect}
chainId={chainId}
pilotAddress={pilotAddress}
/>
)}

{signaturePending && !moduleAddress && (
<AwaitingMultisigExecutionModal
isOpen={signaturePending}
onClose={() => setSignaturePending(false)}
chainId={chainId}
avatarAddress={avatarAddress}
/>
)}
</>
)
}

export default Submit

const AwaitingSignatureModal: React.FC<{
isOpen: boolean
onClose(): void
usesWalletConnectApp: boolean
chainId: ChainId
pilotAddress: string
}> = ({ isOpen, onClose, usesWalletConnectApp, chainId, pilotAddress }) => (
<Modal
isOpen={isOpen}
style={modalStyle}
contentLabel="Sign the batch transaction"
>
<IconButton className={classes.modalClose} title="Cancel" onClick={onClose}>
<RiCloseLine />
</IconButton>
<p>Awaiting your signature ...</p>
{usesWalletConnectApp && (
<>
<br />
<p>
<a
className={classes.safeAppLink}
href={`https://app.safe.global/${NETWORK_PREFIX[chainId]}:${pilotAddress}/apps?appUrl=https://apps.gnosis-safe.io/wallet-connect`}
target="_blank"
rel="noreferrer"
>
<RiExternalLinkLine />
WalletConnect Safe app
</a>
</p>
</>
)}
</Modal>
)

const AwaitingMultisigExecutionModal: React.FC<{
isOpen: boolean
onClose(): void
chainId: ChainId
avatarAddress: string
}> = ({ isOpen, onClose, chainId, avatarAddress }) => (
<Modal
isOpen={isOpen}
style={modalStyle}
contentLabel="Sign the batch transaction"
>
<IconButton className={classes.modalClose} title="Cancel" onClick={onClose}>
<RiCloseLine />
</IconButton>
<p>Awaiting execution of Safe transaction ...</p>

<br />
<p>
<a
className={classes.safeAppLink}
href={`https://app.safe.global/${NETWORK_PREFIX[chainId]}:${avatarAddress}/transactions/queue`}
target="_blank"
rel="noreferrer"
>
<RiExternalLinkLine />
Collect signatures and trigger execution
</a>
</p>
</Modal>
)

Modal.setAppElement('#root')

const modalStyle: Styles = {
Expand Down
11 changes: 9 additions & 2 deletions extension/src/browser/Drawer/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ export const Transaction: React.FC<Props> = ({
justifyContent="space-between"
className={classes.transactionSubtitle}
>
<ContractAddress address={input.to} explorerLink />
<ContractAddress
address={input.to}
explorerLink
className={classes.contractName}
/>
<EtherValue input={input} />
</Flex>
</Box>
Expand All @@ -199,6 +203,9 @@ export const TransactionBadge: React.FC<Props> = ({
input,
scrollIntoView,
}) => {
const { connection } = useConnection()
const showRoles = connection.moduleType === KnownContracts.ROLES

const elementRef = useScrollIntoView(scrollIntoView)

return (
Expand All @@ -214,7 +221,7 @@ export const TransactionBadge: React.FC<Props> = ({
<SimulatedExecutionCheck transactionHash={transactionHash} mini />
)}

<RolePermissionCheck transaction={input} mini />
{showRoles && <RolePermissionCheck transaction={input} mini />}
</Box>
)
}
Expand Down
6 changes: 6 additions & 0 deletions extension/src/browser/Drawer/style.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
word-wrap: break-word;
}

.contractName {
/** Important to not use auto width, because long contract names would make overflow the container */
width: 1px;
flex-grow: 1;
}

.inputType {
padding-left: var(--spacing-1);
opacity: 0.7;
Expand Down
5 changes: 3 additions & 2 deletions extension/src/browser/ProvideProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {
} from 'react'
import { decodeSingle, encodeMulti, encodeSingle } from 'react-multisend'

import { ChainId } from '../networks'
import { ChainId, EXPLORER_API_KEY } from '../networks'
import {
ForkProvider,
useTenderlyProvider,
Expand Down Expand Up @@ -96,7 +96,8 @@ const ProvideProvider: React.FC<Props> = ({ simulate, children }) => {
chainId as ChainId,
address,
data,
new Web3Provider(provider)
new Web3Provider(provider),
EXPLORER_API_KEY[chainId as ChainId] || undefined
),
txId
)
Expand Down
2 changes: 1 addition & 1 deletion extension/src/browser/fetchAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const abiForAddress = async (
blockExplorerApiKey = ''
): Promise<string> => {
const abiLoader = new loaders.MultiABILoader([
new loaders.SourcifyABILoader(),
// new loaders.SourcifyABILoader(), // doesn't work in the current version (v0.2.1)
new loaders.EtherscanABILoader({
apiKey: blockExplorerApiKey,
baseURL: EXPLORER_API_URL[network],
Expand Down
Loading

0 comments on commit b810e95

Please sign in to comment.