├── docs ├── pages │ ├── libraries │ │ ├── tevm.mdx │ │ └── whatsabi.mdx │ ├── getting-started │ │ ├── call.mdx │ │ ├── configuration.mdx │ │ └── result.mdx │ ├── notes.mdx │ ├── index.mdx │ ├── overview.mdx │ ├── using-locally.mdx │ └── architecture.mdx ├── public │ └── favicon.ico ├── utils.ts ├── styles.css ├── footer.tsx └── components │ ├── link.tsx │ ├── badge.tsx │ ├── architecture.tsx │ └── progress.tsx ├── public ├── og │ └── home.png ├── images │ ├── share.png │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── safari-pinned-tab.svg └── site.webmanifest ├── tea.yaml ├── postcss.config.cjs ├── src ├── lib │ ├── types │ │ ├── config.ts │ │ ├── templates.ts │ │ ├── site.ts │ │ ├── gas.ts │ │ └── providers.ts │ ├── store │ │ └── use-styles.ts │ ├── hooks │ │ └── use-media-query.tsx │ ├── dev.ts │ ├── native-token.ts │ ├── constants │ │ ├── defaults.ts │ │ └── site.ts │ ├── local-storage.ts │ ├── whatsabi.ts │ └── tevm │ │ ├── client.ts │ │ ├── account.ts │ │ └── op-stack.ts ├── components │ ├── config │ │ ├── analytics.tsx │ │ ├── theme-provider.tsx │ │ └── theme-switcher.tsx │ ├── ui │ │ ├── skeleton.tsx │ │ ├── collapsible.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── separator.tsx │ │ ├── sonner.tsx │ │ ├── slider.tsx │ │ ├── tooltip.tsx │ │ ├── switch.tsx │ │ ├── badge.tsx │ │ ├── hover-card.tsx │ │ ├── popover.tsx │ │ ├── toggle.tsx │ │ ├── scroll-area.tsx │ │ ├── toggle-group.tsx │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── accordion.tsx │ │ ├── breadcrumb.tsx │ │ ├── table.tsx │ │ ├── drawer.tsx │ │ ├── dialog.tsx │ │ ├── sheet.tsx │ │ ├── alert-dialog.tsx │ │ └── command.tsx │ ├── core │ │ ├── interact.tsx │ │ ├── interface │ │ │ └── index.tsx │ │ ├── header.tsx │ │ ├── example.tsx │ │ ├── selection │ │ │ ├── skip-balance.tsx │ │ │ ├── config-menu │ │ │ │ ├── desktop.tsx │ │ │ │ └── mobile.tsx │ │ │ └── native-price.tsx │ │ ├── tx-history │ │ │ ├── index.tsx │ │ │ ├── collapsible-mobile.tsx │ │ │ └── total-fees.tsx │ │ └── account-state.tsx │ ├── layouts │ │ ├── base.tsx │ │ └── container.tsx │ ├── common │ │ ├── theme-toggle.tsx │ │ ├── site-header.tsx │ │ ├── typography.tsx │ │ ├── gwei-amount.tsx │ │ ├── elapsed-time.tsx │ │ ├── currency-amount.tsx │ │ ├── tooltip-responsive.tsx │ │ └── shrinked-address.tsx │ ├── companion │ │ └── welcome.tsx │ └── templates │ │ └── table │ │ ├── view.tsx │ │ ├── column-header.tsx │ │ ├── data-table.tsx │ │ └── pagination.tsx ├── app │ ├── loading.tsx │ ├── address │ │ └── [account] │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── not-found.tsx │ ├── (api) │ │ ├── token-price │ │ │ └── route.ts │ │ └── abi │ │ │ └── route.ts │ ├── page.tsx │ ├── global-error.tsx │ └── layout.tsx └── styles │ └── globals.css ├── vercel.json ├── .prettierignore ├── .env.local.example ├── .vscode └── settings.json ├── components.json ├── .gitignore ├── tsconfig.json ├── prettier.config.js ├── LICENSE ├── .eslintrc.json ├── next.config.mjs ├── vocs.config.mjs ├── package.json └── tailwind.config.cjs /docs/pages/libraries/tevm.mdx: -------------------------------------------------------------------------------- 1 | # Tevm 2 | 3 | _TODO_ 4 | -------------------------------------------------------------------------------- /docs/pages/libraries/whatsabi.mdx: -------------------------------------------------------------------------------- 1 | # WhatsABI 2 | 3 | _TODO_ 4 | -------------------------------------------------------------------------------- /docs/pages/getting-started/call.mdx: -------------------------------------------------------------------------------- 1 | # Making a call 2 | 3 | _TODO_ 4 | -------------------------------------------------------------------------------- /docs/pages/getting-started/configuration.mdx: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | _TODO_ 4 | -------------------------------------------------------------------------------- /public/og/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/og/home.png -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /public/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/share.png -------------------------------------------------------------------------------- /docs/pages/getting-started/result.mdx: -------------------------------------------------------------------------------- 1 | # Visualizing the result of a transaction 2 | 3 | _TODO_ 4 | -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/favicon-16x16.png -------------------------------------------------------------------------------- /public/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/apple-touch-icon.png -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1.0.0 3 | codeOwners: 4 | - '0x2282758a2c7CF0c1b956008A8dCC6ea564DA4527' 5 | quorum: 1 6 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polareth/savvy/HEAD/public/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/lib/types/config.ts: -------------------------------------------------------------------------------- 1 | import { GetAccountResult } from 'tevm'; 2 | 3 | export type Account = GetAccountResult & { ens?: string }; 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/:match*.html", 5 | "destination": "/:match" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | cache 2 | .cache 3 | package.json 4 | pnpm-lock.yaml 5 | public 6 | CHANGELOG.md 7 | .yarn 8 | dist 9 | node_modules 10 | .next 11 | build 12 | .tevm -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | ALCHEMY_API_KEY="" 2 | COINMARKETCAP_API_KEY="" 3 | ETHERSCAN_API_KEY="" 4 | ARBISCAN_API_KEY="" 5 | BASESCAN_API_KEY="" 6 | OPTIMISTIC_ETHERSCAN_API_KEY="" 7 | POLYGONSCAN_API_KEY="" -------------------------------------------------------------------------------- /docs/utils.ts: -------------------------------------------------------------------------------- 1 | import clsx, { ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | // Merge Tailwind CSS classes with clsx 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "tailwindCSS.experimental.classRegex": [ 6 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 7 | ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | @layer vocs_preflight { 2 | @tailwind base; 3 | } 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | :root { 8 | --accent: 246 100% 83%; /* --vocs-color_textAccent */ 9 | } 10 | 11 | :root.dark { 12 | --accent: 246 100% 83%; /* --vocs-color_textAccent */ 13 | } 14 | -------------------------------------------------------------------------------- /src/components/config/analytics.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Analytics as VercelAnalytics } from '@vercel/analytics/react'; 4 | 5 | /** 6 | * @notice The analytics component for the app 7 | * @dev This is just a wrapper around Vercel Analytics to enable statistics. 8 | */ 9 | export const Analytics = () => { 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /src/app/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from '@/components/common/icons'; 2 | 3 | /** 4 | * @notice The root component for the loading page 5 | */ 6 | const Loading = () => { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | }; 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /src/app/address/[account]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from '@/components/common/icons'; 2 | 3 | /** 4 | * @notice The component for the loading page on an account 5 | */ 6 | const Loading = () => { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | }; 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from './components/link'; 2 | 3 | const Footer = () => { 4 | return ( 5 |
6 |
This open-source initiative is released under the MIT License.
7 |
8 | It is mostly built upon Tevm. 9 |
10 |
11 | ); 12 | }; 13 | 14 | export default Footer; 15 | -------------------------------------------------------------------------------- /docs/components/link.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | import { cn } from '../utils'; 4 | 5 | // TODO TEMP until Link is available 6 | export const Link = ({ 7 | href, 8 | className, 9 | children, 10 | }: { 11 | href: string; 12 | className?: string; 13 | children: ReactNode; 14 | }) => ( 15 | 21 | {children} 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /src/components/core/interact.tsx: -------------------------------------------------------------------------------- 1 | import ArbitraryCall from '@/components/core/arbitrary-call'; 2 | import Interface from '@/components/core/interface'; 3 | 4 | /** 5 | * @notice The main interaction component, where the user can select a caller, call a method 6 | * and interact with the contract's interface (or perform arbitrary calls) 7 | */ 8 | const Interact = () => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default Interact; 18 | -------------------------------------------------------------------------------- /src/components/config/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 5 | import { ThemeProviderProps } from 'next-themes/dist/types'; 6 | 7 | /** 8 | * @notice Theme provider for the application (dark/light mode). 9 | * @dev This wraps the entire application, along with the tooltip provider. 10 | */ 11 | export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => { 12 | return {children}; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/layouts/base.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from 'react'; 2 | 3 | import SiteHeader from '@/components/common/site-header'; 4 | 5 | type BaseLayoutProps = { 6 | children: ReactNode; 7 | }; 8 | 9 | /** 10 | * @notice The base layout component for all pages 11 | */ 12 | const BaseLayout: FC = ({ children }) => { 13 | return ( 14 | <> 15 | 16 |
{children}
17 | 18 | ); 19 | }; 20 | 21 | BaseLayout.displayName = 'BaseLayout'; 22 | 23 | export default BaseLayout; 24 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /docs/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # Tevm 40 | .tevm 41 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | import { Button } from '@/components/ui/button'; 4 | 5 | /** 6 | * @notice The root component for the 404 page 7 | */ 8 | const NotFound = () => { 9 | return ( 10 |
11 |

Page not found

12 |

13 | It seems like the page you are looking for does not exist. 14 |

15 | 18 |
19 | ); 20 | }; 21 | 22 | export default NotFound; 23 | -------------------------------------------------------------------------------- /src/app/(api)/token-price/route.ts: -------------------------------------------------------------------------------- 1 | const apiKey = process.env.COINMARKETCAP_API_KEY || ''; 2 | const url = 3 | 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest'; 4 | 5 | export async function GET() { 6 | try { 7 | const response = await fetch(url, { 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | 'X-CMC_PRO_API_KEY': apiKey, 11 | }, 12 | }); 13 | 14 | if (!response.ok) { 15 | return Response.json({ error: 'Failed to fetch token price' }); 16 | } 17 | 18 | const data = await response.json(); 19 | return Response.json(data); 20 | } catch (err) { 21 | return Response.json({ error: 'Failed to fetch token price' }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "savvy", 3 | "short_name": "savvy", 4 | "icons": [ 5 | { "src": "/images/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, 6 | { "src": "/images/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }, 7 | { "src": "/images/apple-touch-icon.png", "sizes": "180x180", "type": "image/png"}, 8 | { "src": "/images/favicon-16x16.png", "sizes": "16x16", "type": "image/png" }, 9 | { "src": "/images/favicon-32x32.png", "sizes": "32x32", "type": "image/png" }, 10 | { "src": "/images/safari-pinned-tab.svg", "color": "#b1a8ff" } 11 | ], 12 | "theme_color": "#000", 13 | "background_color": "#000", 14 | "display": "standalone" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/core/interface/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useConfigStore } from '@/lib/store/use-config'; 4 | import InterfaceTable from '@/components/core/interface/table'; 5 | 6 | /** 7 | * @notice The interface of the contract after it's been retrieved 8 | */ 9 | const Interface = () => { 10 | const { abi, fetchingAccount, fetchingAbi } = useConfigStore((state) => ({ 11 | // Get the abi of the current contract (null if not fetched yet) 12 | abi: state.abi, 13 | // Get the loading (fetching) status 14 | fetchingAccount: state.fetchingAccount, 15 | fetchingAbi: state.fetchingAbi, 16 | })); 17 | 18 | if (!abi && !fetchingAccount && !fetchingAbi) return null; 19 | return ; 20 | }; 21 | 22 | export default Interface; 23 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import dynamic from 'next/dynamic'; 4 | 5 | import { Separator } from '@/components/ui/separator'; 6 | import Welcome from '@/components/companion/welcome'; 7 | 8 | // Import dynamically to avoid SSR issues due to persisted state (see zustand stores) 9 | const Header = dynamic(() => import('@/components/core/header')); 10 | const TxHistory = dynamic(() => import('@/components/core/tx-history')); 11 | 12 | /** 13 | * @notice The home page, where the user can set the configuration and search for an account 14 | */ 15 | const Home = () => { 16 | return ( 17 |
18 |
19 | 20 | 21 | 22 |
23 | ); 24 | }; 25 | 26 | export default Home; 27 | -------------------------------------------------------------------------------- /src/lib/store/use-styles.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { createJSONStorage, persist } from 'zustand/middleware'; 3 | 4 | type Config = { 5 | style: 'new-york'; 6 | theme: 'neutral'; 7 | radius: number; 8 | }; 9 | 10 | /** 11 | * @notice Creates a store for shadcn/ui configuration 12 | * @returns a persistent store with the configuration state and methods to update it. 13 | * @see https://github.com/shadcn-ui/ui/blob/main/apps/www/hooks/use-config.ts 14 | * modified from jotai to zustand 15 | */ 16 | export const useStylesStore = create( 17 | persist( 18 | () => ({ 19 | style: 'new-york', 20 | theme: 'neutral', 21 | radius: 0.5, 22 | }), 23 | { 24 | name: 'shadcn-styles', 25 | storage: createJSONStorage(() => localStorage), 26 | }, 27 | ), 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as LabelPrimitive from '@radix-ui/react-label'; 5 | import { cva, type VariantProps } from 'class-variance-authority'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const labelVariants = cva( 10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |