diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index d92839e..77797e7 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -3,7 +3,6 @@ import { Link, useLocation } from 'react-router-dom'; import { useAuth, useForum, useNetwork } from '@/hooks'; import { EVerificationStatus } from '@opchan/core'; import { localDatabase } from '@opchan/core'; -import { DelegationFullStatus } from '@opchan/core'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -49,9 +48,7 @@ import { WalletWizard } from '@/components/ui/wallet-wizard'; import { WakuHealthDot } from '@/components/ui/waku-health-indicator'; const Header = () => { - const { currentUser, delegationStatus } = useAuth(); - const [delegationInfo, setDelegationInfo] = - useState(null); + const { currentUser, delegationInfo } = useAuth(); const {statusMessage} = useNetwork(); const location = useLocation() @@ -76,13 +73,6 @@ const Header = () => { const [walletWizardOpen, setWalletWizardOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - - - - React.useEffect(() => { - delegationStatus().then(setDelegationInfo).catch(console.error); - }, [delegationStatus]); - useEffect(() => { console.log({currentUser}) diff --git a/app/src/components/ui/delegation-step.tsx b/app/src/components/ui/delegation-step.tsx index ea2befc..ac237d2 100644 --- a/app/src/components/ui/delegation-step.tsx +++ b/app/src/components/ui/delegation-step.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Button } from './button'; import { useAuth } from '@opchan/react'; import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react'; -import { DelegationDuration, DelegationFullStatus } from '@opchan/core'; +import { DelegationDuration } from '@opchan/core'; interface DelegationStepProps { onComplete: () => void; @@ -17,15 +17,7 @@ export function DelegationStep({ isLoading, setIsLoading, }: DelegationStepProps) { - const { currentUser, isAuthenticating, getDelegationStatus } = useAuth(); - const [delegationInfo, setDelegationInfo] = - useState(null); - const { delegateKey, clearDelegation } = useAuth(); - - // Load delegation status - useEffect(() => { - getDelegationStatus().then(setDelegationInfo).catch(console.error); - }, [getDelegationStatus]); + const { currentUser, delegationInfo, delegate, clearDelegation } = useAuth(); const [selectedDuration, setSelectedDuration] = React.useState('7days'); @@ -42,11 +34,11 @@ export function DelegationStep({ setDelegationResult(null); try { - const success = await delegateKey(selectedDuration); + const success = await delegate(selectedDuration); if (success) { - const expiryDate = currentUser.delegationExpiry - ? new Date(currentUser.delegationExpiry).toLocaleString() + const expiryDate = delegationInfo?.expiresAt + ? delegationInfo.expiresAt.toLocaleString() : `${selectedDuration === '7days' ? '1 week' : '30 days'} from now`; setDelegationResult({ @@ -211,13 +203,7 @@ export function DelegationStep({
diff --git a/app/src/pages/ProfilePage.tsx b/app/src/pages/ProfilePage.tsx index 75a904f..b1a647d 100644 --- a/app/src/pages/ProfilePage.tsx +++ b/app/src/pages/ProfilePage.tsx @@ -40,7 +40,7 @@ export default function ProfilePage() { const { toast } = useToast(); // Get current user from auth context for the address - const { currentUser, delegation } = useAuth(); + const { currentUser, delegationInfo } = useAuth(); const address = currentUser?.address; // Debug current user ENS info @@ -461,7 +461,7 @@ export default function ProfilePage() { Security
- {delegation.hasDelegation && ( + {delegationInfo.hasDelegation && ( )} @@ -482,25 +482,25 @@ export default function ProfilePage() { Delegation - {delegation.isValid ? 'Active' : 'Inactive'} + {delegationInfo.isValid ? 'Active' : 'Inactive'} {/* Expiry Date */} - {delegation.expiresAt && ( + {delegationInfo.expiresAt && (
Valid until
- {delegation.expiresAt.toLocaleDateString()} + {delegationInfo.expiresAt.toLocaleDateString()}
)} @@ -513,12 +513,12 @@ export default function ProfilePage() { - {delegation.isValid ? 'Valid' : 'Not signed'} + {delegationInfo.isValid ? 'Valid' : 'Not signed'} @@ -530,17 +530,17 @@ export default function ProfilePage() {
- {delegation.publicKey - ? `${delegation.publicKey.slice(0, 12)}...${delegation.publicKey.slice(-8)}` + {delegationInfo.publicKey + ? `${delegationInfo.publicKey.slice(0, 12)}...${delegationInfo.publicKey.slice(-8)}` : 'Not delegated'}
- {delegation.publicKey && ( + {delegationInfo.publicKey && (
{/* Warning for expired delegation */} - {(!delegation.isValid && delegation.hasDelegation) && ( + {(!delegationInfo.isValid && delegationInfo.hasDelegation) && (
diff --git a/app/src/providers/OpchanWithAppKit.tsx b/app/src/providers/OpchanWithAppKit.tsx index fafc55a..2ffa63e 100644 --- a/app/src/providers/OpchanWithAppKit.tsx +++ b/app/src/providers/OpchanWithAppKit.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import { OpChanProvider, type WalletAdapter, type WalletAdapterAccount } from '@opchan/react'; -import { useAppKitAccount } from '@reown/appkit/react'; +import { useAppKitAccount, modal } from '@reown/appkit/react'; +import { AppKit } from '@reown/appkit'; import type { OpChanClientConfig } from '@opchan/core'; +import { walletManager } from '@opchan/core'; interface Props { config: OpChanClientConfig; children: React.ReactNode } @@ -23,16 +25,31 @@ export const OpchanWithAppKit: React.FC = ({ config, children }) => { listenersRef.current.add(cb); return () => { listenersRef.current.delete(cb); }; }, - }), [getCurrent]); + }), [getCurrent]); // Notify listeners when AppKit account changes React.useEffect(() => { const account = getCurrent(); listenersRef.current.forEach(cb => { - try { cb(account); } catch (e) { /* ignore */ } + try { cb(account); } catch { /* ignore */ } }); }, [getCurrent]); + React.useEffect(() => { + if (!modal) return; + try { + const hasBtc = btc.isConnected && !!btc.address; + const hasEth = eth.isConnected && !!eth.address; + if (hasBtc || hasEth) { + walletManager.create(modal as AppKit, btc, eth); + } else if (walletManager.hasInstance()) { + walletManager.clear(); + } + } catch (err) { + console.warn('WalletManager initialization error', err); + } + }, [btc, btc.isConnected, btc.address, eth, eth.isConnected, eth.address]); + return ( {children} diff --git a/packages/core/src/lib/wallet/index.ts b/packages/core/src/lib/wallet/index.ts index 6933327..905c034 100644 --- a/packages/core/src/lib/wallet/index.ts +++ b/packages/core/src/lib/wallet/index.ts @@ -10,7 +10,6 @@ import { config } from './config'; import { Provider } from '@reown/appkit-controllers'; import { WalletInfo, ActiveWallet } from './types'; import { Inscription } from 'ordiscan'; -import { environment } from '../utils/environment'; export class WalletManager { private static instance: WalletManager | null = null; diff --git a/packages/react/src/v1/hooks/useAuth.ts b/packages/react/src/v1/hooks/useAuth.ts index b3b3777..f9f2675 100644 --- a/packages/react/src/v1/hooks/useAuth.ts +++ b/packages/react/src/v1/hooks/useAuth.ts @@ -6,6 +6,7 @@ import { EVerificationStatus, DelegationDuration, EDisplayPreference, + walletManager, } from '@opchan/core'; import type { DelegationFullStatus } from '@opchan/core'; @@ -101,17 +102,17 @@ export function useAuth() { }, [client, currentUser]); const delegate = React.useCallback(async ( - signFunction: (message: string) => Promise, duration: DelegationDuration = '7days', ): Promise => { const user = currentUser; if (!user) return false; try { + const signer = ((message: string) => walletManager.getInstance().signMessage(message)); const ok = await client.delegation.delegate( user.address, user.walletType, duration, - signFunction, + signer, ); const status = await client.delegation.getStatus(user.address, user.walletType); @@ -132,12 +133,18 @@ export function useAuth() { return client.delegation.getStatus(user.address, user.walletType); }, [client, currentUser]); - const clearDelegation = React.useCallback(async () => { - await client.delegation.clear(); - setOpchanState(prev => ({ - ...prev, - session: { ...prev.session, delegation: null }, - })); + const clearDelegation = React.useCallback(async (): Promise => { + try { + await client.delegation.clear(); + setOpchanState(prev => ({ + ...prev, + session: { ...prev.session, delegation: null }, + })); + return true; + } catch (e) { + console.error('clearDelegation failed', e); + return false; + } }, [client]); const updateProfile = React.useCallback(async (updates: { callSign?: string; displayPreference?: EDisplayPreference }): Promise => { @@ -167,19 +174,20 @@ export function useAuth() { } }, [client, currentUser]); + const delegationInfo = React.useMemo(() => { + const base: DelegationFullStatus = + delegation ?? ({ hasDelegation: false, isValid: false } as const); + const expiresAt = base?.proof?.expiryTimestamp + ? new Date(base.proof.expiryTimestamp) + : undefined; + return { ...base, expiresAt }; + }, [delegation]); + return { currentUser, verificationStatus, isAuthenticated: currentUser !== null, - // Provide a stable, non-null delegation object for UI safety - delegation: ((): DelegationFullStatus & { expiresAt?: Date } => { - const base: DelegationFullStatus = - delegation ?? ({ hasDelegation: false, isValid: false } as const); - const expiresAt = base?.proof?.expiryTimestamp - ? new Date(base.proof.expiryTimestamp) - : undefined; - return { ...base, expiresAt }; - })(), + delegationInfo, connect, disconnect, verifyOwnership,