fix: message signing

This commit is contained in:
Danish Arora 2025-09-24 18:58:13 +05:30
parent 0ad1cce551
commit 367cb72834
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
6 changed files with 69 additions and 69 deletions

View File

@ -3,7 +3,6 @@ import { Link, useLocation } from 'react-router-dom';
import { useAuth, useForum, useNetwork } from '@/hooks'; import { useAuth, useForum, useNetwork } from '@/hooks';
import { EVerificationStatus } from '@opchan/core'; import { EVerificationStatus } from '@opchan/core';
import { localDatabase } from '@opchan/core'; import { localDatabase } from '@opchan/core';
import { DelegationFullStatus } from '@opchan/core';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; 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'; import { WakuHealthDot } from '@/components/ui/waku-health-indicator';
const Header = () => { const Header = () => {
const { currentUser, delegationStatus } = useAuth(); const { currentUser, delegationInfo } = useAuth();
const [delegationInfo, setDelegationInfo] =
useState<DelegationFullStatus | null>(null);
const {statusMessage} = useNetwork(); const {statusMessage} = useNetwork();
const location = useLocation() const location = useLocation()
@ -76,13 +73,6 @@ const Header = () => {
const [walletWizardOpen, setWalletWizardOpen] = useState(false); const [walletWizardOpen, setWalletWizardOpen] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
React.useEffect(() => {
delegationStatus().then(setDelegationInfo).catch(console.error);
}, [delegationStatus]);
useEffect(() => { useEffect(() => {
console.log({currentUser}) console.log({currentUser})

View File

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
import { Button } from './button'; import { Button } from './button';
import { useAuth } from '@opchan/react'; import { useAuth } from '@opchan/react';
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react'; import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
import { DelegationDuration, DelegationFullStatus } from '@opchan/core'; import { DelegationDuration } from '@opchan/core';
interface DelegationStepProps { interface DelegationStepProps {
onComplete: () => void; onComplete: () => void;
@ -17,15 +17,7 @@ export function DelegationStep({
isLoading, isLoading,
setIsLoading, setIsLoading,
}: DelegationStepProps) { }: DelegationStepProps) {
const { currentUser, isAuthenticating, getDelegationStatus } = useAuth(); const { currentUser, delegationInfo, delegate, clearDelegation } = useAuth();
const [delegationInfo, setDelegationInfo] =
useState<DelegationFullStatus | null>(null);
const { delegateKey, clearDelegation } = useAuth();
// Load delegation status
useEffect(() => {
getDelegationStatus().then(setDelegationInfo).catch(console.error);
}, [getDelegationStatus]);
const [selectedDuration, setSelectedDuration] = const [selectedDuration, setSelectedDuration] =
React.useState<DelegationDuration>('7days'); React.useState<DelegationDuration>('7days');
@ -42,11 +34,11 @@ export function DelegationStep({
setDelegationResult(null); setDelegationResult(null);
try { try {
const success = await delegateKey(selectedDuration); const success = await delegate(selectedDuration);
if (success) { if (success) {
const expiryDate = currentUser.delegationExpiry const expiryDate = delegationInfo?.expiresAt
? new Date(currentUser.delegationExpiry).toLocaleString() ? delegationInfo.expiresAt.toLocaleString()
: `${selectedDuration === '7days' ? '1 week' : '30 days'} from now`; : `${selectedDuration === '7days' ? '1 week' : '30 days'} from now`;
setDelegationResult({ setDelegationResult({
@ -211,13 +203,7 @@ export function DelegationStep({
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
onClick={async () => { onClick={async () => {
const ok = await clearDelegation(); await clearDelegation();
if (ok) {
// Refresh status so UI immediately reflects cleared state
getDelegationStatus()
.then(setDelegationInfo)
.catch(console.error);
}
}} }}
variant="outline" variant="outline"
size="sm" size="sm"
@ -245,7 +231,7 @@ export function DelegationStep({
<Button <Button
onClick={handleDelegate} onClick={handleDelegate}
className="w-full bg-blue-600 hover:bg-blue-700 text-white" className="w-full bg-blue-600 hover:bg-blue-700 text-white"
disabled={isLoading || isAuthenticating} disabled={isLoading}
> >
{isLoading ? 'Delegating...' : 'Delegate Key'} {isLoading ? 'Delegating...' : 'Delegate Key'}
</Button> </Button>

View File

@ -40,7 +40,7 @@ export default function ProfilePage() {
const { toast } = useToast(); const { toast } = useToast();
// Get current user from auth context for the address // Get current user from auth context for the address
const { currentUser, delegation } = useAuth(); const { currentUser, delegationInfo } = useAuth();
const address = currentUser?.address; const address = currentUser?.address;
// Debug current user ENS info // Debug current user ENS info
@ -461,7 +461,7 @@ export default function ProfilePage() {
<Shield className="h-5 w-5 text-cyber-accent" /> <Shield className="h-5 w-5 text-cyber-accent" />
Security Security
</div> </div>
{delegation.hasDelegation && ( {delegationInfo.hasDelegation && (
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@ -469,7 +469,7 @@ export default function ProfilePage() {
className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30" className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
> >
<Settings className="w-4 h-4 mr-2" /> <Settings className="w-4 h-4 mr-2" />
{delegation.isValid ? 'Renew' : 'Setup'} {delegationInfo.isValid ? 'Renew' : 'Setup'}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
@ -482,25 +482,25 @@ export default function ProfilePage() {
Delegation Delegation
</span> </span>
<Badge <Badge
variant={delegation.isValid ? 'default' : 'secondary'} variant={delegationInfo.isValid ? 'default' : 'secondary'}
className={ className={
delegation.isValid delegationInfo.isValid
? 'bg-green-500/20 text-green-400 border-green-500/30' ? 'bg-green-500/20 text-green-400 border-green-500/30'
: 'bg-red-500/20 text-red-400 border-red-500/30' : 'bg-red-500/20 text-red-400 border-red-500/30'
} }
> >
{delegation.isValid ? 'Active' : 'Inactive'} {delegationInfo.isValid ? 'Active' : 'Inactive'}
</Badge> </Badge>
</div> </div>
{/* Expiry Date */} {/* Expiry Date */}
{delegation.expiresAt && ( {delegationInfo.expiresAt && (
<div className="space-y-1"> <div className="space-y-1">
<span className="text-xs text-cyber-neutral"> <span className="text-xs text-cyber-neutral">
Valid until Valid until
</span> </span>
<div className="text-sm font-mono text-cyber-light"> <div className="text-sm font-mono text-cyber-light">
{delegation.expiresAt.toLocaleDateString()} {delegationInfo.expiresAt.toLocaleDateString()}
</div> </div>
</div> </div>
)} )}
@ -513,12 +513,12 @@ export default function ProfilePage() {
<Badge <Badge
variant="outline" variant="outline"
className={ className={
delegation.isValid delegationInfo.isValid
? 'text-green-400 border-green-500/30 bg-green-500/10' ? 'text-green-400 border-green-500/30 bg-green-500/10'
: 'text-red-400 border-red-500/30 bg-red-500/10' : 'text-red-400 border-red-500/30 bg-red-500/10'
} }
> >
{delegation.isValid ? 'Valid' : 'Not signed'} {delegationInfo.isValid ? 'Valid' : 'Not signed'}
</Badge> </Badge>
</div> </div>
</div> </div>
@ -530,17 +530,17 @@ export default function ProfilePage() {
</Label> </Label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 font-mono text-xs bg-cyber-dark/50 border border-cyber-muted/30 px-2 py-1 rounded text-cyber-light"> <div className="flex-1 font-mono text-xs bg-cyber-dark/50 border border-cyber-muted/30 px-2 py-1 rounded text-cyber-light">
{delegation.publicKey {delegationInfo.publicKey
? `${delegation.publicKey.slice(0, 12)}...${delegation.publicKey.slice(-8)}` ? `${delegationInfo.publicKey.slice(0, 12)}...${delegationInfo.publicKey.slice(-8)}`
: 'Not delegated'} : 'Not delegated'}
</div> </div>
{delegation.publicKey && ( {delegationInfo.publicKey && (
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => onClick={() =>
copyToClipboard( copyToClipboard(
delegation.publicKey!, delegationInfo.publicKey!,
'Public Key' 'Public Key'
) )
} }
@ -553,7 +553,7 @@ export default function ProfilePage() {
</div> </div>
{/* Warning for expired delegation */} {/* Warning for expired delegation */}
{(!delegation.isValid && delegation.hasDelegation) && ( {(!delegationInfo.isValid && delegationInfo.hasDelegation) && (
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md"> <div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md">
<div className="flex items-center gap-2 text-orange-400"> <div className="flex items-center gap-2 text-orange-400">
<AlertTriangle className="w-4 h-4" /> <AlertTriangle className="w-4 h-4" />

View File

@ -1,7 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { OpChanProvider, type WalletAdapter, type WalletAdapterAccount } from '@opchan/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 type { OpChanClientConfig } from '@opchan/core';
import { walletManager } from '@opchan/core';
interface Props { config: OpChanClientConfig; children: React.ReactNode } interface Props { config: OpChanClientConfig; children: React.ReactNode }
@ -23,16 +25,31 @@ export const OpchanWithAppKit: React.FC<Props> = ({ config, children }) => {
listenersRef.current.add(cb); listenersRef.current.add(cb);
return () => { listenersRef.current.delete(cb); }; return () => { listenersRef.current.delete(cb); };
}, },
}), [getCurrent]); }), [getCurrent]);
// Notify listeners when AppKit account changes // Notify listeners when AppKit account changes
React.useEffect(() => { React.useEffect(() => {
const account = getCurrent(); const account = getCurrent();
listenersRef.current.forEach(cb => { listenersRef.current.forEach(cb => {
try { cb(account); } catch (e) { /* ignore */ } try { cb(account); } catch { /* ignore */ }
}); });
}, [getCurrent]); }, [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 ( return (
<OpChanProvider config={config} walletAdapter={adapter}> <OpChanProvider config={config} walletAdapter={adapter}>
{children} {children}

View File

@ -10,7 +10,6 @@ import { config } from './config';
import { Provider } from '@reown/appkit-controllers'; import { Provider } from '@reown/appkit-controllers';
import { WalletInfo, ActiveWallet } from './types'; import { WalletInfo, ActiveWallet } from './types';
import { Inscription } from 'ordiscan'; import { Inscription } from 'ordiscan';
import { environment } from '../utils/environment';
export class WalletManager { export class WalletManager {
private static instance: WalletManager | null = null; private static instance: WalletManager | null = null;

View File

@ -6,6 +6,7 @@ import {
EVerificationStatus, EVerificationStatus,
DelegationDuration, DelegationDuration,
EDisplayPreference, EDisplayPreference,
walletManager,
} from '@opchan/core'; } from '@opchan/core';
import type { DelegationFullStatus } from '@opchan/core'; import type { DelegationFullStatus } from '@opchan/core';
@ -101,17 +102,17 @@ export function useAuth() {
}, [client, currentUser]); }, [client, currentUser]);
const delegate = React.useCallback(async ( const delegate = React.useCallback(async (
signFunction: (message: string) => Promise<string>,
duration: DelegationDuration = '7days', duration: DelegationDuration = '7days',
): Promise<boolean> => { ): Promise<boolean> => {
const user = currentUser; const user = currentUser;
if (!user) return false; if (!user) return false;
try { try {
const signer = ((message: string) => walletManager.getInstance().signMessage(message));
const ok = await client.delegation.delegate( const ok = await client.delegation.delegate(
user.address, user.address,
user.walletType, user.walletType,
duration, duration,
signFunction, signer,
); );
const status = await client.delegation.getStatus(user.address, user.walletType); 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); return client.delegation.getStatus(user.address, user.walletType);
}, [client, currentUser]); }, [client, currentUser]);
const clearDelegation = React.useCallback(async () => { const clearDelegation = React.useCallback(async (): Promise<boolean> => {
await client.delegation.clear(); try {
setOpchanState(prev => ({ await client.delegation.clear();
...prev, setOpchanState(prev => ({
session: { ...prev.session, delegation: null }, ...prev,
})); session: { ...prev.session, delegation: null },
}));
return true;
} catch (e) {
console.error('clearDelegation failed', e);
return false;
}
}, [client]); }, [client]);
const updateProfile = React.useCallback(async (updates: { callSign?: string; displayPreference?: EDisplayPreference }): Promise<boolean> => { const updateProfile = React.useCallback(async (updates: { callSign?: string; displayPreference?: EDisplayPreference }): Promise<boolean> => {
@ -167,19 +174,20 @@ export function useAuth() {
} }
}, [client, currentUser]); }, [client, currentUser]);
const delegationInfo = React.useMemo<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 };
}, [delegation]);
return { return {
currentUser, currentUser,
verificationStatus, verificationStatus,
isAuthenticated: currentUser !== null, isAuthenticated: currentUser !== null,
// Provide a stable, non-null delegation object for UI safety delegationInfo,
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 };
})(),
connect, connect,
disconnect, disconnect,
verifyOwnership, verifyOwnership,