mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
253 lines
6.9 KiB
TypeScript
253 lines
6.9 KiB
TypeScript
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
|
import {
|
|
CryptoService,
|
|
DelegationDuration,
|
|
} from '../CryptoService';
|
|
import { AppKit } from '@reown/appkit';
|
|
import { getEnsName } from '@wagmi/core';
|
|
import { ChainNamespace } from '@reown/appkit-common';
|
|
import { config } from './config';
|
|
import { Provider } from '@reown/appkit-controllers';
|
|
|
|
export interface WalletInfo {
|
|
address: string;
|
|
walletType: 'bitcoin' | 'ethereum';
|
|
ensName?: string;
|
|
isConnected: boolean;
|
|
}
|
|
|
|
class WalletService {
|
|
private cryptoService: CryptoService;
|
|
private bitcoinAccount?: UseAppKitAccountReturn;
|
|
private ethereumAccount?: UseAppKitAccountReturn;
|
|
private appKit?: AppKit;
|
|
|
|
constructor() {
|
|
this.cryptoService = new CryptoService();
|
|
}
|
|
|
|
/**
|
|
* Set account references from AppKit hooks
|
|
*/
|
|
setAccounts(
|
|
bitcoinAccount: UseAppKitAccountReturn,
|
|
ethereumAccount: UseAppKitAccountReturn
|
|
) {
|
|
this.bitcoinAccount = bitcoinAccount;
|
|
this.ethereumAccount = ethereumAccount;
|
|
}
|
|
|
|
/**
|
|
* Set the AppKit instance for accessing adapters
|
|
*/
|
|
setAppKit(appKit: AppKit) {
|
|
this.appKit = appKit;
|
|
}
|
|
|
|
/**
|
|
* Check if a wallet type is available and connected
|
|
*/
|
|
isWalletAvailable(walletType: 'bitcoin' | 'ethereum'): boolean {
|
|
if (walletType === 'bitcoin') {
|
|
return this.bitcoinAccount?.isConnected ?? false;
|
|
} else {
|
|
return this.ethereumAccount?.isConnected ?? false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the active account based on wallet type
|
|
*/
|
|
private getActiveAccount(
|
|
walletType: 'bitcoin' | 'ethereum'
|
|
): UseAppKitAccountReturn | undefined {
|
|
return walletType === 'bitcoin'
|
|
? this.bitcoinAccount
|
|
: this.ethereumAccount;
|
|
}
|
|
|
|
/**
|
|
* Get the active address for a given wallet type
|
|
*/
|
|
getActiveAddress(walletType: 'bitcoin' | 'ethereum'): string | undefined {
|
|
const account = this.getActiveAccount(walletType);
|
|
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 adapter
|
|
*/
|
|
async signMessage(
|
|
messageBytes: Uint8Array,
|
|
walletType: 'bitcoin' | 'ethereum'
|
|
): Promise<string> {
|
|
if (!this.appKit) {
|
|
throw new Error('AppKit instance not set. Call setAppKit() first.');
|
|
}
|
|
|
|
const account = this.getActiveAccount(walletType);
|
|
if (!account?.address) {
|
|
throw new Error(`No ${walletType} wallet connected`);
|
|
}
|
|
|
|
const namespace = this.getNamespace(walletType);
|
|
|
|
// Convert message bytes to string for signing
|
|
const messageString = new TextDecoder().decode(messageBytes);
|
|
|
|
try {
|
|
// Access the adapter through the appKit instance
|
|
// The adapter is available through the appKit's chainAdapters property
|
|
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: messageString,
|
|
address: account.address,
|
|
provider: provider as Provider,
|
|
});
|
|
|
|
return result.signature;
|
|
} catch (error) {
|
|
console.error(`Error signing message with ${walletType} wallet:`, error);
|
|
throw new Error(
|
|
`Failed to sign message with ${walletType} wallet: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a key delegation for the connected wallet
|
|
*/
|
|
async createKeyDelegation(
|
|
walletType: 'bitcoin' | 'ethereum',
|
|
duration: DelegationDuration = '7days'
|
|
): Promise<boolean> {
|
|
try {
|
|
const account = this.getActiveAccount(walletType);
|
|
if (!account?.address) {
|
|
throw new Error(`No ${walletType} wallet connected`);
|
|
}
|
|
|
|
// Generate a new browser keypair
|
|
const keypair = this.cryptoService.generateKeypair();
|
|
|
|
// Create delegation message with expiry
|
|
const expiryHours = CryptoService.getDurationHours(duration);
|
|
const expiryTimestamp = Date.now() + expiryHours * 60 * 60 * 1000;
|
|
const delegationMessage = this.cryptoService.createDelegationMessage(
|
|
keypair.publicKey,
|
|
account.address,
|
|
expiryTimestamp
|
|
);
|
|
|
|
const messageBytes = new TextEncoder().encode(delegationMessage);
|
|
|
|
// Sign the delegation message
|
|
const signature = await this.signMessage(messageBytes, walletType);
|
|
|
|
// Create and store the delegation
|
|
this.cryptoService.createDelegation(
|
|
account.address,
|
|
signature,
|
|
keypair.publicKey,
|
|
keypair.privateKey,
|
|
duration,
|
|
walletType
|
|
);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Error creating key delegation for ${walletType}:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get delegation status for the connected wallet
|
|
*/
|
|
getDelegationStatus(walletType: 'bitcoin' | 'ethereum'): {
|
|
hasDelegation: boolean;
|
|
isValid: boolean;
|
|
timeRemaining?: number;
|
|
} {
|
|
const account = this.getActiveAccount(walletType);
|
|
const currentAddress = account?.address;
|
|
|
|
const hasDelegation = this.cryptoService.getBrowserPublicKey() !== null;
|
|
const isValid = this.cryptoService.isDelegationValid(
|
|
currentAddress,
|
|
walletType
|
|
);
|
|
const timeRemaining = this.cryptoService.getDelegationTimeRemaining();
|
|
|
|
return {
|
|
hasDelegation,
|
|
isValid,
|
|
timeRemaining: timeRemaining > 0 ? timeRemaining : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clear delegation for the connected wallet
|
|
*/
|
|
clearDelegation(): void {
|
|
this.cryptoService.clearDelegation();
|
|
}
|
|
|
|
/**
|
|
* Get wallet connection info with ENS resolution for Ethereum
|
|
*/
|
|
async getWalletInfo(): Promise<WalletInfo | null> {
|
|
if (this.bitcoinAccount?.isConnected) {
|
|
return {
|
|
address: this.bitcoinAccount.address as string,
|
|
walletType: 'bitcoin',
|
|
isConnected: true,
|
|
};
|
|
} else if (this.ethereumAccount?.isConnected) {
|
|
// Use Wagmi to resolve ENS name
|
|
let ensName: string | undefined;
|
|
try {
|
|
const resolvedName = await getEnsName(config, {
|
|
address: this.ethereumAccount.address as `0x${string}`,
|
|
});
|
|
ensName = resolvedName || undefined;
|
|
} catch (error) {
|
|
console.warn('Failed to resolve ENS name:', error);
|
|
// Continue without ENS name
|
|
}
|
|
|
|
return {
|
|
address: this.ethereumAccount.address as string,
|
|
walletType: 'ethereum',
|
|
ensName,
|
|
isConnected: true,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const walletService = new WalletService();
|
|
export default walletService;
|