Skip to content

Commit

Permalink
Merge pull request #65 from ethereum-optimism/02-13-feat_interop-alph…
Browse files Browse the repository at this point in the history
…a_sponsored_sender_support_misc_fixes

feat: interop-alpha, sponsored sender support, misc fixes
  • Loading branch information
jakim929 authored Feb 14, 2025
2 parents 8136b42 + 5a52a70 commit 7864591
Show file tree
Hide file tree
Showing 23 changed files with 371 additions and 152 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-files-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eth-optimism/super-cli": patch
---

added interop-alpha support and sponsored sender flow
6 changes: 3 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"build": "tsc && resolve-tspaths && pnpm copy:signer-frontend",
"build:watch": "tsc --watch",
"dev": "tsx src/cli.tsx",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"lint": "oxlint && pnpm prettier --check \"**/*.{ts,tsx}\"",
"lint:fix": "oxlint --fix && pnpm prettier \"**/*.{ts,tsx}\" --write --loglevel=warn",
"start": "node dist/cli.js",
"typecheck": "tsc --noEmit",
"copy:signer-frontend": "cp -r ../signer-frontend/dist dist/signer-frontend",
Expand All @@ -25,7 +25,7 @@
"drizzle"
],
"dependencies": {
"@eth-optimism/viem": "^0.3.1",
"@eth-optimism/viem": "^0.3.2",
"@hono/node-server": "^1.13.7",
"@inkjs/ui": "^2.0.0",
"@libsql/client": "^0.14.0",
Expand Down
14 changes: 4 additions & 10 deletions packages/cli/src/actions/bridge/wizard/steps/SelectChains.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {useBridgeWizardStore} from '@/actions/bridge/wizard/bridgeWizardStore';
import {
rollupChainToIdentifier,
sourceChainByIdentifier,
} from '@/util/chains/chainIdentifier';
import {rollupChains} from '@/util/chains/chains';
import {rollupChainToIdentifier} from '@/util/chains/chainIdentifier';
import {networkByName} from '@/util/chains/networks';
import {MultiSelect} from '@inkjs/ui';
import {Box, Text} from 'ink';
import {useState} from 'react';
Expand All @@ -17,10 +14,7 @@ export const SelectChains = () => {
throw new Error('Invalid state');
}

const sourceChain = sourceChainByIdentifier[wizardState.network]!;
const chains = rollupChains.filter(
chain => chain.sourceId === sourceChain.id,
);
const network = networkByName[wizardState.network]!;

return (
<Box flexDirection="column">
Expand All @@ -37,7 +31,7 @@ export const SelectChains = () => {
<Text color="gray"> to confirm)</Text>
</Text>
<MultiSelect
options={chains.map(chain => ({
options={network.chains.map(chain => ({
label: `${chain.name} (${chain.id})`,
value: rollupChainToIdentifier(chain).split('/')[1]!,
}))}
Expand Down
18 changes: 8 additions & 10 deletions packages/cli/src/actions/bridge/wizard/steps/SelectNetwork.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {useBridgeWizardStore} from '@/actions/bridge/wizard/bridgeWizardStore';
import {SupportedNetwork} from '@/util/fetchSuperchainRegistryChainList';
import {chainIdByParentChainName} from '@/queries/chains';
import {chainById} from '@/util/chains/chains';
import {zodSupportedNetwork} from '@/util/fetchSuperchainRegistryChainList';
import {Select} from '@inkjs/ui';
import {Box, Text} from 'ink';

const supportedNetworks: SupportedNetwork[] = [
'mainnet',
'sepolia',
'supersim',
];

export const SelectNetwork = () => {
const {wizardState, submitSelectNetwork} = useBridgeWizardStore();

Expand All @@ -18,10 +14,12 @@ export const SelectNetwork = () => {

return (
<Box flexDirection="column">
<Text bold>Which L1 network do you want to bridge from?</Text>
<Text bold>Select which Superchain network the chain is based on?</Text>
<Select
options={supportedNetworks.map(network => ({
label: network,
options={zodSupportedNetwork.options.map(network => ({
label: `${network} (${
chainById[chainIdByParentChainName[network]]?.name
})`,
value: network,
}))}
onChange={value => submitSelectNetwork({network: value})}
Expand Down
21 changes: 18 additions & 3 deletions packages/cli/src/actions/deploy-create2/DeployCreate2Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ import {
deployCreate2,
executeTransactionOperation,
} from '@/actions/deploy-create2/deployCreate2';
import {sendAllTransactionTasks} from '@/actions/deploy-create2/sendAllTransactionTasks';
import {
sendAllTransactionTasksWithPrivateKeyAccount,
sendAllTransactionTasksWithCustomWalletRpc,
} from '@/actions/deploy-create2/sendAllTransactionTasks';
import {getSponsoredSenderWalletRpcUrl} from '@/util/sponsoredSender';

// Prepares any required data or loading state if waiting
export const DeployCreate2Command = ({
Expand Down Expand Up @@ -215,9 +219,17 @@ const DeployCreate2CommandInner = ({
onSubmit={async executionOption => {
if (executionOption.type === 'privateKey') {
setExecutionOption(executionOption);
await sendAllTransactionTasks(
await sendAllTransactionTasksWithPrivateKeyAccount(
privateKeyToAccount(executionOption.privateKey),
);
} else if (executionOption.type === 'sponsoredSender') {
setExecutionOption(executionOption);
await sendAllTransactionTasksWithCustomWalletRpc(chainId =>
getSponsoredSenderWalletRpcUrl(
executionOption.apiKey,
chainId,
),
);
} else {
setExecutionOption(executionOption);
}
Expand Down Expand Up @@ -390,7 +402,10 @@ const DeployStatus = ({
);
}

if (executionOption.type === 'privateKey') {
if (
executionOption.type === 'privateKey' ||
executionOption.type === 'sponsoredSender'
) {
return (
<PrivateKeyExecution
chain={chain}
Expand Down
36 changes: 34 additions & 2 deletions packages/cli/src/actions/deploy-create2/sendAllTransactionTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import {
onTaskSuccess,
useTransactionTaskStore,
} from '@/stores/transactionTaskStore';
import {sendTransaction} from '@wagmi/core';
import {chainById} from '@/util/chains/chains';
import {http, sendTransaction} from '@wagmi/core';
import {createWalletClient, zeroAddress} from 'viem';
import {PrivateKeyAccount} from 'viem/accounts';

export const sendAllTransactionTasks = async (account: PrivateKeyAccount) => {
export const sendAllTransactionTasksWithPrivateKeyAccount = async (
account: PrivateKeyAccount,
) => {
const taskEntryById = useTransactionTaskStore.getState().taskEntryById;

await Promise.all(
Expand All @@ -22,3 +26,31 @@ export const sendAllTransactionTasks = async (account: PrivateKeyAccount) => {
}),
);
};

export const sendAllTransactionTasksWithCustomWalletRpc = async (
getRpcUrl: (chainId: number) => string,
) => {
const taskEntryById = useTransactionTaskStore.getState().taskEntryById;

await Promise.all(
Object.values(taskEntryById).map(async task => {
const chain = chainById[task.request.chainId];
if (!chain) {
throw new Error(`Chain ${task.request.chainId} not found`);
}

const walletClient = createWalletClient({
transport: http(getRpcUrl(chain.id)),
});

const hash = await walletClient.sendTransaction({
to: task.request.to,
data: task.request.data,
account: zeroAddress, // will be ignored
chain,
});

onTaskSuccess(task.id, hash);
}),
);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {useDeployCreate2WizardStore} from '@/actions/deploy-create2/wizard/deployCreate2WizardStore';
import {
rollupChainToIdentifier,
sourceChainByIdentifier,
} from '@/util/chains/chainIdentifier';
import {rollupChains} from '@/util/chains/chains';
import {rollupChainToIdentifier} from '@/util/chains/chainIdentifier';
import {networkByName} from '@/util/chains/networks';
import {MultiSelect} from '@inkjs/ui';
import {Box, Text} from 'ink';
import {useState} from 'react';
Expand All @@ -16,16 +13,13 @@ export const SelectChains = () => {
throw new Error('Invalid state');
}

const sourceChain = sourceChainByIdentifier[wizardState.network]!;
const chains = rollupChains.filter(
chain => chain.sourceId === sourceChain.id,
);
const network = networkByName[wizardState.network]!;

return (
<Box flexDirection="column">
<Text>
<Text color="cyan" bold>
Select chains to bridge to{' '}
Select chains to deploy to{' '}
</Text>
<Text color="gray">(</Text>
<Text color="yellow">↑↓</Text>
Expand All @@ -36,7 +30,7 @@ export const SelectChains = () => {
<Text color="gray"> to confirm)</Text>
</Text>
<MultiSelect
options={chains.map(chain => ({
options={network.chains.map(chain => ({
label: `${chain.name} (${chain.id})`,
value: rollupChainToIdentifier(chain).split('/')[1]!,
}))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
SupportedNetwork,
zodSupportedNetwork,
} from '@/util/fetchSuperchainRegistryChainList';
import {chainIdByParentChainName} from '@/queries/chains';
import {chainById} from '@/util/chains/chains';

export const SelectNetwork = () => {
const {wizardState, submitSelectNetwork} = useDeployCreate2WizardStore();
Expand All @@ -18,7 +20,9 @@ export const SelectNetwork = () => {
<Text>Select which network the L2 chain is based on</Text>
<Select
options={zodSupportedNetwork.options.map(network => ({
label: network,
label: `${network} (${
chainById[chainIdByParentChainName[network]]?.name
})`,
value: network,
}))}
onChange={(network: string) =>
Expand Down
44 changes: 22 additions & 22 deletions packages/cli/src/commands/bridge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {zodSupportedNetwork} from '@/util/fetchSuperchainRegistryChainList';
import {createTransactionTaskId} from '@/util/transactionTask';
import {getBlockExplorerTxHashLink} from '@/util/blockExplorer';
import {zodAddress, zodPrivateKey, zodValueAmount} from '@/util/schemas';
import {viemChainById} from '@/util/viemChainById';
import {chainById} from '@/util/chains/chains';
import {Badge, Spinner} from '@inkjs/ui';
import {Box, Text} from 'ink';
import {option} from 'pastel';
Expand Down Expand Up @@ -99,7 +99,7 @@ export const BridgeEntrypoint = ({
chain => chainByIdentifier[`${options.network}/${chain}`]!,
);

const sourceChain = viemChainById[chains[0]!.sourceId!]!;
const sourceChain = chainById[chains[0]!.sourceId!]!;

// Defaults to the private key address if no recipient is provided
const recipientAddress =
Expand Down Expand Up @@ -485,26 +485,26 @@ const getContractWriteParams = ({
address: '0xcA11bde05977b3631167028862bE2a173976CA11',
args: [
chains.map(chain => {
const l1StandardBridgeAddress: Address =
// @ts-expect-error
chain.contracts?.l1StandardBridge?.[chain.sourceId!]?.address;

if (!l1StandardBridgeAddress) {
// Invariant: we should always have a l1StandardBridge defined (see chains.ts)
throw new Error('l1StandardBridge not found');
}

return {
target: l1StandardBridgeAddress,
allowFailure: false,
callData: encodeFunctionData({
abi: l1StandardBridgeAbi,
functionName: 'bridgeETHTo',
args: [recipientAddress, 1000000, toHex('')],
}),
value: amountPerChain,
};
}),
const l1StandardBridgeAddress: Address =
// @ts-expect-error
chain.contracts?.l1StandardBridge?.[chain.sourceId!]?.address;

if (!l1StandardBridgeAddress) {
// Invariant: we should always have a l1StandardBridge defined (see chains.ts)
throw new Error('l1StandardBridge not found');
}

return {
target: l1StandardBridgeAddress,
allowFailure: false,
callData: encodeFunctionData({
abi: l1StandardBridgeAbi,
functionName: 'bridgeETHTo',
args: [recipientAddress, 1000000, toHex('')],
}),
value: amountPerChain,
};
}),
],
value: amountPerChain * BigInt(chains.length),
} as const;
Expand Down
49 changes: 49 additions & 0 deletions packages/cli/src/components/ChooseExecutionOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export type ExecutionOption =
}
| {
type: 'externalSigner';
}
| {
type: 'sponsoredSender';
apiKey: string;
};

export const ChooseExecutionOption = ({
Expand All @@ -24,6 +28,8 @@ export const ChooseExecutionOption = ({
onSubmit: (option: ExecutionOption) => void;
}) => {
const [chosePrivateKey, setChosePrivateKey] = useState(false);
const [choseSponsoredSender, setChoseSponsoredSender] = useState(false);

return (
<Box flexDirection="column" gap={1}>
<Box gap={1}>
Expand All @@ -37,12 +43,18 @@ export const ChooseExecutionOption = ({
label: '🔌 Connect a wallet (Metamask / WalletConnect)',
value: 'externalSigner',
},
{
label: '💰 EthDenver sponsored sender',
value: 'sponsoredSender',
},
]}
onChange={option => {
if (option === 'privateKey') {
setChosePrivateKey(true);
} else if (option === 'externalSigner') {
onSubmit({type: 'externalSigner'});
} else if (option === 'sponsoredSender') {
setChoseSponsoredSender(true);
}
}}
/>
Expand All @@ -53,6 +65,13 @@ export const ChooseExecutionOption = ({
}}
/>
)}
{choseSponsoredSender && (
<ApiKeyInput
onSubmit={apiKey => {
onSubmit({type: 'sponsoredSender', apiKey});
}}
/>
)}
</Box>
);
};
Expand Down Expand Up @@ -98,3 +117,33 @@ const PrivateKeyInput = ({onSubmit}: {onSubmit: (privateKey: Hex) => void}) => {
</Box>
);
};

const ApiKeyInput = ({onSubmit}: {onSubmit: (apiKey: string) => void}) => {
const [errorMessage, setErrorMessage] = useState<string>('');
const [resetKey, setResetKey] = useState(0);

return (
<Box flexDirection="column">
<Box>
<Text bold>Enter your API key for the sponsored sender:</Text>
</Box>
<TextInput
key={resetKey}
onSubmit={apiKey => {
if (!apiKey || apiKey.trim() === '') {
setErrorMessage('API key cannot be empty');
setResetKey(prev => prev + 1);
return;
}

onSubmit(apiKey.trim());
}}
/>
{errorMessage && (
<Box>
<Text color="red">{errorMessage ? `❌ ${errorMessage}` : ' '}</Text>
</Box>
)}
</Box>
);
};
Loading

0 comments on commit 7864591

Please sign in to comment.