mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-05-18 06:39:27 +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 activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
|
||||||
const address = activeAccount.address;
|
const address = activeAccount.address;
|
||||||
|
|
||||||
// Create manager instances that persist between renders
|
// Create manager instances
|
||||||
const walletManager = useMemo(() => new WalletManager(), []);
|
|
||||||
const delegationManager = useMemo(() => new DelegationManager(), []);
|
const delegationManager = useMemo(() => new DelegationManager(), []);
|
||||||
|
|
||||||
// Set AppKit instance and accounts in WalletManager
|
// Create wallet manager when we have all dependencies
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modal) {
|
if (modal && (bitcoinAccount.isConnected || ethereumAccount.isConnected)) {
|
||||||
walletManager.setAppKit(modal);
|
try {
|
||||||
|
WalletManager.create(modal, bitcoinAccount, ethereumAccount);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to create WalletManager:', error);
|
||||||
|
WalletManager.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WalletManager.clear();
|
||||||
}
|
}
|
||||||
}, [walletManager]);
|
}, [bitcoinAccount, ethereumAccount]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
walletManager.setAccounts(bitcoinAccount, ethereumAccount);
|
|
||||||
}, [bitcoinAccount, ethereumAccount, walletManager]);
|
|
||||||
|
|
||||||
// Helper functions for user persistence
|
// Helper functions for user persistence
|
||||||
const loadStoredUser = (): User | null => {
|
const loadStoredUser = (): User | null => {
|
||||||
@ -115,7 +117,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
} else if (user.walletType === 'ethereum') {
|
} else if (user.walletType === 'ethereum') {
|
||||||
try {
|
try {
|
||||||
const walletInfo = await walletManager.getWalletInfo();
|
const walletInfo = WalletManager.hasInstance()
|
||||||
|
? await WalletManager.getInstance().getWalletInfo()
|
||||||
|
: null;
|
||||||
const hasENS = !!walletInfo?.ensName;
|
const hasENS = !!walletInfo?.ensName;
|
||||||
const ensName = walletInfo?.ensName;
|
const ensName = walletInfo?.ensName;
|
||||||
|
|
||||||
@ -147,12 +151,18 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
duration: DelegationDuration = '7days'
|
duration: DelegationDuration = '7days'
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const walletType = user.walletType;
|
if (!WalletManager.hasInstance()) {
|
||||||
const isAvailable = walletManager.isWalletConnected(walletType);
|
|
||||||
|
|
||||||
if (!isAvailable) {
|
|
||||||
throw new Error(
|
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
|
// Sign the delegation message with wallet
|
||||||
const signature = await walletManager.signMessage(
|
const signature = await walletManager.signMessage(delegationMessage);
|
||||||
delegationMessage,
|
|
||||||
walletType
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create and store the delegation
|
// Create and store the delegation
|
||||||
delegationManager.createDelegation(
|
delegationManager.createDelegation(
|
||||||
@ -181,7 +188,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
keypair.publicKey,
|
keypair.publicKey,
|
||||||
keypair.privateKey,
|
keypair.privateKey,
|
||||||
duration,
|
duration,
|
||||||
walletType
|
user.walletType
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -216,30 +223,38 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
// For Ethereum wallets, try to check ENS ownership immediately
|
// For Ethereum wallets, try to check ENS ownership immediately
|
||||||
if (isEthereumConnected) {
|
if (isEthereumConnected) {
|
||||||
walletManager
|
try {
|
||||||
.getWalletInfo()
|
const walletManager = WalletManager.getInstance();
|
||||||
.then(walletInfo => {
|
walletManager
|
||||||
if (walletInfo?.ensName) {
|
.getWalletInfo()
|
||||||
const updatedUser = {
|
.then(walletInfo => {
|
||||||
...newUser,
|
if (walletInfo?.ensName) {
|
||||||
ensDetails: { ensName: walletInfo.ensName },
|
const updatedUser = {
|
||||||
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
...newUser,
|
||||||
};
|
ensDetails: { ensName: walletInfo.ensName },
|
||||||
setCurrentUser(updatedUser);
|
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||||
setVerificationStatus('verified-owner');
|
};
|
||||||
saveUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
} else {
|
setVerificationStatus('verified-owner');
|
||||||
|
saveUser(updatedUser);
|
||||||
|
} else {
|
||||||
|
setCurrentUser(newUser);
|
||||||
|
setVerificationStatus('verified-basic');
|
||||||
|
saveUser(newUser);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Fallback to basic verification if ENS check fails
|
||||||
setCurrentUser(newUser);
|
setCurrentUser(newUser);
|
||||||
setVerificationStatus('verified-basic');
|
setVerificationStatus('verified-basic');
|
||||||
saveUser(newUser);
|
saveUser(newUser);
|
||||||
}
|
});
|
||||||
})
|
} catch {
|
||||||
.catch(() => {
|
// WalletManager not ready, fallback to basic verification
|
||||||
// Fallback to basic verification if ENS check fails
|
setCurrentUser(newUser);
|
||||||
setCurrentUser(newUser);
|
setVerificationStatus('verified-basic');
|
||||||
setVerificationStatus('verified-basic');
|
saveUser(newUser);
|
||||||
saveUser(newUser);
|
}
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setCurrentUser(newUser);
|
setCurrentUser(newUser);
|
||||||
setVerificationStatus('verified-basic');
|
setVerificationStatus('verified-basic');
|
||||||
@ -267,14 +282,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setCurrentUser(null);
|
setCurrentUser(null);
|
||||||
setVerificationStatus('unverified');
|
setVerificationStatus('unverified');
|
||||||
}
|
}
|
||||||
}, [
|
}, [isConnected, address, isBitcoinConnected, isEthereumConnected, toast]);
|
||||||
isConnected,
|
|
||||||
address,
|
|
||||||
isBitcoinConnected,
|
|
||||||
isEthereumConnected,
|
|
||||||
toast,
|
|
||||||
walletManager,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { disconnect } = useDisconnect();
|
const { disconnect } = useDisconnect();
|
||||||
|
|
||||||
|
|||||||
@ -7,177 +7,81 @@ import { Provider } from '@reown/appkit-controllers';
|
|||||||
import { WalletInfo, ActiveWallet } from './types';
|
import { WalletInfo, ActiveWallet } from './types';
|
||||||
|
|
||||||
export class WalletManager {
|
export class WalletManager {
|
||||||
private appKit?: AppKit;
|
private static instance: WalletManager | null = null;
|
||||||
private bitcoinAccount?: UseAppKitAccountReturn;
|
|
||||||
private ethereumAccount?: UseAppKitAccountReturn;
|
|
||||||
|
|
||||||
/**
|
private appKit: AppKit;
|
||||||
* Set the AppKit instance for accessing adapters
|
private activeAccount: UseAppKitAccountReturn;
|
||||||
*/
|
private activeWalletType: 'bitcoin' | 'ethereum';
|
||||||
setAppKit(appKit: AppKit): void {
|
private namespace: ChainNamespace;
|
||||||
this.appKit = appKit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private constructor(
|
||||||
* Set account references from AppKit hooks
|
appKit: AppKit,
|
||||||
*/
|
|
||||||
setAccounts(
|
|
||||||
bitcoinAccount: UseAppKitAccountReturn,
|
bitcoinAccount: UseAppKitAccountReturn,
|
||||||
ethereumAccount: UseAppKitAccountReturn
|
ethereumAccount: UseAppKitAccountReturn
|
||||||
): void {
|
) {
|
||||||
this.bitcoinAccount = bitcoinAccount;
|
this.appKit = appKit;
|
||||||
this.ethereumAccount = ethereumAccount;
|
|
||||||
|
// 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 {
|
static create(
|
||||||
if (this.bitcoinAccount?.isConnected && this.bitcoinAccount.address) {
|
appKit: AppKit,
|
||||||
return {
|
bitcoinAccount: UseAppKitAccountReturn,
|
||||||
type: 'bitcoin',
|
ethereumAccount: UseAppKitAccountReturn
|
||||||
address: this.bitcoinAccount.address,
|
): WalletManager {
|
||||||
isConnected: true,
|
// Always create a new instance to reflect current wallet state
|
||||||
};
|
WalletManager.instance = new WalletManager(
|
||||||
}
|
appKit,
|
||||||
|
bitcoinAccount,
|
||||||
if (this.ethereumAccount?.isConnected && this.ethereumAccount.address) {
|
ethereumAccount
|
||||||
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)
|
|
||||||
);
|
);
|
||||||
|
return WalletManager.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a specific wallet type is connected
|
* Get the current instance (throws if not created)
|
||||||
*/
|
*/
|
||||||
isWalletConnected(walletType: 'bitcoin' | 'ethereum'): boolean {
|
static getInstance(): WalletManager {
|
||||||
const account =
|
if (!WalletManager.instance) {
|
||||||
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);
|
|
||||||
throw new Error(
|
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> {
|
static hasInstance(): boolean {
|
||||||
if (this.bitcoinAccount?.isConnected) {
|
return WalletManager.instance !== null;
|
||||||
return {
|
}
|
||||||
address: this.bitcoinAccount.address as string,
|
|
||||||
walletType: 'bitcoin',
|
|
||||||
isConnected: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ethereumAccount?.isConnected) {
|
/**
|
||||||
const address = this.ethereumAccount.address as string;
|
* Clear the singleton instance
|
||||||
|
*/
|
||||||
// Try to resolve ENS name
|
static clear(): void {
|
||||||
let ensName: string | undefined;
|
WalletManager.instance = null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve ENS name for an Ethereum address
|
* Resolve ENS name for an Ethereum address
|
||||||
*/
|
*/
|
||||||
async resolveENS(address: string): Promise<string | null> {
|
static async resolveENS(address: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const ensName = await getEnsName(config, {
|
const ensName = await getEnsName(config, {
|
||||||
address: address as `0x${string}`,
|
address: address as `0x${string}`,
|
||||||
@ -188,9 +92,123 @@ export class WalletManager {
|
|||||||
return null;
|
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
|
// Convenience exports for singleton access
|
||||||
export const walletManager = new WalletManager();
|
export const walletManager = {
|
||||||
|
create: WalletManager.create,
|
||||||
|
getInstance: WalletManager.getInstance,
|
||||||
|
hasInstance: WalletManager.hasInstance,
|
||||||
|
clear: WalletManager.clear,
|
||||||
|
resolveENS: WalletManager.resolveENS,
|
||||||
|
};
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './config';
|
export * from './config';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user