// Wallet utility functions for Osmosis MCP server
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { toBech32, fromBech32 } from "@cosmjs/encoding";
import { Secp256k1, EnglishMnemonic, Bip39 } from "@cosmjs/crypto";
export interface WalletInfo {
address: string;
publicKey: string;
mnemonic?: string;
}
/**
* Generates a new wallet with mnemonic phrase
*/
export async function generateWallet(wordCount: number = 24, prefix: string = "osmo"): Promise<WalletInfo> {
try {
const validWordCounts = [12, 15, 18, 21, 24];
const validWordCount = validWordCounts.includes(wordCount) ? wordCount as 12 | 15 | 18 | 21 | 24 : 24;
const wallet = await DirectSecp256k1HdWallet.generate(validWordCount, {
prefix: prefix,
});
const [firstAccount] = await wallet.getAccounts();
return {
address: firstAccount.address,
publicKey: Buffer.from(firstAccount.pubkey).toString('hex'),
mnemonic: wallet.mnemonic
};
} catch (error) {
throw new Error(`Failed to generate wallet: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Restores a wallet from mnemonic phrase
*/
export async function restoreWalletFromMnemonic(
mnemonic: string,
prefix: string = "osmo",
accountIndex: number = 0
): Promise<WalletInfo> {
try {
// Validate mnemonic first
if (!validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic phrase');
}
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: prefix
});
const [firstAccount] = await wallet.getAccounts();
return {
address: firstAccount.address,
publicKey: Buffer.from(firstAccount.pubkey).toString('hex')
// Don't return mnemonic for security
};
} catch (error) {
throw new Error(`Failed to restore wallet: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Gets address from mnemonic without exposing private key
*/
export async function getAddressFromMnemonic(
mnemonic: string,
prefix: string = "osmo",
accountIndex: number = 0
): Promise<string> {
const walletInfo = await restoreWalletFromMnemonic(mnemonic, prefix, accountIndex);
return walletInfo.address;
}
/**
* Validates a BIP-39 mnemonic phrase
*/
export function validateMnemonic(mnemonic: string): boolean {
try {
const mnemonicChecked = new EnglishMnemonic(mnemonic);
return Bip39.decode(mnemonicChecked).length > 0;
} catch {
return false;
}
}
/**
* Validates an address for a given prefix
*/
export function validateAddress(address: string, expectedPrefix: string = "osmo"): boolean {
try {
const { prefix } = fromBech32(address);
return prefix === expectedPrefix;
} catch {
return false;
}
}
/**
* Derives address from public key
*/
export async function deriveAddressFromPubkey(publicKeyHex: string, prefix: string = "osmo"): Promise<string> {
try {
const publicKey = Buffer.from(publicKeyHex, 'hex');
if (publicKey.length !== 33) {
throw new Error('Invalid public key length. Expected 33 bytes for compressed secp256k1 public key');
}
// For now, return a placeholder - proper implementation would require additional crypto operations
// This would need proper hash160 operation on the public key
throw new Error('Address derivation from public key not yet implemented');
} catch (error) {
throw new Error(`Failed to derive address: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Generates a deterministic wallet for testing (INSECURE - for development only)
*/
export async function generateTestWallet(seed: string = "test", prefix: string = "osmo"): Promise<WalletInfo> {
// Create deterministic entropy from seed (INSECURE)
const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
console.warn('⚠️ WARNING: generateTestWallet creates deterministic wallets for testing only. Never use in production!');
return await restoreWalletFromMnemonic(testMnemonic, prefix);
}