mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 21:03:09 +00:00
355 lines
9.4 KiB
TypeScript
355 lines
9.4 KiB
TypeScript
import { User } from '@/types';
|
|
import { WalletService } from '../wallets/index';
|
|
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
|
import { AppKit } from '@reown/appkit';
|
|
import { OrdinalAPI } from '../ordinal';
|
|
import { MessageSigning } from '../signatures/message-signing';
|
|
import { KeyDelegation, DelegationDuration } from '../signatures/key-delegation';
|
|
import { OpchanMessage } from '@/types';
|
|
|
|
export interface AuthResult {
|
|
success: boolean;
|
|
user?: User;
|
|
error?: string;
|
|
}
|
|
|
|
export class AuthService {
|
|
private walletService: WalletService;
|
|
private ordinalAPI: OrdinalAPI;
|
|
public messageSigning: MessageSigning;
|
|
private keyDelegation: KeyDelegation;
|
|
|
|
constructor() {
|
|
this.walletService = new WalletService();
|
|
this.ordinalAPI = new OrdinalAPI();
|
|
this.keyDelegation = new KeyDelegation();
|
|
this.messageSigning = new MessageSigning(this.keyDelegation);
|
|
}
|
|
|
|
/**
|
|
* Set AppKit accounts for wallet service
|
|
*/
|
|
setAccounts(bitcoinAccount: UseAppKitAccountReturn, ethereumAccount: UseAppKitAccountReturn) {
|
|
this.walletService.setAccounts(bitcoinAccount, ethereumAccount);
|
|
}
|
|
|
|
/**
|
|
* Set AppKit instance for wallet service
|
|
*/
|
|
setAppKit(appKit: AppKit) {
|
|
this.walletService.setAppKit(appKit);
|
|
}
|
|
|
|
/**
|
|
* Get the active wallet address
|
|
*/
|
|
private getActiveAddress(): string | null {
|
|
const isBitcoinConnected = this.walletService.isWalletAvailable('bitcoin');
|
|
const isEthereumConnected = this.walletService.isWalletAvailable('ethereum');
|
|
|
|
if (isBitcoinConnected) {
|
|
return this.walletService.getActiveAddress('bitcoin') || null;
|
|
} else if (isEthereumConnected) {
|
|
return this.walletService.getActiveAddress('ethereum') || null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the active wallet type
|
|
*/
|
|
private getActiveWalletType(): 'bitcoin' | 'ethereum' | null {
|
|
if (this.walletService.isWalletAvailable('bitcoin')) {
|
|
return 'bitcoin';
|
|
} else if (this.walletService.isWalletAvailable('ethereum')) {
|
|
return 'ethereum';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Connect to wallet and create user
|
|
*/
|
|
async connectWallet(): Promise<AuthResult> {
|
|
try {
|
|
// Check which wallet is connected
|
|
const isBitcoinConnected = this.walletService.isWalletAvailable('bitcoin');
|
|
const isEthereumConnected = this.walletService.isWalletAvailable('ethereum');
|
|
|
|
if (!isBitcoinConnected && !isEthereumConnected) {
|
|
return {
|
|
success: false,
|
|
error: 'No wallet connected'
|
|
};
|
|
}
|
|
|
|
// Determine which wallet is active
|
|
const walletType = isBitcoinConnected ? 'bitcoin' : 'ethereum';
|
|
const address = this.getActiveAddress();
|
|
|
|
if (!address) {
|
|
return {
|
|
success: false,
|
|
error: 'No wallet address available'
|
|
};
|
|
}
|
|
|
|
const user: User = {
|
|
address: address,
|
|
walletType: walletType,
|
|
verificationStatus: 'unverified',
|
|
lastChecked: Date.now(),
|
|
};
|
|
|
|
// Add ENS info for Ethereum wallets (if available)
|
|
if (walletType === 'ethereum') {
|
|
try {
|
|
const walletInfo = await this.walletService.getWalletInfo();
|
|
user.ensName = walletInfo?.ensName;
|
|
user.ensOwnership = !!(walletInfo?.ensName);
|
|
} catch (error) {
|
|
console.warn('Failed to resolve ENS during wallet connection:', error);
|
|
user.ensName = undefined;
|
|
user.ensOwnership = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
user
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to connect wallet'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disconnect wallet and clear stored data
|
|
*/
|
|
async disconnectWallet(): Promise<void> {
|
|
// Clear any existing delegations when disconnecting
|
|
this.keyDelegation.clearDelegation();
|
|
this.walletService.clearDelegation('bitcoin');
|
|
this.walletService.clearDelegation('ethereum');
|
|
|
|
// Clear stored user data
|
|
this.clearStoredUser();
|
|
}
|
|
|
|
/**
|
|
* Clear delegation for current wallet
|
|
*/
|
|
clearDelegation(): void {
|
|
this.keyDelegation.clearDelegation();
|
|
}
|
|
|
|
/**
|
|
* Verify ordinal ownership for Bitcoin users or ENS ownership for Ethereum users
|
|
*/
|
|
async verifyOwnership(user: User): Promise<AuthResult> {
|
|
try {
|
|
if (user.walletType === 'bitcoin') {
|
|
return await this.verifyBitcoinOrdinal(user);
|
|
} else if (user.walletType === 'ethereum') {
|
|
return await this.verifyEthereumENS(user);
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: 'Unknown wallet type'
|
|
};
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to verify ownership'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify Bitcoin Ordinal ownership
|
|
*/
|
|
private async verifyBitcoinOrdinal(user: User): Promise<AuthResult> {
|
|
// TODO: revert when the API is ready
|
|
// const response = await this.ordinalApi.getOperatorDetails(user.address);
|
|
// const hasOperators = response.has_operators;
|
|
const hasOperators = true;
|
|
|
|
const updatedUser = {
|
|
...user,
|
|
ordinalOwnership: hasOperators,
|
|
lastChecked: Date.now(),
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
user: updatedUser
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verify Ethereum ENS ownership
|
|
*/
|
|
private async verifyEthereumENS(user: User): Promise<AuthResult> {
|
|
try {
|
|
// Get wallet info with ENS resolution
|
|
const walletInfo = await this.walletService.getWalletInfo();
|
|
|
|
const hasENS = !!(walletInfo?.ensName);
|
|
const ensName = walletInfo?.ensName;
|
|
|
|
const updatedUser = {
|
|
...user,
|
|
ensOwnership: hasENS,
|
|
ensName: ensName,
|
|
lastChecked: Date.now(),
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
user: updatedUser
|
|
};
|
|
} catch (error) {
|
|
console.error('Error verifying ENS ownership:', error);
|
|
|
|
// Fall back to no ENS ownership on error
|
|
const updatedUser = {
|
|
...user,
|
|
ensOwnership: false,
|
|
ensName: undefined,
|
|
lastChecked: Date.now(),
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
user: updatedUser
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up key delegation for the user
|
|
*/
|
|
async delegateKey(user: User, duration: DelegationDuration = '7days'): Promise<AuthResult> {
|
|
try {
|
|
const walletType = user.walletType;
|
|
const isAvailable = this.walletService.isWalletAvailable(walletType);
|
|
|
|
if (!isAvailable) {
|
|
return {
|
|
success: false,
|
|
error: `${walletType} wallet is not available or connected. Please ensure it is connected.`
|
|
};
|
|
}
|
|
|
|
const success = await this.walletService.createKeyDelegation(walletType, duration);
|
|
|
|
if (!success) {
|
|
return {
|
|
success: false,
|
|
error: 'Failed to create key delegation'
|
|
};
|
|
}
|
|
|
|
// Get delegation status to update user
|
|
const delegationStatus = this.walletService.getDelegationStatus(walletType);
|
|
|
|
// Get the actual browser public key from the delegation
|
|
const browserPublicKey = this.keyDelegation.getBrowserPublicKey();
|
|
|
|
const updatedUser = {
|
|
...user,
|
|
browserPubKey: browserPublicKey || undefined,
|
|
delegationSignature: delegationStatus.isValid ? 'valid' : undefined,
|
|
delegationExpiry: delegationStatus.timeRemaining ? Date.now() + delegationStatus.timeRemaining : undefined,
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
user: updatedUser
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to delegate key'
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Check if delegation is valid
|
|
*/
|
|
isDelegationValid(): boolean {
|
|
// Only check the currently connected wallet type
|
|
const activeWalletType = this.getActiveWalletType();
|
|
if (!activeWalletType) return false;
|
|
|
|
const status = this.walletService.getDelegationStatus(activeWalletType);
|
|
return status.isValid;
|
|
}
|
|
|
|
/**
|
|
* Get delegation time remaining
|
|
*/
|
|
getDelegationTimeRemaining(): number {
|
|
// Only check the currently connected wallet type
|
|
const activeWalletType = this.getActiveWalletType();
|
|
if (!activeWalletType) return 0;
|
|
|
|
const status = this.walletService.getDelegationStatus(activeWalletType);
|
|
return status.timeRemaining || 0;
|
|
}
|
|
|
|
/**
|
|
* Get current wallet info
|
|
*/
|
|
async getWalletInfo() {
|
|
// Use the wallet service to get detailed wallet info including ENS
|
|
return await this.walletService.getWalletInfo();
|
|
}
|
|
|
|
/**
|
|
* Load user from localStorage
|
|
*/
|
|
loadStoredUser(): User | null {
|
|
const storedUser = localStorage.getItem('opchan-user');
|
|
if (!storedUser) return null;
|
|
|
|
try {
|
|
const user = JSON.parse(storedUser);
|
|
const lastChecked = user.lastChecked || 0;
|
|
const expiryTime = 24 * 60 * 60 * 1000;
|
|
|
|
if (Date.now() - lastChecked < expiryTime) {
|
|
return user;
|
|
} else {
|
|
localStorage.removeItem('opchan-user');
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to parse stored user data", e);
|
|
localStorage.removeItem('opchan-user');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save user to localStorage
|
|
*/
|
|
saveUser(user: User): void {
|
|
localStorage.setItem('opchan-user', JSON.stringify(user));
|
|
}
|
|
|
|
/**
|
|
* Clear stored user data
|
|
*/
|
|
clearStoredUser(): void {
|
|
localStorage.removeItem('opchan-user');
|
|
}
|
|
}
|