diff --git a/packages/wallet-v2/.eslintrc.json b/packages/wallet-v2/.eslintrc.json
new file mode 100644
index 00000000..37224185
--- /dev/null
+++ b/packages/wallet-v2/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["next/core-web-vitals", "next/typescript"]
+}
diff --git a/packages/wallet-v2/.gitignore b/packages/wallet-v2/.gitignore
new file mode 100644
index 00000000..fd3dbb57
--- /dev/null
+++ b/packages/wallet-v2/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/packages/wallet-v2/README.md b/packages/wallet-v2/README.md
new file mode 100644
index 00000000..e215bc4c
--- /dev/null
+++ b/packages/wallet-v2/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/packages/wallet-v2/app/assets/images/logo.png b/packages/wallet-v2/app/assets/images/logo.png
new file mode 100644
index 00000000..a92e0928
Binary files /dev/null and b/packages/wallet-v2/app/assets/images/logo.png differ
diff --git a/packages/wallet-v2/app/favicon.ico b/packages/wallet-v2/app/favicon.ico
new file mode 100644
index 00000000..c8232a3c
Binary files /dev/null and b/packages/wallet-v2/app/favicon.ico differ
diff --git a/packages/wallet-v2/app/fonts/GeistMonoVF.woff b/packages/wallet-v2/app/fonts/GeistMonoVF.woff
new file mode 100644
index 00000000..f2ae185c
Binary files /dev/null and b/packages/wallet-v2/app/fonts/GeistMonoVF.woff differ
diff --git a/packages/wallet-v2/app/fonts/GeistVF.woff b/packages/wallet-v2/app/fonts/GeistVF.woff
new file mode 100644
index 00000000..1b62daac
Binary files /dev/null and b/packages/wallet-v2/app/fonts/GeistVF.woff differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Black.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Black.ttf
new file mode 100644
index 00000000..f3a42d28
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Black.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Bold.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Bold.ttf
new file mode 100644
index 00000000..e422c709
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Bold.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ExtraBold.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ExtraBold.ttf
new file mode 100644
index 00000000..9f42409a
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ExtraBold.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Light.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Light.ttf
new file mode 100644
index 00000000..a67ee942
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Light.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Medium.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Medium.ttf
new file mode 100644
index 00000000..8cdc3917
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Medium.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Regular.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Regular.ttf
new file mode 100644
index 00000000..2ae8cc91
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Regular.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-SemiBold.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-SemiBold.ttf
new file mode 100644
index 00000000..ae2ae936
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-SemiBold.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ThicccAF.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ThicccAF.ttf
new file mode 100644
index 00000000..53675b4d
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-ThicccAF.ttf differ
diff --git a/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Thin.ttf b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Thin.ttf
new file mode 100644
index 00000000..fada1fef
Binary files /dev/null and b/packages/wallet-v2/app/fonts/THICCCBOI/THICCCBOI-Thin.ttf differ
diff --git a/packages/wallet-v2/app/globals.css b/packages/wallet-v2/app/globals.css
new file mode 100644
index 00000000..aea6451a
--- /dev/null
+++ b/packages/wallet-v2/app/globals.css
@@ -0,0 +1,94 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ }
+}
+
+body {
+ font-family: var(--font-thicccboi);
+}
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
+ }
+}
+
+@layer base {
+ :root {
+ /* --background: 0 0% 100%; */
+ --foreground: 0 0% 3.9%;
+ /* --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%; */
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ /* --radius: 0.5rem; */
+ }
+ .dark {
+ /* --background: 0 0% 3.9%; */
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ /* --muted: 0 0% 14.9%; */
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+.gradient-border {
+ border-width: 3.2px;
+ border-style: solid;
+ border-image: linear-gradient(139.26deg, #303441 -0.73%, #232836 100.78%);
+ border-image-slice: 1;
+ border-radius: 20px;
+}
diff --git a/packages/wallet-v2/app/layout.tsx b/packages/wallet-v2/app/layout.tsx
new file mode 100644
index 00000000..0f197232
--- /dev/null
+++ b/packages/wallet-v2/app/layout.tsx
@@ -0,0 +1,68 @@
+import type { Metadata } from 'next'
+import localFont from 'next/font/local'
+import './globals.css'
+import { Header } from '@/shared/Header'
+import Footer from '@/shared/Footer'
+import { Toaster } from '@/components/ui/toaster'
+
+const geistSans = localFont({
+ src: './fonts/GeistVF.woff',
+ variable: '--font-geist-sans',
+ display: 'swap',
+})
+
+const geistMono = localFont({
+ src: './fonts/GeistMonoVF.woff',
+ variable: '--font-geist-mono',
+ display: 'swap',
+})
+
+const thicccBoi = localFont({
+ src: [
+ {
+ path: './fonts/THICCCBOI/THICCCBOI-Regular.ttf',
+ weight: '400',
+ style: 'normal',
+ },
+ {
+ path: './fonts/THICCCBOI/THICCCBOI-Medium.ttf',
+ weight: '500',
+ style: 'normal',
+ },
+ {
+ path: './fonts/THICCCBOI/THICCCBOI-Bold.ttf',
+ weight: '700',
+ style: 'normal',
+ },
+ ],
+ variable: '--font-thicccboi',
+ display: 'swap',
+})
+
+export const metadata: Metadata = {
+ title: 'Avail Wallet',
+ description: 'A decentralized wallet application',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>): JSX.Element {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+
+
+ )
+}
diff --git a/packages/wallet-v2/app/logo.png b/packages/wallet-v2/app/logo.png
new file mode 100644
index 00000000..a92e0928
Binary files /dev/null and b/packages/wallet-v2/app/logo.png differ
diff --git a/packages/wallet-v2/app/logo.svg b/packages/wallet-v2/app/logo.svg
new file mode 100644
index 00000000..2e05de95
--- /dev/null
+++ b/packages/wallet-v2/app/logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/wallet-v2/app/page.tsx b/packages/wallet-v2/app/page.tsx
new file mode 100644
index 00000000..8f631930
--- /dev/null
+++ b/packages/wallet-v2/app/page.tsx
@@ -0,0 +1,142 @@
+'use client'
+
+import Image from 'next/image'
+import { useEffect } from 'react'
+import { motion } from 'framer-motion'
+import { Button } from '@/components/ui/button'
+import { TextCard } from '@/shared/TextCard'
+import SendDialog from '@/shared/Dialog/SendDialog'
+import LoadingModal from '@/shared/LoadingModal'
+import { useAvailSnap } from '@/services/metamask'
+import { useHasMetamask } from '@/hooks/useHasMetamask'
+import useWalletStore from '@/slices/walletSlice'
+import {useNetworkStore} from '@/slices/networkSlice'
+import { useUIStore } from '@/slices/UISlice'
+import Line from '@/assets/images/line.svg'
+import Sign from '@/assets/images/signature.svg'
+import Qr from '@/assets/images/qr.svg'
+import { GET_FAUCENT_URL, SNAPS_DOC_URL } from '@/utils/constants'
+import { BalanceDisplay } from '@/shared/BalanceDisplay'
+import ReceiveDialog from '@/shared/Dialog/RecieveDialog'
+import SignMessageDialog from '@/shared/Dialog/SignMessageDialog'
+
+export default function Home(): JSX.Element {
+ const { initSnap, checkConnection, getWalletData } = useAvailSnap()
+ const accounts = useWalletStore((state) => state.accounts)
+ const _balance = useWalletStore((state) => state.tokenBalance) // Unused but prefixed
+ const provider = useWalletStore((state) => state.provider)
+ const connected = useWalletStore((state) => state.connected)
+ const forceReconnect = useWalletStore((state) => state.forceReconnect)
+ const networks = useNetworkStore((state) => state.items)
+ const activeNetwork = useNetworkStore((state) => state.activeNetwork)
+ const { hasMetamask } = useHasMetamask()
+ const { loader } = useUIStore()
+ const address =
+ accounts?.length > 0
+ ? (accounts[0] as string)
+ : '0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+
+ useEffect(() => {
+ if (!provider) return
+
+ if (connected) {
+ initSnap()
+ }
+
+ if (hasMetamask && !connected && !forceReconnect) {
+ checkConnection()
+ }
+ }, [connected, forceReconnect, hasMetamask, provider]);
+
+
+ useEffect(() => {
+ if (provider && networks.length > 0) {
+ const chainId = networks[activeNetwork].chainId
+ getWalletData(chainId, true)
+ }
+ }, [activeNetwork, provider, networks])
+
+ const fadeInUp = {
+ initial: { opacity: 0, y: 20 },
+ animate: { opacity: 1, y: 0 },
+ transition: { duration: 0.6 },
+ }
+
+ return (
+
+ {loader.isLoading &&
}
+ {/*
+
+
+
+ {connected ? '35875' : '--'} AVL
+
+
+ 4748.45 USD
+
+
+
+ */}
+
+
+ {true ? (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/packages/wallet-v2/assets/images/discord.svg b/packages/wallet-v2/assets/images/discord.svg
new file mode 100644
index 00000000..7650fadc
--- /dev/null
+++ b/packages/wallet-v2/assets/images/discord.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/wallet-v2/assets/images/empty-box.svg b/packages/wallet-v2/assets/images/empty-box.svg
new file mode 100644
index 00000000..a8e9049d
--- /dev/null
+++ b/packages/wallet-v2/assets/images/empty-box.svg
@@ -0,0 +1,58 @@
+
diff --git a/packages/wallet-v2/assets/images/fav.svg b/packages/wallet-v2/assets/images/fav.svg
new file mode 100644
index 00000000..62141669
--- /dev/null
+++ b/packages/wallet-v2/assets/images/fav.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/wallet-v2/assets/images/github.svg b/packages/wallet-v2/assets/images/github.svg
new file mode 100644
index 00000000..5634ab7f
--- /dev/null
+++ b/packages/wallet-v2/assets/images/github.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/wallet-v2/assets/images/green.svg b/packages/wallet-v2/assets/images/green.svg
new file mode 100644
index 00000000..5688d470
--- /dev/null
+++ b/packages/wallet-v2/assets/images/green.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/wallet-v2/assets/images/key.svg b/packages/wallet-v2/assets/images/key.svg
new file mode 100644
index 00000000..9d5ace70
--- /dev/null
+++ b/packages/wallet-v2/assets/images/key.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/wallet-v2/assets/images/line.svg b/packages/wallet-v2/assets/images/line.svg
new file mode 100644
index 00000000..a381e20c
--- /dev/null
+++ b/packages/wallet-v2/assets/images/line.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/wallet-v2/assets/images/linkedin.svg b/packages/wallet-v2/assets/images/linkedin.svg
new file mode 100644
index 00000000..42477a0a
--- /dev/null
+++ b/packages/wallet-v2/assets/images/linkedin.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/wallet-v2/assets/images/logo.png b/packages/wallet-v2/assets/images/logo.png
new file mode 100644
index 00000000..a92e0928
Binary files /dev/null and b/packages/wallet-v2/assets/images/logo.png differ
diff --git a/packages/wallet-v2/assets/images/logout.svg b/packages/wallet-v2/assets/images/logout.svg
new file mode 100644
index 00000000..9bd412f9
--- /dev/null
+++ b/packages/wallet-v2/assets/images/logout.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/wallet-v2/assets/images/qr.svg b/packages/wallet-v2/assets/images/qr.svg
new file mode 100644
index 00000000..60335ea4
--- /dev/null
+++ b/packages/wallet-v2/assets/images/qr.svg
@@ -0,0 +1,13 @@
+
diff --git a/packages/wallet-v2/assets/images/signature.svg b/packages/wallet-v2/assets/images/signature.svg
new file mode 100644
index 00000000..71402af3
--- /dev/null
+++ b/packages/wallet-v2/assets/images/signature.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/wallet-v2/assets/images/twitter.svg b/packages/wallet-v2/assets/images/twitter.svg
new file mode 100644
index 00000000..3f175db8
--- /dev/null
+++ b/packages/wallet-v2/assets/images/twitter.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/wallet-v2/components.json b/packages/wallet-v2/components.json
new file mode 100644
index 00000000..d66adba8
--- /dev/null
+++ b/packages/wallet-v2/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
diff --git a/packages/wallet-v2/components/ui/button.tsx b/packages/wallet-v2/components/ui/button.tsx
new file mode 100644
index 00000000..3a5f058f
--- /dev/null
+++ b/packages/wallet-v2/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
+ outline:
+ 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
+ secondary:
+ 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2',
+ sm: 'h-8 rounded-md px-3 text-xs',
+ lg: 'h-10 rounded-md px-8',
+ icon: 'h-9 w-9',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button'
+ return (
+
+ )
+ }
+)
+Button.displayName = 'Button'
+
+export { Button, buttonVariants }
diff --git a/packages/wallet-v2/components/ui/card.tsx b/packages/wallet-v2/components/ui/card.tsx
new file mode 100644
index 00000000..16da7dd8
--- /dev/null
+++ b/packages/wallet-v2/components/ui/card.tsx
@@ -0,0 +1,76 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = 'Card'
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = 'CardHeader'
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = 'CardTitle'
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = 'CardDescription'
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = 'CardContent'
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = 'CardFooter'
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/packages/wallet-v2/components/ui/dialog.tsx b/packages/wallet-v2/components/ui/dialog.tsx
new file mode 100644
index 00000000..ac5f3a86
--- /dev/null
+++ b/packages/wallet-v2/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+'use client'
+
+import * as React from 'react'
+import * as DialogPrimitive from '@radix-ui/react-dialog'
+import { Cross2Icon } from '@radix-ui/react-icons'
+
+import { cn } from '@/lib/utils'
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = 'DialogHeader'
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = 'DialogFooter'
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/packages/wallet-v2/components/ui/dropdown-menu.tsx b/packages/wallet-v2/components/ui/dropdown-menu.tsx
new file mode 100644
index 00000000..05a4b259
--- /dev/null
+++ b/packages/wallet-v2/components/ui/dropdown-menu.tsx
@@ -0,0 +1,205 @@
+'use client'
+
+import * as React from 'react'
+import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
+import {
+ CheckIcon,
+ ChevronRightIcon,
+ DotFilledIcon,
+} from '@radix-ui/react-icons'
+
+import { cn } from '@/lib/utils'
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/packages/wallet-v2/components/ui/input.tsx b/packages/wallet-v2/components/ui/input.tsx
new file mode 100644
index 00000000..0bab5d53
--- /dev/null
+++ b/packages/wallet-v2/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = 'Input'
+
+export { Input }
diff --git a/packages/wallet-v2/components/ui/popover.tsx b/packages/wallet-v2/components/ui/popover.tsx
new file mode 100644
index 00000000..75b2da74
--- /dev/null
+++ b/packages/wallet-v2/components/ui/popover.tsx
@@ -0,0 +1,33 @@
+'use client'
+
+import * as React from 'react'
+import * as PopoverPrimitive from '@radix-ui/react-popover'
+
+import { cn } from '@/lib/utils'
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverAnchor = PopoverPrimitive.Anchor
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/packages/wallet-v2/components/ui/scroll-area.tsx b/packages/wallet-v2/components/ui/scroll-area.tsx
new file mode 100644
index 00000000..6a85ae0d
--- /dev/null
+++ b/packages/wallet-v2/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import * as React from 'react'
+import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
+
+import { cn } from '@/lib/utils'
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = 'vertical', ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/packages/wallet-v2/components/ui/separator.tsx b/packages/wallet-v2/components/ui/separator.tsx
new file mode 100644
index 00000000..6c55e0b2
--- /dev/null
+++ b/packages/wallet-v2/components/ui/separator.tsx
@@ -0,0 +1,31 @@
+'use client'
+
+import * as React from 'react'
+import * as SeparatorPrimitive from '@radix-ui/react-separator'
+
+import { cn } from '@/lib/utils'
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = 'horizontal', decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/packages/wallet-v2/components/ui/tabs.tsx b/packages/wallet-v2/components/ui/tabs.tsx
new file mode 100644
index 00000000..455fdd55
--- /dev/null
+++ b/packages/wallet-v2/components/ui/tabs.tsx
@@ -0,0 +1,55 @@
+'use client'
+
+import * as React from 'react'
+import * as TabsPrimitive from '@radix-ui/react-tabs'
+
+import { cn } from '@/lib/utils'
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/packages/wallet-v2/components/ui/textarea.tsx b/packages/wallet-v2/components/ui/textarea.tsx
new file mode 100644
index 00000000..7bf41c40
--- /dev/null
+++ b/packages/wallet-v2/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Textarea.displayName = 'Textarea'
+
+export { Textarea }
diff --git a/packages/wallet-v2/components/ui/toast.tsx b/packages/wallet-v2/components/ui/toast.tsx
new file mode 100644
index 00000000..cc4e0ab2
--- /dev/null
+++ b/packages/wallet-v2/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import * as React from "react"
+import { Cross2Icon } from "@radix-ui/react-icons"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/packages/wallet-v2/components/ui/toaster.tsx b/packages/wallet-v2/components/ui/toaster.tsx
new file mode 100644
index 00000000..171beb46
--- /dev/null
+++ b/packages/wallet-v2/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import { useToast } from "@/hooks/use-toast"
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title}}
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/packages/wallet-v2/hooks/redux.ts b/packages/wallet-v2/hooks/redux.ts
new file mode 100644
index 00000000..13a9896e
--- /dev/null
+++ b/packages/wallet-v2/hooks/redux.ts
@@ -0,0 +1,12 @@
+import { create } from 'zustand'
+
+// Define your Zustand store
+interface AppState {
+ counter: number
+ increment: () => void
+}
+
+export const useAppStore = create((set) => ({
+ counter: 0,
+ increment: () => set((state) => ({ counter: state.counter + 1 })),
+}))
diff --git a/packages/wallet-v2/hooks/use-toast.ts b/packages/wallet-v2/hooks/use-toast.ts
new file mode 100644
index 00000000..02e111d8
--- /dev/null
+++ b/packages/wallet-v2/hooks/use-toast.ts
@@ -0,0 +1,194 @@
+"use client"
+
+// Inspired by react-hot-toast library
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
diff --git a/packages/wallet-v2/hooks/useHasMetamask.ts b/packages/wallet-v2/hooks/useHasMetamask.ts
new file mode 100644
index 00000000..8da239cf
--- /dev/null
+++ b/packages/wallet-v2/hooks/useHasMetamask.ts
@@ -0,0 +1,84 @@
+import { useUIStore } from '@/slices/UISlice'
+import useWalletStore from '@/slices/walletSlice'
+import detectEthereumProvider from '@metamask/detect-provider'
+import { useEffect, useState } from 'react'
+
+export const useHasMetamask = () => {
+ const { enableLoadingWithMessage, disableLoading } = useUIStore()
+ const { setProvider } = useWalletStore()
+ const [hasMetamask, setHasMetamask] = useState(null)
+
+ useEffect(() => {
+ const init = async () => {
+ try {
+ enableLoadingWithMessage('Detecting Metamask...')
+
+ // Check if MetaMask is installed
+ if (await detectMetamask()) {
+ const _provider = await getProvider()
+ setProvider(_provider)
+ setHasMetamask(_provider != null)
+ } else {
+ setProvider(null)
+ setHasMetamask(false)
+ }
+ } catch (err) {
+ setProvider(null)
+ setHasMetamask(false)
+ } finally {
+ disableLoading()
+ }
+ }
+
+ init()
+ }, [setProvider, enableLoadingWithMessage, disableLoading])
+
+ return {
+ hasMetamask,
+ }
+}
+
+// Detect if MetaMask is installed
+export const detectMetamask = async () => {
+ try {
+ const hasMetamask = await detectEthereumProvider({ mustBeMetaMask: true })
+ return !!hasMetamask
+ } catch (e) {
+ console.error('Error', e)
+ return false
+ }
+}
+
+// Get the Ethereum provider (MetaMask or others)
+export const getProvider = async () => {
+ const { ethereum } = window as any
+ let providers = [ethereum]
+
+ if ('detected' in ethereum) {
+ providers = ethereum['detected']
+ } else if ('providers' in ethereum) {
+ providers = ethereum['providers']
+ }
+
+ // Detect provider by sending request
+ for (const provider of providers) {
+ if (provider && (await isSupportSnap(provider))) {
+ window.ethereum = provider
+ return window.ethereum
+ }
+ }
+
+ return null
+}
+
+// Check if the provider supports Snap
+const isSupportSnap = async (provider: any) => {
+ try {
+ await provider.request({
+ method: 'wallet_getSnaps',
+ })
+ return true
+ } catch {
+ return false
+ }
+}
diff --git a/packages/wallet-v2/hooks/useHasMetamaskFlask.ts b/packages/wallet-v2/hooks/useHasMetamaskFlask.ts
new file mode 100644
index 00000000..1a0b07f2
--- /dev/null
+++ b/packages/wallet-v2/hooks/useHasMetamaskFlask.ts
@@ -0,0 +1,41 @@
+//TODO: remove when metamask is released with snap support
+import detectEthereumProvider from '@metamask/detect-provider'
+import { useEffect, useState } from 'react'
+
+export const useHasMetamaskFlask = () => {
+ const [hasMetamaskFlask, setHasMetamaskFlask] = useState(null)
+
+ const detectMetamaskFlask = async () => {
+ try {
+ const provider = (await detectEthereumProvider({
+ mustBeMetaMask: false,
+ silent: true,
+ })) as any | undefined
+ const isFlask = (
+ await provider?.request({ method: 'web3_clientVersion' })
+ )?.includes('flask')
+ if (provider && isFlask) {
+ return true
+ }
+ return false
+ } catch (e) {
+ console.log('Error', e)
+ return false
+ }
+ }
+
+ useEffect(() => {
+ detectMetamaskFlask()
+ .then((result) => {
+ setHasMetamaskFlask(result)
+ })
+ .catch((err) => {
+ console.error(err)
+ setHasMetamaskFlask(false)
+ })
+ }, [])
+
+ return {
+ hasMetamaskFlask,
+ }
+}
diff --git a/packages/wallet-v2/hooks/useInitializeProvider.ts b/packages/wallet-v2/hooks/useInitializeProvider.ts
new file mode 100644
index 00000000..72a5afd2
--- /dev/null
+++ b/packages/wallet-v2/hooks/useInitializeProvider.ts
@@ -0,0 +1,22 @@
+'use client'
+import { useEffect } from 'react'
+import { ethers } from 'ethers'
+import { useWalletStore } from '@/slices/walletSlice'
+
+// Custom hook to initialize the provider
+export const useInitializeProvider = () => {
+ const setProvider = useWalletStore((state) => state.setProvider) // Get the setProvider function from Zustand
+
+ useEffect(() => {
+ const initializeProvider = async () => {
+ if (window.ethereum) {
+ const provider = new ethers.providers.Web3Provider(window.ethereum)
+ setProvider(provider) // Store the provider in Zustand
+ } else {
+ console.error('No Ethereum provider found')
+ }
+ }
+
+ initializeProvider() // Call the function inside useEffect to avoid invalid hook call
+ }, [setProvider]) // Effect depends on setProvider, so it will only re-run if setProvider changes
+}
diff --git a/packages/wallet-v2/lib/constants.ts b/packages/wallet-v2/lib/constants.ts
new file mode 100644
index 00000000..78064ce0
--- /dev/null
+++ b/packages/wallet-v2/lib/constants.ts
@@ -0,0 +1,16 @@
+export const DECIMALS_DISPLAYED_MAX_LENGTH = 11
+
+export const GOLDBERG_TESTNET_EXPLORER = 'https://avail-goldberg.subscan.io/'
+export const MAINNET_EXPLORER = 'https://avail.subscan.io/'
+export const TURING_TESTNET_EXPLORER = 'https://avail-turing.subscan.io/'
+export const SNAPS_DOC_URL = 'https://docs.metamask.io/guide/snaps.html'
+
+export const INPUT_MAX_LENGTH = 100
+
+export const POPOVER_DURATION = 3000
+
+export const TRANSACTIONS_REFRESH_FREQUENCY = 60000
+
+export const TOKEN_BALANCE_REFRESH_FREQUENCY = 60000
+
+export const TIMEOUT_DURATION = 10000
diff --git a/packages/wallet-v2/lib/utils.ts b/packages/wallet-v2/lib/utils.ts
new file mode 100644
index 00000000..157ee989
--- /dev/null
+++ b/packages/wallet-v2/lib/utils.ts
@@ -0,0 +1,188 @@
+import { KeyboardEvent } from 'react'
+import { ethers } from 'ethers'
+import {
+ ApiPromise,
+ initialize,
+ isValidAddress as isValidAvailAddress,
+} from 'avail-js-sdk'
+import {
+ DECIMALS_DISPLAYED_MAX_LENGTH,
+ TURING_TESTNET_EXPLORER,
+ TIMEOUT_DURATION,
+ GOLDBERG_TESTNET_EXPLORER,
+ MAINNET_EXPLORER,
+} from './constants'
+import { Erc20Token, Erc20TokenBalance, Network } from '@/types'
+
+export const shortenAddress = (address: string, num = 3) => {
+ if (!address) return ''
+ return (
+ !!address &&
+ `${address.substring(0, num + 2)}...${address.substring(address.length - num - 1)}`
+ )
+}
+
+export async function initializeApi(endpoint: string) {
+ try {
+ const api = await initialize(endpoint)
+ console.log('Api connected successfully')
+ return api
+ } catch (error) {
+ console.error('Error initializing Api :', error)
+ setTimeout(initializeApi, 5000)
+ }
+}
+
+export const getRpcEndpoint = (chainId: number) => {
+ switch (chainId) {
+ case 0: // turing
+ return 'wss://turing-rpc.avail.so/ws'
+ case 1: // goldberg
+ return 'wss://goldberg-rpc.slowops.xyz/ws'
+ case 2: // mainnet
+ return 'wss://mainnet-rpc.avail.so/ws'
+ default:
+ throw new Error('Unsupported chain ID')
+ }
+}
+
+export const openExplorerTab = (
+ address: string,
+ type: string,
+ chainId: number
+) => {
+ let explorerUrl = TURING_TESTNET_EXPLORER
+ switch (chainId) {
+ case 0:
+ explorerUrl = TURING_TESTNET_EXPLORER
+ break
+ case 1:
+ explorerUrl = GOLDBERG_TESTNET_EXPLORER
+ break
+ case 2:
+ explorerUrl = MAINNET_EXPLORER
+ break
+ }
+ window.open(explorerUrl + type + '/' + address, '_blank')?.focus()
+}
+
+export const isValidAddress = (toCheck: string) => {
+ if (isValidAvailAddress(toCheck)) {
+ return true
+ } else {
+ return false
+ }
+}
+
+export const addMissingPropertiesToToken = (
+ token: Erc20Token,
+ balance?: string,
+ usdPrice?: number
+): Erc20TokenBalance => {
+ return {
+ ...token,
+ amount: ethers.BigNumber.from(balance ? balance : '0x0'),
+ }
+}
+
+export const getHumanReadableAmount = (asset: Erc20TokenBalance) => {
+ // const amountStr = assetAmount
+ // ? assetAmount
+ // : ethers.utils.formatUnits(asset.amount, asset.decimals);
+ if (asset.amount > 0) {
+ const amountStr = ethers.utils.formatUnits(asset.amount, asset.decimals)
+ const indexDecimal = amountStr.indexOf('.')
+ const integerPart = amountStr.substring(0, indexDecimal)
+ let decimalPart = amountStr.substring(
+ indexDecimal + 1,
+ indexDecimal + 5 - integerPart.length
+ )
+ if (integerPart === '0') {
+ decimalPart = amountStr.substring(indexDecimal + 1)
+ }
+ const decimalPartArray = decimalPart.split('')
+ const firstNonZeroIndex = decimalPartArray.findIndex((char) => char !== '0')
+ if (firstNonZeroIndex === -1) {
+ return integerPart
+ }
+
+ return amountStr.substring(0, indexDecimal + firstNonZeroIndex + 3)
+ } else {
+ return '0'
+ }
+}
+
+export const getMaxDecimalsReadable = (
+ asset: Erc20TokenBalance,
+ assetAmount?: string
+) => {
+ const amountStr = assetAmount
+ ? assetAmount
+ : ethers.utils.formatUnits(asset.amount, asset.decimals)
+ const indexDecimal = amountStr.indexOf('.')
+ const decimalPart = amountStr.substring(indexDecimal + 1).split('')
+ const firstNonZeroIndexReverse = decimalPart
+ .reverse()
+ .findIndex((char) => char !== '0')
+ if (firstNonZeroIndexReverse !== -1) {
+ let lastNonZeroIndex = amountStr.length - firstNonZeroIndexReverse
+ if (lastNonZeroIndex - indexDecimal > DECIMALS_DISPLAYED_MAX_LENGTH) {
+ lastNonZeroIndex = indexDecimal + 1 + DECIMALS_DISPLAYED_MAX_LENGTH
+ }
+ return amountStr.substring(0, lastNonZeroIndex)
+ }
+ return amountStr.substring(0, indexDecimal)
+}
+
+export const getAmountPrice = (
+ asset: Erc20TokenBalance,
+ assetAmount: number,
+ usdMode: boolean
+) => {
+ // if (asset.usdPrice) {
+ // if (!usdMode) {
+ // const result = asset.usdPrice * assetAmount;
+ // return result.toFixed(2).toString();
+ // } else {
+ // const result = assetAmount / asset.usdPrice;
+ // return result.toFixed(getMaxDecimals(asset)).toString();
+ // }
+ // }
+ return ''
+}
+
+export const getMaxDecimals = (asset: Erc20TokenBalance) => {
+ const MAX_DECIMALS = 6
+ if (asset.decimals > MAX_DECIMALS) {
+ return MAX_DECIMALS
+ }
+ return asset.decimals
+}
+
+export const isSpecialInputKey = (event: KeyboardEvent) => {
+ return (
+ event.key === 'Backspace' ||
+ event.ctrlKey ||
+ event.key === 'ArrowRight' ||
+ event.key === 'ArrowLeft' ||
+ event.metaKey
+ )
+}
+
+export const fetchWithTimeout = async (
+ resource: string,
+ options = { timeout: TIMEOUT_DURATION }
+) => {
+ const controller = new AbortController()
+ const id = setTimeout(() => controller.abort(), options.timeout)
+ const response = await fetch(resource, {
+ ...options,
+ signal: controller.signal,
+ })
+ clearTimeout(id)
+ return response
+}
+
+export function cn(...classes: string[]) {
+ return classes.filter(Boolean).join(' ')
+}
diff --git a/packages/wallet-v2/next.config.mjs b/packages/wallet-v2/next.config.mjs
new file mode 100644
index 00000000..fcfd5a0e
--- /dev/null
+++ b/packages/wallet-v2/next.config.mjs
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true, // This helps with identifying potential issues during development
+}
+
+export default nextConfig
diff --git a/packages/wallet-v2/package.json b/packages/wallet-v2/package.json
new file mode 100644
index 00000000..bea52c9b
--- /dev/null
+++ b/packages/wallet-v2/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "wallet-v2",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@avail-project/avail-snap": "^1.0.6",
+ "@avail-project/metamask-avail-adapter": "^1.0.6",
+ "@polkadot/api": "^13.0.1",
+ "@polkadot/extension-dapp": "^0.52.3",
+ "@polkadot/rpc-core": "^13.0.1",
+ "@polkadot/util": "^13.1.1",
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
+ "@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-popover": "^1.1.1",
+ "@radix-ui/react-scroll-area": "^1.1.0",
+ "@radix-ui/react-separator": "^1.1.0",
+ "@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-tabs": "^1.1.0",
+ "atomize_icons": "^0.2.9",
+ "avail-js-sdk": "^0.2.13",
+ "class-variance-authority": "^0.7.0",
+ "framer-motion": "^11.11.9",
+ "next": "14.2.11",
+ "react": "^18",
+ "react-dom": "^18",
+ "shadcn-ui": "^0.9.1",
+ "tailwindcss-animate": "^1.0.7",
+ "zustand": "^5.0.0-rc.2"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "14.2.11",
+ "postcss": "^8",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
+ }
+}
diff --git a/packages/wallet-v2/postcss.config.mjs b/packages/wallet-v2/postcss.config.mjs
new file mode 100644
index 00000000..0dc456ad
--- /dev/null
+++ b/packages/wallet-v2/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+}
+
+export default config
diff --git a/packages/wallet-v2/services/availSnapUtils.ts b/packages/wallet-v2/services/availSnapUtils.ts
new file mode 100644
index 00000000..c9503c98
--- /dev/null
+++ b/packages/wallet-v2/services/availSnapUtils.ts
@@ -0,0 +1,56 @@
+import { MetamaskAvailSnap } from '@avail-project/metamask-avail-adapter/build/snap'
+import { enableAvailSnap } from '@avail-project/metamask-avail-adapter'
+import type { InjectedExtension } from '@polkadot/extension-inject/types'
+import { SnapNetworks } from '@avail-project/metamask-avail-types'
+import { InjectedMetamaskExtension } from '@avail-project/metamask-avail-adapter/build/types'
+import { web3EnablePromise } from '@polkadot/extension-dapp'
+
+export const defaultSnapId = 'npm:@avail-project/avail-snap'
+
+export async function isAvailSnapInstalled(): Promise {
+ return !!(await getInjectedMetamaskExtension())
+}
+
+export async function installAvailSnap(): Promise {
+ const snapId = defaultSnapId
+ try {
+ await enableAvailSnap({ networkName: 'turing' }, snapId)
+ return true
+ } catch (err) {
+ return false
+ }
+}
+export interface SnapInitializationResponse {
+ isSnapInstalled: boolean
+ snap?: MetamaskAvailSnap
+}
+export async function initiateAvailSnap(
+ network: SnapNetworks
+): Promise {
+ const snapId = process.env.REACT_APP_SNAP_ID
+ ? process.env.REACT_APP_SNAP_ID
+ : defaultSnapId
+
+ try {
+ const metamaskAvailSnap = await enableAvailSnap(
+ { networkName: network },
+ snapId
+ )
+ return { isSnapInstalled: true, snap: metamaskAvailSnap }
+ } catch (e) {
+ return { isSnapInstalled: false }
+ }
+}
+
+function getMetamaskExtension(
+ extensions: InjectedExtension[]
+): InjectedMetamaskExtension | undefined {
+ return extensions.find(
+ (item) => item.name === 'metamask-avail-snap'
+ ) as unknown as InjectedMetamaskExtension | undefined
+}
+
+export async function getInjectedMetamaskExtension(): Promise {
+ const extensions = await web3EnablePromise
+ return getMetamaskExtension(extensions || []) || null
+}
diff --git a/packages/wallet-v2/services/availscan.ts b/packages/wallet-v2/services/availscan.ts
new file mode 100644
index 00000000..43b71a3f
--- /dev/null
+++ b/packages/wallet-v2/services/availscan.ts
@@ -0,0 +1,8 @@
+export function getAvailscanTxUrl(txHash: string, network: string): string {
+ switch (network) {
+ case 'turing':
+ return `https://goldberg.avail.tools/#/explorer/${txHash}`
+ default:
+ return ''
+ }
+}
diff --git a/packages/wallet-v2/services/format.ts b/packages/wallet-v2/services/format.ts
new file mode 100644
index 00000000..8d2a60e3
--- /dev/null
+++ b/packages/wallet-v2/services/format.ts
@@ -0,0 +1,11 @@
+export function shortAddress(address: string): string {
+ return address.slice(0, 7) + '.....' + address.slice(-7)
+}
+
+export function getCurrency(network: string): string {
+ switch (network) {
+ case 'turing':
+ return 'turing'
+ }
+ return ''
+}
diff --git a/packages/wallet-v2/services/metamask copy.ts b/packages/wallet-v2/services/metamask copy.ts
new file mode 100644
index 00000000..2822d1d9
--- /dev/null
+++ b/packages/wallet-v2/services/metamask copy.ts
@@ -0,0 +1,228 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { create } from 'zustand'
+import { Account, Erc20TokenBalance, Transaction } from '@/types'
+
+let ethers: any
+if (typeof window !== 'undefined') {
+ import('ethers').then((module) => {
+ ethers = module
+ })
+}
+
+export interface WalletState {
+ connected: boolean
+ isLoading: boolean
+ forceReconnect: boolean
+ accounts: Account[]
+ tokenBalance: Erc20TokenBalance
+ transactions: Transaction[]
+ transactionDeploy?: Transaction
+ provider?: any
+ metamaskState: any
+ setProvider: (provider: any) => void
+ setWalletConnection: (connected: boolean) => void
+ setForceReconnect: (forceReconnect: boolean) => void
+ setAccounts: (accounts: Account[] | Account) => void
+ setErc20TokenBalances: (balance: Erc20TokenBalance) => void
+ setErc20TokenBalanceSelected: (balance: Erc20TokenBalance) => void
+ setTransactions: (transactions: Transaction[]) => void
+ setTransactionDeploy: (transaction: Transaction) => void
+ clearAccounts: () => void
+ resetWallet: () => void
+}
+
+const initialState = {
+ connected: false,
+ isLoading: false,
+ forceReconnect: false,
+ accounts: [],
+ tokenBalance: {} as Erc20TokenBalance,
+ transactions: [],
+ transactionDeploy: undefined,
+ provider: undefined,
+ metamaskState: {},
+}
+
+export const useWalletStore = create((set) => ({
+ ...initialState,
+ setProvider: (provider: any) => set({ provider }),
+ setWalletConnection: (connected: boolean) => set({ connected }),
+ setForceReconnect: (forceReconnect: boolean) => set({ forceReconnect }),
+ setAccounts: (accounts: Account[] | Account) =>
+ set((state) => {
+ if (Array.isArray(accounts)) {
+ return { accounts: accounts.map((account) => account) }
+ }
+ return { accounts: [...state.accounts, accounts] }
+ }),
+ setErc20TokenBalances: (balance: Erc20TokenBalance) =>
+ set({ tokenBalance: balance }),
+ setErc20TokenBalanceSelected: (balance: Erc20TokenBalance) =>
+ set({ tokenBalance: balance }),
+ setTransactions: (transactions: Transaction[]) => set({ transactions }),
+ setTransactionDeploy: (transaction: Transaction) =>
+ set({ transactionDeploy: transaction }),
+ clearAccounts: () => set({ accounts: [] }),
+ resetWallet: () =>
+ set((state) => ({
+ ...initialState,
+ provider: state.provider,
+ forceReconnect: true,
+ })),
+}))
+
+export const useAvailSnap = () => {
+ const [loading, setLoading] = useState(false)
+ const {
+ setProvider,
+ setWalletConnection,
+ setForceReconnect,
+ setAccounts,
+ setErc20TokenBalanceSelected,
+ setTransactions,
+ resetWallet,
+ connected,
+ accounts,
+ transactions,
+ provider,
+ } = useWalletStore()
+
+ const snapId =
+ process.env.NEXT_PUBLIC_SNAP_ID || 'npm:@avail-project/avail-snap'
+ const snapVersion = process.env.NEXT_PUBLIC_SNAP_VERSION || '*'
+
+ useEffect(() => {
+ if (typeof window !== 'undefined' && !ethers) {
+ import('ethers').then((module) => {
+ ethers = module
+ })
+ }
+ }, [])
+
+ const connectToSnap = async () => {
+ setLoading(true)
+ try {
+ if (typeof window === 'undefined' || !window.ethereum) {
+ throw new Error('MetaMask is not installed')
+ }
+
+ if (!ethers) {
+ throw new Error('Ethers library is not loaded')
+ }
+
+ const provider = new ethers.providers.Web3Provider(window.ethereum)
+ setProvider(provider)
+
+ // Request account access
+ await provider.send('eth_requestAccounts', [])
+
+ // Request Snap installation
+ await window.ethereum.request({
+ method: 'wallet_requestSnaps',
+ params: {
+ [snapId]: { version: snapVersion },
+ },
+ })
+
+ setWalletConnection(true)
+ setForceReconnect(false)
+ console.log('Successfully connected to Avail Snap')
+ } catch (error) {
+ console.error('Error connecting to Avail Snap:', error)
+ setWalletConnection(false)
+ throw error // Re-throw the error for the UI to handle
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const getNetworks = async () => {
+ return [
+ { name: 'turing', displayName: 'Turing Testnet', chainId: '1' },
+ { name: 'goldberg', displayName: 'Goldberg Testnet', chainId: '2' },
+ { name: 'mainnet', displayName: 'Mainnet', chainId: '3' },
+ ]
+ }
+
+ const switchNetwork = async (network: number, chainId: string) => {
+ setLoading(true)
+ try {
+ if (!provider) {
+ throw new Error('Provider is not initialized')
+ }
+
+ const result = await provider.send('wallet_switchEthereumChain', [
+ { chainId },
+ ])
+ console.log(`Switched to network: ${network}, chainId: ${chainId}`)
+ return true
+ } catch (error) {
+ console.error('Error switching network:', error)
+ return false
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const getWalletData = async (networkId: number, updateAccounts: boolean) => {
+ try {
+ if (updateAccounts) {
+ if (!provider) {
+ throw new Error('Provider is not initialized')
+ }
+
+ const accounts = await provider.listAccounts()
+ setAccounts(
+ accounts.map((address: string) => ({ address, publicKey: '' }))
+ )
+
+ // This is a placeholder - you'll need to implement actual balance fetching
+ setErc20TokenBalanceSelected({
+ amount: '1000',
+ symbol: 'AVAIL',
+ decimals: 18,
+ })
+ }
+ } catch (error) {
+ console.error('Error getting wallet data:', error)
+ throw error
+ }
+ }
+
+ const initSnap = async () => {
+ try {
+ const nets = await getNetworks()
+ await getWalletData(0, true)
+ } catch (error) {
+ console.error('Error initializing Snap:', error)
+ throw error
+ }
+ }
+
+ const checkConnection = async () => {
+ try {
+ if (!provider) {
+ setWalletConnection(false)
+ return
+ }
+
+ const accounts = await provider.listAccounts()
+ setWalletConnection(accounts.length > 0)
+ } catch (error) {
+ console.error('Error checking connection:', error)
+ setWalletConnection(false)
+ }
+ }
+
+ return {
+ connectToSnap,
+ getNetworks,
+ getWalletData,
+ initSnap,
+ checkConnection,
+ switchNetwork,
+ loading,
+ }
+}
diff --git a/packages/wallet-v2/services/metamask.ts b/packages/wallet-v2/services/metamask.ts
new file mode 100644
index 00000000..a57023c0
--- /dev/null
+++ b/packages/wallet-v2/services/metamask.ts
@@ -0,0 +1,375 @@
+import { Account, Erc20TokenBalance, Network } from '@/types';
+import { enableAvailSnap } from '@avail-project/metamask-avail-adapter';
+import { MetamaskAvailSnap } from '@avail-project/metamask-avail-adapter/build/snap';
+import { InjectedMetamaskExtension } from '@avail-project/metamask-avail-adapter/build/types';
+import { SnapNetworks } from '@avail-project/metamask-avail-types';
+import { web3EnablePromise } from '@polkadot/extension-dapp';
+import type { InjectedExtension } from '@polkadot/extension-inject/types';
+import useWalletStore from '../slices/walletSlice';
+import { ethers } from 'ethers';
+import {useNetworkStore} from '@/slices/networkSlice';
+import {useMetamaskStore} from '@/slices/metamaskSlice';
+import { useUIStore } from '@/slices/UISlice'
+
+const { setIsLoading, setLoadingMessage, setWalletConnection } = useWalletStore.getState();
+
+export function hasMetaMask(): boolean {
+ if (!window.ethereum) {
+ return false;
+ }
+ return window.ethereum.isMetaMask;
+}
+
+export const defaultSnapId = 'npm:@avail-project/avail-snap';
+
+export async function installAvailSnap(): Promise {
+ const snapId = process.env.REACT_APP_SNAP_ID ? process.env.REACT_APP_SNAP_ID : defaultSnapId;
+ try {
+ await enableAvailSnap({ networkName: 'mainnet' }, snapId);
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
+export async function isAvailSnapInstalled(): Promise {
+ return !!(await getInjectedMetamaskExtension());
+}
+
+export async function getInjectedMetamaskExtension(): Promise {
+ const extensions = await web3EnablePromise;
+ return getMetamaskExtension(extensions || []) || null;
+}
+
+function getMetamaskExtension(
+ extensions: InjectedExtension[]
+): InjectedMetamaskExtension | undefined {
+ return extensions.find((item) => item.name === 'metamask-avail-snap') as unknown as
+ | InjectedMetamaskExtension
+ | undefined;
+}
+
+export interface SnapInitializationResponse {
+ isSnapInstalled: boolean;
+ snap?: MetamaskAvailSnap;
+}
+
+export async function initiateAvailSnap(
+ network: SnapNetworks
+): Promise {
+ const snapId = process.env.REACT_APP_SNAP_ID ? process.env.REACT_APP_SNAP_ID : defaultSnapId;
+
+ try {
+ const metamaskAvailSnap = await enableAvailSnap({ networkName: network }, snapId);
+ return { isSnapInstalled: true, snap: metamaskAvailSnap };
+ } catch (e) {
+ return { isSnapInstalled: false };
+ }
+}
+
+export const useAvailSnap = () => {
+ const { enableLoadingWithMessage, disableLoading } = useUIStore()
+ const loader = useUIStore((state) => state.loader)
+ const {
+ setWalletConnection,
+ setForceReconnect,
+ setProvider,
+ setAccounts,
+ setTransactions,
+ setErc20TokenBalances,
+ setErc20TokenBalanceSelected,
+ resetWallet
+ } = useWalletStore();
+ const { setNetworks, setActiveNetwork } = useNetworkStore();
+ const networkState = useNetworkStore((state) => state.items)
+ const { setData, availSnap } = useMetamaskStore();
+ const provider = useWalletStore((state) => state.provider);
+ const snapId = process.env.REACT_APP_SNAP_ID
+ ? process.env.REACT_APP_SNAP_ID
+ : 'npm:@avail-project/avail-snap';
+ const snapVersion = process.env.REACT_APP_SNAP_VERSION ? process.env.REACT_APP_SNAP_VERSION : '*';
+ const debugLevel =
+ process.env.REACT_APP_DEBUG_LEVEL !== undefined ? process.env.REACT_APP_DEBUG_LEVEL : 'all';
+ const metamaskState = useMetamaskStore((state) => state.availSnap)
+ const metamask = useMetamaskStore((state) => state.hasMetaMask)
+ const defaultParam = {
+ debugLevel
+ };
+
+ const connectToSnap = async () => {
+ setWalletConnection(true);
+ setForceReconnect(false);
+
+ setIsLoading(true);
+ enableLoadingWithMessage('Connecting to the wallet...')
+
+ try {
+ provider.request({
+ method: 'wallet_requestSnaps',
+ params: {
+ [snapId]: { version: snapVersion }
+ }
+ });
+
+ // On success, update connection state again
+ setWalletConnection(true);
+ setForceReconnect(false);
+ } catch (error) {
+ console.error('Error connecting to snap:', error);
+ setWalletConnection(false);
+ setLoadingMessage('Failed to connect to the wallet. Please try again.'); // Update message on error
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const getNetworks = async () => {
+ return [
+ {
+ name: 'turing',
+ displayName: 'Turing Testnet',
+ chainId: '1'
+ },
+ {
+ name: 'goldberg',
+ displayName: 'Goldberg Testnet',
+ chainId: '2'
+ },
+ {
+ name: 'mainnet',
+ displayName: 'Mainnet',
+ chainId: '3'
+ }
+ ] as Network[];
+ };
+
+ const switchNetwork = async (network: number, chainId: string) => {
+ enableLoadingWithMessage('Switching Network...')
+ if (metamask && metamaskState.isInstalled) {
+ await metamaskState?.api?.setConfiguration({
+ networkName: networkState[network].name
+ });
+ disableLoading();
+ return true;
+ } else {
+ disableLoading();
+ return false;
+ }
+ };
+
+ const getCurrentNetwork = async () => {};
+
+ // const getWalletData = async (
+ // networkId: number,
+ // updateAccounts: boolean,
+ // networks?: Network[]
+ // ) => {
+ // if (!loader.isLoading && !networks) {
+ // enableLoadingWithMessage('Getting network data ...');
+ // }
+ // // const { setProvider } = useWalletStore.getState();
+
+ // if (updateAccounts) {
+ // const metamaskState = useMetamaskStore((state) => state.availSnap);
+ // const api = metamaskState.snap?.getMetamaskSnapApi();
+ // const updatedData: any = {
+ // isInstalled: true,
+ // snap: metamaskState.snap,
+ // address: await api?.getAddress(),
+ // publicKey: await api?.getPublicKey(),
+ // balance: await api?.getBalance(),
+ // latestBlock: await api?.getLatestBlock(),
+ // transactions: await api?.getAllTransactions(),
+ // api
+ // };
+
+ // setAvailSnap(updatedData);
+ // }
+
+ // const acc = [
+ // {
+ // address: metamaskState.address,
+ // publicKey: metamaskState.publicKey
+ // }
+ // ] as Account[];
+
+ // // Set networks if provided
+ // if (networks) {
+ // setNetworks(networks);
+ // }
+
+ // // Update the accounts and transactions
+ // setAccounts(acc);
+ // setTransactions(metamaskState.transactions);
+
+ // // If there are accounts, set the token balance and show the info modal
+ // if (acc.length > 0) {
+ // setErc20TokenBalanceSelected({
+ // amount: metamaskState.balance,
+ // symbol: 'AVAIL',
+ // decimals: 18
+ // } as Erc20TokenBalance);
+
+ // // setInfoModalVisible(true);
+ // }
+
+ // // Stop the loading state
+ // disableLoading();
+ // };
+
+ const getWalletData = async (
+ networkId: number,
+ updateAccounts: boolean,
+ networks?: Network[]
+ ) => {
+ if (!loader.isLoading && !networks) {
+ enableLoadingWithMessage('Getting network data ...');
+ }
+ if (updateAccounts) {
+ setData({
+ isInstalled: true,
+ snap: metamaskState.snap,
+ address: await metamaskState.snap?.getMetamaskSnapApi().getAddress(),
+ publicKey: await metamaskState.snap?.getMetamaskSnapApi().getPublicKey(),
+ balance: await metamaskState.snap?.getMetamaskSnapApi().getBalance(),
+ latestBlock: await metamaskState.snap?.getMetamaskSnapApi().getLatestBlock(),
+ transactions: await metamaskState.snap
+ ?.getMetamaskSnapApi()
+ .getAllTransactions(),
+ api: metamaskState.snap?.getMetamaskSnapApi()
+ })
+ }
+ console.log({setData, metamaskState, availSnap})
+ const acc = [
+ {
+ address: metamaskState.address,
+ publicKey: metamaskState.publicKey
+ }
+ ] as Account[];
+
+ if (networks) {
+ setNetworks(networks);
+ }
+ setAccounts(acc);
+ setTransactions(metamaskState.transactions);
+ if (acc.length > 0) {
+ setErc20TokenBalanceSelected({
+ amount: metamaskState.balance,
+ symbol: 'AVAIL',
+ decimals: 18
+ } as Erc20TokenBalance)
+ // dispatch(setInfoModalVisible(true));
+ }
+ disableLoading();
+ };
+ const initSnap = async () => {
+ if (!loader.isLoading) {
+ enableLoadingWithMessage('Initializing wallet ...');
+ }
+
+ try {
+ const nets = await getNetworks();
+ if (nets.length === 0) {
+ throw new Error('No networks available.');
+ }
+ const installResult = await initiateAvailSnap(nets[0].name);
+ if (!installResult.isSnapInstalled) {
+ resetWallet();
+ throw new Error('Snap installation not accepted.');
+ }
+
+ const snapApi = installResult.snap?.getMetamaskSnapApi();
+ if (!snapApi) {
+ throw new Error('Failed to initialize Snap API.');
+ }
+
+ const address = await snapApi.getAddress();
+ const publicKey = await snapApi.getPublicKey();
+ const balance = await snapApi.getBalance();
+ const transactions = await snapApi.getAllTransactions();
+ const latestBlock = await snapApi.getLatestBlock();
+
+ // setProvider(installResult.snap);
+ // setAccounts([{ address }]);
+ // // setErc20TokenBalances(balance);
+ // setTransactions(transactions);
+ // setWalletConnection(true);
+
+ setData({
+ isInstalled: true,
+ snap: installResult.snap,
+ address: address,
+ publicKey: publicKey,
+ balance: balance,
+ latestBlock: latestBlock,
+ transactions: transactions,
+ api: snapApi,
+ })
+
+ const net = { chainId: '1' };
+ const idx = nets.findIndex((e) => e.chainId === net.chainId);
+ setActiveNetwork(idx);
+ await getWalletData(0, false, nets);
+ } catch(err) {
+ console.error('Error initializing wallet:', err);
+ resetWallet();
+ }
+ disableLoading()
+ };
+
+ const checkConnection = async () => {
+ const isInstalled = await isAvailSnapInstalled();
+ if (isInstalled) {
+ setWalletConnection(true);
+ } else {
+ setWalletConnection(false);
+ }
+ };
+ const getPrivateKeyFromAddress = async () => {
+ if (!availSnap.snap) {
+ console.error('Snap is not initialized.');
+ return;
+ }
+
+ const api = availSnap.snap.getMetamaskSnapApi();
+
+ if (!api) {
+ console.error('Failed to get Metamask Snap API.');
+ return;
+ }
+
+ try {
+ // Requesting permissions (this may vary based on your Snap setup)
+ await window.ethereum.request({
+ method: 'wallet_requestPermissions',
+ params: [{
+ eth_accounts: {},
+ }],
+ });
+
+ // Now try to export the seed
+ const privateKey = await api.exportSeed();
+
+ if (privateKey) {
+ console.log({ privateKey });
+ } else {
+ console.warn('exportSeed returned null. Please check permissions and Snap configuration.');
+ }
+ } catch (error) {
+ console.error('Error requesting permissions or exporting seed:', error);
+ }
+ };
+
+
+
+ return {
+ connectToSnap,
+ getNetworks,
+ getCurrentNetwork,
+ getWalletData,
+ initSnap,
+ checkConnection,
+ getPrivateKeyFromAddress,
+ switchNetwork
+ };
+};
diff --git a/packages/wallet-v2/shared/BalanceDisplay.tsx b/packages/wallet-v2/shared/BalanceDisplay.tsx
new file mode 100644
index 00000000..948f87b5
--- /dev/null
+++ b/packages/wallet-v2/shared/BalanceDisplay.tsx
@@ -0,0 +1,60 @@
+import React from 'react'
+import { motion } from 'framer-motion'
+import { SeperatorDot } from './SeperatorDot'
+import useWalletStore from '@/slices/walletSlice'
+
+export const BalanceDisplay: React.FC = () => {
+ const connected = useWalletStore((state) => state.connected)
+ const balance = useWalletStore((state) => state.tokenBalance)
+
+ const containerVariants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.2,
+ },
+ },
+ }
+
+ const itemVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: { opacity: 1, y: 0 },
+ }
+
+ const formatBalance = (balance: number | undefined): string => {
+ return balance ? balance.toLocaleString('en-US', { maximumFractionDigits: 2 }) : '--'
+ }
+
+ const calculateUSDValue = (balance: number | undefined): string => {
+ const rate = 0.1324 // Example exchange rate, replace with actual rate
+ return balance ? (balance * rate).toLocaleString('en-US', { maximumFractionDigits: 2 }) : '--'
+ }
+
+ return (
+
+
+
+
+
+
+ {formatBalance(balance.amount)} AVL
+
+
+ {calculateUSDValue(balance.amount)} USD
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/ConnectButton.tsx b/packages/wallet-v2/shared/ConnectButton.tsx
new file mode 100644
index 00000000..780e0773
--- /dev/null
+++ b/packages/wallet-v2/shared/ConnectButton.tsx
@@ -0,0 +1,41 @@
+'use client'
+
+import { useState } from 'react'
+import { motion } from 'framer-motion'
+import { Button } from '@/components/ui/button'
+import { useAvailSnap } from '@/services/metamask'
+
+export const ConnectButton: React.FC = () => {
+ const { connectToSnap } = useAvailSnap()
+ const [isHovered, setIsHovered] = useState(false)
+
+ const buttonVariants = {
+ initial: { scale: 1 },
+ hover: { scale: 1.05 },
+ tap: { scale: 0.95 },
+ }
+
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Dialog/DialogLayout.tsx b/packages/wallet-v2/shared/Dialog/DialogLayout.tsx
new file mode 100644
index 00000000..91dfc77b
--- /dev/null
+++ b/packages/wallet-v2/shared/Dialog/DialogLayout.tsx
@@ -0,0 +1,76 @@
+import React from 'react'
+import { motion, AnimatePresence } from 'framer-motion'
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogClose,
+ DialogTrigger,
+ DialogDescription,
+} from '@/components/ui/dialog'
+import { Separator } from '@/components/ui/separator'
+
+interface DialogLayoutProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ title: string
+ children: React.ReactNode
+ footerContent: React.ReactNode
+ trigger: React.ReactNode
+ closeButton?: React.ReactNode
+}
+
+const DialogLayout: React.FC = ({
+ open,
+ onOpenChange,
+ title,
+ children,
+ footerContent,
+ closeButton,
+ trigger,
+}) => {
+ const dialogAnimation = {
+ hidden: { opacity: 0, scale: 0.95 },
+ visible: { opacity: 1, scale: 1 },
+ exit: { opacity: 0, scale: 0.95 },
+ }
+
+ return (
+
+ )
+}
+
+export default DialogLayout
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Dialog/ExportKey.tsx b/packages/wallet-v2/shared/Dialog/ExportKey.tsx
new file mode 100644
index 00000000..9e36d0bd
--- /dev/null
+++ b/packages/wallet-v2/shared/Dialog/ExportKey.tsx
@@ -0,0 +1,134 @@
+import React, { useState } from 'react'
+import { motion } from 'framer-motion'
+import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
+import { Textarea } from '@/components/ui/textarea'
+import { Button } from '@/components/ui/button'
+import { Copy, Info } from 'atomize_icons'
+import { CheckCircledIcon } from '@radix-ui/react-icons'
+import Image from 'next/image'
+import DialogLayout from './DialogLayout'
+import Key from '@/assets/images/key.svg'
+import { useAvailSnap } from '@/services/metamask'
+
+
+interface ExportKeyProps {
+ onOpen: (open: boolean) => void
+}
+
+const ExportKey: React.FC = ({ onOpen }) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [isCopied, setIsCopied] = useState(false)
+
+ const {getPrivateKeyFromAddress} = useAvailSnap()
+
+ const openDialog = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ setIsOpen(true)
+ }
+
+ const closeDialog = () => {
+ setIsOpen(false)
+ onOpen(false)
+ }
+
+ const copyToClipboard = () => {
+ getPrivateKeyFromAddress()
+ navigator.clipboard.writeText("Your secret recovery phrase here")
+ .then(() => {
+ setIsCopied(true)
+ setTimeout(() => setIsCopied(false), 3000)
+ })
+ .catch(err => console.error('Failed to copy text: ', err))
+ }
+
+ return (
+ <>
+
+
+ Export private key
+
+
+ Cancel
+
+ }
+ closeButton={
+
+ }
+ >
+
+
+ The Secret Recovery Phrase (SRP) provides full access to your wallet
+ and funds.
+
+
+ MetaMask snap is a non-custodial wallet. That means you're the owner
+ of your SRP.
+
+
+
+
+
Seed Phrase
+
+ {!isCopied ? (
+ <>
+ Copy to clipboard
+ >
+ ) : (
+
+ Copied
+
+ )}
+
+
+
+
+
+
+ Make sure no one is looking at your screen. MetaMask Support will
+ never request this.
+
+
+
+
+ >
+ )
+}
+
+export default ExportKey
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Dialog/RecieveDialog.tsx b/packages/wallet-v2/shared/Dialog/RecieveDialog.tsx
new file mode 100644
index 00000000..5da069b6
--- /dev/null
+++ b/packages/wallet-v2/shared/Dialog/RecieveDialog.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import React, { useState, useCallback } from 'react'
+import { motion } from 'framer-motion'
+import { QRCodeSVG } from 'qrcode.react'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Toast } from '@/components/ui/toast'
+import Image from 'next/image'
+import Qr from '@/assets/images/qr.svg'
+import useWalletStore from '@/slices/walletSlice'
+import DialogLayout from './DialogLayout'
+import { useToast } from '@/hooks/use-toast'
+
+const ReceiveDialog: React.FC = () => {
+ const [isOpen, setIsOpen] = useState(false)
+ const { toast } = useToast()
+ const account = useWalletStore((state) => state.accounts[0])
+
+ const openDialog = () => setIsOpen(true)
+ const closeDialog = () => setIsOpen(false)
+
+ const copyToClipboard = useCallback(() => {
+ if (account) {
+ navigator.clipboard.writeText(account).then(() => {
+ toast({
+ title: "Address Copied",
+ description: "The address has been copied to your clipboard.",
+
+ })
+ }).catch((err) => {
+ console.error('Failed to copy: ', err)
+ toast({
+ title: "Copy Failed",
+ description: "Failed to copy the address. Please try again.",
+ variant: "destructive",
+ })
+ })
+ }
+ }, [account, toast])
+
+ return (
+
+
+ Receive
+
+ }
+ footerContent={
+ <>
+
+
+ >
+ }
+ >
+
+
+
+
+ )
+}
+
+export default ReceiveDialog
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Dialog/SendDialog.tsx b/packages/wallet-v2/shared/Dialog/SendDialog.tsx
new file mode 100644
index 00000000..737c708a
--- /dev/null
+++ b/packages/wallet-v2/shared/Dialog/SendDialog.tsx
@@ -0,0 +1,93 @@
+'use client'
+
+import React, { useState } from 'react'
+import { motion } from 'framer-motion'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Info, Send } from 'atomize_icons'
+import DialogLayout from './DialogLayout'
+
+const SendDialog: React.FC = () => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [recipient, setRecipient] = useState('')
+ const [amount, setAmount] = useState('')
+
+ const openDialog = () => setIsOpen(true)
+ const closeDialog = () => setIsOpen(false)
+
+ const handleSend = () => {
+ // Implement send logic here
+ console.log('Sending', amount, 'to', recipient)
+ closeDialog()
+ }
+
+ return (
+
+ Send
+
+ }
+ footerContent={
+ <>
+
+
+ >
+ }
+ >
+
+
+
To
+
setRecipient(e.target.value)}
+ />
+
+
+
+ Please only enter a valid Avail address. Sending funds to a
+ different network might result in permanent loss.
+
+
+
+
+
Amount
+ setAmount(e.target.value)}
+ />
+
+
+
+ )
+}
+
+export default SendDialog
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Dialog/SignMessageDialog.tsx b/packages/wallet-v2/shared/Dialog/SignMessageDialog.tsx
new file mode 100644
index 00000000..e7407625
--- /dev/null
+++ b/packages/wallet-v2/shared/Dialog/SignMessageDialog.tsx
@@ -0,0 +1,137 @@
+'use client'
+
+import React, { useState, useCallback } from 'react'
+import { motion } from 'framer-motion'
+import { Button } from '@/components/ui/button'
+import { Textarea } from '@/components/ui/textarea'
+import { useToast } from '@/hooks/use-toast'
+import Image from 'next/image'
+import Sign from '@/assets/images/signature.svg'
+import useWalletStore from '@/slices/walletSlice'
+import DialogLayout from './DialogLayout'
+import { CheckCircledIcon } from '@radix-ui/react-icons'
+
+const SignMessageDialog: React.FC = () => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [message, setMessage] = useState('')
+ const [signingState, setSigningState] = useState<'idle' | 'signing' | 'success'>('idle')
+ const { toast } = useToast()
+ const account = useWalletStore((state) => state.accounts[0])
+
+ const openDialog = () => setIsOpen(true)
+ const closeDialog = () => {
+ setIsOpen(false)
+ setMessage('')
+ setSigningState('idle')
+ }
+
+ const handleSign = useCallback(async () => {
+ if (!message) {
+ toast({
+ title: "Error",
+ description: "Please enter a message to sign.",
+ variant: "destructive",
+ })
+ return
+ }
+
+ setSigningState('signing')
+
+ // Simulating the signing process
+ await new Promise(resolve => setTimeout(resolve, 2000))
+
+ setSigningState('success')
+
+ // In a real application, you would use a wallet provider to sign the message
+ // For example, with ethers.js:
+ // const signature = await signer.signMessage(message);
+
+ }, [message, toast])
+
+ return (
+
+
+ Sign Message
+
+ }
+ footerContent={
+ signingState === 'success' ? (
+
+ ) : (
+ <>
+
+
+ >
+ )
+ }
+ >
+
+ {signingState === 'success' ? (
+
+
+
Transaction successful
+
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default SignMessageDialog
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/Footer.tsx b/packages/wallet-v2/shared/Footer.tsx
new file mode 100644
index 00000000..058e2ff8
--- /dev/null
+++ b/packages/wallet-v2/shared/Footer.tsx
@@ -0,0 +1,94 @@
+'use client'
+
+import React, { useEffect, useState } from 'react'
+import {useMetamaskStore} from '@/slices/metamaskSlice'
+import { shortenAddress } from '@/lib/utils'
+import Image from 'next/image'
+import { motion, AnimatePresence } from 'framer-motion'
+import Green from '@/assets/images/green.svg'
+import Discord from '@/assets/images/discord.svg'
+import Twitter from '@/assets/images/twitter.svg'
+import Linkedin from '@/assets/images/linkedin.svg'
+import Github from '@/assets/images/github.svg'
+
+const Footer: React.FC = () => {
+ const availSnap = useMetamaskStore((state) => state.availSnap)
+ const [prevBlock, setPrevBlock] = useState(availSnap.latestBlock)
+
+ useEffect(() => {
+ if (availSnap.latestBlock.number !== prevBlock.number) {
+ setPrevBlock(availSnap.latestBlock)
+ }
+ }, [availSnap.latestBlock, prevBlock])
+
+ const socialLinks = [
+ { icon: Discord, alt: 'Discord', href: '#' },
+ { icon: Github, alt: 'GitHub', href: '#' },
+ { icon: Twitter, alt: 'Twitter', href: '#' },
+ { icon: Linkedin, alt: 'LinkedIn', href: '#' },
+ ]
+
+ const isLoading = availSnap.latestBlock.number === ''
+
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/packages/wallet-v2/shared/Header.tsx b/packages/wallet-v2/shared/Header.tsx
new file mode 100644
index 00000000..cdae8915
--- /dev/null
+++ b/packages/wallet-v2/shared/Header.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import Image from 'next/image'
+import { ConnectButton } from './ConnectButton'
+import WalletCopy from './WalletCopy'
+import MainnetTestnetSwitch from './MainnetTestnetSwitch'
+import TransactionLayout from './Transactions/TransactionLayout'
+import MenuBar from './MenuBar'
+import useWalletStore from '@/slices/walletSlice'
+import Logo from '@/assets/images/logo.png'
+import { TransactionType } from '@/types'
+import { useState } from 'react'
+
+export const Header = () => {
+ const connected = useWalletStore((state) => state.connected)
+ const accounts = useWalletStore((state) => state.accounts)
+
+ const address =
+ accounts?.length > 0
+ ? (accounts[0] as string)
+ : '0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+ return (
+
+ )
+}
diff --git a/packages/wallet-v2/shared/LoadingModal.tsx b/packages/wallet-v2/shared/LoadingModal.tsx
new file mode 100644
index 00000000..ab76d274
--- /dev/null
+++ b/packages/wallet-v2/shared/LoadingModal.tsx
@@ -0,0 +1,68 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { motion, AnimatePresence } from 'framer-motion'
+import { useUIStore } from '@/slices/UISlice'
+import Image from 'next/image'
+import Logo from '@/assets/images/fav.svg'
+
+export default function LoadingScreen() {
+ const { loader } = useUIStore()
+ const [prevMessage, setPrevMessage] = useState(
+ loader.loadingMessage || 'Please wait while we process your request.'
+ )
+
+ useEffect(() => {
+ if (loader.loadingMessage !== prevMessage) {
+ setPrevMessage(
+ loader.loadingMessage || 'Please wait while we process your request.'
+ )
+ }
+ }, [loader.loadingMessage, prevMessage])
+
+ if (!loader.isLoading) return null
+
+ return (
+
+
+
+
+
+
+
+
+ {prevMessage}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/MainnetTestnetSwitch.tsx b/packages/wallet-v2/shared/MainnetTestnetSwitch.tsx
new file mode 100644
index 00000000..903ba7de
--- /dev/null
+++ b/packages/wallet-v2/shared/MainnetTestnetSwitch.tsx
@@ -0,0 +1,56 @@
+import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { useAvailSnap } from '@/services/metamask';
+import { useNetworkStore } from '@/slices/networkSlice';
+import useWalletStore from '@/slices/walletSlice';
+
+const MainnetTuringSwitch: React.FC = () => {
+ const { switchNetwork } = useAvailSnap();
+ const { clearAccounts } = useWalletStore();
+ const { setActiveNetwork } = useNetworkStore();
+
+ const networks = useNetworkStore((state) => state.items);
+ const activeNetwork = useNetworkStore((state) => state.activeNetwork);
+
+ const changeNetwork = async (networkIndex: number) => {
+ const selectedNetwork = networks[networkIndex];
+ const { chainId } = selectedNetwork;
+
+ console.log('Switching to network:', selectedNetwork.name, 'with chain ID:', chainId);
+
+ let result = false;
+ if (activeNetwork !== networkIndex) {
+ result = await switchNetwork(networkIndex, chainId);
+ }
+
+ if (result) {
+ clearAccounts();
+ setActiveNetwork(networkIndex);
+ }
+ };
+
+ return (
+
+
+ changeNetwork(2)} // Assuming mainnet is at index 2
+ className={`px-3 py-2 rounded-full transition-all duration-300 font-semibold ${activeNetwork === 2 ? 'bg-white/12 text-white' : ''}`}
+ >
+ Avail Mainnet
+
+ changeNetwork(0)} // Turing at index 0
+ className={`px-3 py-2 rounded-full transition-all duration-300 font-semibold ${activeNetwork === 0 ? 'bg-white/12 text-white' : ''}`}
+ >
+ Turing Testnet
+
+
+
+ );
+};
+
+export default MainnetTuringSwitch;
diff --git a/packages/wallet-v2/shared/MenuBar.tsx b/packages/wallet-v2/shared/MenuBar.tsx
new file mode 100644
index 00000000..a9f792d1
--- /dev/null
+++ b/packages/wallet-v2/shared/MenuBar.tsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react'
+import Image from 'next/image'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+ DropdownMenuSeparator,
+ DropdownMenuItem,
+} from '@/components/ui/dropdown-menu'
+import { Menu, Stepper, TransactionsFill } from 'atomize_icons'
+import Logout from '@/assets/images/logout.svg'
+import ExportKey from './Dialog/ExportKey'
+import { Dialog } from '@radix-ui/react-dialog'
+
+const MenuBar: React.FC = () => {
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false)
+
+ return (
+
+ )
+}
+
+export default MenuBar
diff --git a/packages/wallet-v2/shared/SeperatorDot.tsx b/packages/wallet-v2/shared/SeperatorDot.tsx
new file mode 100644
index 00000000..18e4325c
--- /dev/null
+++ b/packages/wallet-v2/shared/SeperatorDot.tsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import Image from 'next/image'
+import Line from '@/assets/images/line.svg'
+
+export const SeperatorDot: React.FC = () => {
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/wallet-v2/shared/TextCard.tsx b/packages/wallet-v2/shared/TextCard.tsx
new file mode 100644
index 00000000..1dd09063
--- /dev/null
+++ b/packages/wallet-v2/shared/TextCard.tsx
@@ -0,0 +1,35 @@
+import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
+import { ArrowRight } from 'atomize_icons'
+import { motion } from 'framer-motion'
+
+interface TextCardProps {
+ title: string
+ content: string
+ link: string
+}
+
+export const TextCard: React.FC = ({ title, content, link }) => {
+ return (
+
+
+
+ {title}
+
+
+ {content}
+
+
+
+ Read more
+
+
+
+
+ )
+}
diff --git a/packages/wallet-v2/shared/Transactions/Transaction.tsx b/packages/wallet-v2/shared/Transactions/Transaction.tsx
new file mode 100644
index 00000000..f1d9dbc2
--- /dev/null
+++ b/packages/wallet-v2/shared/Transactions/Transaction.tsx
@@ -0,0 +1,33 @@
+import { Send } from 'atomize_icons'
+import { TransactionType } from '@/types'
+
+interface TransactionProps {
+ tx: TransactionType
+}
+
+const Transaction: React.FC = ({ tx }) => {
+ return (
+
+
+
+ {tx.type}
+ {tx.amount && (
+ {tx.amount}
+ )}
+
+ {tx.from && (
+
+ From {tx.from} to{' '}
+ {tx.to}
+
+ )}
+ {tx.txHash && (
+
+ Tx hash: {tx.txHash}
+
+ )}
+
+ )
+}
+
+export default Transaction
diff --git a/packages/wallet-v2/shared/Transactions/TransactionLayout.tsx b/packages/wallet-v2/shared/Transactions/TransactionLayout.tsx
new file mode 100644
index 00000000..4daf4c1a
--- /dev/null
+++ b/packages/wallet-v2/shared/Transactions/TransactionLayout.tsx
@@ -0,0 +1,83 @@
+import { useState } from 'react'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover'
+import { Separator } from '@/components/ui/separator'
+import { Cross, Notification } from 'atomize_icons'
+import { TransactionType } from '@/types'
+import { motion, AnimatePresence } from 'framer-motion'
+import TransactionList from './TransactionLists'
+
+const TransactionLayout: React.FC = () => {
+ const [transactions] = useState([
+ {
+ id: '1',
+ date: '2024-09-25',
+ type: 'Send',
+ amount: '0.32 AVL',
+ from: '0xbc24...ds92',
+ to: '0x234h...32ds',
+ },
+ { id: '2', date: '2024-09-26', type: 'Sign', txHash: '0xbc24...ds92' },
+ {
+ id: '3',
+ date: '2024-09-27',
+ type: 'Send',
+ amount: '0.32 AVL',
+ from: '0xbc24...ds92',
+ to: '0x234h...32ds',
+ },
+ // ... (other transactions)
+ ])
+
+ const [isOpen, setIsOpen] = useState(false)
+
+ return (
+
+
+
+
+
+
+
+ {isOpen && (
+
+
+
+
+ Transaction history
+
+ setIsOpen(false)}
+ aria-label="Close transaction history"
+ >
+
+
+
+
+
+
+
+ )}
+
+
+ )
+}
+
+export default TransactionLayout
diff --git a/packages/wallet-v2/shared/Transactions/TransactionLists.tsx b/packages/wallet-v2/shared/Transactions/TransactionLists.tsx
new file mode 100644
index 00000000..51d608ae
--- /dev/null
+++ b/packages/wallet-v2/shared/Transactions/TransactionLists.tsx
@@ -0,0 +1,49 @@
+import Image from 'next/image'
+import { ScrollArea } from '@/components/ui/scroll-area'
+import Transaction from './Transaction'
+import { TransactionType } from '@/types'
+import EmptyBox from '@/assets/images/empty-box.svg'
+import { motion } from 'framer-motion'
+
+interface TransactionListProps {
+ transactions: TransactionType[]
+}
+
+const TransactionList: React.FC = ({ transactions }) => {
+ if (transactions.length === 0) {
+ return (
+
+
+ No transactions found!
+
+ )
+ }
+
+ return (
+
+ {transactions.map((tx, index) => (
+
+
+
+ ))}
+
+ )
+}
+
+export default TransactionList
diff --git a/packages/wallet-v2/shared/WalletCopy.tsx b/packages/wallet-v2/shared/WalletCopy.tsx
new file mode 100644
index 00000000..443dbc6d
--- /dev/null
+++ b/packages/wallet-v2/shared/WalletCopy.tsx
@@ -0,0 +1,44 @@
+import { Copy } from 'atomize_icons'
+import { shortenAddress } from '@/lib/utils'
+import { motion } from 'framer-motion'
+import { useState } from 'react'
+
+interface Props {
+ address: string
+}
+
+const WalletCopy: React.FC = ({ address }) => {
+ const [isCopied, setIsCopied] = useState(false)
+
+ const handleCopy = () => {
+ navigator.clipboard
+ .writeText(address)
+ .then(() => {
+ setIsCopied(true)
+ setTimeout(() => setIsCopied(false), 2000)
+ })
+ .catch((err) => {
+ console.error('Failed to copy address: ', err)
+ })
+ }
+
+ return (
+
+ {shortenAddress(address)}
+
+
+
+
+ )
+}
+
+export default WalletCopy
diff --git a/packages/wallet-v2/slices/UISlice.ts b/packages/wallet-v2/slices/UISlice.ts
new file mode 100644
index 00000000..d5d00ec2
--- /dev/null
+++ b/packages/wallet-v2/slices/UISlice.ts
@@ -0,0 +1,19 @@
+import { create } from 'zustand'
+
+interface LoaderState {
+ loader: { isLoading: boolean; loadingMessage: string }
+ enableLoadingWithMessage: (message: string) => void
+ disableLoading: () => void
+}
+
+export const useUIStore = create((set) => ({
+ loader: { isLoading: false, loadingMessage: '' },
+ enableLoadingWithMessage: (message: string) =>
+ set((state) => ({
+ loader: { ...state.loader, isLoading: true, loadingMessage: message },
+ })),
+ disableLoading: () =>
+ set((state) => ({
+ loader: { ...state.loader, isLoading: false, loadingMessage: '' },
+ })),
+}))
diff --git a/packages/wallet-v2/slices/metamaskSlice.ts b/packages/wallet-v2/slices/metamaskSlice.ts
new file mode 100644
index 00000000..5e1e9bb8
--- /dev/null
+++ b/packages/wallet-v2/slices/metamaskSlice.ts
@@ -0,0 +1,43 @@
+import { create } from 'zustand'
+import { MetamaskAvailSnap } from '@avail-project/metamask-avail-adapter/build/snap'
+import { SnapNetworks, Transaction } from '@avail-project/metamask-avail-types'
+
+export interface IAvailSnap {
+ isInstalled: boolean
+ message: string
+ snap?: MetamaskAvailSnap
+ balance: string
+ address: string
+ publicKey: string
+ latestBlock: { hash: string; number: string }
+ transactions: Transaction[]
+ network: SnapNetworks
+ api: any | null
+}
+
+interface MetamaskState {
+ availSnap: IAvailSnap
+ hasMetaMask: boolean
+ setData: (data: Partial) => void
+}
+
+const hasMetaMask = (): boolean => window.ethereum?.isMetaMask ?? false
+
+export const useMetamaskStore = create((set) => ({
+ hasMetaMask: hasMetaMask(),
+ availSnap: {
+ isInstalled: false,
+ message: '',
+ balance: '0',
+ address: '',
+ publicKey: '',
+ latestBlock: { hash: '', number: '' },
+ transactions: [],
+ network: 'mainnet',
+ api: null,
+ },
+ setData: (data: Partial) =>
+ set((state) => ({
+ availSnap: { ...state.availSnap, ...data },
+ })),
+}))
diff --git a/packages/wallet-v2/slices/modalSlice.ts b/packages/wallet-v2/slices/modalSlice.ts
new file mode 100644
index 00000000..657c8c60
--- /dev/null
+++ b/packages/wallet-v2/slices/modalSlice.ts
@@ -0,0 +1,11 @@
+import { create } from 'zustand'
+
+interface ModalState {
+ infoModalVisible: boolean
+ setInfoModalVisible: (isVisible: boolean) => void
+}
+
+export const useModalStore = create((set) => ({
+ infoModalVisible: false,
+ setInfoModalVisible: (isVisible) => set({ infoModalVisible: isVisible }),
+}))
diff --git a/packages/wallet-v2/slices/networkSlice.ts b/packages/wallet-v2/slices/networkSlice.ts
new file mode 100644
index 00000000..174839ba
--- /dev/null
+++ b/packages/wallet-v2/slices/networkSlice.ts
@@ -0,0 +1,22 @@
+import { create } from 'zustand';
+import { Network } from '@/types';
+
+export interface NetworkState {
+ items: Network[];
+ activeNetwork: number;
+ setNetworks: (networks: Network[]) => void;
+ setActiveNetwork: (index: number) => void;
+ resetNetwork: () => void;
+}
+
+const initialState: Omit = {
+ items: [],
+ activeNetwork: 0,
+};
+
+export const useNetworkStore = create((set) => ({
+ ...initialState,
+ setNetworks: (networks) => set({ items: networks }),
+ setActiveNetwork: (index) => set({ activeNetwork: index }),
+ resetNetwork: () => set({ ...initialState }),
+}));
diff --git a/packages/wallet-v2/slices/walletSlice copy.ts b/packages/wallet-v2/slices/walletSlice copy.ts
new file mode 100644
index 00000000..c444665a
--- /dev/null
+++ b/packages/wallet-v2/slices/walletSlice copy.ts
@@ -0,0 +1,68 @@
+'use client'
+
+import { ethers } from 'ethers'
+import { useState } from 'react'
+import { create } from 'zustand'
+import { Account, Erc20TokenBalance, Transaction } from '@/types'
+
+export interface WalletState {
+ connected: boolean
+ isLoading: boolean
+ forceReconnect: boolean
+ accounts: Account[]
+ tokenBalance: Erc20TokenBalance
+ transactions: Transaction[]
+ transactionDeploy?: Transaction
+ provider?: any // TODO: Metamask SDK does not export types
+ metamaskState: any
+ setProvider: (provider: any) => void
+ setWalletConnection: (connected: boolean) => void
+ setForceReconnect: (forceReconnect: boolean) => void
+ setAccounts: (accounts: Account[] | Account) => void
+ setErc20TokenBalances: (balance: Erc20TokenBalance) => void
+ setErc20TokenBalanceSelected: (balance: Erc20TokenBalance) => void
+ setTransactions: (transactions: Transaction[]) => void
+ setTransactionDeploy: (transaction: Transaction) => void
+ clearAccounts: () => void
+ resetWallet: () => void
+}
+
+const initialState = {
+ connected: false,
+ isLoading: false,
+ forceReconnect: false,
+ accounts: [],
+ tokenBalance: {} as Erc20TokenBalance,
+ transactions: [],
+ transactionDeploy: undefined,
+ provider: undefined,
+ metamaskState: {},
+}
+
+export const useWalletStore = create((set) => ({
+ ...initialState,
+ setProvider: (provider: any) => set({ provider }),
+ setWalletConnection: (connected: boolean) => set({ connected }),
+ setForceReconnect: (forceReconnect: boolean) => set({ forceReconnect }),
+ setAccounts: (accounts: Account[] | Account) =>
+ set((state) => {
+ if (Array.isArray(accounts)) {
+ return { accounts: accounts.map((account) => account) }
+ }
+ return { accounts: [...state.accounts, accounts] }
+ }),
+ setErc20TokenBalances: (balance: Erc20TokenBalance) =>
+ set({ tokenBalance: balance }),
+ setErc20TokenBalanceSelected: (balance: Erc20TokenBalance) =>
+ set({ tokenBalance: balance }),
+ setTransactions: (transactions: Transaction[]) => set({ transactions }),
+ setTransactionDeploy: (transaction: Transaction) =>
+ set({ transactionDeploy: transaction }),
+ clearAccounts: () => set({ accounts: [] }),
+ resetWallet: () =>
+ set((state) => ({
+ ...initialState,
+ provider: state.provider,
+ forceReconnect: true,
+ })),
+}))
diff --git a/packages/wallet-v2/slices/walletSlice.ts b/packages/wallet-v2/slices/walletSlice.ts
new file mode 100644
index 00000000..e76c9931
--- /dev/null
+++ b/packages/wallet-v2/slices/walletSlice.ts
@@ -0,0 +1,92 @@
+// stores/walletStore.ts
+import { Account, Erc20TokenBalance, Transaction } from '@/types';
+import { create } from 'zustand';
+
+interface WalletState {
+ connected: boolean;
+ isLoading: boolean;
+ loadingMessage: string; // Add this to keep track of loading messages
+ forceReconnect: boolean;
+ accounts: string[];
+ tokenBalance: Erc20TokenBalance;
+ transactions: Transaction[];
+ transactionDeploy?: Transaction;
+ provider?: any;
+
+ setProvider: (provider: any) => void;
+ setIsLoading: (isLoading: boolean) => void;
+ setLoadingMessage: (message: string) => void; // New method to set loading message
+ setWalletConnection: (connected: boolean) => void;
+ setForceReconnect: (reconnect: boolean) => void;
+ setAccounts: (accounts: Account[] | Account) => void;
+ setErc20TokenBalances: (balance: Erc20TokenBalance) => void;
+ setErc20TokenBalanceSelected: (balance: Erc20TokenBalance) => void;
+ setTransactions: (transactions: Transaction[]) => void;
+ setTransactionDeploy: (transaction: Transaction) => void;
+ clearAccounts: () => void;
+ resetWallet: () => void;
+}
+
+const initialState: Omit<
+ WalletState,
+ | 'setIsLoading'
+ | 'setLoadingMessage'
+ | 'setProvider'
+ | 'setWalletConnection'
+ | 'setForceReconnect'
+ | 'setAccounts'
+ | 'setErc20TokenBalances'
+ | 'setErc20TokenBalanceSelected'
+ | 'setTransactions'
+ | 'setTransactionDeploy'
+ | 'clearAccounts'
+ | 'resetWallet'
+> = {
+ connected: false,
+ isLoading: false,
+ loadingMessage: '',
+ forceReconnect: false,
+ accounts: [],
+ tokenBalance: {} as Erc20TokenBalance,
+ transactions: [],
+ transactionDeploy: undefined,
+ provider: undefined,
+};
+
+const useWalletStore = create((set) => ({
+ ...initialState,
+
+ setProvider: (provider) => set({ provider }),
+ setIsLoading: (isLoading) => set({ isLoading }),
+ setLoadingMessage: (message) => set({ loadingMessage: message }), // New implementation
+
+ setWalletConnection: (connected) => set({ connected }),
+ setForceReconnect: (forceReconnect) => set({ forceReconnect }),
+
+ setAccounts: (accounts) => {
+ set((state) => ({
+ accounts: Array.isArray(accounts)
+ ? accounts.map((account) => account.address)
+ : [...state.accounts, accounts.address]
+ }));
+ },
+
+ setErc20TokenBalances: (tokenBalance) => set({ tokenBalance }),
+
+ setErc20TokenBalanceSelected: (tokenBalance) => set({ tokenBalance }),
+
+ setTransactions: (transactions) => set({ transactions }),
+
+ setTransactionDeploy: (transactionDeploy) => set({ transactionDeploy }),
+
+ clearAccounts: () => set({ accounts: [] }),
+
+ resetWallet: () =>
+ set((state) => ({
+ ...initialState,
+ provider: state.provider,
+ forceReconnect: true
+ })),
+}));
+
+export default useWalletStore;
diff --git a/packages/wallet-v2/store/store.ts b/packages/wallet-v2/store/store.ts
new file mode 100644
index 00000000..390215e0
--- /dev/null
+++ b/packages/wallet-v2/store/store.ts
@@ -0,0 +1,89 @@
+import { Transaction } from '@/types'
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+
+// Wallet State
+interface WalletState {
+ forceReconnect: boolean
+ setForceReconnect: (value: boolean) => void
+}
+
+export const useWalletStore = create((set) => ({
+ balance: 90,
+ transactions: [],
+ network: 'turing',
+ setBalance: (balance: any) => set({ balance }),
+ addTransaction: (tx: any) =>
+ set((state: { transactions: any }) => ({
+ transactions: [...state.transactions, tx],
+ })),
+ setNetwork: (network: any) => set({ network }),
+}))
+
+// Network State
+interface NetworkState {
+ activeNetwork: string
+ setActiveNetwork: (network: string) => void
+}
+
+export const useNetworkStore = create()(
+ persist(
+ (set) => ({
+ activeNetwork: 'mainnet',
+ setActiveNetwork: (network) => set({ activeNetwork: network }),
+ }),
+ { name: 'network-storage' } // Specify the storage key for network state
+ )
+)
+
+// Modal State
+interface ModalState {
+ isModalOpen: boolean
+ toggleModal: () => void
+}
+
+export const useModalStore = create((set) => ({
+ isModalOpen: false,
+ toggleModal: () => set((state) => ({ isModalOpen: !state.isModalOpen })),
+}))
+
+// UI State (if you need it)
+interface UIState {
+ theme: 'light' | 'dark'
+ setTheme: (theme: 'light' | 'dark') => void
+}
+
+export const useUIStore = create((set) => ({
+ theme: 'light',
+ setTheme: (theme) => set({ theme }),
+}))
+
+// Metamask State (if necessary)
+interface MetamaskState {
+ isConnected: boolean
+ setIsConnected: (value: boolean) => void
+}
+
+export const useMetamaskStore = create((set) => ({
+ isInstalled: false,
+ snap: null,
+ address: null,
+ publicKey: null,
+ balance: null,
+ transactions: [],
+ api: null,
+ setData: (data: any) => set((state: any) => ({ ...state, ...data })),
+}))
+
+// Transaction State
+interface TransactionStore {
+ transactions: Transaction[]
+ setTransactions: (transactions: Transaction[]) => void
+ loading?: boolean
+}
+
+export const useTransactionStore = create((set) => ({
+ transactions: [],
+ setTransactions: (transactions) => set({ transactions }),
+ loading: false,
+}))
diff --git a/packages/wallet-v2/tailwind.config.ts b/packages/wallet-v2/tailwind.config.ts
new file mode 100644
index 00000000..71012958
--- /dev/null
+++ b/packages/wallet-v2/tailwind.config.ts
@@ -0,0 +1,67 @@
+import type { Config } from 'tailwindcss'
+
+const config: Config = {
+ darkMode: ['class'],
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ './@/**/*.{ts,tsx}',
+ './shared/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ main: '#1C1E26',
+ sec: '#232735',
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))',
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))',
+ },
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)',
+ },
+ },
+ },
+ plugins: [require('tailwindcss-animate')],
+}
+export default config
diff --git a/packages/wallet-v2/tsconfig.json b/packages/wallet-v2/tsconfig.json
new file mode 100644
index 00000000..83444d57
--- /dev/null
+++ b/packages/wallet-v2/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"],
+ "@/lib/*": ["./lib/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/wallet-v2/types/index.d.ts b/packages/wallet-v2/types/index.d.ts
new file mode 100644
index 00000000..3b831e7e
--- /dev/null
+++ b/packages/wallet-v2/types/index.d.ts
@@ -0,0 +1,64 @@
+import type { SnapConfig } from '@avail-project/metamask-avail-types'
+
+declare module '@avail-project/metamask-avail-adapter' {
+ export function injectMetamaskAvailSnapProvider(
+ network: 'turing',
+ config?: SnapConfig,
+ pluginOrigin?: string
+ ): void
+}
+
+export type Network = Pick<
+ Types.Network,
+ 'name' | 'chainId' | 'baseUrl' | 'nodeUrl' | 'displayName'
+>
+export type Account = Pick
+
+export interface Erc20TokenBalance {
+ amount: BigNumber
+ symbol: string
+ decimals: number
+}
+
+export interface Erc20Token {
+ address: string // in hex
+ name: string
+ symbol: string
+ decimals: number
+ chainId: string // in hex
+}
+
+export type TransactionStatusOptions =
+ | 'Received'
+ | 'Pending'
+ | 'Accepted on L2'
+ | 'Accepted on L1'
+ | 'Rejected'
+ | 'Not Received'
+
+export enum ExplorerTransactionType { // for retrieving txns from Explorer
+ DEPLOY = 'deploy',
+ DEPLOY_ACCOUNT = 'deploy_account',
+ INVOKE = 'invoke',
+}
+
+export enum TransactionStatus { // for retrieving txn from Avail feeder gateway
+ RECEIVED = 'RECEIVED',
+ PENDING = 'PENDING',
+ ACCEPTED_ON_L2 = 'ACCEPTED_ON_L2',
+ ACCEPTED_ON_L1 = 'ACCEPTED_ON_L1',
+ NOT_RECEIVED = 'NOT_RECEIVED',
+ REJECTED = 'REJECTED',
+}
+
+export type { Transaction } from '@avail-project/metamask-avail-types'
+
+export type TransactionType = {
+ id: string
+ date: string
+ type: string
+ amount?: string
+ from?: string
+ to?: string
+ txHash?: string
+}
diff --git a/packages/wallet-v2/utils/constants.ts b/packages/wallet-v2/utils/constants.ts
new file mode 100644
index 00000000..5cb8a3d1
--- /dev/null
+++ b/packages/wallet-v2/utils/constants.ts
@@ -0,0 +1,18 @@
+export const DECIMALS_DISPLAYED_MAX_LENGTH = 11
+
+export const GOLDBERG_TESTNET_EXPLORER = 'https://avail-goldberg.subscan.io/'
+export const MAINNET_EXPLORER = 'https://avail.subscan.io/'
+export const TURING_TESTNET_EXPLORER = 'https://avail-turing.subscan.io/'
+export const SNAPS_DOC_URL = 'https://docs.metamask.io/guide/snaps.html'
+
+export const GET_FAUCENT_URL = 'https://faucet.avail.tools/'
+
+export const INPUT_MAX_LENGTH = 100
+
+export const POPOVER_DURATION = 3000
+
+export const TRANSACTIONS_REFRESH_FREQUENCY = 60000
+
+export const TOKEN_BALANCE_REFRESH_FREQUENCY = 60000
+
+export const TIMEOUT_DURATION = 10000
diff --git a/packages/wallet-v2/utils/utils.ts b/packages/wallet-v2/utils/utils.ts
new file mode 100644
index 00000000..5f5f50e9
--- /dev/null
+++ b/packages/wallet-v2/utils/utils.ts
@@ -0,0 +1,185 @@
+import { KeyboardEvent } from 'react'
+import { ethers } from 'ethers'
+import {
+ ApiPromise,
+ initialize,
+ isValidAddress as isValidAvailAddress,
+} from 'avail-js-sdk'
+// import { Erc20Token, Erc20TokenBalance, Network } from÷ '@/types';
+import {
+ DECIMALS_DISPLAYED_MAX_LENGTH,
+ TURING_TESTNET_EXPLORER,
+ TIMEOUT_DURATION,
+ GOLDBERG_TESTNET_EXPLORER,
+ MAINNET_EXPLORER,
+} from './constants'
+import { Erc20Token, Erc20TokenBalance } from '@/types'
+
+export const shortenAddress = (address: string, num = 3) => {
+ if (!address) return ''
+ return (
+ !!address &&
+ `${address.substring(0, num + 2)}...${address.substring(address.length - num - 1)}`
+ )
+}
+
+export async function initializeApi(endpoint: string) {
+ try {
+ const api = await initialize(endpoint)
+ console.log('Api connected successfully')
+ return api
+ } catch (error) {
+ console.error('Error initializing Api :', error)
+ setTimeout(initializeApi, 5000)
+ }
+}
+
+export const getRpcEndpoint = (chainId: number) => {
+ switch (chainId) {
+ case 0: // turing
+ return 'wss://turing-rpc.avail.so/ws'
+ case 1: // goldberg
+ return 'wss://goldberg-rpc.slowops.xyz/ws'
+ case 2: // mainnet
+ return 'wss://mainnet-rpc.avail.so/ws'
+ default:
+ throw new Error('Unsupported chain ID')
+ }
+}
+
+export const openExplorerTab = (
+ address: string,
+ type: string,
+ chainId: number
+) => {
+ let explorerUrl = TURING_TESTNET_EXPLORER
+ switch (chainId) {
+ case 0:
+ explorerUrl = TURING_TESTNET_EXPLORER
+ break
+ case 1:
+ explorerUrl = GOLDBERG_TESTNET_EXPLORER
+ break
+ case 2:
+ explorerUrl = MAINNET_EXPLORER
+ break
+ }
+ window.open(explorerUrl + type + '/' + address, '_blank')?.focus()
+}
+
+export const isValidAddress = (toCheck: string) => {
+ if (isValidAvailAddress(toCheck)) {
+ return true
+ } else {
+ return false
+ }
+}
+
+export const addMissingPropertiesToToken = (
+ token: Erc20Token,
+ balance?: string,
+ usdPrice?: number
+): Erc20TokenBalance => {
+ return {
+ ...token,
+ amount: ethers.BigNumber.from(balance ? balance : '0x0'),
+ }
+}
+
+export const getHumanReadableAmount = (asset: Erc20TokenBalance) => {
+ // const amountStr = assetAmount
+ // ? assetAmount
+ // : ethers.utils.formatUnits(asset.amount, asset.decimals);
+ if (asset.amount > 0) {
+ const amountStr = ethers.utils.formatUnits(asset.amount, asset.decimals)
+ const indexDecimal = amountStr.indexOf('.')
+ const integerPart = amountStr.substring(0, indexDecimal)
+ let decimalPart = amountStr.substring(
+ indexDecimal + 1,
+ indexDecimal + 5 - integerPart.length
+ )
+ if (integerPart === '0') {
+ decimalPart = amountStr.substring(indexDecimal + 1)
+ }
+ const decimalPartArray = decimalPart.split('')
+ const firstNonZeroIndex = decimalPartArray.findIndex((char) => char !== '0')
+ if (firstNonZeroIndex === -1) {
+ return integerPart
+ }
+
+ return amountStr.substring(0, indexDecimal + firstNonZeroIndex + 3)
+ } else {
+ return '0'
+ }
+}
+
+export const getMaxDecimalsReadable = (
+ asset: Erc20TokenBalance,
+ assetAmount?: string
+) => {
+ const amountStr = assetAmount
+ ? assetAmount
+ : ethers.utils.formatUnits(asset.amount, asset.decimals)
+ const indexDecimal = amountStr.indexOf('.')
+ const decimalPart = amountStr.substring(indexDecimal + 1).split('')
+ const firstNonZeroIndexReverse = decimalPart
+ .reverse()
+ .findIndex((char) => char !== '0')
+ if (firstNonZeroIndexReverse !== -1) {
+ let lastNonZeroIndex = amountStr.length - firstNonZeroIndexReverse
+ if (lastNonZeroIndex - indexDecimal > DECIMALS_DISPLAYED_MAX_LENGTH) {
+ lastNonZeroIndex = indexDecimal + 1 + DECIMALS_DISPLAYED_MAX_LENGTH
+ }
+ return amountStr.substring(0, lastNonZeroIndex)
+ }
+ return amountStr.substring(0, indexDecimal)
+}
+
+export const getAmountPrice = (
+ asset: Erc20TokenBalance,
+ assetAmount: number,
+ usdMode: boolean
+) => {
+ if (asset.usdPrice) {
+ if (!usdMode) {
+ const result = asset.usdPrice * assetAmount
+ return result.toFixed(2).toString()
+ } else {
+ const result = assetAmount / asset.usdPrice
+ return result.toFixed(getMaxDecimals(asset)).toString()
+ }
+ }
+ return ''
+}
+
+export const getMaxDecimals = (asset: Erc20TokenBalance) => {
+ const MAX_DECIMALS = 6
+ if (asset.decimals > MAX_DECIMALS) {
+ return MAX_DECIMALS
+ }
+ return asset.decimals
+}
+
+export const isSpecialInputKey = (event: KeyboardEvent) => {
+ return (
+ event.key === 'Backspace' ||
+ event.ctrlKey ||
+ event.key === 'ArrowRight' ||
+ event.key === 'ArrowLeft' ||
+ event.metaKey
+ )
+}
+
+export const fetchWithTimeout = async (
+ resource: string,
+ options = { timeout: TIMEOUT_DURATION }
+) => {
+ const controller = new AbortController()
+ const id = setTimeout(() => controller.abort(), options.timeout)
+ const response = await fetch(resource, {
+ ...options,
+ signal: controller.signal,
+ })
+ clearTimeout(id)
+ return response
+}