mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-03 21:33:09 +00:00
fix: message signing
This commit is contained in:
parent
0ad1cce551
commit
367cb72834
@ -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})
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user