├── .env.local.example ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── app ├── deployment │ ├── layout.tsx │ ├── page.tsx │ └── step │ │ ├── chain-type │ │ └── page.tsx │ │ ├── configure │ │ └── page.tsx │ │ ├── deploy-local │ │ └── page.tsx │ │ ├── download │ │ └── page.tsx │ │ ├── keyset │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── raas │ │ └── page.tsx ├── favicon.ico ├── layout.tsx ├── opengraph-image.alt.txt ├── opengraph-image.jpg ├── page.tsx ├── twitter-image.alt.txt └── twitter-image.jpg ├── next.config.js ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public ├── AlchemyLogo.png ├── AltLayer.svg ├── ArbOneLogo.svg ├── AsphereLogo.png ├── Caldera.svg ├── CircleWarning.svg ├── Conduit.svg ├── Gelato.svg ├── NovaLogo.svg ├── QuickNodeLogo.svg ├── ZeeveLogo.png ├── fonts │ ├── Unica77LLSub-Black.woff │ ├── Unica77LLSub-Black.woff2 │ ├── Unica77LLSub-BlackItalic.woff │ ├── Unica77LLSub-BlackItalic.woff2 │ ├── Unica77LLSub-Bold.woff │ ├── Unica77LLSub-Bold.woff2 │ ├── Unica77LLSub-BoldItalic.woff │ ├── Unica77LLSub-BoldItalic.woff2 │ ├── Unica77LLSub-ExtraBlack.woff │ ├── Unica77LLSub-ExtraBlack.woff2 │ ├── Unica77LLSub-ExtraBlackItalic.woff │ ├── Unica77LLSub-ExtraBlackItalic.woff2 │ ├── Unica77LLSub-Italic.woff │ ├── Unica77LLSub-Italic.woff2 │ ├── Unica77LLSub-Light.woff │ ├── Unica77LLSub-Light.woff2 │ ├── Unica77LLSub-LightItalic.woff │ ├── Unica77LLSub-LightItalic.woff2 │ ├── Unica77LLSub-Medium.woff │ ├── Unica77LLSub-Medium.woff2 │ ├── Unica77LLSub-MediumItalic.woff │ ├── Unica77LLSub-MediumItalic.woff2 │ ├── Unica77LLSub-Regular.woff │ ├── Unica77LLSub-Regular.woff2 │ ├── Unica77LLSub-Thin.woff │ ├── Unica77LLSub-Thin.woff2 │ ├── Unica77LLSub-ThinItalic.woff │ ├── Unica77LLSub-ThinItalic.woff2 │ ├── Unica77LLWeb-Black.woff │ ├── Unica77LLWeb-Black.woff2 │ ├── Unica77LLWeb-BlackItalic.woff │ ├── Unica77LLWeb-BlackItalic.woff2 │ ├── Unica77LLWeb-Bold.woff │ ├── Unica77LLWeb-Bold.woff2 │ ├── Unica77LLWeb-BoldItalic.woff │ ├── Unica77LLWeb-BoldItalic.woff2 │ ├── Unica77LLWeb-ExtraBlack.woff │ ├── Unica77LLWeb-ExtraBlack.woff2 │ ├── Unica77LLWeb-ExtraBlackItalic.woff │ ├── Unica77LLWeb-ExtraBlackItalic.woff2 │ ├── Unica77LLWeb-Italic.woff │ ├── Unica77LLWeb-Italic.woff2 │ ├── Unica77LLWeb-Light.woff │ ├── Unica77LLWeb-Light.woff2 │ ├── Unica77LLWeb-LightItalic.woff │ ├── Unica77LLWeb-LightItalic.woff2 │ ├── Unica77LLWeb-Medium.woff │ ├── Unica77LLWeb-Medium.woff2 │ ├── Unica77LLWeb-MediumItalic.woff │ ├── Unica77LLWeb-MediumItalic.woff2 │ ├── Unica77LLWeb-Regular.woff │ ├── Unica77LLWeb-Regular.woff2 │ ├── Unica77LLWeb-Thin.woff │ ├── Unica77LLWeb-Thin.woff2 │ ├── Unica77LLWeb-ThinItalic.woff │ └── Unica77LLWeb-ThinItalic.woff2 ├── grainient.png ├── icon-circle-minus.svg ├── illustration-orbit.webp ├── logo.png ├── logo.svg ├── orbit_horizontal_logo_white.svg ├── robots.txt ├── space.webp └── stylus_white.svg ├── src ├── common │ └── constants.ts ├── components │ ├── AppSidebar.tsx │ ├── BackButton.tsx │ ├── Card.tsx │ ├── ChainTypeInfoPanel.tsx │ ├── ChainTypePicker.tsx │ ├── CodeComponent.tsx │ ├── ConnectMenuItem.tsx │ ├── CustomAvatar.tsx │ ├── DeploymentPageContext.tsx │ ├── DeploymentSummary.tsx │ ├── DocsPanel.tsx │ ├── EditableInput.tsx │ ├── ExternalLink.tsx │ ├── GasTokenInput.tsx │ ├── InfoCircleWithTooltip.tsx │ ├── MobileAppSidebar.tsx │ ├── NextButton.tsx │ ├── Providers.tsx │ ├── RaasProviderCard.tsx │ ├── RaasProviderGrid.tsx │ ├── RemovableInput.tsx │ ├── ResetButton.tsx │ ├── ScrollWrapper.tsx │ ├── StepTitle.tsx │ ├── Stepper.tsx │ ├── TextInputWithInfoLink.tsx │ ├── WalletAddressManager.tsx │ └── WrongChainAlert.tsx ├── fonts.ts ├── hooks │ ├── useConfigDownloads.tsx │ ├── useIsMounted.tsx │ └── useStep.tsx ├── setupWagmi.ts ├── styles │ └── globals.css ├── types │ ├── ChainId.ts │ ├── ChainType.ts │ ├── L3Config.ts │ ├── RollupContracts.ts │ ├── Steps.ts │ └── rollupConfigDataType.ts └── utils │ ├── configBuilders.ts │ ├── constants.ts │ ├── defaults.ts │ ├── deployRollup.ts │ ├── errors.ts │ ├── getBlockExplorerUrl.ts │ ├── getRandomWallet.ts │ ├── getRpcUrl.ts │ ├── isUserRejectedError.ts │ ├── localStorageHandler.ts │ ├── schemas.ts │ ├── validators.ts │ └── wallets.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.env.local.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_ARBITRUM_DOCS_BASE_URL= 2 | NEXT_PUBLIC_POSTHOG_KEY= 3 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= 4 | 5 | ADMIN_UI_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | # wagmi 38 | src/generated.ts 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # orbit-deployment-ui 2 | Introducing the Orbit Chain Deployment Interface, a user-friendly tool to effortlessly deploy your own Orbit chain. This repository streamlines the setup process, allowing developers to focus on their decentralized applications. 3 | 4 | ## Getting Started 5 | This tool utilized Next.JS framework. To run this tool, first start the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | 12 | ``` 13 | 14 | Also you can first build the project and then start it: 15 | 16 | ```bash 17 | yarn build 18 | # then 19 | yarn start 20 | 21 | ``` 22 | 23 | Now open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | ## Deployment Procedure 26 | 27 | 1. Navigate to the main page where you will find four selectable options. Click on the Parameters tab. 28 | 2. On the Parameters page, you have the ability to specify your desired Configuration Parameters. Once you have adjusted these to your satisfaction, confirm by sending the transaction. 29 | 3. Upon successful deployment, you are presented with two choices: either set a batch poster or define validators. 30 | 4. If you select the Batch Poster option, you will be directed to a new page. Here, you can add the desired address as a new batch poster by signing the transaction and then sending it. 31 | 5. If you choose to Set Validators, you will be led to another page where you can determine the number of validators you wish to add or remove. In the generated box, enter the corresponding addresses. Checking the checkbox next to each address signals your intent to add that particular address to the validator. 32 | -------------------------------------------------------------------------------- /app/deployment/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { DeploymentPageContextProvider } from '@/components/DeploymentPageContext'; 3 | import { useIsMounted } from '@/hooks/useIsMounted'; 4 | import { PropsWithChildren } from 'react'; 5 | 6 | export default function DeploymentPageWithContext({ children }: PropsWithChildren) { 7 | const isMounted = useIsMounted(); 8 | 9 | if (!isMounted) return null; 10 | return ( 11 | 12 |
13 |
14 | 15 |

This is currently intended only for local devnet development

16 |
17 | {children} 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/deployment/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ExternalLink } from '@/components/ExternalLink'; 4 | import { NextButton } from '@/components/NextButton'; 5 | import { FIRST_STEP, useStep } from '@/hooks/useStep'; 6 | 7 | export default function InfoPage() { 8 | const { goToStep } = useStep(); 9 | return ( 10 | <> 11 |
12 |

13 | All parameters are prefilled with defaults. This includes some randomly generated 14 | addresses. 15 |
16 | We recommend using the defaults for testing purposes. 17 |

18 |

19 | More information around parameter customization and guidance can be found in the{' '} 20 | 24 | documentation 25 | 26 | . We recommend opening the documentation in a side window to follow along. 27 |
28 |

29 |

30 | Please ensure you have at least 1.2 Arbitrum Sepolia ETH (or 0.8 Arbitrum 31 | Sepolia ETH plus 0.4 native token for custom fee token chain) before getting started. 32 |

33 |
34 |

If you don't have enough Sepolia ETH, you can use these faucets:

35 |
    36 |
  • 37 | 38 | https://sepoliafaucet.com/ 39 | 40 |
  • 41 |
  • 42 | 43 | https://www.infura.io/faucet/sepolia 44 | 45 |
  • 46 |
47 |

48 | After you use the faucet you'll have to{' '} 49 | 53 | bridge 54 | {' '} 55 | it over from Sepolia to Arbitrum Sepolia 56 |

57 |
58 |
59 |
60 | goToStep(FIRST_STEP)} /> 61 |
62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /app/deployment/step/chain-type/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ChainTypePicker } from '@/components/ChainTypePicker'; 4 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 5 | import { StepTitle } from '@/components/StepTitle'; 6 | import { ChainTypeInfoPanel } from '@/components/ChainTypeInfoPanel'; 7 | import { useStep } from '@/hooks/useStep'; 8 | import { ChainType } from '@/types/ChainType'; 9 | import Image from 'next/image'; 10 | import { twMerge } from 'tailwind-merge'; 11 | import { useState } from 'react'; 12 | 13 | export default function ChainTypePage() { 14 | const [{ chainType }, dispatch] = useDeploymentPageContext(); 15 | const { nextStep, pickChainFormRef } = useStep(); 16 | const [selectedChainType, setSelectedChainType] = useState(chainType); 17 | const [error, setError] = useState(undefined); 18 | 19 | const handleChainTypeChange = (newChainType: ChainType) => { 20 | setError(''); 21 | setSelectedChainType(newChainType); 22 | }; 23 | 24 | const handleSubmit = (e: React.FormEvent) => { 25 | e.preventDefault(); 26 | if (!selectedChainType) { 27 | setError('Please select a chain type'); 28 | return; 29 | } 30 | setError(''); 31 | dispatch({ 32 | type: 'set_chain_type', 33 | payload: selectedChainType, 34 | }); 35 | nextStep(); 36 | }; 37 | 38 | return ( 39 |
40 |
41 |
42 |
43 | Choose Chain Type 44 | 51 | 58 |
59 |

{error}

60 |
61 |
62 |
68 | {selectedChainType === ChainType.AnyTrust && } 69 | {selectedChainType === ChainType.Rollup && } 70 |
71 |
72 | ); 73 | } 74 | 75 | const AnyTrustInfoPanel = () => ( 76 | 84 | AnyTrust Data Availability Committee 85 | 86 | } 87 | gasFee="Typically less than $0.01" 88 | exampleChain="Arbitrum Nova" 89 | logo={Logo} 90 | /> 91 | ); 92 | 93 | const RollupInfoPanel = () => ( 94 | } 101 | /> 102 | ); 103 | -------------------------------------------------------------------------------- /app/deployment/step/configure/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { FormProvider, useForm } from 'react-hook-form'; 5 | import { useAccount, usePublicClient, useWalletClient } from 'wagmi'; 6 | import { z } from 'zod'; 7 | 8 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 9 | import { DocsPanel } from '@/components/DocsPanel'; 10 | import { GasTokenInput } from '@/components/GasTokenInput'; 11 | import { ScrollWrapper } from '@/components/ScrollWrapper'; 12 | import { StepTitle } from '@/components/StepTitle'; 13 | import { TextInputWithInfoLink } from '@/components/TextInputWithInfoLink'; 14 | import { WalletAddressManager } from '@/components/WalletAddressManager'; 15 | import { useStep } from '@/hooks/useStep'; 16 | import { deployRollup } from '@/utils/deployRollup'; 17 | import { AddressSchema } from '@/utils/schemas'; 18 | import { compareWallets } from '@/utils/wallets'; 19 | import { zodResolver } from '@hookform/resolvers/zod'; 20 | import { showSigningErrorToast } from '@/utils/errors'; 21 | 22 | const WalletAddressListSchema = z.array(AddressSchema).superRefine((data, ctx) => { 23 | const seen = new Set(); 24 | 25 | data.forEach((address, index) => { 26 | if (seen.has(address)) { 27 | ctx.addIssue({ 28 | code: z.ZodIssueCode.custom, 29 | message: 'Duplicate addresses are not allowed', 30 | path: [index], 31 | }); 32 | } else { 33 | seen.add(address); 34 | } 35 | }); 36 | }); 37 | 38 | const rollupConfigSchema = z.object({ 39 | chainId: z.number().gt(0), 40 | chainName: z.string().nonempty(), 41 | confirmPeriodBlocks: z.number().gt(0), 42 | stakeToken: z.string(), 43 | baseStake: z.number().gt(0), 44 | owner: AddressSchema, 45 | nativeToken: AddressSchema, 46 | validators: WalletAddressListSchema, 47 | batch_posters: WalletAddressListSchema, 48 | }); 49 | 50 | export type RollupConfigFormValues = z.infer; 51 | 52 | export default function RollupConfigPage() { 53 | const [ 54 | { rollupConfig, chainType, validators: savedWallets, batch_posters: savedBatchPosters }, 55 | dispatch, 56 | ] = useDeploymentPageContext(); 57 | const { nextStep, rollupConfigFormRef } = useStep(); 58 | const [tokenDecimals, setTokenDecimals] = useState(18); 59 | const { address } = useAccount(); 60 | const { data: walletClient } = useWalletClient(); 61 | const publicClient = usePublicClient(); 62 | 63 | // refines the schema to check if token decimals is 18 64 | // done here because zod schema must be synchronous 65 | const refinedRollupConfigSchema = rollupConfigSchema.refine( 66 | (data) => { 67 | return tokenDecimals && tokenDecimals === 18; 68 | }, 69 | { 70 | message: 'Token decimals must be 18.', 71 | path: ['nativeToken'], 72 | }, 73 | ); 74 | 75 | const methods = useForm>({ 76 | defaultValues: { 77 | ...rollupConfig, 78 | validators: savedWallets?.map((wallet) => wallet.address) || [], 79 | batch_posters: savedBatchPosters?.map((wallet) => wallet.address) || [], 80 | }, 81 | mode: 'onBlur', 82 | resolver: zodResolver(refinedRollupConfigSchema), 83 | }); 84 | 85 | const { 86 | handleSubmit, 87 | register, 88 | formState: { errors }, 89 | } = methods; 90 | 91 | const onSubmit = async (updatedRollupConfig: RollupConfigFormValues) => { 92 | try { 93 | dispatch({ 94 | type: 'set_rollup_config', 95 | payload: { ...rollupConfig, ...updatedRollupConfig, stakeToken: rollupConfig.stakeToken }, 96 | }); 97 | 98 | // Compare and update validators 99 | const updatedValidators = compareWallets(savedWallets || [], updatedRollupConfig.validators); 100 | dispatch({ 101 | type: 'set_validators', 102 | payload: updatedValidators, 103 | }); 104 | 105 | // Compare and update batch posters 106 | const updatedBatchPosters = compareWallets( 107 | savedBatchPosters || [], 108 | updatedRollupConfig.batch_posters, 109 | ); 110 | dispatch({ 111 | type: 'set_batch_posters', 112 | payload: updatedBatchPosters, 113 | }); 114 | 115 | dispatch({ type: 'set_is_loading', payload: true }); 116 | if (!walletClient || !address) return; 117 | const rollupArgs = { 118 | rollupConfig: { 119 | ...rollupConfig, 120 | ...updatedRollupConfig, 121 | stakeToken: rollupConfig.stakeToken, 122 | }, 123 | validators: updatedValidators, 124 | batchPosters: updatedBatchPosters, 125 | chainType, 126 | account: address, 127 | publicClient, 128 | walletClient, 129 | }; 130 | const rollupContracts = await deployRollup(rollupArgs); 131 | dispatch({ type: 'set_rollup_contracts', payload: rollupContracts }); 132 | nextStep(); 133 | } catch (e) { 134 | console.error(e); 135 | showSigningErrorToast(e); 136 | } finally { 137 | dispatch({ type: 'set_is_loading', payload: false }); 138 | } 139 | }; 140 | 141 | return ( 142 | 143 |
144 |
145 |
146 | Review & Deploy 147 | 153 | register('chainId', { 154 | setValueAs: (value) => Number(value), 155 | }) 156 | } 157 | error={errors.chainId?.message} 158 | anchor={'chain-id'} 159 | /> 160 | register('chainName')} 165 | anchor={'chain-name'} 166 | /> 167 | 168 | 174 | register('confirmPeriodBlocks', { 175 | setValueAs: (value) => Number(value), 176 | }) 177 | } 178 | anchor={'challenge-period-blocks'} 179 | /> 180 | 181 | 187 | 188 | 195 | register('baseStake', { 196 | setValueAs: (value) => Number(value), 197 | }) 198 | } 199 | anchor={'base-stake'} 200 | /> 201 | 202 | register('owner')} 206 | error={errors.owner?.message} 207 | anchor={'owner'} 208 | /> 209 | 210 | 215 | 216 | 221 | 222 |
223 |
224 | 225 |
226 |
227 |
228 |
229 | ); 230 | } 231 | -------------------------------------------------------------------------------- /app/deployment/step/deploy-local/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useMemo, useState } from 'react'; 4 | import { useNetwork } from 'wagmi'; 5 | import { useClipboard } from 'use-clipboard-copy'; 6 | 7 | import { ExternalLink } from '@/components/ExternalLink'; 8 | import { StepTitle } from '@/components/StepTitle'; 9 | import { useConfigDownloads } from '@/hooks/useConfigDownloads'; 10 | import { ChainId } from '@/types/ChainId'; 11 | import { getRpcUrl } from '@/utils/getRpcUrl'; 12 | 13 | const CodeSnippet = ({ code }: { code: string }) => { 14 | const [showTooltip, setShowTooltip] = useState(false); 15 | const clipboard = useClipboard(); 16 | const copyToClipboard = (dataToCopy: any) => { 17 | clipboard.copy(dataToCopy); 18 | setShowTooltip(true); 19 | setTimeout(() => setShowTooltip(false), 500); 20 | }; 21 | 22 | return ( 23 |
24 |
25 | 36 |
37 |
{code}
38 |
39 | ); 40 | }; 41 | 42 | export default function DeployLocallyPage() { 43 | const { chain } = useNetwork(); 44 | const { downloadRollupConfig, downloadL3Config } = useConfigDownloads(); 45 | 46 | const parentChainRpcUrl = useMemo( 47 | () => chain?.rpcUrls?.default?.http[0] ?? getRpcUrl(ChainId.ArbitrumSepolia), 48 | [chain], 49 | ); 50 | 51 | return ( 52 |
53 | Deploy Locally 54 |

55 | Once you've downloaded both config files, please follow the steps below to complete local 56 | deployment of your Orbit chain. For more details on the steps involved and additional 57 | context, please visit the{' '} 58 | 62 | documentation 63 | 64 | . 65 |

66 |
    67 |
  1. 68 | Clone the{' '} 69 | 73 | https://github.com/OffchainLabs/orbit-setup-script 74 | {' '} 75 | repository, and run: 76 |
    77 |
    78 | 79 |
    80 | Then, move both the{' '} 81 | 82 | nodeConfig.json 83 | {' '} 84 | and{' '} 85 | 86 | orbitSetupScriptConfig.json 87 | {' '} 88 | files into the config directory within the cloned repo. 89 |
  2. 90 |
    91 |
  3. 92 | Launch Docker, and in the base directory, run:
    93 |
    94 | 95 |
    96 | This will launch the node with a Public RPC reachable at{' '} 97 | 98 | http://localhost:8449 99 | {' '} 100 | and a corresponding BlockScout explorer instance, viewable at{' '} 101 | 102 | http://localhost 103 | 104 | . 105 |
  4. 106 |
    107 |
  5. 108 | Then, add the private key for the wallet you used to deploy the rollup contracts earlier 109 | in the following command, and run it:
    110 |
    111 | 114 |
  6. 115 |
    116 |
  7. 117 | The Orbit chain is now set up. You can find all the information about the newly deployed 118 | chain in the outputInfo.json file, which is in the main directory of the script 119 | folder. 120 |
  8. 121 |
    122 |
  9. 123 | Optionally, to track logs, run the following command within the base directory: 124 |
    125 |
    126 | 127 |
  10. 128 |
129 |
130 | ); 131 | } 132 | -------------------------------------------------------------------------------- /app/deployment/step/download/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { CodeComponent } from '@/components/CodeComponent'; 4 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 5 | import { DeploymentSummary } from '@/components/DeploymentSummary'; 6 | import { StepTitle } from '@/components/StepTitle'; 7 | import { useConfigDownloads } from '@/hooks/useConfigDownloads'; 8 | 9 | export default function DownloadPage() { 10 | const { rollupConfigDownloadData, rollupConfigDisplayData, l3Config, downloadZippedConfigs } = 11 | useConfigDownloads(); 12 | const [{}, dispatch] = useDeploymentPageContext(); 13 | 14 | return ( 15 |
16 |
17 | Download Config 18 |

Configuration files are required to deploy locally.

19 | 28 |

29 | You are able to configure even more settings.{' '} 30 | 34 | Read more in the SDK docs. 35 | 36 |

37 |
38 |
39 |
40 |

Rollup Config

41 | {!rollupConfigDownloadData ? ( 42 |
No rollup data found.
43 | ) : ( 44 | 49 | )} 50 |
51 |
52 |

L3 Config

53 | {!l3Config ? ( 54 |
No L3 configuration data found.
55 | ) : ( 56 | 61 | )} 62 |
63 |
64 | 65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /app/deployment/step/keyset/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useAccount, usePublicClient, useWalletClient } from 'wagmi'; 4 | import { setValidKeyset } from '@arbitrum/orbit-sdk'; 5 | 6 | import { useStep } from '@/hooks/useStep'; 7 | import { assertIsAddress } from '@/utils/validators'; 8 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 9 | import { InfoCircleWithTooltip } from '@/components/InfoCircleWithTooltip'; 10 | import { StepTitle } from '@/components/StepTitle'; 11 | import { showSigningErrorToast } from '@/utils/errors'; 12 | 13 | const DEFAULT_KEYSET_STRING = 14 | '0x00000000000000010000000000000001012160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 15 | 16 | export default function KeysetPage() { 17 | const { nextStep, keysetFormRef } = useStep(); 18 | const [{ rollupContracts }, dispatch] = useDeploymentPageContext(); 19 | const { address } = useAccount(); 20 | const { data: walletClient } = useWalletClient(); 21 | const publicClient = usePublicClient(); 22 | 23 | const handleSubmit = async (e: React.FormEvent) => { 24 | e.preventDefault(); 25 | 26 | try { 27 | dispatch({ type: 'set_is_loading', payload: true }); 28 | if (!walletClient || !address) return; 29 | 30 | const upgradeExecutor = rollupContracts?.upgradeExecutor; 31 | const sequencerInbox = rollupContracts?.sequencerInbox; 32 | 33 | assertIsAddress(upgradeExecutor); 34 | assertIsAddress(sequencerInbox); 35 | 36 | await setValidKeyset({ 37 | coreContracts: { upgradeExecutor, sequencerInbox }, 38 | keyset: DEFAULT_KEYSET_STRING, 39 | walletClient, 40 | publicClient, 41 | }); 42 | nextStep(); 43 | } catch (e) { 44 | console.error(e); 45 | showSigningErrorToast(e); 46 | } finally { 47 | dispatch({ type: 'set_is_loading', payload: false }); 48 | } 49 | }; 50 | 51 | return ( 52 |
53 | Configure Keyset 54 |

55 | A Keyset specifies the public keys of Committee members and the number of signatures 56 | required for a Data Availability Certificate to be valid. Keysets make Committee membership 57 | changes possible and provide Committee members the ability to change their keys. 58 |

59 |

Default Keyset

60 |
61 |
62 |           {DEFAULT_KEYSET_STRING}
63 |         
64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/deployment/step/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { BackButton } from '@/components/BackButton'; 3 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 4 | import { ExternalLink } from '@/components/ExternalLink'; 5 | import { NextButton } from '@/components/NextButton'; 6 | import { ResetButton } from '@/components/ResetButton'; 7 | import { Stepper } from '@/components/Stepper'; 8 | import { WrongChainAlert } from '@/components/WrongChainAlert'; 9 | import { useStep } from '@/hooks/useStep'; 10 | import { ChainId } from '@/types/ChainId'; 11 | import { DownloadAnyTrustConfig, DownloadConfig, RaasProviders } from '@/types/Steps'; 12 | import { ConnectButton } from '@rainbow-me/rainbowkit'; 13 | import { PropsWithChildren } from 'react'; 14 | import { useAccount, useNetwork } from 'wagmi'; 15 | 16 | export default function StepLayout({ children }: PropsWithChildren) { 17 | const { submitForm, currentStep, isLastStep } = useStep(); 18 | const [{ isLoading, isDownloadCompleted }] = useDeploymentPageContext(); 19 | const isDownloadRequired = 20 | !isDownloadCompleted && 21 | (currentStep === DownloadConfig || currentStep === DownloadAnyTrustConfig); 22 | const { isConnected, address } = useAccount(); 23 | const { chain } = useNetwork(); 24 | 25 | const isWrongChain = chain?.id !== ChainId.ArbitrumSepolia; 26 | 27 | const shouldShowStepper = currentStep !== RaasProviders; 28 | 29 | return ( 30 | <> 31 |
{shouldShowStepper && }
32 | {(!isConnected || !address) && ( 33 |
34 |
35 |

Please connect your wallet to continue.

36 | 37 |
38 |
39 | )} 40 | {isConnected && isWrongChain && } 41 | {isConnected && !isWrongChain && ( 42 | <> 43 | 47 | Open supporting documentation for this flow 48 | 49 | {children} 50 | {isDownloadRequired && ( 51 |

Please download zip file before continuing

52 | )} 53 |
54 | 55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 | 63 | )} 64 | 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /app/deployment/step/raas/page.tsx: -------------------------------------------------------------------------------- 1 | import { RaasProviderGrid } from '@/components/RaasProviderGrid'; 2 | import { StepTitle } from '@/components/StepTitle'; 3 | import Link from 'next/link'; 4 | 5 | export default function RaasProvidersPage() { 6 | return ( 7 |
8 |
9 | Chain Management & Next Steps 10 | 11 |
12 |

Manage Your Chain

13 |

14 | Now that your testnet Orbit chain is deployed and running, you can access the Orbit 15 | Admin UI to manage your chain. The admin interface provides tools for configuring chain 16 | parameters. 17 |

18 | 23 | Go to Orbit Admin 24 | 25 |
26 | 27 |
28 |

Rollup-as-a-Service Providers

29 |

30 | As an optional next step, if you'd like to deploy to mainnet, we suggest using a 31 | Rollup-as-a-Service Provider. They have the context and experience to help with 32 | deploying contracts, protocol modifications, and maintaining infrastructure. 33 |

34 | 35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/app/favicon.ico -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { AppSidebar } from '@/components/AppSidebar'; 2 | import { MobileAppSidebar } from '@/components/MobileAppSidebar'; 3 | import { Providers } from '@/components/Providers'; 4 | import { unica77 } from '@/fonts'; 5 | import { Metadata } from 'next'; 6 | import posthog from 'posthog-js'; 7 | 8 | if (typeof window !== 'undefined' && typeof process.env.NEXT_PUBLIC_POSTHOG_KEY === 'string') { 9 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { 10 | api_host: 'https://app.posthog.com', 11 | loaded: (posthog) => { 12 | if (process.env.NODE_ENV !== 'production') { 13 | // when in dev, you can see data that would be sent in prod (in devtools) 14 | posthog.debug(); 15 | } 16 | }, 17 | // store data in temporary memory that expires with each session 18 | persistence: 'memory', 19 | autocapture: true, 20 | disable_session_recording: true, 21 | }); 22 | } 23 | 24 | const metadataInfo = { 25 | title: 'Arbitrum Orbit Deployment UI', 26 | description: `Utilize the Orbit chain deployment portal to launch your own devnet Arbitrum Orbit chain. By following these steps, you will have a local devnet chain that hosts EVM-compatible contracts (you will not have a mainnet L2 chain) 27 | 28 | We are building the tech and docs that will help you move your project from "local devnet chain that settles to Arbitrum Sepolia" to "public production-ready chain that settles to Arbitrum One or Arbitrum Nova". Stay tuned!`, 29 | }; 30 | 31 | export const metadata: Metadata = { 32 | ...metadataInfo, 33 | twitter: { 34 | card: 'summary_large_image', 35 | ...metadataInfo, 36 | }, 37 | }; 38 | 39 | export default function RootLayout({ children }: { children: React.ReactNode }) { 40 | return ( 41 | 42 | 43 | 44 |
45 | 46 |
47 | 48 |
{children}
49 |
50 |
51 |
52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /app/opengraph-image.alt.txt: -------------------------------------------------------------------------------- 1 | Arbitrum Orbit Deployment UI -------------------------------------------------------------------------------- /app/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/app/opengraph-image.jpg -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import Image from 'next/image'; 3 | import { 4 | DocumentTextIcon, 5 | RocketLaunchIcon, 6 | WrenchScrewdriverIcon, 7 | } from '@heroicons/react/24/outline'; 8 | 9 | import { Card } from '@/components/Card'; 10 | import { ExternalLink } from '@/components/ExternalLink'; 11 | import { DISCORD_LINK, GET_HELP_LINK } from '@/common/constants'; 12 | import { RaasProviderGrid } from '@/components/RaasProviderGrid'; 13 | 14 | const metadataContent = { 15 | title: 'Arbitrum - Launch your own Orbit Chain!', 16 | description: 17 | 'Arbitrum Orbit is the ideal way to permissionlessly launch your own custom chain. Build a chain on top of Arbitrum One and Ethereum.', 18 | }; 19 | 20 | // Generate server-side metadata for this page 21 | export function generateMetadata(): Metadata { 22 | return { 23 | title: metadataContent.title, 24 | description: metadataContent.description, 25 | openGraph: { 26 | title: metadataContent.title, 27 | description: metadataContent.description, 28 | locale: 'en_US', 29 | type: 'website', 30 | }, 31 | twitter: { 32 | card: 'summary_large_image', 33 | title: metadataContent.title, 34 | description: metadataContent.description, 35 | }, 36 | }; 37 | } 38 | 39 | type OptionalOrbitPageParams = { 40 | searchParams: { 41 | orbitChain?: string; 42 | }; 43 | }; 44 | 45 | export default function LaunchPage(params: OptionalOrbitPageParams) { 46 | return ( 47 |
48 | {/* Banner Image */} 49 | 50 |

Orbit

51 | Bridge 58 |

Launch your own Orbit Chain

59 | Bridge 66 |
67 | 68 |
69 | {/* Orbit Docs card */} 70 | 76 |
77 |
78 | 79 |
80 |

Read the docs

81 |

82 | Everything you need to know about Orbit chains. 83 |

84 |
85 |
86 | 87 | {/* Playground card */} 88 | 94 |
95 |
96 | 97 |
98 |

Launch on testnet

99 |

100 | A step-by-step guide for launching your own Orbit chain in under 10 minutes. 101 |

102 |
103 |
104 | 105 | {/* Admin UI Card */} 106 | 112 |
113 |
114 | 115 |
116 |

Manage your chain

117 |

118 | Modify and optimize the parameters of your existing chains.{' '} 119 |

120 |
121 |
122 |
123 | 124 | {/* Stylus card */} 125 | 130 |
131 | Stylus 138 | 139 |
140 |
141 |
142 | Stylus is now on mainnet. Do More. With Stylus. 143 |
144 |
145 | 146 | Learn More 147 |
148 |
149 |
150 | 151 | {/* RaaS list */} 152 |
153 | 154 |
Launch to Mainnet
155 |
156 |
157 | Use a third-party Rollup as a Service providers can help take your testnet orbit chain 158 | to mainnet. 159 |
160 | 161 |
162 |
163 | 164 | {/* Orbit SDK link */} 165 | 171 |
172 |
Get started on your own
173 |
174 |
175 |

Utilize the Orbit SDK to launch your chain to mainnet without any support.

176 |

177 | Please be aware that running a chain involves various infrastructure needs. For 178 | increased chances of success, we highly recommend using a Rollup as a Service 179 | provider. 180 |

181 |
182 | 183 |
Dive into the SDK
184 |
185 |
186 | 187 | {/* Other links */} 188 |
189 | {/* Discord */} 190 | 196 |
197 |
198 | Receive support by joining the{' '} 199 | #orbit-support{' '} 200 | channel in Discord. 201 |
202 | 203 |
Get support
204 |
205 |
206 | 207 | {/* Contact us */} 208 | 214 |
215 |
216 | Connect with us to learn if an Orbit chain makes sense for you. 217 |
218 |
Get in touch
219 |
220 |
221 |
222 |
223 | ); 224 | } 225 | -------------------------------------------------------------------------------- /app/twitter-image.alt.txt: -------------------------------------------------------------------------------- 1 | Arbitrum Orbit Deployment UI -------------------------------------------------------------------------------- /app/twitter-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/app/twitter-image.jpg -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { ADMIN_UI_URL } = process.env; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | reactStrictMode: true, 6 | redirects: () => [ 7 | { 8 | source: '/deployment/step/(\\d)', 9 | destination: '/deployment/step/chain-type', 10 | permanent: true, 11 | }, 12 | ], 13 | async rewrites() { 14 | return [ 15 | { 16 | source: '/:path*', 17 | destination: `/:path*`, 18 | }, 19 | { 20 | source: '/admin', 21 | destination: `${ADMIN_UI_URL}/admin`, 22 | }, 23 | { 24 | source: '/admin/:path*', 25 | destination: `${ADMIN_UI_URL}/admin/:path*`, 26 | }, 27 | ]; 28 | }, 29 | }; 30 | 31 | module.exports = nextConfig; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-for-l3", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "prettier:format": "prettier --config-precedence file-override --write \"src/**/*.{tsx,ts,scss,md,json}\"" 11 | }, 12 | "dependencies": { 13 | "@arbitrum/orbit-sdk": "^0.20.0", 14 | "@ethersproject/providers": "^5.7.2", 15 | "@heroicons/react": "^2.1.1", 16 | "@hookform/resolvers": "^3.2.0", 17 | "@offchainlabs/cobalt": "0.3.12", 18 | "@rainbow-me/rainbowkit": "^1.0.8", 19 | "boring-avatars": "^1.10.2", 20 | "encoding": "^0.1.13", 21 | "jszip": "^3.10.1", 22 | "lokijs": "^1.5.12", 23 | "next": "^13.5.6", 24 | "pino-pretty": "^10.2.0", 25 | "posthog-js": "^1.67.1", 26 | "primeflex": "^3.3.1", 27 | "primeicons": "^6.0.1", 28 | "query-string": "^8.1.0", 29 | "react": "18.2.0", 30 | "react-dom": "18.2.0", 31 | "react-hook-form": "^7.45.4", 32 | "react-syntax-highlighter": "^15.5.0", 33 | "react-toastify": "^10.0.4", 34 | "react-transition-group": "^4.4.5", 35 | "use-clipboard-copy": "^0.2.0", 36 | "use-query-params": "^2.2.1", 37 | "viem": "^1.20.0", 38 | "wagmi": "^1.3.9", 39 | "zod": "^3.21.4" 40 | }, 41 | "devDependencies": { 42 | "@offchainlabs/prettier-config": "^0.2.1", 43 | "@types/dotenv": "^8.2.0", 44 | "@types/node": "18.16.3", 45 | "@types/react": "18.2.2", 46 | "@types/react-dom": "18.2.3", 47 | "@types/react-syntax-highlighter": "^15.5.7", 48 | "autoprefixer": "10.4.14", 49 | "dotenv": "^16.0.3", 50 | "eslint": "8.39.0", 51 | "eslint-config-next": "13.4.13", 52 | "postcss": "8.4.23", 53 | "prettier": "^2.8.8", 54 | "prettier-plugin-tailwindcss": "^0.3.0", 55 | "tailwind-merge": "^1.14.0", 56 | "tailwindcss": "^3.3.3", 57 | "typescript": "5.0.4" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss': {}, 5 | 'autoprefixer': {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@offchainlabs/prettier-config'), 3 | 4 | // Plugins 5 | plugins: [require('prettier-plugin-tailwindcss')], 6 | }; 7 | -------------------------------------------------------------------------------- /public/AlchemyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/AlchemyLogo.png -------------------------------------------------------------------------------- /public/ArbOneLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/AsphereLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/AsphereLogo.png -------------------------------------------------------------------------------- /public/CircleWarning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/Conduit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/Gelato.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/NovaLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/QuickNodeLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/ZeeveLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/ZeeveLogo.png -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Black.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Black.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-BlackItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-BlackItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Bold.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ExtraBlack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ExtraBlack.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ExtraBlack.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ExtraBlack.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ExtraBlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ExtraBlackItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ExtraBlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ExtraBlackItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Light.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-LightItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-LightItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Medium.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-MediumItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-MediumItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Regular.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Thin.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-Thin.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ThinItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLSub-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLSub-ThinItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Black.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Black.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-BlackItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-BlackItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Bold.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ExtraBlack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ExtraBlack.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ExtraBlack.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ExtraBlack.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ExtraBlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ExtraBlackItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ExtraBlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ExtraBlackItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Light.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-LightItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-LightItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Medium.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-MediumItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-MediumItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Regular.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Thin.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-Thin.woff2 -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ThinItalic.woff -------------------------------------------------------------------------------- /public/fonts/Unica77LLWeb-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/fonts/Unica77LLWeb-ThinItalic.woff2 -------------------------------------------------------------------------------- /public/grainient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/grainient.png -------------------------------------------------------------------------------- /public/icon-circle-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/illustration-orbit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/illustration-orbit.webp -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/logo.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/orbit_horizontal_logo_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/space.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/arbitrum-orbit-deployment-ui/e4e82fca2428ad4c8a20ef19b7b958293ffef958/public/space.webp -------------------------------------------------------------------------------- /public/stylus_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const GET_HELP_LINK = 'https://support.arbitrum.io/'; 2 | 3 | export const DISCORD_LINK = 'https://discord.com/invite/ZpZuw7p'; 4 | -------------------------------------------------------------------------------- /src/components/AppSidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import dynamic from 'next/dynamic'; 3 | import { usePostHog } from 'posthog-js/react'; 4 | 5 | // Dynamically import the Sidebar component with SSR disabled 6 | const DynamicSidebar = dynamic( 7 | () => import('@offchainlabs/cobalt').then((mod) => ({ default: mod.Sidebar })), 8 | { ssr: false }, 9 | ); 10 | 11 | export const AppSidebar = () => { 12 | const posthog = usePostHog(); 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/BackButton.tsx: -------------------------------------------------------------------------------- 1 | import { useStep } from '@/hooks/useStep'; 2 | import { ButtonHTMLAttributes, FC } from 'react'; 3 | import { twMerge } from 'tailwind-merge'; 4 | 5 | interface BackButtonProps extends ButtonHTMLAttributes { 6 | isLoading?: boolean; 7 | } 8 | 9 | export const BackButton: FC = ({ isLoading }) => { 10 | const { currentStep, previousStep } = useStep(); 11 | const isFirstStep = currentStep?.previous === null; 12 | const isDisabled = isFirstStep || isLoading; 13 | return ( 14 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link, { LinkProps as NextLinkProps } from 'next/link'; 4 | import React, { PropsWithChildren } from 'react'; 5 | import { usePostHog } from 'posthog-js/react'; 6 | import { ArrowRightIcon } from '@heroicons/react/24/outline'; 7 | import { twMerge } from 'tailwind-merge'; 8 | import { ExternalLink } from './ExternalLink'; 9 | 10 | // Our card component can act as a Next-Link / External Link / Button or a simple div 11 | type CardType = 'link' | 'externalLink' | 'button' | 'div'; 12 | 13 | type AnalyticsProps = { 14 | eventName: string; 15 | eventProperties?: { [property: string]: string }; 16 | }; 17 | 18 | type LinkProps = { cardType: 'link' } & NextLinkProps; 19 | type ExternalLinkProps = { 20 | cardType: 'externalLink'; 21 | } & React.AnchorHTMLAttributes; 22 | type ButtonProps = { 23 | cardType: 'button'; 24 | } & React.ButtonHTMLAttributes; 25 | type DivProps = React.HTMLProps; 26 | 27 | type CardProps = LinkProps | ButtonProps | ExternalLinkProps | DivProps; 28 | type CardAnalyticsProps = { 29 | className?: string; 30 | cardType?: CardType; 31 | analyticsProps?: AnalyticsProps; 32 | showExternalLinkArrow?: boolean; 33 | }; 34 | type CardPropsWithAnalytics = CardProps & CardAnalyticsProps; 35 | 36 | const ExternalLinkArrow = () => ( 37 | 38 | ); 39 | 40 | export const Card = ({ 41 | children, 42 | cardType = 'div', 43 | showExternalLinkArrow, 44 | ...props 45 | }: PropsWithChildren) => { 46 | const posthog = usePostHog(); 47 | 48 | const commonClassName = twMerge( 49 | 'group w-full overflow-hidden rounded-lg bg-default-black p-4 text-sm relative transition-colors duration-300', 50 | props.className, 51 | ); 52 | 53 | const captureEventOnClick = (e: any) => { 54 | try { 55 | if (props.analyticsProps) { 56 | posthog?.capture(props.analyticsProps.eventName, props.analyticsProps.eventProperties); 57 | } 58 | } catch (e) {} 59 | 60 | // execute the actual click event 61 | if (typeof props.onClick === 'function') { 62 | props.onClick(e); 63 | } 64 | }; 65 | 66 | // Card that can also act as a next-optimized internal link 67 | if (cardType === 'link') { 68 | const { analyticsProps, cardType, ...cardProps } = props as LinkProps & CardAnalyticsProps; 69 | return ( 70 | 75 | {children} 76 | {showExternalLinkArrow && } 77 | 78 | ); 79 | } 80 | 81 | // Card that can also act as an external link 82 | if (cardType === 'externalLink') { 83 | const cardProps = props as ExternalLinkProps; 84 | return ( 85 | 90 | {children} 91 | 92 | {showExternalLinkArrow && } 93 | 94 | ); 95 | } 96 | 97 | // Card that can also act as a button 98 | if (cardType === 'button') { 99 | const cardProps = props as ButtonProps; 100 | return ( 101 | 109 | ); 110 | } 111 | 112 | // Normal Card without any additional functionality 113 | const cardProps = props as DivProps; 114 | return ( 115 |
123 | {children} 124 | {showExternalLinkArrow && } 125 |
126 | ); 127 | }; 128 | -------------------------------------------------------------------------------- /src/components/ChainTypeInfoPanel.tsx: -------------------------------------------------------------------------------- 1 | export const ChainTypeInfoPanel = ({ 2 | header, 3 | description, 4 | dataAvailabilityLayer, 5 | gasFee, 6 | exampleChain, 7 | logo, 8 | }: { 9 | header: string; 10 | description: string; 11 | dataAvailabilityLayer: string | JSX.Element; 12 | gasFee: string; 13 | exampleChain: string | JSX.Element; 14 | logo: JSX.Element; 15 | }) => { 16 | return ( 17 |
18 |

{header}

19 |

{description}

20 |
21 |

Data Availability Layer

22 |

{dataAvailabilityLayer}

23 |
24 |
25 |
26 |

Gas Fee

27 |

{gasFee}

28 |
29 |
30 |

Example Chain

31 |
32 | {logo} 33 | {exampleChain} 34 |
35 |
36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/ChainTypePicker.tsx: -------------------------------------------------------------------------------- 1 | import { ChainType } from '@/types/ChainType'; 2 | import { twJoin } from 'tailwind-merge'; 3 | 4 | type ChainTypePickerProps = { 5 | selectedChainType?: string; 6 | onClick: (chainType: ChainType) => void; 7 | chainType?: ChainType; 8 | label: string; 9 | description: string; 10 | }; 11 | 12 | export const ChainTypePicker: React.FC = ({ 13 | selectedChainType, 14 | onClick, 15 | chainType, 16 | label, 17 | description, 18 | }) => { 19 | return ( 20 |
{ 26 | if (!chainType) return; 27 | onClick(chainType); 28 | }} 29 | > 30 |
31 |

{label}

32 |

{description}

33 |
34 |
35 |
36 |

37 | Transaction data posted{' '} 38 | {chainType === ChainType.Rollup ? 'to Ethereum' : 'by a Data Availability Committee'} 39 |

40 |
41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/CodeComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useConfigDownloads } from '@/hooks/useConfigDownloads'; 2 | import { FC, useState } from 'react'; 3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 4 | const { nightOwl } = require('react-syntax-highlighter/dist/cjs/styles/prism'); 5 | import { useClipboard } from 'use-clipboard-copy'; 6 | 7 | interface CodeComponentProps { 8 | fileName: string; 9 | dataToDownload: any; 10 | dataToDisplay: any; 11 | } 12 | 13 | export const CodeComponent: FC = ({ 14 | fileName, 15 | dataToDownload, 16 | dataToDisplay, 17 | }) => { 18 | const [showTooltip, setShowTooltip] = useState(false); 19 | const clipboard = useClipboard(); 20 | 21 | const copyToClipboard = (dataToCopy: any) => { 22 | clipboard.copy(JSON.stringify(dataToCopy, null, 2)); 23 | setShowTooltip(true); 24 | setTimeout(() => setShowTooltip(false), 500); 25 | }; 26 | 27 | return ( 28 |
29 |
30 | 41 |
42 | 49 | {JSON.stringify(dataToDisplay, null, 2)} 50 | 51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/ConnectMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import { PlusCircleIcon } from '@heroicons/react/24/outline'; 2 | import { MenuItem } from '@offchainlabs/cobalt'; 3 | import { ConnectButton } from '@rainbow-me/rainbowkit'; 4 | import { CustomAvatar } from './CustomAvatar'; 5 | 6 | export const CustomConnectButton = () => { 7 | return ( 8 | 9 | {({ 10 | account, 11 | chain, 12 | openAccountModal, 13 | openChainModal, 14 | openConnectModal, 15 | authenticationStatus, 16 | mounted, 17 | }) => { 18 | const ready = mounted && authenticationStatus !== 'loading'; 19 | const connected = 20 | ready && 21 | account && 22 | chain && 23 | (!authenticationStatus || authenticationStatus === 'authenticated'); 24 | 25 | return ( 26 |
36 | {(() => { 37 | if (!connected) { 38 | return ( 39 | } 43 | className="border-lime-dark bg-lime-dark py-3" 44 | isMobile 45 | /> 46 | ); 47 | } 48 | 49 | return ( 50 |
51 | 67 | {chain.unsupported ? ( 68 | 81 | ) : ( 82 | 106 | )} 107 |
108 | ); 109 | })()} 110 |
111 | ); 112 | }} 113 |
114 | ); 115 | }; 116 | -------------------------------------------------------------------------------- /src/components/CustomAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { AvatarComponent } from '@rainbow-me/rainbowkit'; 2 | import BoringAvatar from 'boring-avatars'; 3 | import { useEnsName } from 'wagmi'; 4 | 5 | export const CustomBoringAvatar = ({ size, name }: { size: number; name?: string }) => { 6 | return ( 7 | 13 | ); 14 | }; 15 | export const CustomAvatar: AvatarComponent = ({ address, ensImage, size }) => { 16 | const { data: ensName } = useEnsName({ 17 | address: address as `0x${string}`, 18 | chainId: 1, 19 | }); 20 | 21 | return ensImage ? ( 22 | 23 | ) : ( 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/DeploymentPageContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { CoreContracts } from '@arbitrum/orbit-sdk'; 3 | import { Wallet } from '@/types/RollupContracts'; 4 | import { RollupConfig } from '@/types/rollupConfigDataType'; 5 | import { 6 | Dispatch, 7 | RefObject, 8 | createContext, 9 | useContext, 10 | useEffect, 11 | useReducer, 12 | useRef, 13 | } from 'react'; 14 | import { useAccount } from 'wagmi'; 15 | import { generateChainId } from '@arbitrum/orbit-sdk/utils'; 16 | import { ChainType } from '@/types/ChainType'; 17 | import { RollupConfigFormValues } from '../../app/deployment/step/configure/page'; 18 | 19 | type DeploymentPageContextState = { 20 | rollupContracts?: CoreContracts; 21 | rollupConfig: RollupConfig; 22 | validators?: Wallet[]; 23 | batch_posters?: Wallet[]; 24 | chainType?: ChainType; 25 | isLoading: boolean; 26 | isDownloadCompleted: boolean; 27 | }; 28 | 29 | const generateDefaultRollupConfig: () => RollupConfig = () => ({ 30 | confirmPeriodBlocks: 150, 31 | stakeToken: '0x0000000000000000000000000000000000000000', 32 | baseStake: 0.1, 33 | owner: '', 34 | extraChallengeTimeBlocks: 0, 35 | loserStakeEscrow: '0x0000000000000000000000000000000000000000', 36 | chainId: generateChainId(), 37 | chainName: 'My Arbitrum L3 Chain', 38 | chainConfig: '0x0000000000000000000000000000000000000000000000000000000000000000', 39 | genesisBlockNum: 0, 40 | nativeToken: '0x0000000000000000000000000000000000000000', 41 | sequencerInboxMaxTimeVariation: { 42 | delayBlocks: 5760, 43 | futureBlocks: 48, 44 | delaySeconds: 86400, 45 | futureSeconds: 3600, 46 | }, 47 | }); 48 | 49 | function getDefaultRollupConfig(owner: string = '') { 50 | return { ...generateDefaultRollupConfig(), owner }; 51 | } 52 | 53 | const deploymentPageContextStateDefaultValue: DeploymentPageContextState = { 54 | rollupConfig: generateDefaultRollupConfig(), 55 | rollupContracts: undefined, 56 | validators: undefined, 57 | batch_posters: undefined, 58 | chainType: undefined, 59 | isLoading: false, 60 | isDownloadCompleted: false, 61 | }; 62 | 63 | function getDeploymentPageContextStateInitialValue(): DeploymentPageContextState { 64 | if (typeof window === 'undefined') { 65 | return deploymentPageContextStateDefaultValue; 66 | } 67 | 68 | const stateInLocalStorage = localStorage.getItem('arbitrum:orbit:state'); 69 | 70 | if (stateInLocalStorage === null) { 71 | return deploymentPageContextStateDefaultValue; 72 | } 73 | 74 | return JSON.parse(stateInLocalStorage); 75 | } 76 | 77 | type DeploymentPageContextAction = 78 | | { type: 'set_rollup_contracts'; payload: CoreContracts } 79 | | { type: 'set_rollup_config'; payload: Partial } 80 | | { type: 'set_chain_type'; payload: ChainType } 81 | | { type: 'set_validators'; payload: Wallet[] } 82 | | { type: 'set_batch_posters'; payload: Wallet[] } 83 | | { type: 'set_is_loading'; payload: boolean } 84 | | { type: 'set_is_download_completed'; payload: boolean } 85 | | { type: 'reset'; payload: string }; 86 | 87 | type DeploymentPageContextValue = [ 88 | DeploymentPageContextState, 89 | Dispatch, 90 | { [key: string]: RefObject | null }, 91 | ]; 92 | 93 | export const DeploymentPageContext = createContext([ 94 | getDeploymentPageContextStateInitialValue(), 95 | () => {}, 96 | {}, 97 | ]); 98 | 99 | function reducer( 100 | state: DeploymentPageContextState, 101 | action: DeploymentPageContextAction, 102 | ): DeploymentPageContextState { 103 | switch (action.type) { 104 | case 'set_rollup_contracts': 105 | return { ...state, rollupContracts: action.payload }; 106 | 107 | case 'set_rollup_config': 108 | return { ...state, rollupConfig: { ...state.rollupConfig, ...action.payload } }; 109 | 110 | case 'set_chain_type': 111 | return { ...state, chainType: action.payload }; 112 | 113 | case 'set_validators': 114 | return { ...state, validators: action.payload }; 115 | 116 | case 'set_batch_posters': 117 | return { ...state, batch_posters: action.payload }; 118 | 119 | case 'set_is_loading': 120 | return { ...state, isLoading: action.payload }; 121 | 122 | case 'set_is_download_completed': 123 | return { ...state, isDownloadCompleted: action.payload }; 124 | 125 | case 'reset': 126 | return { 127 | ...deploymentPageContextStateDefaultValue, 128 | rollupConfig: getDefaultRollupConfig(action.payload), 129 | }; 130 | 131 | default: 132 | return state; 133 | } 134 | } 135 | 136 | export function DeploymentPageContextProvider({ children }: { children: React.ReactNode }) { 137 | const { address } = useAccount({ 138 | onConnect: ({ address }) => { 139 | if (address) { 140 | // Update owner field when use connects their wallet 141 | dispatch({ type: 'set_rollup_config', payload: { owner: address as string } }); 142 | } 143 | }, 144 | }); 145 | 146 | const [state, dispatch] = useReducer(reducer, address, (address) => ({ 147 | ...getDeploymentPageContextStateInitialValue(), 148 | rollupConfig: getDefaultRollupConfig(address), 149 | })); 150 | 151 | const pickChainFormRef = useRef(null); 152 | const rollupConfigFormRef = useRef(null); 153 | const keysetFormRef = useRef(null); 154 | 155 | useEffect(() => { 156 | localStorage.setItem( 157 | 'arbitrum:orbit:state', 158 | JSON.stringify({ 159 | rollupConfig: state.rollupConfig, 160 | chainType: state.chainType, 161 | rollupContracts: state.rollupContracts, 162 | validators: state.validators, 163 | batch_posters: state.batch_posters, 164 | isDownloadCompleted: state.isDownloadCompleted, 165 | }), 166 | ); 167 | }, [state]); 168 | 169 | return ( 170 | 181 | {children} 182 | 183 | ); 184 | } 185 | 186 | export function useDeploymentPageContext() { 187 | return useContext(DeploymentPageContext); 188 | } 189 | -------------------------------------------------------------------------------- /src/components/DeploymentSummary.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useMemo } from 'react'; 4 | import { useNetwork } from 'wagmi'; 5 | 6 | import { ChainId } from '@/types/ChainId'; 7 | import { useDeploymentPageContext } from './DeploymentPageContext'; 8 | import { getBlockExplorerUrl } from '@/utils/getBlockExplorerUrl'; 9 | 10 | function BlockExplorerLink({ href, children }: { href: string; children: React.ReactNode }) { 11 | return ( 12 | 18 | {children} 19 | 20 | ); 21 | } 22 | 23 | export function DeploymentSummary() { 24 | const { chain } = useNetwork(); 25 | 26 | const [{ rollupContracts, validators = [], batch_posters: batchPosters = [] }] = 27 | useDeploymentPageContext(); 28 | 29 | const blockExplorerUrl = useMemo( 30 | () => chain?.blockExplorers?.default?.url ?? getBlockExplorerUrl(ChainId.ArbitrumSepolia), 31 | [chain], 32 | ); 33 | 34 | return ( 35 |
36 |
37 | {rollupContracts && ( 38 |
39 |

Rollup Contracts

40 |
    41 |
  • 42 | Rollup address: 43 | 44 | {rollupContracts.rollup} 45 | 46 |
  • 47 |
  • 48 | 49 | Inbox address: 50 | 51 | 52 | {rollupContracts.inbox} 53 | 54 |
  • 55 |
  • 56 | 57 | Outbox address: 58 | 59 | 60 | {rollupContracts.outbox} 61 | 62 |
  • 63 |
  • 64 | 65 | Admin Proxy address:{' '} 66 | 67 | 70 | {rollupContracts.adminProxy} 71 | 72 |
  • 73 |
  • 74 | Sequencer Inbox address: 75 | 78 | {rollupContracts.sequencerInbox} 79 | 80 |
  • 81 |
  • 82 | Bridge address: 83 | 84 | {rollupContracts.bridge} 85 | 86 |
  • 87 |
  • 88 | Validator Utils address: 89 | 92 | {rollupContracts.validatorUtils} 93 | 94 |
  • 95 |
  • 96 | Validator Wallet Creator address: 97 | 100 | {rollupContracts.validatorWalletCreator} 101 | 102 |
  • 103 |
  • 104 | Upgrade Executor address: 105 | 108 | {rollupContracts.upgradeExecutor} 109 | 110 |
  • 111 |
  • 112 | Deployed at block number: 113 | 116 | {rollupContracts.deployedAtBlockNumber} 117 | 118 |
  • 119 |
120 |
121 | )} 122 |
123 |
124 | {validators.length > 0 && ( 125 |
126 |

Validators

127 |
    128 | {validators.map((validator, index) => ( 129 |
  • 130 | 131 | {validator.address} 132 | 133 |
  • 134 | ))} 135 |
136 |
137 | )} 138 | {batchPosters.length > 0 && ( 139 |
140 |

Batch Posters

141 |
    142 | {batchPosters.map((batchPoster, index) => ( 143 |
  • 144 | 145 | {batchPoster.address} 146 | 147 |
  • 148 | ))} 149 |
150 |
151 | )} 152 |
153 |
154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/components/EditableInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { InternalFieldName, UseFormRegisterReturn } from 'react-hook-form'; 3 | import { twMerge } from 'tailwind-merge'; 4 | 5 | interface EditableInputProps extends React.HTMLProps { 6 | register?: () => UseFormRegisterReturn; 7 | name?: string; 8 | error?: string; 9 | } 10 | 11 | export const EditableInput = ({ register, ...props }: EditableInputProps) => { 12 | const getRegister = () => { 13 | if (register) return register(); 14 | return []; 15 | }; 16 | 17 | const [isEditing, setIsEditing] = useState(false); 18 | 19 | return ( 20 |
21 | 33 | {!props.disabled && !isEditing && ( 34 |
setIsEditing(true)} 37 | > 38 | 39 |
40 | )} 41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | export function ExternalLink({ 2 | children, 3 | ...props 4 | }: React.AnchorHTMLAttributes) { 5 | return ( 6 | 7 | {children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/GasTokenInput.tsx: -------------------------------------------------------------------------------- 1 | import { ChainType } from '@/types/ChainType'; 2 | import { ChangeEvent, Dispatch, SetStateAction, useEffect, useState } from 'react'; 3 | import { useFormContext } from 'react-hook-form'; 4 | import { twJoin } from 'tailwind-merge'; 5 | import { zeroAddress } from 'viem'; 6 | import { erc20ABI, useContractRead, useToken } from 'wagmi'; 7 | import { useDeploymentPageContext } from './DeploymentPageContext'; 8 | import { ScrollWrapper } from './ScrollWrapper'; 9 | 10 | enum GAS_TOKEN_KIND { 11 | ETH = 'ETH', 12 | CUSTOM = 'CUSTOM', 13 | } 14 | const ether = { name: 'Ether', symbol: 'ETH' }; 15 | 16 | export const GasTokenInput = (props: { setTokenDecimals: Dispatch> }) => { 17 | const [{ rollupConfig, chainType }] = useDeploymentPageContext(); 18 | const { 19 | register, 20 | setValue, 21 | watch, 22 | clearErrors, 23 | formState: { errors }, 24 | } = useFormContext(); 25 | 26 | // todo: debounce? though don't think anyone will actually type it character by character 27 | const nativeToken = watch('nativeToken'); 28 | 29 | const { data: nativeTokenData = ether, isError: tokenNotFound } = useToken({ 30 | address: nativeToken === zeroAddress ? undefined : (nativeToken as `0x${string}`), 31 | }); 32 | 33 | const [selectedToken, setSelectedToken] = useState( 34 | nativeToken === zeroAddress ? GAS_TOKEN_KIND.ETH : GAS_TOKEN_KIND.CUSTOM, 35 | ); 36 | 37 | // Fetch token decimals 38 | const { data: tokenDecimals } = useContractRead( 39 | // checks if it's not ETH to avoid unnecessary calls 40 | nativeToken !== zeroAddress 41 | ? { 42 | address: nativeToken, 43 | abi: erc20ABI, 44 | functionName: 'decimals', 45 | } 46 | : {}, 47 | ); 48 | 49 | // Reset state for rollup chains incase user switches back to rollup from anytrust 50 | useEffect(() => { 51 | if (chainType === ChainType.Rollup) { 52 | props.setTokenDecimals(18); 53 | setSelectedToken(GAS_TOKEN_KIND.ETH); 54 | setValue('nativeToken', zeroAddress); 55 | } 56 | }, [chainType]); 57 | 58 | useEffect(() => { 59 | if (selectedToken === GAS_TOKEN_KIND.ETH || tokenDecimals === undefined) { 60 | props.setTokenDecimals(18); 61 | } else { 62 | props.setTokenDecimals(tokenDecimals); 63 | } 64 | }, [selectedToken, tokenDecimals]); 65 | 66 | const handleSelectChange = (event: ChangeEvent) => { 67 | clearErrors('nativeToken'); 68 | setSelectedToken(event.target.value as GAS_TOKEN_KIND); 69 | if (event.target.value === GAS_TOKEN_KIND.ETH) { 70 | setValue('nativeToken', zeroAddress); 71 | } 72 | }; 73 | 74 | return ( 75 | 76 | 79 | 88 | {selectedToken === GAS_TOKEN_KIND.CUSTOM && ( 89 | 99 | )} 100 | {chainType === ChainType.Rollup && ( 101 | 102 | Only AnyTrust chains support custom Native Tokens 103 | 104 | )} 105 | {chainType === ChainType.AnyTrust && ( 106 | <> 107 | {tokenNotFound ? ( 108 | 109 | Failed to detect a valid ERC-20 contract at the given address. 110 | 111 | ) : ( 112 | 113 | The chain will use{' '} 114 | 115 | {nativeTokenData?.name} ({nativeTokenData?.symbol}) 116 | {' '} 117 | as the native token for paying gas fees. 118 | 119 | )} 120 | {errors.nativeToken && ( 121 | {errors.nativeToken.message as string} 122 | )} 123 | 124 | )} 125 | 126 | ); 127 | }; 128 | -------------------------------------------------------------------------------- /src/components/InfoCircleWithTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from 'react'; 2 | 3 | interface InfoCircleWithTooltipProps { 4 | href: string; 5 | infoText: string | JSX.Element; 6 | } 7 | 8 | export const InfoCircleWithTooltip: FC = ({ href, infoText }) => { 9 | const [isTooltipVisible, setIsTooltipVisible] = useState(false); 10 | 11 | const handleMouseEnter = () => setIsTooltipVisible(true); 12 | const handleMouseLeave = () => setIsTooltipVisible(false); 13 | 14 | return ( 15 | 20 | 21 | 22 | 23 | {isTooltipVisible && ( 24 |
25 | {infoText} 26 |
27 | )} 28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/MobileAppSidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { MobileSidebar } from '@offchainlabs/cobalt'; 3 | import { ConnectButton } from '@rainbow-me/rainbowkit'; 4 | import { usePostHog } from 'posthog-js/react'; 5 | import { CustomConnectButton } from './ConnectMenuItem'; 6 | 7 | export const MobileAppSidebar = () => { 8 | const posthog = usePostHog(); 9 | return ( 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/NextButton.tsx: -------------------------------------------------------------------------------- 1 | import { useStep } from '@/hooks/useStep'; 2 | import { 3 | ConfigureAnyTrust, 4 | ConfigureKeyset, 5 | ConfigureRollup, 6 | DownloadAnyTrustConfig, 7 | DownloadConfig, 8 | } from '@/types/Steps'; 9 | import { ButtonHTMLAttributes, FC, MouseEvent, useMemo } from 'react'; 10 | import { twMerge } from 'tailwind-merge'; 11 | import { useDeploymentPageContext } from './DeploymentPageContext'; 12 | 13 | interface NextButtonProps extends ButtonHTMLAttributes { 14 | className?: string; 15 | onClick: (event: MouseEvent) => void; 16 | isLoading?: boolean; 17 | } 18 | 19 | export const NextButton: FC = ({ className, onClick, isLoading }) => { 20 | const { currentStep } = useStep(); 21 | const [{ isDownloadCompleted }] = useDeploymentPageContext(); 22 | 23 | const isDeploymentStep = currentStep === ConfigureAnyTrust || currentStep === ConfigureRollup; 24 | const isDownloadRequired = 25 | !isDownloadCompleted && 26 | (currentStep === DownloadConfig || currentStep === DownloadAnyTrustConfig); 27 | const isTransactionStep = currentStep === ConfigureKeyset; 28 | 29 | const getLabel = () => { 30 | if (isDeploymentStep) { 31 | if (isLoading) return 'Deploying'; 32 | return 'Deploy'; 33 | } 34 | if (isTransactionStep) { 35 | if (isLoading) return 'Setting Keyset'; 36 | return 'Set Keyset'; 37 | } 38 | return 'Next'; 39 | }; 40 | 41 | const getIcon = () => { 42 | if (isLoading) return ; 43 | return; 44 | }; 45 | return ( 46 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /src/components/Providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit'; 4 | import posthog from 'posthog-js'; 5 | import { PostHogProvider } from 'posthog-js/react'; 6 | import { WagmiConfig } from 'wagmi'; 7 | import { appInfo, chains, wagmiConfig } from '@/setupWagmi'; 8 | import { ToastContainer } from 'react-toastify'; 9 | 10 | import 'react-toastify/dist/ReactToastify.css'; 11 | import '@rainbow-me/rainbowkit/styles.css'; 12 | import 'primeicons/primeicons.css'; // icons 13 | import '@/styles/globals.css'; 14 | import { PropsWithChildren } from 'react'; 15 | import { CustomAvatar } from './CustomAvatar'; 16 | 17 | if (typeof window !== 'undefined' && typeof process.env.NEXT_PUBLIC_POSTHOG_KEY === 'string') { 18 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { 19 | api_host: 'https://app.posthog.com', 20 | loaded: (posthog) => { 21 | if (process.env.NODE_ENV !== 'production') { 22 | // when in dev, you can see data that would be sent in prod (in devtools) 23 | posthog.debug(); 24 | } 25 | }, 26 | // store data in temporary memory that expires with each session 27 | persistence: 'memory', 28 | autocapture: true, 29 | disable_session_recording: true, 30 | }); 31 | } 32 | 33 | export const Providers = ({ children }: PropsWithChildren) => { 34 | return ( 35 | 36 | 37 | 49 | {children} 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/components/RaasProviderCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | 4 | export const RaasProviderCard = ({ 5 | name, 6 | description, 7 | caption, 8 | link, 9 | logo, 10 | }: { 11 | name: string; 12 | description: string; 13 | caption: string; 14 | link: string; 15 | logo: string; 16 | }) => { 17 | return ( 18 | 19 |
20 |
21 |
22 | {`${name} 23 |
24 |
25 |

{name}

26 |

{description}

27 |
28 |

{caption}

29 |
30 |
31 |

32 | Go to website 33 |

34 |
35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/RaasProviderGrid.tsx: -------------------------------------------------------------------------------- 1 | import { RaasProviderCard } from './RaasProviderCard'; 2 | 3 | export const RaasProviderGrid = () => { 4 | return ( 5 |
6 | 13 | 20 | 27 | 34 | 41 | 48 | 55 | 62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/RemovableInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UseFormRegisterReturn } from 'react-hook-form'; 3 | import { twMerge } from 'tailwind-merge'; 4 | 5 | interface RemovableInputProps extends React.HTMLProps { 6 | placeholder: string; 7 | register: UseFormRegisterReturn; 8 | onRemove: () => void; 9 | error?: string; 10 | } 11 | 12 | export const RemovableInput: React.FC = ({ 13 | placeholder, 14 | register, 15 | onRemove, 16 | error, 17 | }) => { 18 | return ( 19 |
20 | 29 | 36 | {error &&

{error}

} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/ResetButton.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/navigation'; 2 | import { useDeploymentPageContext } from './DeploymentPageContext'; 3 | import { useAccount } from 'wagmi'; 4 | import { ButtonHTMLAttributes, FC } from 'react'; 5 | import { twMerge } from 'tailwind-merge'; 6 | import { FIRST_STEP } from '@/hooks/useStep'; 7 | 8 | interface ResetButtonProps extends ButtonHTMLAttributes { 9 | className?: string; 10 | } 11 | 12 | export const ResetButton: FC = ({ className }) => { 13 | const router = useRouter(); 14 | const { address } = useAccount(); 15 | const [, dispatch] = useDeploymentPageContext(); 16 | 17 | function reset() { 18 | localStorage.removeItem('rollupData'); 19 | localStorage.removeItem('l3Config'); 20 | 21 | dispatch({ type: 'set_is_loading', payload: false }); 22 | dispatch({ type: 'reset', payload: address ? address : '' }); 23 | router.push(`/deployment/step/${FIRST_STEP.id}`); 24 | } 25 | 26 | return ( 27 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/ScrollWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | /** 5 | * A wrapper component that scrolls to the anchor and highlights the item briefly when clicked. 6 | */ 7 | export const ScrollWrapper = ({ 8 | anchor, 9 | children, 10 | className, 11 | }: PropsWithChildren<{ anchor?: string; className?: string }>) => { 12 | const highlightElement = (element: HTMLElement) => { 13 | element.classList.add('highlight-effect'); 14 | setTimeout(() => { 15 | element.classList.remove('highlight-effect'); 16 | }, 2000); 17 | }; 18 | 19 | const handleClick = () => { 20 | if (anchor) { 21 | const element = document.getElementById(anchor); 22 | if (element) { 23 | element.scrollIntoView({ behavior: 'smooth', block: 'start' }); 24 | highlightElement(element); 25 | } 26 | } 27 | }; 28 | 29 | return ( 30 |
handleClick()} className={twMerge(className, 'cursor-pointer')}> 31 | {children} 32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/StepTitle.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export const StepTitle = ({ 5 | children, 6 | className, 7 | }: PropsWithChildren<{ 8 | className?: string; 9 | }>) => { 10 | return ( 11 |

12 | {children} 13 |

14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Stepper.tsx: -------------------------------------------------------------------------------- 1 | import { useIsMounted } from '@/hooks/useIsMounted'; 2 | import { useStep } from '@/hooks/useStep'; 3 | import { twMerge } from 'tailwind-merge'; 4 | import { useAccount } from 'wagmi'; 5 | import { Step } from '@/types/Steps'; 6 | 7 | export const Stepper = () => { 8 | const { chainStepMap, createSortedStepMapArray } = useStep(); 9 | const steps = createSortedStepMapArray(chainStepMap); 10 | 11 | return ( 12 |
13 | {steps.map((step, index) => ( 14 | 15 | ))} 16 |
17 | ); 18 | }; 19 | 20 | export const StepperStep = ({ step, index }: { step: Step; index: number }) => { 21 | const { currentStep } = useStep(); 22 | const { isConnected } = useAccount(); 23 | const isActiveStep = step.id === currentStep?.id && isConnected; 24 | 25 | return ( 26 |
27 |
28 |
29 |
36 | 42 | {index + 1} 43 | 44 |
45 |
46 |
47 | 53 | {step.label} 54 | 55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /src/components/TextInputWithInfoLink.tsx: -------------------------------------------------------------------------------- 1 | import { UseFormRegisterReturn, InternalFieldName } from 'react-hook-form'; 2 | import { InputHTMLAttributes } from 'react'; 3 | import { EditableInput } from './EditableInput'; 4 | import { ScrollWrapper } from './ScrollWrapper'; 5 | 6 | interface TextInputWithInfoLinkProps extends InputHTMLAttributes { 7 | label: string; 8 | explainerText?: string; 9 | error?: string; 10 | anchor?: string; 11 | register?: () => UseFormRegisterReturn; 12 | } 13 | 14 | export const TextInputWithInfoLink = (props: TextInputWithInfoLinkProps) => { 15 | const { anchor, label, explainerText, error, name } = props; 16 | 17 | return ( 18 | 19 | 25 | 26 | {explainerText && {explainerText}} 27 | {error && {error}} 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/WalletAddressManager.tsx: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@/types/RollupContracts'; 2 | import { getRandomWallet } from '@/utils/getRandomWallet'; 3 | import { useEffect, useMemo } from 'react'; 4 | import { useFormContext } from 'react-hook-form'; 5 | import { twMerge } from 'tailwind-merge'; 6 | import { EditableInput } from './EditableInput'; 7 | import { RemovableInput } from './RemovableInput'; 8 | import { useDeploymentPageContext } from './DeploymentPageContext'; 9 | 10 | type WalletAddressManagerProps = { 11 | fieldName: 'validators' | 'batch_posters'; 12 | label: string; 13 | maxAddresses?: number; 14 | }; 15 | 16 | /** 17 | * Manages a user-editable list of wallets with addresses and generated private keys. 18 | * 19 | * This component allows users to: 20 | * - Add new wallets 21 | * - Edit existing addresses 22 | * - Remove wallets (except the first one) 23 | * 24 | * @note Editing an address removes the associated private key data. 25 | */ 26 | export const WalletAddressManager = ({ 27 | fieldName, 28 | label, 29 | maxAddresses = 16, 30 | }: WalletAddressManagerProps) => { 31 | const [{ [fieldName]: savedWallets }, dispatch] = useDeploymentPageContext(); 32 | const { register, setValue, formState, getValues } = useFormContext(); 33 | const { errors } = formState; 34 | 35 | const wallets = useMemo(() => { 36 | return savedWallets || [getRandomWallet()]; 37 | }, [savedWallets]); 38 | 39 | if(!savedWallets) { 40 | dispatch({ type: `set_${fieldName}` as const, payload: [getRandomWallet()] }); 41 | } 42 | 43 | const addresses = useMemo(() => { 44 | return wallets?.map((wallet: Wallet) => wallet.address); 45 | }, [wallets]); 46 | 47 | const walletCount = addresses.length; 48 | const isMaxAddressCount = walletCount >= maxAddresses; 49 | 50 | const saveWallets = (newWallets: Wallet[]) => { 51 | dispatch({ 52 | type: `set_${fieldName}` as const, 53 | payload: newWallets, 54 | }); 55 | }; 56 | 57 | const addWallet = () => { 58 | if (!isMaxAddressCount) { 59 | const newWallet = getRandomWallet(); 60 | saveWallets([...wallets, newWallet]); 61 | } 62 | }; 63 | 64 | const removeWallet = (index: number) => { 65 | if (walletCount > 1 && index !== 0) { 66 | const newWallets = wallets.filter((w: Wallet, i: number) => i !== index); 67 | saveWallets(newWallets); 68 | // Update form values 69 | const currentAddresses = getValues(fieldName); 70 | currentAddresses.splice(index, 1); 71 | setValue(fieldName, currentAddresses); 72 | } 73 | }; 74 | 75 | useEffect(() => { 76 | setValue(fieldName, addresses); 77 | }, [addresses, fieldName, setValue]); 78 | 79 | useEffect(() => { 80 | addresses.forEach((address: string, index: number) => { 81 | setValue(`${fieldName}.${index}`, address); 82 | }); 83 | }, []); 84 | 85 | // @ts-expect-error - react-hook-form doesn't handle the array properly 86 | const addressErrors = errors[fieldName] as { message: string }[]; 87 | 88 | return ( 89 |
90 | {addresses.map((address: string, index: number) => ( 91 |
92 | {index === 0 ? ( 93 | register(`${fieldName}.${index}`)} 97 | error={addressErrors?.[index]?.message} 98 | /> 99 | ) : ( 100 | removeWallet(index)} 104 | error={addressErrors?.[index]?.message} 105 | /> 106 | )} 107 |
108 | ))} 109 | 128 |
129 | ); 130 | }; 131 | -------------------------------------------------------------------------------- /src/components/WrongChainAlert.tsx: -------------------------------------------------------------------------------- 1 | export const WrongChainAlert = () => ( 2 |
3 |

You are connected to the wrong network.

4 |

Please make sure you are connected to Arbitrum Sepolia.

5 |
6 | ); 7 | -------------------------------------------------------------------------------- /src/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Space_Grotesk } from 'next/font/google'; 2 | import localFont from 'next/font/local'; 3 | 4 | export const spaceGrotesk = Space_Grotesk({ subsets: ['latin'] }); 5 | 6 | export const unica77 = localFont({ 7 | src: [ 8 | { 9 | path: '../public/fonts/Unica77LLWeb-Regular.woff2', 10 | weight: '400', 11 | style: 'normal', 12 | }, 13 | { 14 | path: '../public/fonts/Unica77LLWeb-Italic.woff2', 15 | weight: '400', 16 | style: 'italic', 17 | }, 18 | { 19 | path: '../public/fonts/Unica77LLWeb-Bold.woff2', 20 | weight: '700', 21 | style: 'normal', 22 | }, 23 | { 24 | path: '../public/fonts/Unica77LLWeb-BoldItalic.woff2', 25 | weight: '700', 26 | style: 'italic', 27 | }, 28 | { 29 | path: '../public/fonts/Unica77LLWeb-Black.woff2', 30 | weight: '900', 31 | style: 'normal', 32 | }, 33 | { 34 | path: '../public/fonts/Unica77LLWeb-BlackItalic.woff2', 35 | weight: '900', 36 | style: 'italic', 37 | }, 38 | { 39 | path: '../public/fonts/Unica77LLWeb-ExtraBlack.woff2', 40 | weight: '800', 41 | style: 'normal', 42 | }, 43 | { 44 | path: '../public/fonts/Unica77LLWeb-ExtraBlackItalic.woff2', 45 | weight: '800', 46 | style: 'italic', 47 | }, 48 | { 49 | path: '../public/fonts/Unica77LLWeb-Medium.woff2', 50 | weight: '500', 51 | style: 'normal', 52 | }, 53 | { 54 | path: '../public/fonts/Unica77LLWeb-MediumItalic.woff2', 55 | weight: '500', 56 | style: 'italic', 57 | }, 58 | { 59 | path: '../public/fonts/Unica77LLWeb-Light.woff2', 60 | weight: '300', 61 | style: 'normal', 62 | }, 63 | { 64 | path: '../public/fonts/Unica77LLWeb-LightItalic.woff2', 65 | weight: '300', 66 | style: 'italic', 67 | }, 68 | { 69 | path: '../public/fonts/Unica77LLWeb-Thin.woff2', 70 | weight: '200', 71 | style: 'normal', 72 | }, 73 | { 74 | path: '../public/fonts/Unica77LLWeb-ThinItalic.woff2', 75 | weight: '200', 76 | style: 'italic', 77 | }, 78 | ], 79 | }); 80 | -------------------------------------------------------------------------------- /src/hooks/useConfigDownloads.tsx: -------------------------------------------------------------------------------- 1 | import { NodeConfig } from '@arbitrum/orbit-sdk'; 2 | 3 | import { L3Config } from '@/types/L3Config'; 4 | import { useEffect, useState } from 'react'; 5 | import JSZip from 'jszip'; 6 | 7 | export const useConfigDownloads = () => { 8 | const [rollupConfigData, setRollupConfigData] = useState(null); 9 | const [l3Config, setL3Config] = useState(null); 10 | 11 | const unwantedFields = [ 12 | 'inboxAddress', 13 | 'adminProxy', 14 | 'sequencerInbox', 15 | 'bridge', 16 | 'utils', 17 | 'validatorWalletCreator', 18 | 'blockNumber', 19 | 'rollupAddress', 20 | ]; 21 | 22 | useEffect(() => { 23 | if (typeof window !== 'undefined') { 24 | const rollupData = localStorage.getItem('rollupData'); 25 | if (rollupData) { 26 | let parsedData = JSON.parse(rollupData); 27 | let cleanedData = removeFields(parsedData, unwantedFields); 28 | cleanedData = removeNestedFields(cleanedData); 29 | setRollupConfigData(cleanedData); 30 | } 31 | 32 | const l3ConfigData = localStorage.getItem('l3Config'); 33 | 34 | if (l3ConfigData) { 35 | setL3Config(JSON.parse(l3ConfigData)); 36 | } 37 | } 38 | }, []); 39 | 40 | const dataWithParsedInfoJson = () => { 41 | const parsedData = JSON.parse(JSON.stringify(rollupConfigData)); 42 | const infoJson = JSON.parse(parsedData.chain['info-json']); 43 | parsedData.chain['info-json'] = infoJson; 44 | return parsedData; 45 | }; 46 | 47 | const downloadJSON = (dataToDownload: any, fileName: string) => { 48 | const element = document.createElement('a'); 49 | const file = new Blob([JSON.stringify(dataToDownload, null, 2)], { type: 'application/json' }); 50 | element.href = URL.createObjectURL(file); 51 | element.download = fileName; 52 | document.body.appendChild(element); 53 | element.click(); 54 | }; 55 | 56 | const downloadRollupConfig = () => downloadJSON(rollupConfigData, 'nodeConfig.json'); 57 | 58 | const downloadL3Config = () => downloadJSON(l3Config, 'orbitSetupScriptConfig.json'); 59 | 60 | const downloadZippedConfigs = () => { 61 | const zip = new JSZip(); 62 | zip.file('nodeConfig.json', JSON.stringify(rollupConfigData, null, 2)); 63 | zip.file('orbitSetupScriptConfig.json', JSON.stringify(l3Config, null, 2)); 64 | 65 | zip.generateAsync({ type: 'blob' }).then((file) => { 66 | const element = document.createElement('a'); 67 | element.href = URL.createObjectURL(file); 68 | element.download = 'orbit-config.zip'; 69 | document.body.appendChild(element); 70 | element.click(); 71 | }); 72 | }; 73 | 74 | return { 75 | rollupConfigDownloadData: rollupConfigData, 76 | rollupConfigDisplayData: rollupConfigData ? dataWithParsedInfoJson() : '', 77 | l3Config, 78 | downloadJSON, 79 | downloadRollupConfig, 80 | downloadL3Config, 81 | downloadZippedConfigs, 82 | }; 83 | }; 84 | 85 | // Function to remove unwanted fields 86 | const removeFields = (obj: any, fieldsToRemove: string[]): any => { 87 | let newObj = JSON.parse(JSON.stringify(obj)); 88 | 89 | fieldsToRemove.forEach((field) => { 90 | if (newObj.hasOwnProperty(field)) { 91 | delete newObj[field]; 92 | } 93 | }); 94 | 95 | return newObj; 96 | }; 97 | 98 | // Modifying the parts of 'rollupData' which is needed for configuration 99 | const removeNestedFields = (obj: any): any => { 100 | let newObj = JSON.parse(JSON.stringify(obj)); // Deep clone the original object 101 | 102 | if (newObj.chain && newObj.chain['info-json']) { 103 | if (typeof newObj.chain['info-json'] === 'string') { 104 | newObj.chain['info-json'] = JSON.parse(newObj.chain['info-json']); // Parse the stringified JSON 105 | } 106 | 107 | // Stringify the entire 'info-json' object 108 | newObj.chain['info-json'] = JSON.stringify(newObj.chain['info-json']); 109 | } 110 | 111 | return newObj; 112 | }; 113 | -------------------------------------------------------------------------------- /src/hooks/useIsMounted.tsx: -------------------------------------------------------------------------------- 1 | // This hook prevents next.js from throwing an error on SSR 2 | 3 | import { useState, useEffect } from 'react'; 4 | 5 | // due to wagmi not being available on the server 6 | export const useIsMounted = () => { 7 | const [mounted, setMounted] = useState(false); 8 | useEffect(() => setMounted(true), []); 9 | return mounted; 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/useStep.tsx: -------------------------------------------------------------------------------- 1 | import { useDeploymentPageContext } from '@/components/DeploymentPageContext'; 2 | import { ChainType } from '@/types/ChainType'; 3 | import { 4 | ChooseChainType, 5 | RollupStepMap, 6 | AnyTrustStepMap, 7 | Step, 8 | StepId, 9 | ConfigureKeyset, 10 | ConfigureRollup, 11 | ConfigureAnyTrust, 12 | } from '@/types/Steps'; 13 | import { usePathname, useRouter } from 'next/navigation'; 14 | 15 | export const FIRST_STEP = ChooseChainType; 16 | 17 | function getLastPartOfPath(path: string): string { 18 | const parts = path.split('/'); 19 | return parts[parts.length - 1]; 20 | } 21 | 22 | export const useStep = () => { 23 | const router = useRouter(); 24 | const pathname = usePathname(); 25 | const [ 26 | { chainType }, 27 | , 28 | { pickChainFormRef, rollupConfigFormRef, reviewAndDeployFormRef, keysetFormRef }, 29 | ] = useDeploymentPageContext(); 30 | 31 | const submitForm = () => { 32 | switch (currentStep) { 33 | case ChooseChainType: 34 | if (pickChainFormRef?.current) { 35 | pickChainFormRef.current.requestSubmit(); 36 | } 37 | break; 38 | case ConfigureAnyTrust: 39 | case ConfigureRollup: 40 | if (rollupConfigFormRef?.current) { 41 | rollupConfigFormRef.current.requestSubmit(); 42 | } 43 | break; 44 | case ConfigureKeyset: 45 | if (keysetFormRef?.current) { 46 | keysetFormRef.current.requestSubmit(); 47 | } 48 | break; 49 | default: 50 | nextStep(); 51 | } 52 | }; 53 | 54 | const pushToStepId = (id?: StepId | null) => { 55 | if (!id) { 56 | router.push(`/deployment/step/${FIRST_STEP.id}`); 57 | } else { 58 | router.push(`/deployment/step/${id}`); 59 | } 60 | if (currentStep && currentStep.next) { 61 | router.prefetch(`/deployment/step/${currentStep?.next}`); 62 | } 63 | }; 64 | 65 | const currentStepId = pathname ? getLastPartOfPath(pathname) : FIRST_STEP.id; 66 | 67 | const chainStepMap: Record = 68 | chainType === ChainType.Rollup ? RollupStepMap : AnyTrustStepMap; 69 | 70 | const findStepById = (id: string): Step | undefined => { 71 | const keys = Object.keys(chainStepMap); 72 | const key = keys.find((key) => chainStepMap[key].id === id); 73 | return key ? chainStepMap[key] : undefined; 74 | }; 75 | 76 | const createSortedStepMapArray = (stepMap: Record): Step[] => { 77 | const steps: Step[] = Object.values(stepMap); 78 | const sortedSteps: Step[] = []; 79 | let currentStep = steps.find((step) => step.previous === null); 80 | 81 | while (currentStep) { 82 | sortedSteps.push(currentStep); 83 | currentStep = steps.find((step) => step.label && step.id === currentStep?.next); 84 | } 85 | 86 | return sortedSteps; 87 | }; 88 | 89 | const currentStep = findStepById(currentStepId); 90 | 91 | const isValidStep = currentStep !== undefined; 92 | 93 | const nextStep = () => pushToStepId(currentStep?.next); 94 | 95 | const isLastStep = currentStep?.next === null; 96 | 97 | return { 98 | nextStep, 99 | submitForm, 100 | previousStep: () => pushToStepId(currentStep?.previous), 101 | goToStep: (step: Step) => pushToStepId(step.id), 102 | currentStep, 103 | isValidStep, 104 | chainStepMap, 105 | createSortedStepMapArray, 106 | pickChainFormRef, 107 | rollupConfigFormRef, 108 | reviewAndDeployFormRef, 109 | keysetFormRef, 110 | isLastStep, 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /src/setupWagmi.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { configureChains, createConfig } from 'wagmi'; 3 | import { publicProvider } from 'wagmi/providers/public'; 4 | import { connectorsForWallets, getDefaultWallets } from '@rainbow-me/rainbowkit'; 5 | 6 | import { ChainId } from '@/types/ChainId'; 7 | import { getRpcUrl } from '@/utils/getRpcUrl'; 8 | import { getBlockExplorerUrl } from '@/utils/getBlockExplorerUrl'; 9 | import { ARBITRUM_SEPOLIA_ICON_URL } from './utils/constants'; 10 | 11 | const arbitrumSepolia = { 12 | id: ChainId.ArbitrumSepolia, 13 | name: 'Arbitrum Sepolia', 14 | network: 'arbitrum-sepolia', 15 | iconUrl: ARBITRUM_SEPOLIA_ICON_URL, 16 | nativeCurrency: { 17 | name: 'Ether', 18 | symbol: 'ETH', 19 | decimals: 18, 20 | }, 21 | rpcUrls: { 22 | default: { 23 | http: [getRpcUrl(ChainId.ArbitrumSepolia)], 24 | }, 25 | public: { 26 | http: [getRpcUrl(ChainId.ArbitrumSepolia)], 27 | }, 28 | }, 29 | blockExplorers: { 30 | default: { 31 | name: 'Blockscout', 32 | url: getBlockExplorerUrl(ChainId.ArbitrumSepolia), 33 | }, 34 | }, 35 | testnet: true, 36 | }; 37 | 38 | const { chains, publicClient, webSocketPublicClient } = configureChains( 39 | [ 40 | // Ideally, we wouldn't need to register the L1s, but there's currently an issue with WalletConnect v2: https://github.com/wagmi-dev/references/issues/225 41 | arbitrumSepolia, 42 | ], 43 | [publicProvider()], 44 | ); 45 | 46 | const projectId = process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!; 47 | 48 | const appInfo = { 49 | appName: 'Arbitrum Orbit', 50 | projectId, 51 | }; 52 | 53 | const { wallets } = getDefaultWallets({ ...appInfo, chains }); 54 | 55 | const connectors = connectorsForWallets(wallets); 56 | 57 | const wagmiConfig = createConfig({ 58 | autoConnect: true, 59 | connectors, 60 | publicClient, 61 | webSocketPublicClient, 62 | }); 63 | 64 | export { chains, appInfo, wagmiConfig }; 65 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --primary: transparent; 7 | --secondary: #ffffff30; 8 | } 9 | 10 | /* Firefox */ 11 | * { 12 | scrollbar-width: thin; 13 | scrollbar-color: var(--secondary) var(--primary); 14 | } 15 | 16 | /* Chrome, Edge, and Safari */ 17 | *::-webkit-scrollbar { 18 | width: 8px; 19 | } 20 | 21 | *::-webkit-scrollbar-track { 22 | background: var(--primary); 23 | border-radius: 5px; 24 | } 25 | 26 | *::-webkit-scrollbar-thumb { 27 | background-color: var(--secondary); 28 | border-radius: 14px; 29 | border: 3px solid var(--primary); 30 | } 31 | 32 | .select-wrapper::after { 33 | @apply pointer-events-none absolute right-0 px-4 text-2xl leading-5; /* Adjust size and line-height here */ 34 | top: 50%; 35 | transform: translateY(-80%); 36 | content: '⌄'; 37 | } 38 | 39 | html { 40 | @apply text-white; 41 | background: url('/grainient.png') no-repeat; 42 | background-size: cover; 43 | min-height: 100%; 44 | background-color: black; 45 | } 46 | 47 | select, 48 | input { 49 | @apply text-black; 50 | @apply bg-white; 51 | } 52 | 53 | @keyframes highlightAnimation { 54 | from { 55 | background-color: #5e5e5e; 56 | } 57 | to { 58 | background-color: transparent; 59 | } 60 | } 61 | 62 | .highlight-effect { 63 | animation: highlightAnimation 2s; 64 | } 65 | -------------------------------------------------------------------------------- /src/types/ChainId.ts: -------------------------------------------------------------------------------- 1 | export enum ChainId { 2 | ArbitrumSepolia = 421614, 3 | } 4 | -------------------------------------------------------------------------------- /src/types/ChainType.ts: -------------------------------------------------------------------------------- 1 | export const ChainType = { 2 | Rollup: 'Rollup', 3 | AnyTrust: 'AnyTrust', 4 | } as const; 5 | 6 | export type ChainType = (typeof ChainType)[keyof typeof ChainType]; 7 | -------------------------------------------------------------------------------- /src/types/L3Config.ts: -------------------------------------------------------------------------------- 1 | export interface L3Config { 2 | 'chainOwner': string; 3 | 'rollup': string; 4 | 'inbox': string; 5 | 'outbox': string; 6 | 'adminProxy': string; 7 | 'sequencerInbox': string; 8 | 'bridge': string; 9 | 'utils': string; 10 | 'validatorWalletCreator': string; 11 | 'deployedAtBlockNumber': number; 12 | 'minL2BaseFee': number; 13 | 'networkFeeReceiver': string; 14 | 'infrastructureFeeCollector': string; 15 | 'batchPoster': string; 16 | 'staker': string; 17 | 'chainId': number; 18 | 'chainName': string; 19 | 'parentChainId': number; 20 | 'parent-chain-node-url': string; 21 | } 22 | -------------------------------------------------------------------------------- /src/types/RollupContracts.ts: -------------------------------------------------------------------------------- 1 | import { AddressSchema, PrivateKeySchema } from '@/utils/schemas'; 2 | import { z } from 'zod'; 3 | 4 | export const WalletSchema = z.object({ 5 | address: AddressSchema, 6 | privateKey: PrivateKeySchema.optional(), 7 | }); 8 | export type Wallet = z.infer; 9 | -------------------------------------------------------------------------------- /src/types/Steps.ts: -------------------------------------------------------------------------------- 1 | enum StepIds { 2 | ChooseChainType = 'chain-type', 3 | ConfigureChain = 'configure', 4 | DownloadConfig = 'download', 5 | ConfigureKeyset = 'keyset', 6 | DeployLocally = 'deploy-local', 7 | RaasProviders = 'raas', 8 | } 9 | 10 | export const ChooseChainType = { 11 | id: StepIds.ChooseChainType, 12 | next: StepIds.ConfigureChain, 13 | previous: null, 14 | label: 'Chain Type', 15 | } as const; 16 | 17 | export const ConfigureRollup = { 18 | id: StepIds.ConfigureChain, 19 | next: StepIds.DownloadConfig, 20 | previous: StepIds.ChooseChainType, 21 | label: 'Configure Chain', 22 | } as const; 23 | 24 | export const ConfigureAnyTrust = { 25 | id: StepIds.ConfigureChain, 26 | next: StepIds.ConfigureKeyset, 27 | previous: StepIds.ChooseChainType, 28 | label: 'Configure Chain', 29 | } as const; 30 | 31 | export const ConfigureKeyset = { 32 | id: StepIds.ConfigureKeyset, 33 | next: StepIds.DownloadConfig, 34 | previous: null, 35 | label: 'Keyset', 36 | } as const; 37 | 38 | export const DownloadConfig = { 39 | id: StepIds.DownloadConfig, 40 | next: StepIds.DeployLocally, 41 | previous: null, 42 | label: 'Download', 43 | } as const; 44 | 45 | export const DownloadAnyTrustConfig = { 46 | id: StepIds.DownloadConfig, 47 | next: StepIds.DeployLocally, 48 | previous: StepIds.ConfigureKeyset, 49 | label: 'Download', 50 | } as const; 51 | 52 | export const DeployLocally = { 53 | id: StepIds.DeployLocally, 54 | next: StepIds.RaasProviders, 55 | previous: StepIds.DownloadConfig, 56 | label: 'Deploy Locally', 57 | } as const; 58 | 59 | export const RaasProviders = { 60 | id: StepIds.RaasProviders, 61 | next: null, 62 | previous: StepIds.DeployLocally, 63 | label: null, 64 | } as const; 65 | 66 | export const RollupStepMap = { 67 | ChooseChainType, 68 | ConfigureRollup, 69 | DownloadConfig, 70 | DeployLocally, 71 | RaasProviders, 72 | } as const; 73 | 74 | export const AnyTrustStepMap = { 75 | ChooseChainType, 76 | ConfigureAnyTrust, 77 | ConfigureKeyset, 78 | DownloadConfig: DownloadAnyTrustConfig, 79 | DeployLocally, 80 | RaasProviders, 81 | } as const; 82 | 83 | export type RollupStep = (typeof RollupStepMap)[keyof typeof RollupStepMap]; 84 | export type AnyTrustStep = (typeof AnyTrustStepMap)[keyof typeof AnyTrustStepMap]; 85 | export type Step = RollupStep | AnyTrustStep; 86 | export type StepId = Step['id']; 87 | export type StepMap = typeof RollupStepMap | typeof AnyTrustStepMap; 88 | -------------------------------------------------------------------------------- /src/types/rollupConfigDataType.ts: -------------------------------------------------------------------------------- 1 | export type RollupConfig = { 2 | confirmPeriodBlocks: number; 3 | stakeToken: string; 4 | baseStake: number; 5 | owner: string; 6 | extraChallengeTimeBlocks: number; 7 | loserStakeEscrow: `0x${string}`; 8 | chainId: number; 9 | chainName: string; 10 | chainConfig: string; 11 | genesisBlockNum: number; 12 | nativeToken: string; 13 | sequencerInboxMaxTimeVariation: { 14 | delayBlocks: number; 15 | futureBlocks: number; 16 | delaySeconds: number; 17 | futureSeconds: number; 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/configBuilders.ts: -------------------------------------------------------------------------------- 1 | import { parseEther, GetFunctionArgs } from 'viem'; 2 | import { CoreContracts } from '@arbitrum/orbit-sdk'; 3 | import { rollupCreatorABI } from '@arbitrum/orbit-sdk/contracts/RollupCreator'; 4 | 5 | import { Wallet } from '@/types/RollupContracts'; 6 | import { L3Config } from '@/types/L3Config'; 7 | import { RollupConfig } from '@/types/rollupConfigDataType'; 8 | import { getRpcUrl } from '@/utils/getRpcUrl'; 9 | import { assertIsAddress } from './validators'; 10 | 11 | export type RollupConfigPayload = Omit< 12 | GetFunctionArgs['args'][0]['config'], 13 | 'wasmModuleRoot' | 'chainConfig' 14 | >; 15 | 16 | export const buildRollupConfigPayload = (rollupConfig: RollupConfig): RollupConfigPayload => { 17 | try { 18 | assertIsAddress(rollupConfig.owner); 19 | assertIsAddress(rollupConfig.stakeToken); 20 | 21 | return { 22 | confirmPeriodBlocks: BigInt(rollupConfig.confirmPeriodBlocks), 23 | extraChallengeTimeBlocks: BigInt(rollupConfig.extraChallengeTimeBlocks), 24 | stakeToken: rollupConfig.stakeToken, 25 | baseStake: parseEther(String(rollupConfig.baseStake)), 26 | owner: rollupConfig.owner, 27 | loserStakeEscrow: rollupConfig.loserStakeEscrow, 28 | chainId: BigInt(rollupConfig.chainId), 29 | genesisBlockNum: BigInt(rollupConfig.genesisBlockNum), 30 | sequencerInboxMaxTimeVariation: { 31 | delayBlocks: BigInt(rollupConfig.sequencerInboxMaxTimeVariation.delayBlocks), 32 | futureBlocks: BigInt(rollupConfig.sequencerInboxMaxTimeVariation.futureBlocks), 33 | delaySeconds: BigInt(rollupConfig.sequencerInboxMaxTimeVariation.delaySeconds), 34 | futureSeconds: BigInt(rollupConfig.sequencerInboxMaxTimeVariation.futureSeconds), 35 | }, 36 | }; 37 | } catch (e) { 38 | throw new Error(`Error building rollup config payload: ${e}`); 39 | } 40 | }; 41 | 42 | export type BuildL3ConfigParams = { 43 | address: string; 44 | rollupConfig: RollupConfig; 45 | validators: Wallet[]; 46 | batchPosters: Wallet[]; 47 | coreContracts: CoreContracts; 48 | parentChainId: number; 49 | }; 50 | 51 | export const buildL3Config = async ({ 52 | address, 53 | rollupConfig, 54 | validators, 55 | batchPosters, 56 | coreContracts, 57 | parentChainId, 58 | }: BuildL3ConfigParams): Promise => { 59 | const parentChainRpcUrl = getRpcUrl(parentChainId); 60 | 61 | try { 62 | const l3Config: L3Config = { 63 | 'networkFeeReceiver': address, 64 | 'infrastructureFeeCollector': address, 65 | 'staker': validators[0].address, 66 | 'batchPoster': batchPosters[0].address, 67 | 'chainOwner': rollupConfig.owner, 68 | 'chainId': Number(rollupConfig.chainId), 69 | 'chainName': rollupConfig.chainName, 70 | 'minL2BaseFee': 100000000, 71 | 'parentChainId': parentChainId, 72 | 'parent-chain-node-url': parentChainRpcUrl, 73 | 'utils': coreContracts.validatorUtils, 74 | ...coreContracts, 75 | }; 76 | return l3Config; 77 | } catch (e) { 78 | throw new Error(`Failed to build L3 Config: ${e}`); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const ARBITRUM_SEPOLIA_ICON_URL = 2 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PHJlY3Qgd2lkdGg9IjI2LjYiIGhlaWdodD0iMjYuNiIgeD0iLjciIHk9Ii43IiBmaWxsPSIjMkQzNzRCIiBzdHJva2U9IiM5NkJFREMiIHN0cm9rZS13aWR0aD0iMS40IiByeD0iMTMuMyIvPjxtYXNrIGlkPSJhIiB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHg9IjAiIHk9IjAiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHN0eWxlPSJtYXNrLXR5cGU6YWxwaGEiPjxyZWN0IHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0iI0M0QzRDNCIgcng9IjE0Ii8+PC9tYXNrPjxnIG1hc2s9InVybCgjYSkiPjxwYXRoIGZpbGw9IiMyOEEwRjAiIGQ9Im0xNC4wODYxIDE4LjYwNDEgNi41MDE0IDEwLjIyMzkgNC4wMDU3LTIuMzIxMy03Ljg2LTEyLjM5NDMtMi42NDcxIDQuNDkxN1ptMTMuMDc0NCAzLjQ2OTItLjAwMy0xLjg1OTktNy4zMDY0LTExLjQwNy0yLjMwODcgMy45MTczIDcuMDkxIDExLjQzMDMgMi4xNzItMS4yNTg2YS45NjI4Ljk2MjggMCAwIDAgLjM1NTUtLjcwMDlsLS4wMDA0LS4xMjEyWiIvPjxyZWN0IHdpZHRoPSIyNS45IiBoZWlnaHQ9IjI1LjkiIHg9IjEuMDUiIHk9IjEuMDUiIGZpbGw9InVybCgjYikiIGZpbGwtb3BhY2l0eT0iLjMiIHN0cm9rZT0iIzk2QkVEQyIgc3Ryb2tlLXdpZHRoPSIyLjEiIHJ4PSIxMi45NSIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Im0uMzYzNCAyOC4yMjA3LTMuMDctMS43Njc0LS4yMzQtLjgzMzNMNy43NDYxIDkuMDE5NGMuNzI5OC0xLjE5MTMgMi4zMTk3LTEuNTc1IDMuNzk1Ny0xLjU1NDFsMS43MzIzLjA0NTdMLjM2MzQgMjguMjIwN1pNMTkuMTY1NSA3LjUxMWwtNC41NjUzLjAxNjZMMi4yNCAyNy45NTMzbDMuNjEwMyAyLjA3ODguOTgxOC0xLjY2NTJMMTkuMTY1NSA3LjUxMVoiLz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCIgeDI9IjE0IiB5MT0iMCIgeTI9IjI4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PC9zdmc+Cg=='; 3 | 4 | // todo: allow changing this through the ui 5 | export const deterministicFactoriesDeploymentEnabled = false; 6 | -------------------------------------------------------------------------------- /src/utils/defaults.ts: -------------------------------------------------------------------------------- 1 | // this is for L2-L3, so if we ever do L4, we'll need to change this 2 | export const maxDataSize = BigInt(104_857); 3 | -------------------------------------------------------------------------------- /src/utils/deployRollup.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, WalletClient, Address, zeroAddress } from 'viem'; 2 | import { 3 | prepareChainConfig, 4 | prepareNodeConfig, 5 | CoreContracts, 6 | createRollupEnoughCustomFeeTokenAllowance, 7 | createRollupPrepareCustomFeeTokenApprovalTransactionRequest, 8 | createRollupPrepareDeploymentParamsConfig, 9 | createRollupPrepareTransactionRequest, 10 | createRollupPrepareTransactionReceipt, 11 | } from '@arbitrum/orbit-sdk'; 12 | 13 | import { ChainType } from '@/types/ChainType'; 14 | import { Wallet } from '@/types/RollupContracts'; 15 | import { RollupConfig } from '@/types/rollupConfigDataType'; 16 | import { buildL3Config, buildRollupConfigPayload } from './configBuilders'; 17 | import { updateLocalStorage } from './localStorageHandler'; 18 | import { assertIsAddress, assertIsAddressArray } from './validators'; 19 | import { ChainId } from '@/types/ChainId'; 20 | import { getRpcUrl } from './getRpcUrl'; 21 | 22 | type DeployRollupProps = { 23 | rollupConfig: RollupConfig; 24 | validators: Wallet[]; 25 | batchPosters: Wallet[]; 26 | publicClient: PublicClient; 27 | walletClient: WalletClient; 28 | chainType?: ChainType; 29 | account: Address; 30 | }; 31 | 32 | export async function deployRollup({ 33 | rollupConfig, 34 | validators, 35 | batchPosters, 36 | publicClient, 37 | walletClient, 38 | account, 39 | chainType = ChainType.Rollup, 40 | }: DeployRollupProps): Promise { 41 | try { 42 | assertIsAddress(rollupConfig.owner); 43 | 44 | const chainConfig = prepareChainConfig({ 45 | chainId: rollupConfig.chainId, 46 | arbitrum: { 47 | InitialChainOwner: rollupConfig.owner, 48 | DataAvailabilityCommittee: chainType === ChainType.AnyTrust, 49 | }, 50 | }); 51 | 52 | const rollupConfigPayload = createRollupPrepareDeploymentParamsConfig(publicClient, { 53 | chainConfig, 54 | ...buildRollupConfigPayload(rollupConfig), 55 | }); 56 | 57 | const validatorAddresses = validators.map((v) => v.address); 58 | const batchPosterAddresses = batchPosters.map((bp) => bp.address); 59 | const nativeToken = rollupConfig.nativeToken; 60 | 61 | // custom gas token 62 | if (nativeToken !== zeroAddress) { 63 | // check if enough allowance on rollup creator for custom gas token 64 | const enoughAllowance = await createRollupEnoughCustomFeeTokenAllowance({ 65 | nativeToken: nativeToken as Address, 66 | account: walletClient.account?.address!, 67 | publicClient, 68 | }); 69 | 70 | if (!enoughAllowance) { 71 | // if not, create tx to approve tokens to be spent 72 | const txRequest = await createRollupPrepareCustomFeeTokenApprovalTransactionRequest({ 73 | nativeToken: nativeToken as Address, 74 | account: walletClient.account?.address!, 75 | publicClient, 76 | }); 77 | 78 | // submit and wait for tx to be confirmed 79 | await publicClient.waitForTransactionReceipt({ 80 | hash: await walletClient.sendTransaction(txRequest), 81 | }); 82 | } 83 | } 84 | 85 | console.log(chainConfig); 86 | console.log('Going for deployment'); 87 | 88 | const parentChainId: ChainId = await publicClient.getChainId(); 89 | 90 | assertIsAddressArray(batchPosterAddresses); 91 | assertIsAddress(nativeToken); 92 | assertIsAddressArray(validatorAddresses); 93 | 94 | const txRequest = await createRollupPrepareTransactionRequest({ 95 | params: { 96 | config: rollupConfigPayload, 97 | batchPosters: batchPosterAddresses, 98 | validators: validatorAddresses, 99 | nativeToken, 100 | }, 101 | account: walletClient.account?.address!, 102 | publicClient, 103 | }); 104 | 105 | const txReceipt = createRollupPrepareTransactionReceipt( 106 | await publicClient.waitForTransactionReceipt({ 107 | hash: await walletClient.sendTransaction(txRequest), 108 | }), 109 | ); 110 | 111 | const coreContracts = txReceipt.getCoreContracts(); 112 | 113 | const nodeConfig = prepareNodeConfig({ 114 | chainName: rollupConfig.chainName, 115 | chainConfig, 116 | coreContracts, 117 | batchPosterPrivateKey: batchPosters[0].privateKey || '', 118 | validatorPrivateKey: validators[0].privateKey || '', 119 | parentChainId, 120 | parentChainRpcUrl: getRpcUrl(parentChainId), 121 | dasServerUrl: 'http://das-server', 122 | }); 123 | 124 | // Defining L3 config 125 | const l3Config = await buildL3Config({ 126 | address: account, 127 | rollupConfig, 128 | coreContracts, 129 | validators, 130 | batchPosters, 131 | parentChainId, 132 | }); 133 | 134 | updateLocalStorage(nodeConfig, l3Config); 135 | 136 | return coreContracts; 137 | } catch (e) { 138 | throw new Error(`Failed to deploy rollup:\n${e}`); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | import { toast } from 'react-toastify'; 2 | 3 | export const showSigningErrorToast = (error: unknown) => { 4 | if (error instanceof Error) { 5 | // Shorten the error message sent from MetaMask (or other wallets) by grabbing just the first sentence 6 | const periodIndex = error.message.indexOf('.'); 7 | const message = error.message.substring(0, periodIndex ? periodIndex + 1 : undefined); 8 | toast(message, { type: 'error', pauseOnHover: true }); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/getBlockExplorerUrl.ts: -------------------------------------------------------------------------------- 1 | import { sepolia } from 'wagmi/chains'; 2 | 3 | import { ChainId } from '@/types/ChainId'; 4 | 5 | export function getBlockExplorerUrl(chainId: number) { 6 | switch (chainId) { 7 | case sepolia.id: 8 | return sepolia.blockExplorers.default.url; 9 | 10 | case ChainId.ArbitrumSepolia: 11 | return 'https://sepolia.arbiscan.io'; 12 | 13 | default: 14 | throw new Error(`[getBlockExplorerUrl] Unexpected chainId: ${chainId}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/getRandomWallet.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@/types/RollupContracts'; 2 | import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; 3 | 4 | export const getRandomWallet = () => { 5 | const privateKey = generatePrivateKey(); 6 | const account = privateKeyToAccount(privateKey); 7 | return { privateKey, address: account.address } as Wallet; 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/getRpcUrl.ts: -------------------------------------------------------------------------------- 1 | import { sepolia } from 'wagmi/chains'; 2 | 3 | import { ChainId } from '@/types/ChainId'; 4 | 5 | export function getRpcUrl(chainId: number) { 6 | switch (chainId) { 7 | case sepolia.id: 8 | return sepolia.rpcUrls.default.http[0]; 9 | 10 | case ChainId.ArbitrumSepolia: 11 | return 'https://sepolia-rollup.arbitrum.io/rpc'; 12 | 13 | default: 14 | throw new Error(`[getRpcUrl] Unexpected chainId: ${chainId}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/isUserRejectedError.ts: -------------------------------------------------------------------------------- 1 | export function isUserRejectedError(error: any) { 2 | return error?.code === 4001 || error?.code === 'ACTION_REJECTED'; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/localStorageHandler.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfig } from '@arbitrum/orbit-sdk'; 2 | import { L3Config } from '@/types/L3Config'; 3 | 4 | // Function to update local storage with new rollup data and l3 data 5 | export function updateLocalStorage(data: NodeConfig, l3config: L3Config) { 6 | const currentData = localStorage.getItem('rollupData'); 7 | const currentL3Config = localStorage.getItem('l3Config'); 8 | let updatedData: any = {}; 9 | let updatedL3Config: any = {}; 10 | 11 | if (currentData) { 12 | updatedData = JSON.parse(currentData); 13 | } 14 | if (currentL3Config) { 15 | updatedL3Config = JSON.parse(currentL3Config); 16 | } 17 | 18 | Object.assign(updatedData, data); 19 | Object.assign(updatedL3Config, l3config); 20 | 21 | localStorage.setItem('rollupData', JSON.stringify(updatedData)); 22 | localStorage.setItem('l3Config', JSON.stringify(updatedL3Config)); 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const AddressSchema = z 4 | .string() 5 | .length(42) 6 | .regex(/^0x[0-9a-fA-F]+$/, 'Must be a valid address'); 7 | 8 | export const PrivateKeySchema = z 9 | .string() 10 | .length(2 + 64) 11 | .regex(/^0x[0-9a-fA-F]+$/, 'Must be a valid private key'); 12 | -------------------------------------------------------------------------------- /src/utils/validators.ts: -------------------------------------------------------------------------------- 1 | import { Address, isAddress } from 'viem'; 2 | 3 | export function assertIsAddress(value: any): asserts value is Address { 4 | if (typeof value !== 'string' || !isAddress(value)) { 5 | throw new Error(`${value} is not a valid address`); 6 | } 7 | } 8 | 9 | export function assertIsAddressArray(values: any[]): asserts values is Address[] { 10 | values.forEach(assertIsAddress); 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/wallets.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@/types/RollupContracts'; 2 | 3 | // Remove the private key if the user entered a custom address 4 | export const compareWallets = (wallets: Wallet[], addresses: string[]): Wallet[] => { 5 | return addresses 6 | .map((address) => { 7 | const wallet = wallets.find((w) => w.address === address); 8 | return { 9 | privateKey: wallet ? wallet.privateKey : undefined, 10 | address, 11 | }; 12 | }) 13 | .filter(Boolean); 14 | }; 15 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | important: false, 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | './app/**/*.{js,ts,jsx,tsx,mdx}', 9 | './node_modules/@offchainlabs/cobalt/dist/*.js', 10 | ], 11 | theme: { 12 | extend: { 13 | backgroundImage: { 14 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 15 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 16 | 'gradient-landing-banner': 'linear-gradient(90deg, #12AAFF 18.22%, #1B4ADD 99.99%)', 17 | }, 18 | boxShadow: { 19 | input: '0px 2px 2px rgba(33, 37, 41, 0.06), 0px 0px 1px rgba(33, 37, 41, 0.08)', 20 | }, 21 | colors: { 22 | 'green': '#32CD32', 23 | 'yellow': '#60461F', 24 | 'darkgrey': '#A9A9A9', 25 | 'grey': '#808080', 26 | 'lightgrey': '#D3D3D3', 27 | 'default-black': '#1a1c1d', 28 | 'blue': '#12aaff', 29 | 'lime-dark': '#31572A', 30 | 'stylus-pink': '#F62674', 31 | }, 32 | backgroundColor: { 33 | DEFAULT: '#000000', 34 | }, 35 | textColor: { 36 | DEFAULT: '#FFFFFF', 37 | }, 38 | }, 39 | }, 40 | plugins: [], 41 | corePlugins: { 42 | // Override the input text color 43 | inputTextColor: ({ addBase, theme }) => { 44 | addBase({ 45 | input: { color: theme('colors.black') }, 46 | }); 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "paths": { 22 | "@/*": [ 23 | "./src/*" 24 | ] 25 | }, 26 | "plugins": [ 27 | { 28 | "name": "next" 29 | } 30 | ] 31 | }, 32 | "include": [ 33 | "next-env.d.ts", 34 | "**/*.ts", 35 | "**/*.tsx", 36 | "**/*/*.tsx", 37 | "**/*/*.ts", 38 | ".next/types/**/*.ts" 39 | ], 40 | "exclude": [ 41 | "node_modules" 42 | ] 43 | } --------------------------------------------------------------------------------