mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore: convert WalletManager into a factory class
This commit is contained in:
parent
dc4468078e
commit
4232878d39
@ -53,20 +53,22 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
|
||||
const address = activeAccount.address;
|
||||
|
||||
// Create manager instances that persist between renders
|
||||
const walletManager = useMemo(() => new WalletManager(), []);
|
||||
// Create manager instances
|
||||
const delegationManager = useMemo(() => new DelegationManager(), []);
|
||||
|
||||
// Set AppKit instance and accounts in WalletManager
|
||||
// Create wallet manager when we have all dependencies
|
||||
useEffect(() => {
|
||||
if (modal) {
|
||||
walletManager.setAppKit(modal);
|
||||
if (modal && (bitcoinAccount.isConnected || ethereumAccount.isConnected)) {
|
||||
try {
|
||||
WalletManager.create(modal, bitcoinAccount, ethereumAccount);
|
||||
} catch (error) {
|
||||
console.warn('Failed to create WalletManager:', error);
|
||||
WalletManager.clear();
|
||||
}
|
||||
} else {
|
||||
WalletManager.clear();
|
||||
}
|
||||
}, [walletManager]);
|
||||
|
||||
useEffect(() => {
|
||||
walletManager.setAccounts(bitcoinAccount, ethereumAccount);
|
||||
}, [bitcoinAccount, ethereumAccount, walletManager]);
|
||||
}, [bitcoinAccount, ethereumAccount]);
|
||||
|
||||
// Helper functions for user persistence
|
||||
const loadStoredUser = (): User | null => {
|
||||
@ -115,7 +117,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
} else if (user.walletType === 'ethereum') {
|
||||
try {
|
||||
const walletInfo = await walletManager.getWalletInfo();
|
||||
const walletInfo = WalletManager.hasInstance()
|
||||
? await WalletManager.getInstance().getWalletInfo()
|
||||
: null;
|
||||
const hasENS = !!walletInfo?.ensName;
|
||||
const ensName = walletInfo?.ensName;
|
||||
|
||||
@ -147,12 +151,18 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
duration: DelegationDuration = '7days'
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const walletType = user.walletType;
|
||||
const isAvailable = walletManager.isWalletConnected(walletType);
|
||||
|
||||
if (!isAvailable) {
|
||||
if (!WalletManager.hasInstance()) {
|
||||
throw new Error(
|
||||
`${walletType} wallet is not available or connected. Please ensure it is connected.`
|
||||
'Wallet not connected. Please ensure your wallet is connected.'
|
||||
);
|
||||
}
|
||||
|
||||
const walletManager = WalletManager.getInstance();
|
||||
|
||||
// Verify wallet type matches
|
||||
if (walletManager.getWalletType() !== user.walletType) {
|
||||
throw new Error(
|
||||
`Expected ${user.walletType} wallet, but ${walletManager.getWalletType()} is connected.`
|
||||
);
|
||||
}
|
||||
|
||||
@ -169,10 +179,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
|
||||
// Sign the delegation message with wallet
|
||||
const signature = await walletManager.signMessage(
|
||||
delegationMessage,
|
||||
walletType
|
||||
);
|
||||
const signature = await walletManager.signMessage(delegationMessage);
|
||||
|
||||
// Create and store the delegation
|
||||
delegationManager.createDelegation(
|
||||
@ -181,7 +188,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
keypair.publicKey,
|
||||
keypair.privateKey,
|
||||
duration,
|
||||
walletType
|
||||
user.walletType
|
||||
);
|
||||
|
||||
return true;
|
||||
@ -216,30 +223,38 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// For Ethereum wallets, try to check ENS ownership immediately
|
||||
if (isEthereumConnected) {
|
||||
walletManager
|
||||
.getWalletInfo()
|
||||
.then(walletInfo => {
|
||||
if (walletInfo?.ensName) {
|
||||
const updatedUser = {
|
||||
...newUser,
|
||||
ensDetails: { ensName: walletInfo.ensName },
|
||||
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||
};
|
||||
setCurrentUser(updatedUser);
|
||||
setVerificationStatus('verified-owner');
|
||||
saveUser(updatedUser);
|
||||
} else {
|
||||
try {
|
||||
const walletManager = WalletManager.getInstance();
|
||||
walletManager
|
||||
.getWalletInfo()
|
||||
.then(walletInfo => {
|
||||
if (walletInfo?.ensName) {
|
||||
const updatedUser = {
|
||||
...newUser,
|
||||
ensDetails: { ensName: walletInfo.ensName },
|
||||
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||
};
|
||||
setCurrentUser(updatedUser);
|
||||
setVerificationStatus('verified-owner');
|
||||
saveUser(updatedUser);
|
||||
} else {
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
saveUser(newUser);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback to basic verification if ENS check fails
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
saveUser(newUser);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback to basic verification if ENS check fails
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
saveUser(newUser);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// WalletManager not ready, fallback to basic verification
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
saveUser(newUser);
|
||||
}
|
||||
} else {
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
@ -267,14 +282,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
setCurrentUser(null);
|
||||
setVerificationStatus('unverified');
|
||||
}
|
||||
}, [
|
||||
isConnected,
|
||||
address,
|
||||
isBitcoinConnected,
|
||||
isEthereumConnected,
|
||||
toast,
|
||||
walletManager,
|
||||
]);
|
||||
}, [isConnected, address, isBitcoinConnected, isEthereumConnected, toast]);
|
||||
|
||||
const { disconnect } = useDisconnect();
|
||||
|
||||
|
||||
@ -7,177 +7,81 @@ import { Provider } from '@reown/appkit-controllers';
|
||||
import { WalletInfo, ActiveWallet } from './types';
|
||||
|
||||
export class WalletManager {
|
||||
private appKit?: AppKit;
|
||||
private bitcoinAccount?: UseAppKitAccountReturn;
|
||||
private ethereumAccount?: UseAppKitAccountReturn;
|
||||
private static instance: WalletManager | null = null;
|
||||
|
||||
/**
|
||||
* Set the AppKit instance for accessing adapters
|
||||
*/
|
||||
setAppKit(appKit: AppKit): void {
|
||||
this.appKit = appKit;
|
||||
}
|
||||
private appKit: AppKit;
|
||||
private activeAccount: UseAppKitAccountReturn;
|
||||
private activeWalletType: 'bitcoin' | 'ethereum';
|
||||
private namespace: ChainNamespace;
|
||||
|
||||
/**
|
||||
* Set account references from AppKit hooks
|
||||
*/
|
||||
setAccounts(
|
||||
private constructor(
|
||||
appKit: AppKit,
|
||||
bitcoinAccount: UseAppKitAccountReturn,
|
||||
ethereumAccount: UseAppKitAccountReturn
|
||||
): void {
|
||||
this.bitcoinAccount = bitcoinAccount;
|
||||
this.ethereumAccount = ethereumAccount;
|
||||
) {
|
||||
this.appKit = appKit;
|
||||
|
||||
// Determine active wallet (Bitcoin takes priority)
|
||||
if (bitcoinAccount.isConnected && bitcoinAccount.address) {
|
||||
this.activeAccount = bitcoinAccount;
|
||||
this.activeWalletType = 'bitcoin';
|
||||
this.namespace = 'bip122';
|
||||
} else if (ethereumAccount.isConnected && ethereumAccount.address) {
|
||||
this.activeAccount = ethereumAccount;
|
||||
this.activeWalletType = 'ethereum';
|
||||
this.namespace = 'eip155';
|
||||
} else {
|
||||
throw new Error('No wallet is connected');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active wallet (Bitcoin takes priority)
|
||||
* Create or get the singleton instance
|
||||
*/
|
||||
getActiveWallet(): ActiveWallet | null {
|
||||
if (this.bitcoinAccount?.isConnected && this.bitcoinAccount.address) {
|
||||
return {
|
||||
type: 'bitcoin',
|
||||
address: this.bitcoinAccount.address,
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.ethereumAccount?.isConnected && this.ethereumAccount.address) {
|
||||
return {
|
||||
type: 'ethereum',
|
||||
address: this.ethereumAccount.address,
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any wallet is connected
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return (
|
||||
(this.bitcoinAccount?.isConnected ?? false) ||
|
||||
(this.ethereumAccount?.isConnected ?? false)
|
||||
static create(
|
||||
appKit: AppKit,
|
||||
bitcoinAccount: UseAppKitAccountReturn,
|
||||
ethereumAccount: UseAppKitAccountReturn
|
||||
): WalletManager {
|
||||
// Always create a new instance to reflect current wallet state
|
||||
WalletManager.instance = new WalletManager(
|
||||
appKit,
|
||||
bitcoinAccount,
|
||||
ethereumAccount
|
||||
);
|
||||
return WalletManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific wallet type is connected
|
||||
* Get the current instance (throws if not created)
|
||||
*/
|
||||
isWalletConnected(walletType: 'bitcoin' | 'ethereum'): boolean {
|
||||
const account =
|
||||
walletType === 'bitcoin' ? this.bitcoinAccount : this.ethereumAccount;
|
||||
return account?.isConnected ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address for a specific wallet type
|
||||
*/
|
||||
getAddress(walletType: 'bitcoin' | 'ethereum'): string | undefined {
|
||||
const account =
|
||||
walletType === 'bitcoin' ? this.bitcoinAccount : this.ethereumAccount;
|
||||
return account?.address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate namespace for the wallet type
|
||||
*/
|
||||
private getNamespace(walletType: 'bitcoin' | 'ethereum'): ChainNamespace {
|
||||
return walletType === 'bitcoin' ? 'bip122' : 'eip155';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using the appropriate wallet adapter
|
||||
*/
|
||||
async signMessage(
|
||||
message: string,
|
||||
walletType: 'bitcoin' | 'ethereum'
|
||||
): Promise<string> {
|
||||
if (!this.appKit) {
|
||||
throw new Error('AppKit instance not set. Call setAppKit() first.');
|
||||
}
|
||||
|
||||
const account =
|
||||
walletType === 'bitcoin' ? this.bitcoinAccount : this.ethereumAccount;
|
||||
if (!account?.address) {
|
||||
throw new Error(`No ${walletType} wallet connected`);
|
||||
}
|
||||
|
||||
const namespace = this.getNamespace(walletType);
|
||||
|
||||
try {
|
||||
// Access the adapter through the appKit instance
|
||||
const adapter = this.appKit.chainAdapters?.[namespace];
|
||||
|
||||
if (!adapter) {
|
||||
throw new Error(`No adapter found for namespace: ${namespace}`);
|
||||
}
|
||||
|
||||
// Get the provider for the current connection
|
||||
const provider = this.appKit.getProvider(namespace);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`No provider found for namespace: ${namespace}`);
|
||||
}
|
||||
|
||||
// Call the adapter's signMessage method
|
||||
const result = await adapter.signMessage({
|
||||
message,
|
||||
address: account.address,
|
||||
provider: provider as Provider,
|
||||
});
|
||||
|
||||
return result.signature;
|
||||
} catch (error) {
|
||||
console.error(`Error signing message with ${walletType} wallet:`, error);
|
||||
static getInstance(): WalletManager {
|
||||
if (!WalletManager.instance) {
|
||||
throw new Error(
|
||||
`Failed to sign message with ${walletType} wallet: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
'WalletManager not initialized. Call WalletManager.create() first.'
|
||||
);
|
||||
}
|
||||
return WalletManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive wallet info including ENS resolution for Ethereum
|
||||
* Check if instance exists
|
||||
*/
|
||||
async getWalletInfo(): Promise<WalletInfo | null> {
|
||||
if (this.bitcoinAccount?.isConnected) {
|
||||
return {
|
||||
address: this.bitcoinAccount.address as string,
|
||||
walletType: 'bitcoin',
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
static hasInstance(): boolean {
|
||||
return WalletManager.instance !== null;
|
||||
}
|
||||
|
||||
if (this.ethereumAccount?.isConnected) {
|
||||
const address = this.ethereumAccount.address as string;
|
||||
|
||||
// Try to resolve ENS name
|
||||
let ensName: string | undefined;
|
||||
try {
|
||||
const resolvedName = await getEnsName(config, {
|
||||
address: address as `0x${string}`,
|
||||
});
|
||||
ensName = resolvedName || undefined;
|
||||
} catch (error) {
|
||||
console.warn('Failed to resolve ENS name:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
address,
|
||||
walletType: 'ethereum',
|
||||
ensName,
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* Clear the singleton instance
|
||||
*/
|
||||
static clear(): void {
|
||||
WalletManager.instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve ENS name for an Ethereum address
|
||||
*/
|
||||
async resolveENS(address: string): Promise<string | null> {
|
||||
static async resolveENS(address: string): Promise<string | null> {
|
||||
try {
|
||||
const ensName = await getEnsName(config, {
|
||||
address: address as `0x${string}`,
|
||||
@ -188,9 +92,123 @@ export class WalletManager {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active wallet
|
||||
*/
|
||||
getActiveWallet(): ActiveWallet {
|
||||
return {
|
||||
type: this.activeWalletType,
|
||||
address: this.activeAccount.address!,
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if wallet is connected
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.activeAccount.isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active wallet type
|
||||
*/
|
||||
getWalletType(): 'bitcoin' | 'ethereum' {
|
||||
return this.activeWalletType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address of the active wallet
|
||||
*/
|
||||
getAddress(): string {
|
||||
return this.activeAccount.address!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using the active wallet
|
||||
*/
|
||||
async signMessage(message: string): Promise<string> {
|
||||
try {
|
||||
// Access the adapter through the appKit instance
|
||||
const adapter = this.appKit.chainAdapters?.[this.namespace];
|
||||
|
||||
if (!adapter) {
|
||||
throw new Error(`No adapter found for namespace: ${this.namespace}`);
|
||||
}
|
||||
|
||||
// Get the provider for the current connection
|
||||
const provider = this.appKit.getProvider(this.namespace);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`No provider found for namespace: ${this.namespace}`);
|
||||
}
|
||||
|
||||
if (!this.activeAccount.address) {
|
||||
throw new Error('No address found for active account');
|
||||
}
|
||||
|
||||
// Call the adapter's signMessage method
|
||||
const result = await adapter.signMessage({
|
||||
message,
|
||||
address: this.activeAccount.address,
|
||||
provider: provider as Provider,
|
||||
});
|
||||
|
||||
return result.signature;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error signing message with ${this.activeWalletType} wallet:`,
|
||||
error
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to sign message with ${this.activeWalletType} wallet: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive wallet info including ENS resolution for Ethereum
|
||||
*/
|
||||
async getWalletInfo(): Promise<WalletInfo> {
|
||||
const address = this.activeAccount.address!;
|
||||
|
||||
if (this.activeWalletType === 'bitcoin') {
|
||||
return {
|
||||
address,
|
||||
walletType: 'bitcoin',
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
|
||||
// For Ethereum, try to resolve ENS name
|
||||
let ensName: string | undefined;
|
||||
try {
|
||||
const resolvedName = await getEnsName(config, {
|
||||
address: address as `0x${string}`,
|
||||
});
|
||||
ensName = resolvedName || undefined;
|
||||
} catch (error) {
|
||||
console.warn('Failed to resolve ENS name:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
address,
|
||||
walletType: 'ethereum',
|
||||
ensName,
|
||||
isConnected: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const walletManager = new WalletManager();
|
||||
// Convenience exports for singleton access
|
||||
export const walletManager = {
|
||||
create: WalletManager.create,
|
||||
getInstance: WalletManager.getInstance,
|
||||
hasInstance: WalletManager.hasInstance,
|
||||
clear: WalletManager.clear,
|
||||
resolveENS: WalletManager.resolveENS,
|
||||
};
|
||||
|
||||
export * from './types';
|
||||
export * from './config';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user