fix: wallet reconnection

This commit is contained in:
Danish Arora 2025-07-30 15:55:13 +05:30
parent 2140e41144
commit 1155948d0d
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
6 changed files with 163 additions and 5 deletions

2
.gitignore vendored
View File

@ -2,6 +2,8 @@
comparison.md
.giga/
furps.md
README-task-master.md
.cursor
scripts

View File

@ -4,8 +4,9 @@ import { useAuth } from '@/contexts/useAuth';
import { useForum } from '@/contexts/useForum';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ShieldCheck, LogOut, Terminal, Wifi, WifiOff, AlertTriangle, CheckCircle, Key, RefreshCw, CircleSlash } from 'lucide-react';
import { LogOut, Terminal, Wifi, WifiOff, AlertTriangle, CheckCircle, Key, RefreshCw, CircleSlash } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useToast } from '@/components/ui/use-toast';
const Header = () => {
const {
@ -17,9 +18,11 @@ const Header = () => {
verifyOrdinal,
delegateKey,
isDelegationValid,
delegationTimeRemaining
delegationTimeRemaining,
isWalletAvailable
} = useAuth();
const { isNetworkConnected, isRefreshing } = useForum();
const { toast } = useToast();
const handleConnect = async () => {
await connectWallet();
@ -34,7 +37,20 @@ const Header = () => {
};
const handleDelegateKey = async () => {
await delegateKey();
try {
if (!isWalletAvailable()) {
toast({
title: "Wallet Not Available",
description: "Phantom wallet is not installed or not available. Please install Phantom wallet and try again.",
variant: "destructive",
});
return;
}
await delegateKey();
} catch (error) {
console.error('Error in handleDelegateKey:', error);
}
};
const formatDelegationTime = () => {
@ -72,7 +88,7 @@ const Header = () => {
{hasValidDelegation ? (
<p>Browser key active for ~{timeRemaining}. Wallet signatures not needed for most actions.</p>
) : (
<p>Delegate a browser key for 24h to avoid constant wallet signing.</p>
<p>Delegate a browser key for 24h to avoid constant wallet signing. If your wallet is disconnected, it will be reconnected automatically.</p>
)}
</TooltipContent>
</Tooltip>

View File

@ -17,6 +17,7 @@ interface AuthContextType {
delegateKey: () => Promise<boolean>;
isDelegationValid: () => boolean;
delegationTimeRemaining: () => number;
isWalletAvailable: () => boolean;
messageSigning: {
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
verifyMessage: (message: OpchanMessage) => boolean;
@ -220,6 +221,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
errorMessage = "You declined the signature request. Key delegation is optional but improves your experience.";
} else if (error.message.includes("timeout")) {
errorMessage = "Wallet request timed out. Please try again and approve the signature promptly.";
} else if (error.message.includes("Failed to connect wallet")) {
errorMessage = "Unable to connect to Phantom wallet. Please ensure it's installed and unlocked, then try again.";
} else if (error.message.includes("Wallet is not connected")) {
errorMessage = "Wallet connection was lost. Please reconnect your wallet and try again.";
} else {
errorMessage = error.message;
}
@ -245,6 +250,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
return authServiceRef.current.getDelegationTimeRemaining();
};
const isWalletAvailable = (): boolean => {
return authServiceRef.current.getWalletInfo()?.type === 'phantom';
};
return (
<AuthContext.Provider
value={{
@ -258,6 +267,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
delegateKey,
isDelegationValid,
delegationTimeRemaining,
isWalletAvailable,
messageSigning: {
signMessage: (message: OpchanMessage) => authServiceRef.current.signMessage(message),
verifyMessage: (message: OpchanMessage) => authServiceRef.current.verifyMessage(message),

View File

@ -92,6 +92,14 @@ export class AuthService {
*/
async delegateKey(user: User): Promise<AuthResult> {
try {
const canConnect = await this.walletService.canConnectWallet('phantom');
if (!canConnect) {
return {
success: false,
error: 'Phantom wallet is not available or cannot be connected. Please ensure it is installed and unlocked.'
};
}
const delegationInfo = await this.walletService.setupKeyDelegation(
user.address,
'phantom'

View File

@ -32,6 +32,23 @@ export class WalletService {
public isWalletAvailable(type: WalletType): boolean {
return this.phantomAdapter.isInstalled();
}
/**
* Check if wallet is available and can be connected
*/
public async canConnectWallet(type: WalletType = 'phantom'): Promise<boolean> {
if (!this.isWalletAvailable(type)) {
return false;
}
try {
const isConnected = await this.phantomAdapter.isConnected();
return isConnected;
} catch (error) {
console.debug('WalletService: Cannot connect wallet:', error);
return false;
}
}
/**
* Connect to a specific wallet type
@ -87,8 +104,31 @@ export class WalletService {
walletType: WalletType,
validityPeriod: number = WalletService.DEFAULT_DELEGATION_PERIOD
): Promise<Omit<DelegationInfo, 'browserPrivateKey'>> {
console.debug('WalletService: Starting key delegation for address:', bitcoinAddress);
let isConnected = await this.phantomAdapter.isConnected();
console.debug('WalletService: Initial wallet connection check result:', isConnected);
if (!isConnected) {
console.debug('WalletService: Wallet not connected, attempting to connect automatically');
try {
await this.phantomAdapter.connect();
isConnected = await this.phantomAdapter.isConnected();
console.debug('WalletService: Auto-connection result:', isConnected);
} catch (error) {
console.error('WalletService: Failed to auto-connect wallet:', error);
throw new Error('Failed to connect wallet. Please ensure Phantom wallet is installed and try again.');
}
}
if (!isConnected) {
console.error('WalletService: Wallet is still not connected after auto-connection attempt');
throw new Error('Wallet is not connected. Please connect your wallet first.');
}
// Generate browser keypair
const keypair = this.keyDelegation.generateKeypair();
console.debug('WalletService: Generated browser keypair');
// Calculate expiry in hours
const expiryHours = validityPeriod / (60 * 60 * 1000);
@ -99,9 +139,12 @@ export class WalletService {
bitcoinAddress,
Date.now() + validityPeriod
);
console.debug('WalletService: Created delegation message');
// Sign the delegation message with the Bitcoin wallet
console.debug('WalletService: Requesting signature from wallet');
const signature = await this.phantomAdapter.signMessage(delegationMessage);
console.debug('WalletService: Received signature from wallet');
// Create and store the delegation
const delegationInfo = this.keyDelegation.createDelegation(
@ -113,6 +156,7 @@ export class WalletService {
);
this.keyDelegation.storeDelegation(delegationInfo);
console.debug('WalletService: Stored delegation info');
// Return delegation info (excluding private key)
return {

View File

@ -16,13 +16,71 @@ export class PhantomWalletAdapter {
constructor() {
this.checkWalletAvailability();
this.restoreConnectionState();
}
/**
* Restore connection state from existing wallet connection
*/
private async restoreConnectionState(): Promise<void> {
if (typeof window === 'undefined' || !window?.phantom?.bitcoin) {
console.debug('PhantomWalletAdapter: No wallet available for connection restoration');
return;
}
try {
console.debug('PhantomWalletAdapter: Attempting to restore connection state');
this.provider = window.phantom;
this.btcProvider = window.phantom.bitcoin;
// Check if wallet is already connected by trying to get accounts
if (this.btcProvider?.requestAccounts) {
const btcAccounts = await this.btcProvider.requestAccounts();
if (btcAccounts && btcAccounts.length > 0) {
const ordinalAccount = btcAccounts.find(acc => acc.purpose === 'ordinals');
const account = ordinalAccount || btcAccounts[0];
this.currentAccount = account.address;
this.connectionStatus = WalletConnectionStatus.Connected;
console.debug('PhantomWalletAdapter: Successfully restored connection for account:', account.address);
} else {
console.debug('PhantomWalletAdapter: No accounts found during connection restoration');
}
} else {
console.debug('PhantomWalletAdapter: requestAccounts method not available');
}
} catch (error) {
// If we can't restore the connection, that's okay - user will need to reconnect
console.debug('PhantomWalletAdapter: Could not restore existing wallet connection:', error);
this.connectionStatus = WalletConnectionStatus.Disconnected;
}
}
public getStatus(): WalletConnectionStatus {
return this.connectionStatus;
}
/**
* Check if the wallet is actually connected by attempting to get accounts
*/
public async isConnected(): Promise<boolean> {
if (!this.btcProvider) {
return false;
}
try {
if (this.btcProvider.requestAccounts) {
const accounts = await this.btcProvider.requestAccounts();
return accounts && accounts.length > 0;
}
return false;
} catch (error) {
console.debug('Error checking wallet connection:', error);
return false;
}
}
public isInstalled(): boolean {
if (typeof window === 'undefined') {
return false;
@ -88,11 +146,30 @@ export class PhantomWalletAdapter {
}
async signMessage(message: string): Promise<string> {
console.debug('PhantomWalletAdapter: signMessage called, btcProvider:', !!this.btcProvider, 'currentAccount:', this.currentAccount);
if (!this.btcProvider && window?.phantom?.bitcoin) {
console.debug('PhantomWalletAdapter: Attempting to restore connection before signing');
await this.restoreConnectionState();
}
if (!this.btcProvider || !this.currentAccount) {
console.debug('PhantomWalletAdapter: Wallet not connected, attempting to connect automatically');
try {
await this.connect();
} catch (error) {
console.error('PhantomWalletAdapter: Failed to auto-connect wallet:', error);
throw new Error('Failed to connect wallet. Please ensure Phantom wallet is installed and try again.');
}
}
if (!this.btcProvider) {
console.error('PhantomWalletAdapter: Wallet is not connected - no btcProvider');
throw new Error('Wallet is not connected');
}
if (!this.currentAccount) {
console.error('PhantomWalletAdapter: No active account to sign with');
throw new Error('No active account to sign with');
}
@ -101,6 +178,7 @@ export class PhantomWalletAdapter {
throw new Error('signMessage method not available on wallet provider');
}
console.debug('PhantomWalletAdapter: Signing message for account:', this.currentAccount);
const messageBytes = new TextEncoder().encode(message);
const { signature } = await this.btcProvider.signMessage(
@ -115,7 +193,7 @@ export class PhantomWalletAdapter {
return String(signature);
} catch (error) {
console.error('Error signing message:', error);
console.error('PhantomWalletAdapter: Error signing message:', error);
throw error;
}
}