Skip to content

Commit

Permalink
Use Safe SDK to send batched transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
sembrestels committed Jun 23, 2024
1 parent f9f4058 commit 544b628
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ChevronDownIcon } from '@chakra-ui/icons';
import {
Button,
HStack,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
useBoolean,
} from '@chakra-ui/react';

export function ExecuteButton({
isLoading,
onExecute,
allowBatch,
}: {
isLoading: boolean;
onExecute: (inBatch: boolean) => void;
allowBatch: boolean;
}) {
const [isBatch, setIsBatch] = useBoolean(allowBatch);

return !allowBatch ? (
<Button
variant="overlay"
colorScheme={'green'}
onClick={() => onExecute(isBatch)}
isLoading={isLoading}
loadingText={'Executing'}
size={'md'}
>
Execute
</Button>
) : (
<HStack spacing={0}>
<Button
variant="overlay"
colorScheme={'green'}
onClick={() => onExecute(isBatch)}
isLoading={isLoading}
loadingText={'Executing'}
size={'md'}
borderRightRadius={0}
>
{isBatch ? 'Execute in batch' : 'Execute one by one'}
</Button>
{!isLoading && (
<Menu>
<MenuButton
as={IconButton}
icon={<ChevronDownIcon />}
variant="overlay"
colorScheme={'green'}
size={'md'}
borderLeftRadius={0}
borderLeft="1px solid"
borderLeftColor="green.300"
/>
<MenuList>
<MenuItem onClick={setIsBatch.toggle}>
{isBatch ? 'Execute one by one' : 'Execute in batch'}
</MenuItem>
</MenuList>
</Menu>
)}
</HStack>
);
}
38 changes: 27 additions & 11 deletions packages/evmcrispr-terminal/src/components/ActionButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState } from 'react';
import type { TransactionAction } from '@1hive/evmcrispr';
import { EVMcrispr, isProviderAction, parseScript } from '@1hive/evmcrispr';
import { useWalletClient } from 'wagmi';
import { Button, HStack, VStack, useDisclosure } from '@chakra-ui/react';
import { HStack, VStack, useDisclosure } from '@chakra-ui/react';

import LogModal from '../LogModal';
import ErrorMsg from './ErrorMsg';
Expand All @@ -13,6 +14,8 @@ import {
useTerminalStore,
} from '../TerminalEditor/use-terminal-store';
import { clientToSigner } from '../../utils/ethers';
import { useSafeConnection } from '../../hooks/useSafeConnection';
import { ExecuteButton } from './ExecuteButton';

type ActionButtonsType = {
address: string;
Expand All @@ -36,6 +39,7 @@ export default function ActionButtons({
});

const { data: client } = useWalletClient();
const { isSafe, sdk } = useSafeConnection();

function logListener(message: string, prevMessages: string[]) {
if (!isLogModalOpen) {
Expand All @@ -50,7 +54,7 @@ export default function ActionButtons({
setLogs([]);
}

async function onExecute() {
async function onExecute(inBatch: boolean) {
terminalStoreActions.errors([]);
terminalStoreActions.isLoading(true);

Expand All @@ -65,12 +69,19 @@ export default function ActionButtons({
return;
}

const batched: TransactionAction[] = [];

await new EVMcrispr(ast, async () => clientToSigner(client!))
.registerLogListener(logListener)
.interpret(async (action) => {
if (isProviderAction(action)) {
if (inBatch) {
throw new Error('Batching not supported for provider actions');
}
const [chainParam] = action.params;
await client.switchChain({ id: Number(chainParam.chainId) });
} else if (inBatch) {
batched.push(action);
} else {
const chainId = await client.getChainId();
await client.sendTransaction({
Expand All @@ -83,6 +94,16 @@ export default function ActionButtons({
});
}
});

if (batched.length) {
await sdk?.txs.send({
txs: batched.map((action) => ({
to: action.to,
data: action.data,
value: String(action.value || '0'),
})),
});
}
} catch (err: any) {
const e = err as Error;
console.error(e);
Expand Down Expand Up @@ -113,16 +134,11 @@ export default function ActionButtons({
pr={{ base: 6, lg: 0 }}
>
{address ? (
<Button
variant="overlay"
colorScheme={'green'}
onClick={onExecute}
<ExecuteButton
isLoading={isLoading}
loadingText={'Executing'}
size={'md'}
>
Execute
</Button>
onExecute={onExecute}
allowBatch={isSafe}
/>
) : null}
{errors ? <ErrorMsg errors={errors} /> : null}
</VStack>
Expand Down
23 changes: 17 additions & 6 deletions packages/evmcrispr-terminal/src/hooks/useSafeConnection.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useAccount, useConnect } from 'wagmi';
import type { Connector } from 'wagmi';
import type { SafeInfo } from '@safe-global/safe-apps-sdk';
import SafeAppsSDK from '@safe-global/safe-apps-sdk';

export function useSafeConnection() {
const [sdk, setSdk] = useState<SafeAppsSDK | undefined>(undefined);
const [safe, setSafe] = useState<SafeInfo | undefined>(undefined);
const { connector: activeConnector } = useAccount();
const { connectors, connectAsync } = useConnect();
const isSafe = activeConnector?.id === 'safe';
useEffect(() => {
const safeConnector = connectors.find((c: Connector) => c.id === 'safe');
if (safeConnector && !isSafe && window.parent !== window) {
connectAsync({ connector: safeConnector }).catch(() => {
console.log('error connecting to safe');
});
connectAsync({ connector: safeConnector })
.then(() => {
safeConnector.getProvider().then((provider) => {
setSdk(new SafeAppsSDK((provider as any).sdk));
setSafe((provider as any).safe);
});
})
.catch(() => {
console.log('error connecting to safe');
});
}
}, [connectors, connectAsync, activeConnector, isSafe]);
}, [connectors, connectAsync, activeConnector?.id, isSafe]);

return { isSafe };
return { isSafe, sdk, safe };
}
27 changes: 27 additions & 0 deletions packages/evmcrispr-terminal/src/theme/MenuCustomization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { menuAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';

const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(menuAnatomy.keys);

const baseStyle = definePartsStyle({
list: {
borderRadius: 'none',
background: 'black',
borderColor: 'green.300',
padding: '0',
},
item: {
bg: 'black',
_hover: {
color: 'black',
bg: 'green.300',
},
},
});

const menuTheme = defineMultiStyleConfig({
baseStyle,
});

export default menuTheme;
2 changes: 2 additions & 0 deletions packages/evmcrispr-terminal/src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Switch from './SwitchCustomization';
import Alert from './AlertCustomization';
import Tooltip from './TooltipCustomization';
import Text from './TextCustomization';
import Menu from './MenuCustomization';

const theme = {
initialColorMode: 'dark',
Expand Down Expand Up @@ -106,6 +107,7 @@ const theme = {
Alert,
Tooltip,
Text,
Menu,
},
shadows: {
outline: 'green.900',
Expand Down

0 comments on commit 544b628

Please sign in to comment.