diff --git a/.eslintignore b/.eslintignore
index d3881d1d..9fca3ec5 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,5 @@
node_modules
+interface
lib
cache
typechain-types
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index e5b16e1a..c46904a1 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -48,7 +48,9 @@ jobs:
run: pnpm install --prefer-offline --frozen-lockfile
- name: Prettier and lint
- run: pnpm lint:check
+ run: |
+ pnpm lint:check
+ pnpm lint:check:interface
codespell:
runs-on: ${{ matrix.os }}
@@ -91,5 +93,6 @@ jobs:
- name: Validate URLs
run: |
- awesome_bot ./*.md src/*.sol test/**/*.sol test/public/**/*.sol --allow-dupe --request-delay 0.4 \
+ awesome_bot ./*.md src/*.sol test/**/*.sol test/public/**/*.sol interface/*.md interface/public/*.css interface/src/**/*.css interface/src/**/*.tsx interface/src/**/*.ts interface/src/components/**/*.tsx \
+ --allow-dupe --request-delay 0.4 \
--white-list "https://github.com/pcaversaccio/createx/issues/new?assignees=pcaversaccio&labels=new+deployment+%E2%9E%95&projects=&template=deployment_request.yml&title=%5BNew-Deployment-Request%5D%3A+",https://twitter.com/PaulRBerg/status/1682346315806539776,https://www.createx.rocks,https://foundry.paradigm.xyz,https://github.com/pcaversaccio/createx.git
diff --git a/.prettierignore b/.prettierignore
index d3881d1d..9fca3ec5 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,5 @@
node_modules
+interface
lib
cache
typechain-types
diff --git a/.solhintignore b/.solhintignore
index d3881d1d..9fca3ec5 100644
--- a/.solhintignore
+++ b/.solhintignore
@@ -1,4 +1,5 @@
node_modules
+interface
lib
cache
typechain-types
diff --git a/CNAME b/CNAME
index 74594e48..8d684c20 100644
--- a/CNAME
+++ b/CNAME
@@ -1 +1 @@
-createx.rocks
\ No newline at end of file
+createx.rocks
diff --git a/interface/.eslintignore b/interface/.eslintignore
new file mode 100644
index 00000000..06657422
--- /dev/null
+++ b/interface/.eslintignore
@@ -0,0 +1,3 @@
+node_modules
+.next
+next-env.d.ts
diff --git a/interface/.eslintrc.json b/interface/.eslintrc.json
new file mode 100644
index 00000000..dffe8401
--- /dev/null
+++ b/interface/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["next"]
+}
diff --git a/interface/.gitignore b/interface/.gitignore
new file mode 100644
index 00000000..590e5b57
--- /dev/null
+++ b/interface/.gitignore
@@ -0,0 +1,51 @@
+# Ignore all Visual Studio Code settings
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local history for Visual Studio Code
+.history
+
+# Built Visual Studio Code extensions
+*.vsix
+
+# Dependency directory
+node_modules
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# Log files
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# Yarn integrity file
+.yarn-integrity
+
+# Optional npm cache directory
+.npm
+
+# Modern Yarn files
+.pnp.*
+.yarn/*
+!.yarn/cache
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Next.js
+.next
+next-env.d.ts
diff --git a/interface/.prettierignore b/interface/.prettierignore
new file mode 100644
index 00000000..06657422
--- /dev/null
+++ b/interface/.prettierignore
@@ -0,0 +1,3 @@
+node_modules
+.next
+next-env.d.ts
diff --git a/interface/.prettierrc.yml b/interface/.prettierrc.yml
new file mode 100644
index 00000000..986620f3
--- /dev/null
+++ b/interface/.prettierrc.yml
@@ -0,0 +1,6 @@
+plugins:
+ - "prettier-plugin-tailwindcss"
+ - "@trivago/prettier-plugin-sort-imports"
+importOrder:
+ ["^react(.*)", "next/(.*)", "", "@/(.*)", "^[./]"]
+importOrderSortSpecifiers: true
diff --git a/interface/LICENSE b/interface/LICENSE
new file mode 100644
index 00000000..2cc7ab2d
--- /dev/null
+++ b/interface/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Matt Solomon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/interface/README.md b/interface/README.md
new file mode 100644
index 00000000..9e48da5e
--- /dev/null
+++ b/interface/README.md
@@ -0,0 +1,7 @@
+# [`CreateX`](../src/CreateX.sol) User Interface
+
+[Next.js](https://nextjs.org)-based user interface for [createx.rocks](https://www.createx.rocks).
+
+## 🙏🏼 Acknowledgement
+
+Our code is based on a fork of [Matt Solomon](https://github.com/mds1)'s [`Multicall3`](https://github.com/mds1/multicall) [frontend](https://github.com/mds1/multicall3-frontend), licensed under the [MIT License](https://github.com/mds1/multicall3-frontend/blob/main/LICENSE).
diff --git a/interface/package.json b/interface/package.json
new file mode 100644
index 00000000..9b319102
--- /dev/null
+++ b/interface/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "createx-interface",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Next.js-based user interface for createx.rocks.",
+ "keywords": [
+ "frontend",
+ "interface",
+ "nextjs",
+ "react",
+ "typescript"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/pcaversaccio/createx.git"
+ },
+ "homepage": "https://github.com/pcaversaccio/createx/tree/main/interface#readme",
+ "bugs": {
+ "url": "https://github.com/pcaversaccio/createx/issues"
+ },
+ "author": "pcaversaccio (https://pcaversaccio.com), Matt Solomon (https://mattsolomon.dev)",
+ "license": "MIT",
+ "scripts": {
+ "dev": "npx next dev",
+ "build": "npx next build",
+ "start": "pnpm build && npx next start",
+ "prettier:check": "npx prettier -c **/*.{js,ts,tsx,css,md,json,yml,yaml}",
+ "prettier:fix": "npx prettier -w **/*.{js,ts,tsx,css,md,json,yml,yaml}",
+ "lint:check": "pnpm prettier:check && npx next lint",
+ "lint:fix": "pnpm prettier:fix && npx next lint --fix"
+ },
+ "dependencies": {
+ "@headlessui/react": "^1.7.17",
+ "@heroicons/react": "^2.0.18",
+ "next": "14.0.4",
+ "next-themes": "^0.2.1",
+ "prismjs": "^1.29.0",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "sharp": "^0.33.0"
+ },
+ "devDependencies": {
+ "@trivago/prettier-plugin-sort-imports": "^4.3.0",
+ "@types/node": "20.10.4",
+ "@types/react": "18.2.45",
+ "@types/react-dom": "18.2.18",
+ "autoprefixer": "10.4.16",
+ "eslint": "8.56.0",
+ "eslint-config-next": "14.0.4",
+ "postcss": "8.4.32",
+ "prettier": "^3.1.1",
+ "prettier-plugin-tailwindcss": "^0.5.9",
+ "tailwindcss": "3.3.6",
+ "typescript": "5.3.3"
+ }
+}
diff --git a/interface/postcss.config.js b/interface/postcss.config.js
new file mode 100644
index 00000000..12a703d9
--- /dev/null
+++ b/interface/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/interface/prismjs.d.ts b/interface/prismjs.d.ts
new file mode 100644
index 00000000..043681c5
--- /dev/null
+++ b/interface/prismjs.d.ts
@@ -0,0 +1,5 @@
+declare module "prismjs";
+declare module "prismjs/themes/*" {
+ const content: string;
+ export default content;
+}
diff --git a/interface/public/ethersjs.png b/interface/public/ethersjs.png
new file mode 100644
index 00000000..db131923
Binary files /dev/null and b/interface/public/ethersjs.png differ
diff --git a/interface/public/favicon.ico b/interface/public/favicon.ico
new file mode 100644
index 00000000..1313a22d
Binary files /dev/null and b/interface/public/favicon.ico differ
diff --git a/interface/public/json.svg b/interface/public/json.svg
new file mode 100644
index 00000000..e83b8dfb
--- /dev/null
+++ b/interface/public/json.svg
@@ -0,0 +1,47 @@
+
diff --git a/interface/public/prism-dark.css b/interface/public/prism-dark.css
new file mode 100644
index 00000000..42bf2190
--- /dev/null
+++ b/interface/public/prism-dark.css
@@ -0,0 +1,126 @@
+/**
+ * This file was copied from node_modules/prismjs/themes/prism-tomorrow.css and is required for clean
+ * applying and removing of the prism.js themes when switching between light and dark mode.
+ */
+
+/**
+ * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
+ * Based on https://github.com/chriskempson/tomorrow-theme
+ * @author Rose Pritchard
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+ color: #ccc;
+ background: none;
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
+ font-size: 1em;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
+
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+ padding: 1em;
+ margin: 0.5em 0;
+ overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #2d2d2d;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+ padding: 0.1em;
+ border-radius: 0.3em;
+ white-space: normal;
+}
+
+.token.comment,
+.token.block-comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #999;
+}
+
+.token.punctuation {
+ color: #ccc;
+}
+
+.token.tag,
+.token.attr-name,
+.token.namespace,
+.token.deleted {
+ color: #e2777a;
+}
+
+.token.function-name {
+ color: #6196cc;
+}
+
+.token.boolean,
+.token.number,
+.token.function {
+ color: #f08d49;
+}
+
+.token.property,
+.token.class-name,
+.token.constant,
+.token.symbol {
+ color: #f8c555;
+}
+
+.token.selector,
+.token.important,
+.token.atrule,
+.token.keyword,
+.token.builtin {
+ color: #cc99cd;
+}
+
+.token.string,
+.token.char,
+.token.attr-value,
+.token.regex,
+.token.variable {
+ color: #7ec699;
+}
+
+.token.operator,
+.token.entity,
+.token.url {
+ color: #67cdcc;
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
+
+.token.inserted {
+ color: green;
+}
diff --git a/interface/public/prism-light.css b/interface/public/prism-light.css
new file mode 100644
index 00000000..ba44ca06
--- /dev/null
+++ b/interface/public/prism-light.css
@@ -0,0 +1,149 @@
+/**
+ * This file was copied from node_modules/prismjs/themes/prism.css and is required for clean
+ * applying and removing of the prism.js themes when switching between light and dark mode.
+ */
+
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (https://dabblet.com/)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+ color: black;
+ background: none;
+ text-shadow: 0 1px white;
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
+ font-size: 1em;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
+
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+ text-shadow: none;
+ background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+ text-shadow: none;
+ background: #b3d4fc;
+}
+
+@media print {
+ code[class*="language-"],
+ pre[class*="language-"] {
+ text-shadow: none;
+ }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+ padding: 1em;
+ margin: 0.5em 0;
+ overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+ padding: 0.1em;
+ border-radius: 0.3em;
+ white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: slategray;
+}
+
+.token.punctuation {
+ color: #999;
+}
+
+.token.namespace {
+ opacity: 0.7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+ color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+ color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+ color: #9a6e3a;
+ /* This background color was intended by the author of this theme. */
+ background: hsla(0, 0%, 100%, 0.5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+ color: #07a;
+}
+
+.token.function,
+.token.class-name {
+ color: #dd4a68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+ color: #e90;
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
diff --git a/interface/public/solidity.png b/interface/public/solidity.png
new file mode 100644
index 00000000..b6cce3c3
Binary files /dev/null and b/interface/public/solidity.png differ
diff --git a/interface/public/viem.png b/interface/public/viem.png
new file mode 100644
index 00000000..dae92c2c
Binary files /dev/null and b/interface/public/viem.png differ
diff --git a/interface/src/components/layout/ExternalLink.tsx b/interface/src/components/layout/ExternalLink.tsx
new file mode 100644
index 00000000..bae52a3c
--- /dev/null
+++ b/interface/src/components/layout/ExternalLink.tsx
@@ -0,0 +1,20 @@
+type Props = {
+ href: string;
+ className?: string;
+} & (
+ | { text: string; children?: never }
+ | { text?: never; children: JSX.Element }
+);
+
+export const ExternalLink = ({ href, className, text, children }: Props) => {
+ return (
+
+ {text || children}
+
+ );
+};
diff --git a/interface/src/components/layout/Footer.tsx b/interface/src/components/layout/Footer.tsx
new file mode 100644
index 00000000..335aadd4
--- /dev/null
+++ b/interface/src/components/layout/Footer.tsx
@@ -0,0 +1,82 @@
+import { ExternalLink } from "@/components/layout/ExternalLink";
+import { ThemeSwitcher } from "@/components/layout/ThemeSwitcher";
+import { COMPANY_NAME, COMPANY_URL, GITHUB_URL, X_URL } from "@/lib/constants";
+
+const navigation = [
+ {
+ name: "X",
+ href: X_URL,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ icon: (props: any) => (
+
+ ),
+ },
+ {
+ name: "GitHub",
+ href: GITHUB_URL,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ icon: (props: any) => (
+
+ ),
+ },
+];
+
+export const Footer = () => {
+ const currentYear = new Date().getFullYear();
+ return (
+
+ );
+};
diff --git a/interface/src/components/layout/Head.tsx b/interface/src/components/layout/Head.tsx
new file mode 100644
index 00000000..a976b991
--- /dev/null
+++ b/interface/src/components/layout/Head.tsx
@@ -0,0 +1,32 @@
+import NextHead from "next/head";
+import { SITE_DESCRIPTION, SITE_NAME } from "@/lib/constants";
+
+interface Props {
+ title?: string;
+ description?: string;
+}
+
+export const Head = (props: Props) => {
+ return (
+
+ {props.title ? `${props.title} | ${SITE_NAME}` : SITE_NAME}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/interface/src/components/layout/Header.tsx b/interface/src/components/layout/Header.tsx
new file mode 100644
index 00000000..cab2814b
--- /dev/null
+++ b/interface/src/components/layout/Header.tsx
@@ -0,0 +1,93 @@
+import { useState } from "react";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { ClipboardDocumentIcon } from "@heroicons/react/24/solid";
+import { Notification } from "@/components/ui/Notification";
+import { COMPANY_NAME, CREATEX_ADDRESS, SITE_NAME } from "@/lib/constants";
+import { copyToClipboard } from "@/lib/utils";
+
+export const Header = () => {
+ // -------- Navigation --------
+ const currentPath = useRouter().pathname;
+
+ const NavLink = (props: {
+ path: string;
+ label: string;
+ className?: string;
+ }) => {
+ const activeClass = props.path === currentPath ? "font-bold" : "";
+ return (
+
+ {props.label}
+
+ );
+ };
+
+ // -------- Copy Address to Clipboard --------
+ const [showNotification, setShowNotification] = useState(false);
+
+ const onCopy = (text: string) => {
+ copyToClipboard(text);
+ setShowNotification(true);
+ setTimeout(() => setShowNotification(false), 3000);
+ };
+
+ // -------- Render --------
+ return (
+
+
+
+
+
+
+
+ {COMPANY_NAME}
+
+ {SITE_NAME}
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+
+
+
+ Deployment Address
+
+
+
+ {CREATEX_ADDRESS}
+
+
onCopy(CREATEX_ADDRESS)}
+ />
+
+
+
+
+ );
+};
diff --git a/interface/src/components/layout/Layout.tsx b/interface/src/components/layout/Layout.tsx
new file mode 100644
index 00000000..ddec6197
--- /dev/null
+++ b/interface/src/components/layout/Layout.tsx
@@ -0,0 +1,18 @@
+import { Footer } from "@/components/layout/Footer";
+import { Header } from "@/components/layout/Header";
+
+interface Props {
+ children: JSX.Element;
+}
+
+export const Layout = ({ children }: Props) => {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+};
diff --git a/interface/src/components/layout/ThemeSwitcher.tsx b/interface/src/components/layout/ThemeSwitcher.tsx
new file mode 100644
index 00000000..e2fb31da
--- /dev/null
+++ b/interface/src/components/layout/ThemeSwitcher.tsx
@@ -0,0 +1,18 @@
+import { useCallback } from "react";
+import { MoonIcon, SunIcon } from "@heroicons/react/20/solid";
+import { useTheme } from "next-themes";
+
+export const ThemeSwitcher = ({ className = "" }) => {
+ const { resolvedTheme, setTheme } = useTheme();
+
+ const toggleTheme = useCallback(() => {
+ setTheme(resolvedTheme === "light" ? "dark" : "light");
+ }, [resolvedTheme, setTheme]);
+
+ return (
+
+ );
+};
diff --git a/interface/src/components/ui/LoadingSpinner.tsx b/interface/src/components/ui/LoadingSpinner.tsx
new file mode 100644
index 00000000..6f25e016
--- /dev/null
+++ b/interface/src/components/ui/LoadingSpinner.tsx
@@ -0,0 +1,27 @@
+export const LoadingSpinner = () => {
+ return (
+
+ );
+};
diff --git a/interface/src/components/ui/Notification.tsx b/interface/src/components/ui/Notification.tsx
new file mode 100644
index 00000000..e28f048d
--- /dev/null
+++ b/interface/src/components/ui/Notification.tsx
@@ -0,0 +1,109 @@
+import { Fragment } from "react";
+import { Transition } from "@headlessui/react";
+import {
+ CheckCircleIcon,
+ ExclamationCircleIcon,
+ InformationCircleIcon,
+ XCircleIcon,
+} from "@heroicons/react/24/outline";
+import { XMarkIcon } from "@heroicons/react/24/solid";
+
+function getKindInfo(kind: "success" | "warning" | "error" | "info") {
+ if (kind === "success")
+ return {
+ icon: CheckCircleIcon,
+ iconColor: "text-green-500 dark:text-green-400",
+ };
+ if (kind === "warning")
+ return {
+ icon: ExclamationCircleIcon,
+ iconColor: "text-yellow-500 dark:text-yellow-400",
+ };
+ if (kind === "error")
+ return { icon: XCircleIcon, iconColor: "text-red-600 dark:text-red-500" };
+ return {
+ icon: InformationCircleIcon,
+ iconColor: "text-blue-500 dark:text-blue-400",
+ };
+}
+
+interface Props {
+ show: boolean;
+ setShow: (show: boolean) => void;
+ kind: "success" | "warning" | "error" | "info";
+ title: string;
+ description?: string;
+}
+
+export const Notification = ({
+ show,
+ setShow,
+ kind = "info",
+ title,
+ description,
+}: Props) => {
+ const { icon: Icon, iconColor } = getKindInfo(kind);
+
+ const bgColor = "bg-gray-50 dark:bg-gray-800";
+ const titleTextColor = "text-primary";
+ const descriptionTextColor = "text-secondary";
+
+ return (
+ <>
+ {/* Global notification live region, render this permanently at the end of the document */}
+
+
+ {/* Notification panel, dynamically insert this into the live region when it needs to be displayed */}
+
+
+
+
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/interface/src/lib/constants.ts b/interface/src/lib/constants.ts
new file mode 100644
index 00000000..86bac11d
--- /dev/null
+++ b/interface/src/lib/constants.ts
@@ -0,0 +1,1161 @@
+export const SITE_NAME = "CreateX";
+export const SITE_DESCRIPTION = "A Trustless, Universal Contract Deployer";
+export const COMPANY_NAME = "pcaversaccio";
+export const COMPANY_URL = "https://pcaversaccio.com";
+export const GITHUB_URL = "https://github.com/pcaversaccio/createx";
+export const X_URL = "https://twitter.com/pcaversaccio";
+
+export const CREATEX_ADDRESS = "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed";
+export const CREATEX_ABI = [
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "emitter",
+ type: "address",
+ },
+ ],
+ name: "FailedContractCreation",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "emitter",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "revertData",
+ type: "bytes",
+ },
+ ],
+ name: "FailedContractInitialisation",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "emitter",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "revertData",
+ type: "bytes",
+ },
+ ],
+ name: "FailedEtherTransfer",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "emitter",
+ type: "address",
+ },
+ ],
+ name: "InvalidNonceValue",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "emitter",
+ type: "address",
+ },
+ ],
+ name: "InvalidSalt",
+ type: "error",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ ],
+ name: "ContractCreation",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ name: "ContractCreation",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ ],
+ name: "Create3ProxyContractCreation",
+ type: "event",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes32",
+ name: "initCodeHash",
+ type: "bytes32",
+ },
+ ],
+ name: "computeCreate2Address",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes32",
+ name: "initCodeHash",
+ type: "bytes32",
+ },
+ {
+ internalType: "address",
+ name: "deployer",
+ type: "address",
+ },
+ ],
+ name: "computeCreate2Address",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "pure",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "address",
+ name: "deployer",
+ type: "address",
+ },
+ ],
+ name: "computeCreate3Address",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "pure",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ ],
+ name: "computeCreate3Address",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "uint256",
+ name: "nonce",
+ type: "uint256",
+ },
+ ],
+ name: "computeCreateAddress",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "deployer",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "nonce",
+ type: "uint256",
+ },
+ ],
+ name: "computeCreateAddress",
+ outputs: [
+ {
+ internalType: "address",
+ name: "computedAddress",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate2",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate2",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ {
+ internalType: "address",
+ name: "refundAddress",
+ type: "address",
+ },
+ ],
+ name: "deployCreate2AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ ],
+ name: "deployCreate2AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ {
+ internalType: "address",
+ name: "refundAddress",
+ type: "address",
+ },
+ ],
+ name: "deployCreate2AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ ],
+ name: "deployCreate2AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "address",
+ name: "implementation",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate2Clone",
+ outputs: [
+ {
+ internalType: "address",
+ name: "proxy",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "implementation",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate2Clone",
+ outputs: [
+ {
+ internalType: "address",
+ name: "proxy",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate3",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreate3",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ ],
+ name: "deployCreate3AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ ],
+ name: "deployCreate3AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "salt",
+ type: "bytes32",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ {
+ internalType: "address",
+ name: "refundAddress",
+ type: "address",
+ },
+ ],
+ name: "deployCreate3AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ {
+ internalType: "address",
+ name: "refundAddress",
+ type: "address",
+ },
+ ],
+ name: "deployCreate3AndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ ],
+ name: "deployCreateAndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ components: [
+ {
+ internalType: "uint256",
+ name: "constructorAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "initCallAmount",
+ type: "uint256",
+ },
+ ],
+ internalType: "struct CreateX.Values",
+ name: "values",
+ type: "tuple",
+ },
+ {
+ internalType: "address",
+ name: "refundAddress",
+ type: "address",
+ },
+ ],
+ name: "deployCreateAndInit",
+ outputs: [
+ {
+ internalType: "address",
+ name: "newContract",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "implementation",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ ],
+ name: "deployCreateClone",
+ outputs: [
+ {
+ internalType: "address",
+ name: "proxy",
+ type: "address",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+] as const;
+
+export const CREATEX_ABI_VIEM = `[
+ "error FailedContractCreation(address emitter)",
+ "error FailedContractInitialisation(address emitter, bytes revertData)",
+ "error FailedEtherTransfer(address emitter, bytes revertData)",
+ "error InvalidNonceValue(address emitter)",
+ "error InvalidSalt(address emitter)",
+ "event ContractCreation(address indexed newContract, bytes32 indexed salt)",
+ "event ContractCreation(address indexed newContract)",
+ "event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt)",
+ "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) view returns (address computedAddress)",
+ "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) pure returns (address computedAddress)",
+ "function computeCreate3Address(bytes32 salt, address deployer) pure returns (address computedAddress)",
+ "function computeCreate3Address(bytes32 salt) view returns (address computedAddress)",
+ "function computeCreateAddress(uint256 nonce) view returns (address computedAddress)",
+ "function computeCreateAddress(address deployer, uint256 nonce) view returns (address computedAddress)",
+ "function deployCreate(bytes initCode) payable returns (address newContract)",
+ "function deployCreate2(bytes32 salt, bytes initCode) payable returns (address newContract)",
+ "function deployCreate2(bytes initCode) payable returns (address newContract)",
+ "function deployCreate2AndInit(bytes32 salt, bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values, address refundAddress) payable returns (address newContract)",
+ "function deployCreate2AndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values) payable returns (address newContract)",
+ "function deployCreate2AndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values, address refundAddress) payable returns (address newContract)",
+ "function deployCreate2AndInit(bytes32 salt, bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values) payable returns (address newContract)",
+ "function deployCreate2Clone(bytes32 salt, address implementation, bytes data) payable returns (address proxy)",
+ "function deployCreate2Clone(address implementation, bytes data) payable returns (address proxy)",
+ "function deployCreate3(bytes initCode) payable returns (address newContract)",
+ "function deployCreate3(bytes32 salt, bytes initCode) payable returns (address newContract)",
+ "function deployCreate3AndInit(bytes32 salt, bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values) payable returns (address newContract)",
+ "function deployCreate3AndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values) payable returns (address newContract)",
+ "function deployCreate3AndInit(bytes32 salt, bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values, address refundAddress) payable returns (address newContract)",
+ "function deployCreate3AndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values, address refundAddress) payable returns (address newContract)",
+ "function deployCreateAndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values) payable returns (address newContract)",
+ "function deployCreateAndInit(bytes initCode, bytes data, (uint256 constructorAmount, uint256 initCallAmount) values, address refundAddress) payable returns (address newContract)",
+ "function deployCreateClone(address implementation, bytes data) payable returns (address proxy)",
+] as const;`;
+
+export const CREATEX_ABI_ETHERS = `[
+ "error FailedContractCreation(address)",
+ "error FailedContractInitialisation(address,bytes)",
+ "error FailedEtherTransfer(address,bytes)",
+ "error InvalidNonceValue(address)",
+ "error InvalidSalt(address)",
+ "event ContractCreation(address indexed,bytes32 indexed)",
+ "event ContractCreation(address indexed)",
+ "event Create3ProxyContractCreation(address indexed,bytes32 indexed)",
+ "function computeCreate2Address(bytes32,bytes32) view returns (address)",
+ "function computeCreate2Address(bytes32,bytes32,address) pure returns (address)",
+ "function computeCreate3Address(bytes32,address) pure returns (address)",
+ "function computeCreate3Address(bytes32) view returns (address)",
+ "function computeCreateAddress(uint256) view returns (address)",
+ "function computeCreateAddress(address,uint256) view returns (address)",
+ "function deployCreate(bytes) payable returns (address)",
+ "function deployCreate2(bytes32,bytes) payable returns (address)",
+ "function deployCreate2(bytes) payable returns (address)",
+ "function deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256),address) payable returns (address)",
+ "function deployCreate2AndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)",
+ "function deployCreate2AndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)",
+ "function deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)) payable returns (address)",
+ "function deployCreate2Clone(bytes32,address,bytes) payable returns (address)",
+ "function deployCreate2Clone(address,bytes) payable returns (address)",
+ "function deployCreate3(bytes) payable returns (address)",
+ "function deployCreate3(bytes32,bytes) payable returns (address)",
+ "function deployCreate3AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)) payable returns (address)",
+ "function deployCreate3AndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)",
+ "function deployCreate3AndInit(bytes32,bytes,bytes,tuple(uint256,uint256),address) payable returns (address)",
+ "function deployCreate3AndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)",
+ "function deployCreateAndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)",
+ "function deployCreateAndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)",
+ "function deployCreateClone(address,bytes) payable returns (address)"
+]`;
+
+export const CREATEX_SOLIDITY_INTERFACE = `// SPDX-License-Identifier: AGPL-3.0-only
+pragma solidity ^0.8.4;
+
+/**
+ * @title CreateX Factory Interface Definition
+ * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)
+ * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)
+ */
+interface ICreateX {
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* TYPES */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ struct Values {
+ uint256 constructorAmount;
+ uint256 initCallAmount;
+ }
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* EVENTS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ event ContractCreation(address indexed newContract, bytes32 indexed salt);
+ event ContractCreation(address indexed newContract);
+ event Create3ProxyContractCreation(
+ address indexed newContract,
+ bytes32 indexed salt
+ );
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* CUSTOM ERRORS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ error FailedContractCreation(address emitter);
+ error FailedContractInitialisation(address emitter, bytes revertData);
+ error InvalidSalt(address emitter);
+ error InvalidNonceValue(address emitter);
+ error FailedEtherTransfer(address emitter, bytes revertData);
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* CREATE */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ function deployCreate(
+ bytes memory initCode
+ ) external payable returns (address newContract);
+
+ function deployCreateAndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values,
+ address refundAddress
+ ) external payable returns (address newContract);
+
+ function deployCreateAndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values
+ ) external payable returns (address newContract);
+
+ function deployCreateClone(
+ address implementation,
+ bytes memory data
+ ) external payable returns (address proxy);
+
+ function computeCreateAddress(
+ address deployer,
+ uint256 nonce
+ ) external view returns (address computedAddress);
+
+ function computeCreateAddress(
+ uint256 nonce
+ ) external view returns (address computedAddress);
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* CREATE2 */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ function deployCreate2(
+ bytes32 salt,
+ bytes memory initCode
+ ) external payable returns (address newContract);
+
+ function deployCreate2(
+ bytes memory initCode
+ ) external payable returns (address newContract);
+
+ function deployCreate2AndInit(
+ bytes32 salt,
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values,
+ address refundAddress
+ ) external payable returns (address newContract);
+
+ function deployCreate2AndInit(
+ bytes32 salt,
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values
+ ) external payable returns (address newContract);
+
+ function deployCreate2AndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values,
+ address refundAddress
+ ) external payable returns (address newContract);
+
+ function deployCreate2AndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values
+ ) external payable returns (address newContract);
+
+ function deployCreate2Clone(
+ bytes32 salt,
+ address implementation,
+ bytes memory data
+ ) external payable returns (address proxy);
+
+ function deployCreate2Clone(
+ address implementation,
+ bytes memory data
+ ) external payable returns (address proxy);
+
+ function computeCreate2Address(
+ bytes32 salt,
+ bytes32 initCodeHash,
+ address deployer
+ ) external pure returns (address computedAddress);
+
+ function computeCreate2Address(
+ bytes32 salt,
+ bytes32 initCodeHash
+ ) external view returns (address computedAddress);
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* CREATE3 */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ function deployCreate3(
+ bytes32 salt,
+ bytes memory initCode
+ ) external payable returns (address newContract);
+
+ function deployCreate3(
+ bytes memory initCode
+ ) external payable returns (address newContract);
+
+ function deployCreate3AndInit(
+ bytes32 salt,
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values,
+ address refundAddress
+ ) external payable returns (address newContract);
+
+ function deployCreate3AndInit(
+ bytes32 salt,
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values
+ ) external payable returns (address newContract);
+
+ function deployCreate3AndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values,
+ address refundAddress
+ ) external payable returns (address newContract);
+
+ function deployCreate3AndInit(
+ bytes memory initCode,
+ bytes memory data,
+ Values memory values
+ ) external payable returns (address newContract);
+
+ function computeCreate3Address(
+ bytes32 salt,
+ address deployer
+ ) external pure returns (address computedAddress);
+
+ function computeCreate3Address(
+ bytes32 salt
+ ) external view returns (address computedAddress);
+}
+`;
diff --git a/interface/src/lib/utils.ts b/interface/src/lib/utils.ts
new file mode 100644
index 00000000..9493d24f
--- /dev/null
+++ b/interface/src/lib/utils.ts
@@ -0,0 +1,9 @@
+export const classNames = (...classes: string[]) =>
+ classes.filter(Boolean).join(" ");
+
+export const copyToClipboard = (text: string) => {
+ navigator.clipboard.writeText(text).then(
+ () => console.log("Copying to clipboard was successful!"),
+ (err) => console.error("Could not copy text to clipboard: ", err),
+ );
+};
diff --git a/interface/src/pages/_app.tsx b/interface/src/pages/_app.tsx
new file mode 100644
index 00000000..113c3c06
--- /dev/null
+++ b/interface/src/pages/_app.tsx
@@ -0,0 +1,23 @@
+import * as React from "react";
+import type { AppProps } from "next/app";
+import { ThemeProvider } from "next-themes";
+import { Layout } from "@/components/layout/Layout";
+import "@/styles/globals.css";
+
+function App({ Component, pageProps }: AppProps) {
+ const [mounted, setMounted] = React.useState(false);
+ React.useEffect(() => setMounted(true), []);
+ return (
+
+ {mounted && (
+
+ <>
+
+ >
+
+ )}
+
+ );
+}
+
+export default App;
diff --git a/interface/src/pages/abi.tsx b/interface/src/pages/abi.tsx
new file mode 100644
index 00000000..a907b568
--- /dev/null
+++ b/interface/src/pages/abi.tsx
@@ -0,0 +1,243 @@
+import { useEffect, useState } from "react";
+import Image from "next/image";
+import { Tab } from "@headlessui/react";
+import {
+ ArrowDownTrayIcon,
+ ClipboardDocumentIcon,
+} from "@heroicons/react/24/solid";
+import { useTheme } from "next-themes";
+import Prism from "prismjs";
+import "prismjs/components/prism-json";
+import "prismjs/components/prism-solidity";
+import "prismjs/components/prism-typescript";
+import { Head } from "@/components/layout/Head";
+import { Notification } from "@/components/ui/Notification";
+import {
+ CREATEX_ABI,
+ CREATEX_ABI_ETHERS,
+ CREATEX_ABI_VIEM,
+ CREATEX_SOLIDITY_INTERFACE,
+} from "@/lib/constants";
+import { classNames } from "@/lib/utils";
+import { copyToClipboard } from "@/lib/utils";
+
+const tabs = [
+ {
+ name: "Solidity",
+ href: "#solidity",
+ imgUri: "/solidity.png",
+ imgSize: "sm",
+ language: "solidity",
+ abi: CREATEX_SOLIDITY_INTERFACE,
+ filename: "ICreateX.sol",
+ mimeType: "text/plain",
+ },
+ {
+ name: "ethers.js",
+ href: "#ethers-js",
+ imgUri: "/ethersjs.png",
+ language: "json",
+ abi: CREATEX_ABI_ETHERS,
+ filename: "ICreateX.json",
+ mimeType: "text/plain",
+ },
+ {
+ name: "viem",
+ href: "#viem",
+ imgUri: "/viem.png",
+ language: "typescript",
+ abi: CREATEX_ABI_VIEM,
+ filename: "ICreateX.ts",
+ mimeType: "text/plain",
+ },
+ {
+ name: "JSON",
+ href: "#json",
+ imgUri: "/json.svg",
+ imgSize: "sm",
+ language: "json",
+ abi: JSON.stringify(CREATEX_ABI, null, 2),
+ filename: "ICreateX.json",
+ mimeType: "application/json",
+ },
+];
+
+const hashToIndex = () => {
+ const anchor = window.location.hash || "#solidity";
+ const index = tabs.findIndex((tab) => tab.href === anchor);
+ return index === -1 ? 0 : index;
+};
+
+const indexToHash = (index: number) => {
+ return tabs[index].href || "#solidity";
+};
+
+const Abi = () => {
+ // -------- Syntax Highlighting --------
+ const { resolvedTheme: theme } = useTheme();
+ const [selectedTab, setSelectedTab] = useState(hashToIndex());
+ const [showNotification, setShowNotification] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const onTabChange = (index: number) => {
+ // We set `isLoading` to true to fade out the content while the tab is changing, to avoid
+ // briefly showing un-highlighted code. This is set to false again in the `useEffect` hook.
+ setIsLoading(true);
+ setSelectedTab(index);
+ window.location.hash = indexToHash(index);
+ };
+
+ // This is required to re-highlight the code when the tab changes, and we use `setTimeout` with
+ // a delay of 0 to ensure that the code is highlighted after the tab has changed. Otherwise the
+ // `highlightAll` function runs before the code has been updated, so the code is not highlighted.
+ useEffect(() => {
+ setTimeout(() => {
+ Prism.highlightAll();
+ setIsLoading(false);
+ }, 0);
+ }, [selectedTab]);
+
+ // We conditionally import the Prism theme based on the current theme, to adjust the syntax
+ // highlighting to the theme. The logic in the `importTheme` function combined with the presence
+ // of the `prism-light.css` and `prism-dark.css` files in the `public` folder is what allows us
+ // to ensure all styles from the light theme are removed when the user toggles to the dark theme,
+ // and vice versa.
+ useEffect(() => {
+ const importTheme = async () => {
+ // Define the new stylesheet href based on the theme and get it's element.
+ const newStylesheetHref =
+ theme === "dark" ? "/prism-dark.css" : "/prism-light.css";
+ const existingStylesheet = document.getElementById("dynamic-stylesheet");
+
+ // If there's an existing stylesheet, remove it.
+ existingStylesheet?.parentNode?.removeChild(existingStylesheet);
+
+ // Create a new element for the new stylesheet, and append the stylesheet to the head.
+ const newStylesheet = document.createElement("link");
+ newStylesheet.rel = "stylesheet";
+ newStylesheet.type = "text/css";
+ newStylesheet.href = newStylesheetHref;
+ newStylesheet.id = "dynamic-stylesheet";
+ document.head.appendChild(newStylesheet);
+ };
+ importTheme();
+ }, [theme]);
+
+ // -------- Download and Copy ABI --------
+ const onDownload = (content: string, filename: string, mimeType: string) => {
+ const blob = new Blob([content], { type: mimeType });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+
+ link.href = url;
+ link.download = filename;
+ link.style.display = "none";
+
+ document.body.appendChild(link);
+ link.click();
+
+ setTimeout(() => {
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ }, 100);
+ };
+
+ const onCopy = (text: string) => {
+ copyToClipboard(text);
+ setShowNotification(true);
+ setTimeout(() => setShowNotification(false), 3000);
+ };
+
+ // -------- Render --------
+ return (
+ <>
+
+
+
+
+
+
+ {tabs.map((tab) => {
+ return (
+
+ {({ selected }) => (
+
+
+ {tab.name}
+
+ )}
+
+ );
+ })}
+
+
+ {tabs.map((tab) => {
+ return (
+
+
+
+
+ {tab.abi}
+
+
+ );
+ })}
+
+
+ >
+ );
+};
+
+export default Abi;
diff --git a/interface/src/pages/deployments.tsx b/interface/src/pages/deployments.tsx
new file mode 100644
index 00000000..6efaecf2
--- /dev/null
+++ b/interface/src/pages/deployments.tsx
@@ -0,0 +1,268 @@
+import { useEffect, useRef, useState } from "react";
+import {
+ ArrowTopRightOnSquareIcon,
+ ChevronDownIcon,
+} from "@heroicons/react/20/solid";
+import { ExternalLink } from "@/components/layout/ExternalLink";
+import { Head } from "@/components/layout/Head";
+import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
+
+interface Deployment {
+ name: string;
+ chainId: number;
+ url: string;
+ address?: `0x${string}`;
+}
+
+const Deployments = () => {
+ // -------- Fetch deployments --------
+ const [deployments, setDeployments] = useState([] as Deployment[]);
+ const [isLoading, setIsLoading] = useState(true);
+ const deploymentsUrl =
+ "https://github.com/pcaversaccio/createx/blob/main/deployments/deployments.json";
+ const deploymentsUrlRaw =
+ "https://raw.githubusercontent.com/pcaversaccio/createx/main/deployments/deployments.json";
+
+ useEffect(() => {
+ setIsLoading(true);
+ fetch(deploymentsUrlRaw)
+ .then((response) => response.json())
+ .then((data) => setDeployments(data))
+ .catch((error) => console.error("Error:", error))
+ .finally(() => setIsLoading(false));
+ }, []);
+
+ // -------- Focus search input when user presses Cmd/Ctrl + K --------
+ const searchInputRef = useRef(null);
+ const modifierKey = navigator.userAgent.includes("Mac") ? "⌘ " : "Ctrl + ";
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
+ e.preventDefault();
+ searchInputRef.current?.focus();
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, []);
+
+ // -------- Sorting and filtering --------
+ const [sortField, setSortField] = useState(null as "name" | "chainId" | null);
+ const [sortDirection, setSortDirection] = useState("ascending");
+ const [search, setSearch] = useState("");
+
+ const onHeaderClick = (field: "name" | "chainId") => {
+ if (sortField === field) {
+ setSortDirection(
+ sortDirection === "ascending" ? "descending" : "ascending",
+ );
+ } else {
+ setSortField(field);
+ setSortDirection("ascending");
+ }
+ };
+
+ const sortedDeployments = deployments.sort((a, b) => {
+ // Don't change default sort order if sort field is null.
+ if (sortField === null) return 0;
+
+ const aValue = a[sortField];
+ const bValue = b[sortField];
+ if (sortDirection === "ascending") {
+ return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
+ } else {
+ return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
+ }
+ });
+
+ const filteredDeployments = sortedDeployments.filter(
+ (deployment) =>
+ deployment.name.toLowerCase().includes(search.toLowerCase()) ||
+ deployment.chainId.toString().includes(search),
+ );
+
+ const loadingDiv = () => (
+
+
+
Fetching deployments...
+
+ );
+
+ const errorDiv = () => (
+
+
+
+ 🥴Oops!
+
+
+ Something went wrong fetching the list of deployments.
+
+
+
+
+ <>
+ View as JSON{" "}
+
+ >
+
+
+
+
+ );
+
+ const noDeploymentsDiv = () => (
+
+
+
+ No deployments found.
+
+
+ If you need CreateX deployed on a new chain,
+
+ please{" "}
+ {" "}
+ on GitHub.
+
+
+
+ );
+
+ const showDeploymentsDiv = () => (
+
+
+
+
+
+ onHeaderClick("name")}
+ >
+ Name
+
+
+
+
+ |
+
+ onHeaderClick("chainId")}
+ >
+ Chain ID
+
+
+
+
+ |
+
+ Edit
+ |
+
+
+
+ {filteredDeployments.map((deployment) => (
+
+ window.open(deployment.url, "_blank", "noopener,noreferrer")
+ }
+ >
+
+ {deployment.name}
+ {deployment.address && (
+
+ {`${deployment.address?.slice(
+ 0,
+ 6,
+ )}...${deployment.address?.slice(-4)}`}
+
+ )}
+ |
+
+ {deployment.chainId}
+ |
+
+
+
+
+ Open contract in block explorer
+
+
+ |
+
+ ))}
+
+
+
+
+ Showing {filteredDeployments.length} of {deployments.length}{" "}
+ deployments.
+
+
+
+ );
+
+ const deploymentsTableDiv = () => (
+ <>
+
+
+ {modifierKey}K
+
+ setSearch(e.target.value)}
+ ref={searchInputRef}
+ />
+
+ {filteredDeployments.length === 0 && noDeploymentsDiv()}
+ {filteredDeployments.length > 0 && showDeploymentsDiv()}
+ >
+ );
+
+ // -------- Render --------
+ return (
+ <>
+
+
+
+
+ {isLoading && loadingDiv()}
+ {!isLoading && deployments.length === 0 && errorDiv()}
+ {!isLoading && deployments.length > 0 && deploymentsTableDiv()}
+
+
+
+ >
+ );
+};
+
+export default Deployments;
diff --git a/interface/src/pages/index.tsx b/interface/src/pages/index.tsx
new file mode 100644
index 00000000..4646c532
--- /dev/null
+++ b/interface/src/pages/index.tsx
@@ -0,0 +1,68 @@
+import Link from "next/link";
+import { Head } from "@/components/layout/Head";
+import { SITE_DESCRIPTION } from "@/lib/constants";
+
+const Home = () => {
+ // eslint-disable-next-line no-console
+ console.log(`
+:'######::'########::'########::::'###::::'########:'########:'##::::'##:
+'##... ##: ##.... ##: ##.....::::'## ##:::... ##..:: ##.....::. ##::'##::
+ ##:::..:: ##:::: ##: ##::::::::'##:. ##::::: ##:::: ##::::::::. ##'##:::
+ ##::::::: ########:: ######:::'##:::. ##:::: ##:::: ######:::::. ###::::
+ ##::::::: ##.. ##::: ##...:::: #########:::: ##:::: ##...:::::: ## ##:::
+ ##::: ##: ##::. ##:: ##::::::: ##.... ##:::: ##:::: ##:::::::: ##:. ##::
+. ######:: ##:::. ##: ########: ##:::: ##:::: ##:::: ########: ##:::. ##:
+:......:::..:::::..::........::..:::::..:::::..:::::........::..:::::..::
+`);
+ const cards = [
+ {
+ id: 1,
+ href: "/deployments",
+ title: "Deployments",
+ subtitle: "Deployed on 60+ chains",
+ },
+ { id: 2, href: "/abi", title: "ABI", subtitle: "In any format" },
+ {
+ id: 3,
+ href: "https://github.com/pcaversaccio/createx",
+ title: "Docs",
+ subtitle: "Learn more",
+ },
+ ];
+
+ return (
+ <>
+