From ead68573a5a669dd7f24fa7f24d929b96bb021d1 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Wed, 5 Mar 2025 22:40:24 +0700 Subject: [PATCH] Add cursor and privy provider --- .cursor/rules/backend.mdc | 82 ++++++++++ .cursor/rules/frontend.mdc | 72 +++++++++ .cursorban | 141 ++++++++++++++++++ README.md | 53 +++++++ src/Managing.Application/Users/UserService.cs | 19 ++- src/Managing.WebApp/.env | 1 + src/Managing.WebApp/package.json | 16 +- .../src/components/WalletInfo.tsx | 95 ++++++++++++ .../src/components/mollecules/LogIn/LogIn.tsx | 89 +++++++---- .../components/mollecules/NavBar/NavBar.tsx | 36 ++++- src/Managing.WebApp/src/config/privy.ts | 19 +++ .../src/hooks/usePrivyWallet.ts | 65 ++++++++ src/Managing.WebApp/src/main.tsx | 40 +++-- .../src/pages/authPage/auth.tsx | 42 ++++-- 14 files changed, 698 insertions(+), 72 deletions(-) create mode 100644 .cursor/rules/backend.mdc create mode 100644 .cursor/rules/frontend.mdc create mode 100644 .cursorban create mode 100644 src/Managing.WebApp/src/components/WalletInfo.tsx create mode 100644 src/Managing.WebApp/src/config/privy.ts create mode 100644 src/Managing.WebApp/src/hooks/usePrivyWallet.ts diff --git a/.cursor/rules/backend.mdc b/.cursor/rules/backend.mdc new file mode 100644 index 0000000..f3609a8 --- /dev/null +++ b/.cursor/rules/backend.mdc @@ -0,0 +1,82 @@ +--- +description: Guideline for .NET C# backend +globs: +alwaysApply: false +--- + +# .NET Development Rules for Quantitative Finance + +You are a senior .NET backend developer and experimental quant with deep expertise in financial mathematics, algorithmic trading, and market indicators. + +## Quantitative Finance Core Principles +- Prioritize numerical precision (use `decimal` for monetary calculations) +- Implement proven financial mathematics (e.g., Black-Scholes, Monte Carlo methods) +- Optimize time-series processing for tick data/OHLCV series +- Validate models with historical backtesting frameworks +- Maintain audit trails for financial calculations + + ## Code Style and Structure + - Write concise, idiomatic C# code with accurate examples. + - Follow .NET and ASP.NET Core conventions and best practices. + - Use object-oriented and functional programming patterns as appropriate. + - Prefer LINQ and lambda expressions for collection operations. + - Use descriptive variable and method names (e.g., 'IsUserSignedIn', 'CalculateTotal'). + - Structure files according to .NET conventions (Controllers, Models, Services, etc.). + + ## Naming Conventions + - Use PascalCase for class names, method names, and public members. + - Use camelCase for local variables and private fields. + - Use UPPERCASE for constants. + - Prefix interface names with "I" (e.g., 'IUserService'). + + ## C# and .NET Usage + - Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment). + - Leverage built-in ASP.NET Core features and middleware. + - Use Entity Framework Core effectively for database operations. + + ## Syntax and Formatting + - Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) + - Use C#'s expressive syntax (e.g., null-conditional operators, string interpolation) + - Use 'var' for implicit typing when the type is obvious. + + ## Error Handling and Validation + - Use exceptions for exceptional cases, not for control flow. + - Implement proper error logging using built-in .NET logging or a third-party logger. + - Use Data Annotations or Fluent Validation for model validation. + - Implement global exception handling middleware. + - Return appropriate HTTP status codes and consistent error responses. + + ## API Design + - Follow RESTful API design principles. + - Use attribute routing in controllers. + - Implement versioning for your API. + - Use action filters for cross-cutting concerns. + + ## Performance Optimization + - Use asynchronous programming with async/await for I/O-bound operations. + - Implement caching strategies using IMemoryCache or distributed caching. + - Use efficient LINQ queries and avoid N+1 query problems. + - Implement pagination for large data sets. + + ## Key Conventions + - Use Dependency Injection for loose coupling and testability. + - Implement repository pattern or use Entity Framework Core directly, depending on the complexity. + - Use AutoMapper for object-to-object mapping if needed. + - Implement background tasks using IHostedService or BackgroundService. + + ## Testing + - Write unit tests using xUnit, NUnit, or MSTest. + - Use Moq or NSubstitute for mocking dependencies. + - Implement integration tests for API endpoints. + + ## Security + - Use Authentication and Authorization middleware. + - Implement JWT authentication for stateless API authentication. + - Use HTTPS and enforce SSL. + - Implement proper CORS policies. + + ## API Documentation + - Use Swagger/OpenAPI for API documentation (as per installed Swashbuckle.AspNetCore package). + - Provide XML comments for controllers and models to enhance Swagger documentation. + + Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components. diff --git a/.cursor/rules/frontend.mdc b/.cursor/rules/frontend.mdc new file mode 100644 index 0000000..d72fc2b --- /dev/null +++ b/.cursor/rules/frontend.mdc @@ -0,0 +1,72 @@ +--- +description: Guideline for React Vite app frontend +globs: +alwaysApply: false +--- + + You are an expert in Solidity, TypeScript, Node.js, Next.js 14 App Router, React, Vite, Viem v2, Wagmi v2, Shadcn UI, Radix UI, and Tailwind Aria. + + Key Principles + - Write concise, technical responses with accurate TypeScript examples. + - Use functional, declarative programming. Avoid classes. + - Prefer iteration and modularization over duplication. + - Use descriptive variable names with auxiliary verbs (e.g., isLoading). + - Use lowercase with dashes for directories (e.g., components/auth-wizard). + - Favor named exports for components. + - Use the Receive an Object, Return an Object (RORO) pattern. + + JavaScript/TypeScript + - Use "function" keyword for pure functions. Omit semicolons. + - Use TypeScript for all code. Prefer interfaces over types. Avoid enums, use maps. + - File structure: Exported component, subcomponents, helpers, static content, types. + - Avoid unnecessary curly braces in conditional statements. + - For single-line statements in conditionals, omit curly braces. + - Use concise, one-line syntax for simple conditional statements (e.g., if (condition) doSomething()). + + Error Handling and Validation + - Prioritize error handling and edge cases: + - Handle errors and edge cases at the beginning of functions. + - Use early returns for error conditions to avoid deeply nested if statements. + - Place the happy path last in the function for improved readability. + - Avoid unnecessary else statements; use if-return pattern instead. + - Use guard clauses to handle preconditions and invalid states early. + - Implement proper error logging and user-friendly error messages. + - Consider using custom error types or error factories for consistent error handling. + + React/Next.js + - Use functional components and TypeScript interfaces. + - Use declarative JSX. + - Use function, not const, for components. + - Use Shadcn UI, Radix, and Tailwind Aria for components and styling. + - Implement responsive design with Tailwind CSS. + - Use mobile-first approach for responsive design. + - Place static content and interfaces at file end. + - Use content variables for static content outside render functions. + - Minimize 'use client', 'useEffect', and 'setState'. Favor RSC. + - Use Zod for form validation. + - Wrap client components in Suspense with fallback. + - Use dynamic loading for non-critical components. + - Optimize images: WebP format, size data, lazy loading. + - Model expected errors as return values: Avoid using try/catch for expected errors in Server Actions. Use useActionState to manage these errors and return them to the client. + - Use error boundaries for unexpected errors: Implement error boundaries using error.tsx and global-error.tsx files to handle unexpected errors and provide a fallback UI. + - Use useActionState with react-hook-form for form validation. + - Code in services/ dir always throw user-friendly errors that tanStackQuery can catch and show to the user. + - Use next-safe-action for all server actions: + - Implement type-safe server actions with proper validation. + - Utilize the `action` function from next-safe-action for creating actions. + - Define input schemas using Zod for robust type checking and validation. + - Handle errors gracefully and return appropriate responses. + - Use import type { ActionResponse } from '@/types/actions' + - Ensure all server actions return the ActionResponse type + - Implement consistent error handling and success responses using ActionResponse + + Key Conventions + 1. Rely on Next.js App Router for state changes. + 2. Prioritize Web Vitals (LCP, CLS, FID). + 3. Minimize 'use client' usage: + - Prefer server components and Next.js SSR features. + - Use 'use client' only for Web API access in small components. + - Avoid using 'use client' for data fetching or state management. + + Refer to Next.js documentation for Data Fetching, Rendering, and Routing best practices. + \ No newline at end of file diff --git a/.cursorban b/.cursorban new file mode 100644 index 0000000..6add559 --- /dev/null +++ b/.cursorban @@ -0,0 +1,141 @@ +# Prevent sending sensitive configuration files to AI services + +####################### +# GENERAL RULES +####################### + +# App settings files +**/appsettings*.json +**/appSettings*.json +**/app.settings*.json +**/app.config + +# Environment files +**/.env* +**/*.env + +# Key files and certificates +**/*.pfx +**/*.key +**/*.pem +**/*.p12 +**/*.cer +**/*.crt + +# Credentials and tokens +**/credentials.json +**/token.json +**/secrets.json +**/auth.json + +# Config files that might contain secrets +**/connection.json +**/database.json +**/firebase.json +**/aws.json +**/azure.json +**/google-services.json +**/googleservices.json + +# Local development overrides +**/local.settings.json +**/launchSettings.json + +# User-specific files +**/*.user +**/*.pubxml + +# Other potentially sensitive files +**/privatekey.json +**/private_key.json +**/service-account.json +**/firebaserc +**/firebase-adminsdk.json +**/firebase-service-account.json + +# Log files (may contain sensitive info) +**/logs/ +**/*.log + +####################### +# BACKEND RULES (.NET) +####################### + +# Database migrations and sensitive data models +**/Migrations/ +**/bin/ +**/obj/ + +# .NET specific configuration +**/Properties/PublishProfiles/ +**/Properties/ServiceDependencies/ +**/*.Development.json +**/*.Production.json +**/*.Staging.json + +# Sensitive backend services +**/Services/Auth/ +**/Services/Payment/ +**/Infrastructure/Security/ +**/Infrastructure/Database/Configurations/ + +# API keys and connection strings +**/Managing.Infrastructure.Database/MongoDb/Configurations/ +**/Managing.Infrastructure.Messengers/Discord/ + +####################### +# FRONTEND RULES +####################### + +# Build artifacts +**/node_modules/ +**/dist/ +**/build/ + +# Frontend configuration with sensitive data +**/src/config/api.ts +**/src/config/auth.ts +**/src/config/keys.ts +**/src/config/endpoints.ts + +# Authentication related components +**/src/services/auth/ +**/src/hooks/useAuth.ts +**/src/stores/authStore.ts + +# Web3 wallet configurations +**/src/config/wallet.ts +**/src/config/web3.ts +**/src/config/chains.ts + +# Large generated files +**/src/generated/ +**/*.generated.ts +**/*.graphql.ts + +# Test data with potentially sensitive information +**/src/mocks/ +**/src/__tests__/fixtures/ +**/cypress/fixtures/ + +####################### +# PROJECT SPECIFIC +####################### + +# Discord bot configuration +**/Managing.Infrastructure.Messengers/Discord/DiscordBotConfig.cs + +# Web3 wallet integration +**/Managing.Infrastructure.Web3/Wallets/ + +# Trading strategies with proprietary algorithms +**/Managing.Application/Trading/Strategies/ + +# User data and analytics +**/Managing.Application/Users/ +**/Managing.Application/Analytics/ + +# Database schemas with sensitive fields +**/Managing.Domain/Entities/User.cs +**/Managing.Domain/Entities/Wallet.cs +**/Managing.Domain/Entities/ApiKey.cs \ No newline at end of file diff --git a/README.md b/README.md index 5b2df82..addeb97 100644 --- a/README.md +++ b/README.md @@ -148,3 +148,56 @@ Bot types availables : | ScalpingBot | This bot will open position and wait before opening a new one | | FlippingBot | The flipping bot flipping the position only when a strategy trigger an opposite signal | +## Privy Integration + +This project uses [Privy](https://privy.io/) for wallet authentication and management. Privy provides a seamless authentication experience with support for both embedded wallets and external wallet connections. + +### Supported Networks + +- Ethereum Mainnet +- Arbitrum One + +### Configuration + +To use Privy in this project, you need to: + +1. Create a Privy account at [https://console.privy.io/](https://console.privy.io/) +2. Create a new app in the Privy dashboard +3. Copy your Privy App ID to the `.env` file: + +``` +VITE_PRIVY_APP_ID=your-privy-app-id +``` + +### Features + +- **Multi-chain support**: Connect to Ethereum and Arbitrum networks +- **Embedded wallets**: Create and manage wallets directly in the browser +- **Social login**: Authenticate with email, Google, and other providers +- **Seamless integration**: Works with wagmi for blockchain interactions + +### Usage + +The Privy provider is configured in `src/main.tsx` and can be used throughout the application with the `usePrivy` hook: + +```tsx +import { usePrivy } from '@privy-io/react-auth'; + +function MyComponent() { + const { user, login, logout, authenticated } = usePrivy(); + + if (!authenticated) { + return ; + } + + return ( +
+

Welcome, {user.email}

+ +
+ ); +} +``` + +For more information, see the [Privy documentation](https://docs.privy.io/). + diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 7c04f5c..11f7a9f 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -2,6 +2,7 @@ using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.Users; +using Microsoft.Extensions.Logging; namespace Managing.Application.Users; @@ -10,24 +11,28 @@ public class UserService : IUserService private readonly IEvmManager _evmManager; private readonly IUserRepository _userRepository; private readonly IAccountService _accountService; + private readonly ILogger _logger; - private string[] authorizedAddresses = [ + private string[] authorizedAddresses = + [ "0x6781920674dA695aa5120d95D80c4B1788046806", // Macbook "0xA2B43AFF0992a47838DF2e6099A8439981f0B717", // Phone "0xAD4bcf258852e9d47E580798d312E1a52D59E721", // Razil "0xAd6D6c80910096b40e45690506a9f1052e072dCB", // Teru "0x309b9235edbe1C6f840816771c6C21aDa6c275EE", // Cowchain - "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3" // Local optiflex + "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex + "0x932167388dD9aad41149b3cA23eBD489E2E2DD78" // Embedded wallet ]; public UserService( IEvmManager evmManager, IUserRepository userRepository, - IAccountService accountService) + IAccountService accountService, ILogger logger) { _evmManager = evmManager; _userRepository = userRepository; _accountService = accountService; + _logger = logger; } public async Task Authenticate(string name, string address, string message, string signature) @@ -35,10 +40,16 @@ public class UserService : IUserService var recoveredAddress = _evmManager.VerifySignature(signature, message); if (!authorizedAddresses.Contains(recoveredAddress)) + { + _logger.LogWarning($"Address {recoveredAddress} not authorized"); throw new Exception("Address not authorized"); + } if (recoveredAddress == null || !recoveredAddress.Equals(address)) + { + _logger.LogWarning($"Address {recoveredAddress} not corresponding"); throw new Exception("Address not corresponding"); + } // Check if account exist var account = await _accountService.GetAccountByKey(recoveredAddress, true, false); @@ -97,4 +108,4 @@ public class UserService : IUserService user.Accounts = _accountService.GetAccountsByUser(user).ToList(); return user; } -} +} \ No newline at end of file diff --git a/src/Managing.WebApp/.env b/src/Managing.WebApp/.env index f8d9d0e..6128ea3 100644 --- a/src/Managing.WebApp/.env +++ b/src/Managing.WebApp/.env @@ -4,3 +4,4 @@ VITE_WORKER_URL_LOCAL=https://localhost:5002 VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9 WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5 +VITE_PRIVY_APP_ID=cm7u09v0u002zrkuf2yjjr58p diff --git a/src/Managing.WebApp/package.json b/src/Managing.WebApp/package.json index a984ecc..359333e 100644 --- a/src/Managing.WebApp/package.json +++ b/src/Managing.WebApp/package.json @@ -18,11 +18,15 @@ "dependencies": { "@heroicons/react": "^1.0.6", "@microsoft/signalr": "^6.0.5", - "@tanstack/react-query": "^5.36.1", + "@privy-io/react-auth": "^2.6.1", + "@privy-io/wagmi": "^1.0.3", + "@tailwindcss/typography": "^0.5.0", + "@tanstack/react-query": "^5.67.1", "@wagmi/chains": "^0.2.9", "@wagmi/connectors": "^5.7.3", "@wagmi/core": "^2.16.3", "@walletconnect/universal-provider": "^2.8.6", + "autoprefixer": "^10.4.7", "axios": "^0.27.2", "classnames": "^2.3.1", "connectkit": "^1.8.2", @@ -32,6 +36,7 @@ "lightweight-charts": "git+https://github.com/ntf/lightweight-charts.git", "moment": "^2.29.3", "plotly.js": "^2.18.1", + "postcss": "^8.4.13", "react": "^18.1.0", "react-cookie": "^4.1.1", "react-dom": "^18.1.0", @@ -46,14 +51,11 @@ "react-table": "^7.8.0", "react-toastify": "^9.0.1", "reactflow": "^11.8.3", + "tailwindcss": "^3.0.23", "viem": "2.x", - "wagmi": "^2.14.9", + "wagmi": "^2.14.12", "web3": "^4.16.0", - "zustand": "^4.4.1", - "postcss": "^8.4.13", - "autoprefixer": "^10.4.7", - "@tailwindcss/typography": "^0.5.0", - "tailwindcss": "^3.0.23" + "zustand": "^4.4.1" }, "devDependencies": { "@types/react": "^18.0.9", diff --git a/src/Managing.WebApp/src/components/WalletInfo.tsx b/src/Managing.WebApp/src/components/WalletInfo.tsx new file mode 100644 index 0000000..84bf0c6 --- /dev/null +++ b/src/Managing.WebApp/src/components/WalletInfo.tsx @@ -0,0 +1,95 @@ +import { usePrivy } from '@privy-io/react-auth' +import { useAccount, useBalance, useChainId, useSwitchChain } from 'wagmi' +import { arbitrum, mainnet } from 'viem/chains' +import { useCallback } from 'react' + +/** + * Component to display wallet information and test the Privy integration + */ +export const WalletInfo = () => { + const { user, ready, authenticated, login, logout } = usePrivy() + const { address, isConnected } = useAccount() + const chainId = useChainId() + const { data: balance } = useBalance({ + address, + }) + const { switchChainAsync } = useSwitchChain() + + const switchToEthereum = useCallback(async () => { + if (chainId !== mainnet.id) { + try { + await switchChainAsync({ chainId: mainnet.id }) + } catch (error) { + console.error('Failed to switch to Ethereum:', error) + } + } + }, [chainId, switchChainAsync]) + + const switchToArbitrum = useCallback(async () => { + if (chainId !== arbitrum.id) { + try { + await switchChainAsync({ chainId: arbitrum.id }) + } catch (error) { + console.error('Failed to switch to Arbitrum:', error) + } + } + }, [chainId, switchChainAsync]) + + if (!ready) { + return
Loading...
+ } + + if (!authenticated) { + return ( +
+

Wallet Connection

+

Please connect your wallet to continue.

+ +
+ ) + } + + return ( +
+

Wallet Information

+ +
+

Connected: {isConnected ? 'Yes' : 'No'}

+

Address: {address ? address : 'Not connected'}

+

Chain: {chainId === mainnet.id ? 'Ethereum' : chainId === arbitrum.id ? 'Arbitrum' : chainId}

+

Balance: {balance ? `${balance.formatted} ${balance.symbol}` : 'Unknown'}

+
+ +
+ + +
+ + +
+ ) +} + +export default WalletInfo \ No newline at end of file diff --git a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx index dbe9edb..d73d948 100644 --- a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx +++ b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx @@ -1,7 +1,7 @@ import { StatusOfflineIcon } from '@heroicons/react/solid' import type { SubmitHandler } from 'react-hook-form' import { useForm } from 'react-hook-form' -import { useAccount, useDisconnect, useSignMessage } from 'wagmi' +import { usePrivy, useSignMessage } from '@privy-io/react-auth' import useApiUrlStore from '../../../app/store/apiStore' import { UserClient } from '../../../generated/ManagingApi' @@ -13,38 +13,66 @@ import Toast from '../Toast/Toast' const LogIn = () => { const { apiUrl } = useApiUrlStore() const { register, handleSubmit } = useForm() - const { disconnect } = useDisconnect() - const { address } = useAccount() - const { signMessageAsync } = useSignMessage({}) + const { user, logout, ready, authenticated } = usePrivy() + const { signMessage } = useSignMessage() const { setCookie } = useCookie() const onSubmit: SubmitHandler = async (form) => { - const message = 'wagmi' - const signature = await signMessageAsync({ message }) - const t = new Toast('Creating token') - - if (signature && address) { - const userClient = new UserClient({}, apiUrl) - - await userClient - .user_CreateToken({ - address: address.toString(), - message: message, - name: form.name, - signature: signature, - }) - .then((data) => { - setCookie('token', data, 1) - setTimeout(() => { - location.assign("/"); - }, 1000); - }) - .catch((err: any) => { - t.update('error', 'Error : Some thing bad happen') - }) - }else{ - t.update('error', 'Error : No signature') + if (!authenticated || !user || !user.wallet?.address) { + const t = new Toast('Error: Not authenticated') + t.update('error', 'Please connect your wallet first') + return } + + try { + const message = 'wagmi' + const t = new Toast('Signing message...') + + // Use Privy's signMessage function - returns { signature: string } + const { signature } = await signMessage({ message }) + + t.update('info', 'Creating token...') + + // Use the Privy embedded wallet address + const walletAddress = user.linkedAccounts[1]?.address + + if (signature && walletAddress) { + const userClient = new UserClient({}, apiUrl) + + await userClient + .user_CreateToken({ + address: walletAddress, + message: message, + name: form.name, + signature: signature, + }) + .then((data) => { + setCookie('token', data, 1) + t.update('success', 'Login successful!') + setTimeout(() => { + location.assign("/"); + }, 1000); + }) + .catch((err: any) => { + console.error('Login error:', err) + t.update('error', 'Error: Something went wrong') + }) + } else { + t.update('error', 'Error: No signature or address') + } + } catch (error) { + console.error('Signing error:', error) + const t = new Toast('Error') + t.update('error', `Error signing message: ${error instanceof Error ? error.message : 'Unknown error'}`) + } + } + + if (!ready) { + return
Loading...
+ } + + if (!authenticated) { + return
Please connect your wallet first
} return ( @@ -87,7 +115,8 @@ const LogIn = () => { Sign and login + ) + } + + // Display wallet address or user info if authenticated + const displayAddress = user?.wallet?.address + ? `${user.wallet.address.slice(0, 6)}...${user.wallet.address.slice(-4)}` + : 'Connected' + + return ( +
+ + +
+ ) +} + export function SecondaryNavbar() { const { toggleApiUrl, isProd } = useApiUrlStore() @@ -61,7 +93,7 @@ export function SecondaryNavbar() { /> - + ) } diff --git a/src/Managing.WebApp/src/config/privy.ts b/src/Managing.WebApp/src/config/privy.ts new file mode 100644 index 0000000..a0a1790 --- /dev/null +++ b/src/Managing.WebApp/src/config/privy.ts @@ -0,0 +1,19 @@ +import { arbitrum, mainnet } from 'viem/chains' +import { createConfig } from '@privy-io/wagmi' +import { http } from 'wagmi' + +// Define the Privy App ID - this should be added to your .env file +const PRIVY_APP_ID = import.meta.env.VITE_PRIVY_APP_ID || 'your-privy-app-id' + +// Create the Privy Wagmi config with Ethereum and Arbitrum chains +export const privyWagmiConfig = createConfig({ + chains: [mainnet, arbitrum], + transports: { + // You can customize RPC providers here if needed + [mainnet.id]: http(`https://ethereum.publicnode.com`), + [arbitrum.id]: http(`https://arbitrum-one.publicnode.com`), + }, +}) + +// Export the supported chains for use elsewhere in the app +export const supportedChains = [mainnet, arbitrum] \ No newline at end of file diff --git a/src/Managing.WebApp/src/hooks/usePrivyWallet.ts b/src/Managing.WebApp/src/hooks/usePrivyWallet.ts new file mode 100644 index 0000000..9a9ffe9 --- /dev/null +++ b/src/Managing.WebApp/src/hooks/usePrivyWallet.ts @@ -0,0 +1,65 @@ +import { usePrivy } from '@privy-io/react-auth' +import { useAccount, useConnect, useDisconnect } from 'wagmi' +import { useCallback, useEffect } from 'react' + +/** + * Custom hook to integrate Privy wallet with wagmi + * This hook handles connecting and disconnecting the Privy wallet + * and provides a simplified interface for wallet interactions + */ +export const usePrivyWallet = () => { + const { user, ready, authenticated, login, logout } = usePrivy() + const { isConnected } = useAccount() + const { connectAsync, connectors } = useConnect() + const { disconnectAsync } = useDisconnect() + + // Connect the wallet when the user is authenticated + useEffect(() => { + const connectWallet = async () => { + if (ready && authenticated && user && !isConnected) { + try { + // Get the embedded wallet if available + const embeddedWallet = user.wallet?.address ? user.wallet : null + + if (embeddedWallet && connectors.length > 0) { + // Connect using the Privy connector + // The Privy connector is automatically added by the Privy WagmiProvider + const privyConnector = connectors.find(c => c.name === 'Privy') || connectors[0] + + await connectAsync({ + connector: privyConnector, + chainId: 1 // Default to Ethereum mainnet + }) + } + } catch (error) { + console.error('Failed to connect wallet:', error) + } + } + } + + connectWallet() + }, [ready, authenticated, user, isConnected, connectAsync, connectors]) + + // Disconnect the wallet when the user logs out + const handleLogout = useCallback(async () => { + if (isConnected) { + try { + await disconnectAsync() + } catch (error) { + console.error('Failed to disconnect wallet:', error) + } + } + logout() + }, [isConnected, disconnectAsync, logout]) + + return { + isAuthenticated: authenticated, + isConnected, + isReady: ready, + user, + login, + logout: handleLogout, + } +} + +export default usePrivyWallet \ No newline at end of file diff --git a/src/Managing.WebApp/src/main.tsx b/src/Managing.WebApp/src/main.tsx index 1d8eea1..ec8e651 100644 --- a/src/Managing.WebApp/src/main.tsx +++ b/src/Managing.WebApp/src/main.tsx @@ -1,35 +1,47 @@ import './styles/globals.css' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { ConnectKitProvider, getDefaultConfig } from 'connectkit' +import { PrivyProvider } from '@privy-io/react-auth' +import { WagmiProvider } from '@privy-io/wagmi' import { createRoot } from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' -import { WagmiConfig, createConfig } from 'wagmi' import App from './app' +import { privyWagmiConfig, supportedChains } from './config/privy' import 'react-grid-layout/css/styles.css' import 'react-resizable/css/styles.css' import 'react-toastify/dist/ReactToastify.css' import { ToastContainer } from 'react-toastify' -const config = createConfig( - getDefaultConfig({ - alchemyId: import.meta.env.VITE_ALCHEMY_ID, - appName: 'Managing App', - walletConnectProjectId: import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID, - }) -) - const element = document.getElementById('root') as HTMLElement const root = createRoot(element) const queryClient = new QueryClient() +// Configure Privy login methods and appearance +const privyConfig = { + appearance: { + theme: 'light' as const, + accentColor: '#3B82F6' as `#${string}`, // Customize this to match your app's theme + logo: logo, + }, + loginMethods: ['wallet', 'email', 'google'] as Array<'wallet' | 'email' | 'google'>, + embeddedWallets: { + ethereum: { + createOnLogin: 'all-users' as const + }, + noPromptOnSignature: false + }, +} + root.render( - - + + - - + + ) diff --git a/src/Managing.WebApp/src/pages/authPage/auth.tsx b/src/Managing.WebApp/src/pages/authPage/auth.tsx index a824f93..de71eb0 100644 --- a/src/Managing.WebApp/src/pages/authPage/auth.tsx +++ b/src/Managing.WebApp/src/pages/authPage/auth.tsx @@ -1,37 +1,50 @@ -import { ConnectKitButton } from 'connectkit' +import { usePrivy } from '@privy-io/react-auth' import { useAccount } from 'wagmi' import LogIn from '../../components/mollecules/LogIn/LogIn' import useCookie from '../../hooks/useCookie' import { useEffect, useState } from 'react' - export const Auth = ({ children }: any) => { const { getCookie, deleteCookie } = useCookie() const { isConnected } = useAccount() + const { login, ready, authenticated, user } = usePrivy() const token = getCookie('token') - console.log('token', token) - console.log('isConnected', isConnected) - + const [isLoading, setIsLoading] = useState(true); useEffect(() => { - const timeout = setTimeout(() => { - setIsLoading(false); - }, 2000); // Adjust the timeout duration as needed + if (ready) { + const timeout = setTimeout(() => { + setIsLoading(false); + }, 1000); + + return () => clearTimeout(timeout); + } + }, [ready]); - return () => clearTimeout(timeout); - }, []); - - if (isLoading) { + if (!ready || isLoading) { return
Loading...
; } - if (!isConnected) { + if (!authenticated) { deleteCookie('token') return (
- +
) } else if (!token) { @@ -41,7 +54,6 @@ export const Auth = ({ children }: any) => { } } - const styles = { alignItems: 'center', display: 'flex',