2025-08-06 17:21:56 +05:30
|
|
|
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
2025-08-05 10:51:21 +05:30
|
|
|
import { KeyDelegation } from '../signatures/key-delegation';
|
2025-08-06 17:21:56 +05:30
|
|
|
import { AppKit } from '@reown/appkit';
|
|
|
|
|
import { ChainNamespace } from '@reown/appkit-common';
|
|
|
|
|
import { Provider} from '@reown/appkit-controllers';
|
2025-08-05 10:51:21 +05:30
|
|
|
|
|
|
|
|
export class ReOwnWalletService {
|
|
|
|
|
private keyDelegation: KeyDelegation;
|
|
|
|
|
private bitcoinAccount?: UseAppKitAccountReturn;
|
|
|
|
|
private ethereumAccount?: UseAppKitAccountReturn;
|
2025-08-06 17:21:56 +05:30
|
|
|
private appKit?: AppKit;
|
2025-08-05 10:51:21 +05:30
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.keyDelegation = new KeyDelegation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set account references from AppKit hooks
|
|
|
|
|
*/
|
|
|
|
|
setAccounts(bitcoinAccount: UseAppKitAccountReturn, ethereumAccount: UseAppKitAccountReturn) {
|
|
|
|
|
this.bitcoinAccount = bitcoinAccount;
|
|
|
|
|
this.ethereumAccount = ethereumAccount;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
/**
|
|
|
|
|
* Set the AppKit instance for accessing adapters
|
|
|
|
|
*/
|
|
|
|
|
setAppKit(appKit: AppKit) {
|
|
|
|
|
this.appKit = appKit;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 10:51:21 +05:30
|
|
|
/**
|
|
|
|
|
* Check if a wallet type is available and connected
|
|
|
|
|
*/
|
|
|
|
|
isWalletAvailable(walletType: 'bitcoin' | 'ethereum'): boolean {
|
|
|
|
|
if (walletType === 'bitcoin') {
|
2025-08-06 17:21:56 +05:30
|
|
|
return this.bitcoinAccount?.isConnected ?? false;
|
2025-08-05 10:51:21 +05:30
|
|
|
} else {
|
2025-08-06 17:21:56 +05:30
|
|
|
return this.ethereumAccount?.isConnected ?? false;
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Get the active account based on wallet type
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
private getActiveAccount(walletType: 'bitcoin' | 'ethereum'): UseAppKitAccountReturn | undefined {
|
|
|
|
|
return walletType === 'bitcoin' ? this.bitcoinAccount : this.ethereumAccount;
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Get the active address for a given wallet type
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
getActiveAddress(walletType: 'bitcoin' | 'ethereum'): string | undefined {
|
|
|
|
|
const account = this.getActiveAccount(walletType);
|
|
|
|
|
return account?.address;
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Get the appropriate namespace for the wallet type
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
private getNamespace(walletType: 'bitcoin' | 'ethereum'): ChainNamespace {
|
|
|
|
|
return walletType === 'bitcoin' ? 'bip122' : 'eip155';
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Sign a message using the appropriate adapter
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
async signMessage(messageBytes: Uint8Array, walletType: 'bitcoin' | 'ethereum'): Promise<string> {
|
|
|
|
|
if (!this.appKit) {
|
|
|
|
|
throw new Error('AppKit instance not set. Call setAppKit() first.');
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
const account = this.getActiveAccount(walletType);
|
|
|
|
|
if (!account?.address) {
|
|
|
|
|
throw new Error(`No ${walletType} wallet connected`);
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
const namespace = this.getNamespace(walletType);
|
2025-08-05 10:51:21 +05:30
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
// 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}`);
|
|
|
|
|
}
|
2025-08-05 10:51:21 +05:30
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
// 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'}`);
|
|
|
|
|
}
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Create a key delegation for the connected wallet
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
async createKeyDelegation(walletType: 'bitcoin' | 'ethereum'): 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.keyDelegation.generateKeypair();
|
|
|
|
|
|
|
|
|
|
// Create delegation message with expiry
|
|
|
|
|
const expiryTimestamp = Date.now() + (24 * 60 * 60 * 1000); // 24 hours
|
|
|
|
|
const delegationMessage = this.keyDelegation.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
|
|
|
|
|
const delegationInfo = this.keyDelegation.createDelegation(
|
|
|
|
|
account.address,
|
|
|
|
|
signature,
|
|
|
|
|
keypair.publicKey,
|
|
|
|
|
keypair.privateKey,
|
|
|
|
|
24, // 24 hours
|
|
|
|
|
walletType
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.keyDelegation.storeDelegation(delegationInfo);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Error creating key delegation for ${walletType}:`, error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Sign a message using the delegated key (if available) or fall back to wallet signing
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
async signMessageWithDelegation(messageBytes: Uint8Array, walletType: 'bitcoin' | 'ethereum'): Promise<string> {
|
|
|
|
|
const account = this.getActiveAccount(walletType);
|
|
|
|
|
if (!account?.address) {
|
|
|
|
|
throw new Error(`No ${walletType} wallet connected`);
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
// Check if we have a valid delegation for this specific wallet
|
|
|
|
|
if (this.keyDelegation.isDelegationValid(account.address, walletType)) {
|
|
|
|
|
// Use delegated key for signing
|
|
|
|
|
const messageString = new TextDecoder().decode(messageBytes);
|
|
|
|
|
const signature = this.keyDelegation.signMessage(messageString);
|
|
|
|
|
|
|
|
|
|
if (signature) {
|
|
|
|
|
return signature;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-05 10:51:21 +05:30
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
// Fall back to wallet signing
|
|
|
|
|
return this.signMessage(messageBytes, walletType);
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Get delegation status for the connected wallet
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
getDelegationStatus(walletType: 'bitcoin' | 'ethereum'): {
|
|
|
|
|
hasDelegation: boolean;
|
|
|
|
|
isValid: boolean;
|
|
|
|
|
timeRemaining?: number;
|
|
|
|
|
} {
|
|
|
|
|
const account = this.getActiveAccount(walletType);
|
|
|
|
|
const currentAddress = account?.address;
|
2025-08-05 10:51:21 +05:30
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
const hasDelegation = this.keyDelegation.retrieveDelegation() !== null;
|
|
|
|
|
const isValid = this.keyDelegation.isDelegationValid(currentAddress, walletType);
|
|
|
|
|
const timeRemaining = this.keyDelegation.getDelegationTimeRemaining();
|
2025-08-05 10:51:21 +05:30
|
|
|
|
2025-08-06 17:21:56 +05:30
|
|
|
return {
|
|
|
|
|
hasDelegation,
|
|
|
|
|
isValid,
|
|
|
|
|
timeRemaining: timeRemaining > 0 ? timeRemaining : undefined
|
|
|
|
|
};
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-06 17:21:56 +05:30
|
|
|
* Clear delegation for the connected wallet
|
2025-08-05 10:51:21 +05:30
|
|
|
*/
|
2025-08-06 17:21:56 +05:30
|
|
|
clearDelegation(walletType: 'bitcoin' | 'ethereum'): void {
|
|
|
|
|
this.keyDelegation.clearDelegation();
|
2025-08-05 10:51:21 +05:30
|
|
|
}
|
|
|
|
|
}
|