mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-04 13:53:11 +00:00
fix: wallet reconnection
This commit is contained in:
parent
2140e41144
commit
1155948d0d
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
comparison.md
|
||||
.giga/
|
||||
|
||||
furps.md
|
||||
|
||||
README-task-master.md
|
||||
.cursor
|
||||
scripts
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user