Add cursor and privy provider
This commit is contained in:
82
.cursor/rules/backend.mdc
Normal file
82
.cursor/rules/backend.mdc
Normal file
@@ -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.
|
||||
72
.cursor/rules/frontend.mdc
Normal file
72
.cursor/rules/frontend.mdc
Normal file
@@ -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.
|
||||
|
||||
141
.cursorban
Normal file
141
.cursorban
Normal file
@@ -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
|
||||
53
README.md
53
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 <button onClick={login}>Login</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Welcome, {user.email}</p>
|
||||
<button onClick={logout}>Logout</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
For more information, see the [Privy documentation](https://docs.privy.io/).
|
||||
|
||||
|
||||
@@ -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<UserService> _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<UserService> logger)
|
||||
{
|
||||
_evmManager = evmManager;
|
||||
_userRepository = userRepository;
|
||||
_accountService = accountService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<User> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
95
src/Managing.WebApp/src/components/WalletInfo.tsx
Normal file
95
src/Managing.WebApp/src/components/WalletInfo.tsx
Normal file
@@ -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 <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
return (
|
||||
<div className="p-4 border rounded-lg shadow-sm">
|
||||
<h2 className="text-xl font-bold mb-4">Wallet Connection</h2>
|
||||
<p className="mb-4">Please connect your wallet to continue.</p>
|
||||
<button
|
||||
onClick={login}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
>
|
||||
Connect Wallet
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 border rounded-lg shadow-sm">
|
||||
<h2 className="text-xl font-bold mb-4">Wallet Information</h2>
|
||||
|
||||
<div className="mb-4">
|
||||
<p><strong>Connected:</strong> {isConnected ? 'Yes' : 'No'}</p>
|
||||
<p><strong>Address:</strong> {address ? address : 'Not connected'}</p>
|
||||
<p><strong>Chain:</strong> {chainId === mainnet.id ? 'Ethereum' : chainId === arbitrum.id ? 'Arbitrum' : chainId}</p>
|
||||
<p><strong>Balance:</strong> {balance ? `${balance.formatted} ${balance.symbol}` : 'Unknown'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2 mb-4">
|
||||
<button
|
||||
onClick={switchToEthereum}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
disabled={chainId === mainnet.id}
|
||||
>
|
||||
Switch to Ethereum
|
||||
</button>
|
||||
<button
|
||||
onClick={switchToArbitrum}
|
||||
className="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
|
||||
disabled={chainId === arbitrum.id}
|
||||
>
|
||||
Switch to Arbitrum
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={logout}
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WalletInfo
|
||||
@@ -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<ILoginFormInput>()
|
||||
const { disconnect } = useDisconnect()
|
||||
const { address } = useAccount()
|
||||
const { signMessageAsync } = useSignMessage({})
|
||||
const { user, logout, ready, authenticated } = usePrivy()
|
||||
const { signMessage } = useSignMessage()
|
||||
const { setCookie } = useCookie()
|
||||
|
||||
const onSubmit: SubmitHandler<ILoginFormInput> = async (form) => {
|
||||
const message = 'wagmi'
|
||||
const signature = await signMessageAsync({ message })
|
||||
const t = new Toast('Creating token')
|
||||
if (!authenticated || !user || !user.wallet?.address) {
|
||||
const t = new Toast('Error: Not authenticated')
|
||||
t.update('error', 'Please connect your wallet first')
|
||||
return
|
||||
}
|
||||
|
||||
if (signature && address) {
|
||||
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: address.toString(),
|
||||
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) => {
|
||||
t.update('error', 'Error : Some thing bad happen')
|
||||
console.error('Login error:', err)
|
||||
t.update('error', 'Error: Something went wrong')
|
||||
})
|
||||
}else{
|
||||
t.update('error', 'Error : No signature')
|
||||
} 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 <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
return <div>Please connect your wallet first</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -87,7 +115,8 @@ const LogIn = () => {
|
||||
Sign and login
|
||||
</button>
|
||||
<button
|
||||
onClick={() => disconnect}
|
||||
onClick={() => logout()}
|
||||
type="button"
|
||||
className="btn bg-primary w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
|
||||
>
|
||||
Disconnect wallet{' '}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useIsFetching } from '@tanstack/react-query'
|
||||
import { ConnectKitButton } from 'connectkit'
|
||||
import { usePrivy } from '@privy-io/react-auth'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
@@ -44,6 +44,38 @@ const GlobalLoader = () => {
|
||||
return isFetching ? <Loader size="xs"></Loader> : null
|
||||
}
|
||||
|
||||
// Custom Privy wallet button component
|
||||
const PrivyWalletButton = () => {
|
||||
const { login, logout, authenticated, user } = usePrivy()
|
||||
|
||||
if (!authenticated) {
|
||||
return (
|
||||
<button
|
||||
onClick={login}
|
||||
className="btn btn-primary btn-sm"
|
||||
>
|
||||
Connect Wallet
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="dropdown dropdown-end">
|
||||
<label tabIndex={0} className="btn btn-primary btn-sm">
|
||||
{displayAddress}
|
||||
</label>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a onClick={logout}>Disconnect</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SecondaryNavbar() {
|
||||
const { toggleApiUrl, isProd } = useApiUrlStore()
|
||||
|
||||
@@ -61,7 +93,7 @@ export function SecondaryNavbar() {
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<ConnectKitButton />
|
||||
<PrivyWalletButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
19
src/Managing.WebApp/src/config/privy.ts
Normal file
19
src/Managing.WebApp/src/config/privy.ts
Normal file
@@ -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]
|
||||
65
src/Managing.WebApp/src/hooks/usePrivyWallet.ts
Normal file
65
src/Managing.WebApp/src/hooks/usePrivyWallet.ts
Normal file
@@ -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
|
||||
@@ -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: <img src="/src/assets/logo.svg" alt="logo" />,
|
||||
},
|
||||
loginMethods: ['wallet', 'email', 'google'] as Array<'wallet' | 'email' | 'google'>,
|
||||
embeddedWallets: {
|
||||
ethereum: {
|
||||
createOnLogin: 'all-users' as const
|
||||
},
|
||||
noPromptOnSignature: false
|
||||
},
|
||||
}
|
||||
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WagmiConfig config={config}>
|
||||
<ConnectKitProvider theme="auto">
|
||||
<PrivyProvider
|
||||
appId={import.meta.env.VITE_PRIVY_APP_ID}
|
||||
config={privyConfig}
|
||||
>
|
||||
<WagmiProvider config={privyWagmiConfig}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<ToastContainer
|
||||
@@ -44,7 +56,7 @@ root.render(
|
||||
pauseOnHover
|
||||
/>
|
||||
</BrowserRouter>
|
||||
</ConnectKitProvider>
|
||||
</WagmiConfig>
|
||||
</WagmiProvider>
|
||||
</PrivyProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
@@ -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(() => {
|
||||
if (ready) {
|
||||
const timeout = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 2000); // Adjust the timeout duration as needed
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
}
|
||||
}, [ready]);
|
||||
|
||||
if (isLoading) {
|
||||
if (!ready || isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!isConnected) {
|
||||
if (!authenticated) {
|
||||
deleteCookie('token')
|
||||
return (
|
||||
<div style={{ ...styles }}>
|
||||
<ConnectKitButton />
|
||||
<button
|
||||
onClick={login}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#3B82F6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px'
|
||||
}}
|
||||
>
|
||||
Login with Privy
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
} else if (!token) {
|
||||
@@ -41,7 +54,6 @@ export const Auth = ({ children }: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const styles = {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
|
||||
Reference in New Issue
Block a user