git / moonbite-network / mobile/test/wallet_test.dart · raw

import 'package:flutter_test/flutter_test.dart';import 'package:moonbite_mobile/wallet/moonbite_network.dart';import 'package:moonbite_mobile/wallet/hd_wallet_service.dart';/// Standard BIP39 test vector mnemonic (all-zero entropy). Deterministic, so/// the derived addresses below are fixed for a given set of network params.const _testVectorMnemonic =    'abandon abandon abandon abandon abandon abandon '    'abandon abandon abandon abandon abandon about';void main() {  group('MoonBiteNetwork params', () {    test('mainnet uses moon bech32 + production version bytes', () {      final n = MoonBiteNetwork.mainnet.coins;      expect(n.bech32, 'moon');      expect(n.pubKeyHash, 50); // base58 P2PKH addresses start with 'M'      expect(n.scriptHash, 5);      expect(n.wif, 176);      expect(n.bip32.public, 0x0488b21e);      expect(n.bip32.private, 0x0488ade4);    });    test('testnet uses tmoon bech32 + testnet version bytes', () {      final n = MoonBiteNetwork.testnet.coins;      expect(n.bech32, 'tmoon');      expect(n.pubKeyHash, 111);      expect(n.scriptHash, 196);      expect(n.wif, 239);      expect(n.bip32.public, 0x043587cf);      expect(n.bip32.private, 0x04358394);    });    test('regtest uses rmoon bech32', () {      expect(MoonBiteNetwork.regtest.coins.bech32, 'rmoon');    });    test('byId round-trips and defaults to mainnet', () {      expect(MoonBiteNetwork.byId('testnet').isTestnet, isTrue);      expect(MoonBiteNetwork.byId('mainnet').isTestnet, isFalse);      expect(MoonBiteNetwork.byId('anything-else').id, 'mainnet');    });  });  group('Mnemonic', () {    final svc = HdWalletService(MoonBiteNetwork.testnet);    test('generates a valid 12-word phrase by default', () {      final m = svc.generateMnemonic();      expect(m.split(' ').length, 12);      expect(svc.validateMnemonic(m), isTrue);    });    test('generates a valid 24-word phrase at 256 bits', () {      final m = svc.generateMnemonic(strengthBits: 256);      expect(m.split(' ').length, 24);      expect(svc.validateMnemonic(m), isTrue);    });    test('rejects a tampered phrase', () {      expect(svc.validateMnemonic('not a real mnemonic phrase at all'),          isFalse);    });    test('tolerates surrounding whitespace', () {      expect(svc.validateMnemonic('  $_testVectorMnemonic  '), isTrue);    });  });  group('Derivation — testnet', () {    final svc = HdWalletService(MoonBiteNetwork.testnet);    test('uses BIP84 path with testnet coin type 1', () {      expect(svc.accountPath, "m/84'/1'/0'/0");      expect(svc.derivationPathForIndex(3), "m/84'/1'/0'/0/3");    });    test('produces a tmoon1 bech32 receive address', () {      final acct = svc.deriveAccount(_testVectorMnemonic);      expect(acct.bech32Address.startsWith('tmoon1'), isTrue,          reason: 'got ${acct.bech32Address}');      // Same pubkey hash as the pre-relaunch tbig1 vector, re-encoded with      // the production HRP via wallet.py's _segwit_address_from_pubkey_hash.      expect(acct.bech32Address,          'tmoon1q6rz28mcfaxtmd6v789l9rrlrusdprr9pv34j7r');      expect(acct.publicKeyHex.length, 66); // 33-byte compressed pubkey      expect(acct.wif, isNotEmpty);      expect(acct.derivationPath, "m/84'/1'/0'/0/0");    });    test('is deterministic for the same mnemonic + index', () {      final a = svc.deriveAccount(_testVectorMnemonic);      final b = svc.deriveAccount(_testVectorMnemonic);      expect(a.bech32Address, b.bech32Address);      expect(a.wif, b.wif);      expect(a.publicKeyHex, b.publicKeyHex);    });    test('different indexes yield different addresses', () {      final a0 = svc.deriveAccount(_testVectorMnemonic, index: 0);      final a1 = svc.deriveAccount(_testVectorMnemonic, index: 1);      expect(a0.bech32Address, isNot(a1.bech32Address));      expect(a0.wif, isNot(a1.wif));    });    test('deriveAccounts range matches single derivations', () {      final batch = svc.deriveAccounts(_testVectorMnemonic, count: 3);      expect(batch.length, 3);      for (var i = 0; i < 3; i++) {        final single = svc.deriveAccount(_testVectorMnemonic, index: i);        expect(batch[i].bech32Address, single.bech32Address);        expect(batch[i].index, i);      }    });    test('rejects an invalid mnemonic on derive', () {      expect(() => svc.deriveAccount('totally invalid words'),          throwsArgumentError);    });  });  group('Derivation — mainnet', () {    final svc = HdWalletService(MoonBiteNetwork.mainnet);    test('uses BIP84 path with mainnet coin type 2', () {      expect(svc.accountPath, "m/84'/2'/0'/0");    });    test('produces a moon1 bech32 receive address', () {      final acct = svc.deriveAccount(_testVectorMnemonic);      expect(acct.bech32Address.startsWith('moon1'), isTrue,          reason: 'got ${acct.bech32Address}');    });    test('matches the web wallet derivation (wallet.py / moonbite-address.js)',        () {      // Authoritative vector: wallet.derive_from_seed_phrase(<abandon x11 about>)      // in the Python mirror, which walks m/84'/2'/0'/0/0 and encodes      // P2WPKH over the compressed pubkey with the "moon" HRP.      final acct = svc.deriveAccount(_testVectorMnemonic);      expect(acct.publicKeyHex,          '02e49c9b9b5d0f127235dc26a0c252814c52fb333d651a946773f59d72c2da9904');      expect(acct.bech32Address,          'moon1qjmxnz78nmc8nq77wuxh25n2es7rzm5c2e73r0j');    });    test('mainnet and testnet derive different addresses from same seed', () {      final mainAddr = svc.deriveAccount(_testVectorMnemonic).bech32Address;      final testAddr = HdWalletService(MoonBiteNetwork.testnet)          .deriveAccount(_testVectorMnemonic)          .bech32Address;      expect(mainAddr, isNot(testAddr));    });  });}