git / moonbite-network / mobile/lib/wallet/hd_wallet_service.dart · raw

import 'dart:typed_data';import 'package:bip39/bip39.dart' as bip39;import 'package:coinslib/coinslib.dart';import 'moonbite_network.dart';/// A single derived MoonBite key/address, ready to show, receive to, or spend.class MoonBiteAccount {  final int index;  final String derivationPath;  /// Native SegWit (bech32) address — the primary receive address ("moon1…").  final String bech32Address;  /// Legacy P2PKH address (starts with the pubKeyHash version byte).  final String legacyAddress;  /// Compressed public key (hex) — safe to share.  final String publicKeyHex;  /// WIF-encoded private key — SECRET. Only used locally for signing.  final String wif;  const MoonBiteAccount({    required this.index,    required this.derivationPath,    required this.bech32Address,    required this.legacyAddress,    required this.publicKeyHex,    required this.wif,  });}/// Non-custodial, on-device HD wallet for MoonBite.////// All key material is derived locally from a BIP39 mnemonic; nothing leaves/// the device. Addresses follow BIP84 (native SegWit, `moon1…`) using MoonBite's/// own network parameters ([MoonBiteNetwork]).////// Derivation path: `m/84'/<coinType>'/0'/0/<index>`///   - mainnet coinType 2 (Litecoin-lineage fork)///   - testnet coinType 1 (BIP44 test convention)class HdWalletService {  final MoonBiteNetwork network;  HdWalletService(this.network);  static const int _mainnetCoinType = 2;  static const int _testnetCoinType = 1;  int get _coinType =>      network.isTestnet ? _testnetCoinType : _mainnetCoinType;  /// BIP84 account-level path prefix for this network.  String get accountPath => "m/84'/$_coinType'/0'/0";  /// Full external-chain path for a receive [index].  String derivationPathForIndex(int index) => "$accountPath/$index";  // --- Mnemonic helpers -------------------------------------------------  /// Generates a fresh BIP39 mnemonic. 128 bits => 12 words, 256 => 24 words.  String generateMnemonic({int strengthBits = 128}) =>      bip39.generateMnemonic(strength: strengthBits);  /// True if [mnemonic] is a valid BIP39 phrase (words + checksum).  bool validateMnemonic(String mnemonic) =>      bip39.validateMnemonic(mnemonic.trim());  // --- Derivation -------------------------------------------------------  /// Root HD node for a mnemonic on this network.  HDWallet _root(String mnemonic, {String passphrase = ''}) {    final normalized = mnemonic.trim();    if (!bip39.validateMnemonic(normalized)) {      throw ArgumentError('Invalid BIP39 mnemonic');    }    final Uint8List seed =        bip39.mnemonicToSeed(normalized, passphrase: passphrase);    return HDWallet.fromSeed(seed, network: network.coins);  }  /// Derives the receive account at [index] (default 0) from [mnemonic].  MoonBiteAccount deriveAccount(    String mnemonic, {    int index = 0,    String passphrase = '',  }) {    final path = derivationPathForIndex(index);    final node = _root(mnemonic, passphrase: passphrase).derivePath(path);    final bech32 =        P2WPKH.fromPublicKey(node.pubKeyBytes).address(network.coins);    return MoonBiteAccount(      index: index,      derivationPath: path,      bech32Address: bech32,      legacyAddress: node.address,      publicKeyHex: node.pubKey,      wif: node.wif!,    );  }  /// Derives a contiguous range of accounts `[start, start+count)`.  List<MoonBiteAccount> deriveAccounts(    String mnemonic, {    int start = 0,    int count = 1,    String passphrase = '',  }) {    final normalized = mnemonic.trim();    if (!bip39.validateMnemonic(normalized)) {      throw ArgumentError('Invalid BIP39 mnemonic');    }    final seed = bip39.mnemonicToSeed(normalized, passphrase: passphrase);    final root = HDWallet.fromSeed(seed, network: network.coins);    return List<MoonBiteAccount>.generate(count, (i) {      final index = start + i;      final path = derivationPathForIndex(index);      final node = root.derivePath(path);      final bech32 =          P2WPKH.fromPublicKey(node.pubKeyBytes).address(network.coins);      return MoonBiteAccount(        index: index,        derivationPath: path,        bech32Address: bech32,        legacyAddress: node.address,        publicKeyHex: node.pubKey,        wif: node.wif!,      );    });  }}