Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
A Flutter Wallet for Real Unit Investors.

## Getting Started
Before getting started, please make sure you have the latest version of Flutter, golang and gomobile installed.
```shell
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
```

### 1. Generate translations

Expand All @@ -26,4 +31,4 @@ flutter pub get

```shell
flutter run
```
```
44 changes: 44 additions & 0 deletions assets/images/illustrations/bitbox_connect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions assets/images/illustrations/bitbox_connected.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
"buy_payment_not_possible": "Kauf momentan nicht möglich.",
"buy_payment_not_possible_description": "Es scheint ein Berechtigungsproblem vorzuliegen. Kontaktieren Sie bitte den Support für weitere Informationen.",
"buy_realu": "REALU kaufen",
"cancel": "Abbrechen",
"close": "Schließen",
"collect_interest": "Auszahlen",
"confirm": "Ausführen",
"connect_bitbox_content": "Bitte verbinden Sie Ihre BitBox02 mit Ihrem Smartphone.",
"connect_bitbox_content_ios": "Bitte verbinden Sie Ihre BitBox02 mit Ihrem Smartphone und aktivieren Sie zusätzlich Bluetooth.",
"connect_bitbox_title": "BitBox02 verbinden",
"connect_with": "Mit ${wallet} verbinden",
"connected_bitbox_content": "Bitte folgen Sie nun den Anweisungen auf Ihrer BitBox02.",
"connected_bitbox_title": "Verbindung erfolgreich",
"contact_support": "Support kontaktieren",
"copy_seed": "Seed kopieren",
"country": "Land",
Expand Down
6 changes: 6 additions & 0 deletions assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
"buy_payment_not_possible": "Purchase currently not possible.",
"buy_payment_not_possible_description": "There appears to be a permission issue. Please contact support for further information.",
"buy_realu": "Buy REALU",
"cancel": "Cancel",
"close": "Close",
"collect_interest": "Collect",
"confirm": "Confirm",
"connect_bitbox_content": "Please connect your BitBox02 with your Smartphone.",
"connect_bitbox_content_ios": "Please connect your BitBox02 with your Smartphone and activate Bluetooth.",
"connect_bitbox_title": "Connect BitBox02",
"connect_with": "Connect with ${wallet}",
"connected_bitbox_content": "Please follow the steps on your BitBox02.",
"connected_bitbox_title": "Connection successful",
"contact_support": "Contact support",
"copy_seed": "Copy seed",
"country": "Country",
Expand Down
28 changes: 17 additions & 11 deletions lib/di.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:get_it/get_it.dart';
import 'package:realunit_wallet/packages/hardware_wallet/bitbox.dart';
import 'package:realunit_wallet/packages/open_crypto_pay/open_crypto_pay_service.dart';
import 'package:realunit_wallet/packages/repository/asset_repository.dart';
import 'package:realunit_wallet/packages/repository/balance_repository.dart';
Expand Down Expand Up @@ -76,19 +77,23 @@ void setupRepositories() {
getIt.registerFactory(() => BalanceRepository(getIt<AppDatabase>()));
getIt.registerFactory(() => AssetRepository(getIt<AppDatabase>()));
getIt.registerFactory(() => NodeRepository(getIt<AppDatabase>()));
getIt
.registerFactory(() => TransactionRepository(getIt<AppDatabase>(), getIt<AssetRepository>()));
getIt.registerFactory(() =>
TransactionRepository(getIt<AppDatabase>(), getIt<AssetRepository>()));
}

void setupServices() {
getIt
.registerFactory(() => WalletService(getIt<WalletRepository>(), getIt<SettingsRepository>()));
getIt.registerSingleton(BalanceService(
getIt<BalanceRepository>(), getIt<AssetRepository>(), getIt<AppStore>()));

getIt.registerSingleton(
BalanceService(getIt<BalanceRepository>(), getIt<AssetRepository>(), getIt<AppStore>()));
getIt.registerSingleton(BitboxService());
getIt.registerFactory(() => WalletService(
getIt<BitboxService>(),
getIt<WalletRepository>(),
getIt<SettingsRepository>(),
));

getIt.registerFactory(() => TransactionHistoryService(
getIt<AppStore>(), getIt<AssetRepository>(), getIt<TransactionRepository>()));
getIt.registerFactory(() => TransactionHistoryService(getIt<AppStore>(),
getIt<AssetRepository>(), getIt<TransactionRepository>()));

getIt.registerFactory(() => OpenCryptoPayService());
getIt.registerFactory(() => DFXPriceService(getIt<AppStore>()));
Expand All @@ -97,8 +102,8 @@ void setupServices() {
getIt.registerFactory(() => DfxBrokerbotService(getIt<AppStore>()));

getIt.registerFactory(() => SettingsService(getIt<SettingsRepository>()));
getIt.registerFactory(
() => DFXService(getIt<AppStore>(), getIt<SettingsRepository>(), getIt<AssetRepository>()));
getIt.registerFactory(() => DFXService(getIt<AppStore>(),
getIt<SettingsRepository>(), getIt<AssetRepository>()));
}

void setupBlocs() {
Expand All @@ -112,4 +117,5 @@ void setupBlocs() {
));
}

Future<bool> _existsDatabaseFile() async => File(await AppDatabase.getDatabasePath()).exists();
Future<bool> _existsDatabaseFile() async =>
File(await AppDatabase.getDatabasePath()).exists();
3 changes: 2 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class _WalletAppState extends State<WalletApp> {
void _onDetached() => developer.log('detached');

void _onResumed() {
getIt<BalanceService>().updateERC20Balances(getIt<AppStore>().primaryAddress);
getIt<BalanceService>()
.updateERC20Balances(getIt<AppStore>().primaryAddress);
}

void _onInactive() => developer.log('inactive', name: 'AppLifecycleListener');
Expand Down
26 changes: 26 additions & 0 deletions lib/packages/hardware_wallet/bitbox.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
import 'package:realunit_wallet/packages/hardware_wallet/bitbox_credentials.dart';

class BitboxService {
BitboxService() {
bitboxManager = sdk.BitboxManager();
}

late sdk.BitboxManager bitboxManager;

Future<List<sdk.BitboxDevice>> getAllUsbDevices() => bitboxManager.devices;

BitboxCredentials getCredentials(String address) =>
BitboxCredentials(address)..setBitbox(bitboxManager);

Future<void> connectDevice(sdk.BitboxDevice device) async {
await bitboxManager.connect(device);
final didInit = await bitboxManager.initBitBox();

if (!didInit) throw Exception("Failed to init");

final didVerify = await bitboxManager.channelHashVerify();

if (!didVerify) throw Exception("Failed to verify");
}
}
78 changes: 78 additions & 0 deletions lib/packages/hardware_wallet/bitbox_credentials.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:async';
import 'dart:typed_data';

import 'package:bitbox_flutter/bitbox_manager.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';

class BitboxCredentials extends CredentialsWithKnownAddress {
final String _address;

BitboxManager? bitboxManager;
String? derivationPath;

BitboxCredentials(this._address);

@override
EthereumAddress get address => EthereumAddress.fromHex(_address);

void setBitbox(BitboxManager connection, [String? derivationPath_]) {
bitboxManager = connection;
derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
}

@override
MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");

@override
Future<MsgSignature> signToSignature(Uint8List payload,
Comment thread
konstantinullrich marked this conversation as resolved.
{int? chainId, bool isEIP1559 = false}) async {
if (bitboxManager == null) {
throw Exception("Bitbox not connected");
}

if (isEIP1559) payload = payload.sublist(1);
final sig = await bitboxManager!
.signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559);

final r = bytesToHex(sig.sublist(0, 32));
final s = bytesToHex(sig.sublist(32, 32 + 32));
final v = sig.last.toInt();

if (isEIP1559) {
return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v);
}

var truncChainId = chainId ?? 1;
while (truncChainId.bitLength > 32) {
truncChainId >>= 8;
}

final truncTarget = truncChainId * 2 + 35;

int parity = v;
if (truncTarget & 0xff == v) {
parity = 0;
} else if ((truncTarget + 1) & 0xff == v) {
parity = 1;
}

// https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;

return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
}

@override
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
if (isNotConnected) throw Exception("Bitbox not connected");
return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload);
}

@override
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");

bool get isNotConnected => bitboxManager == null;
}
11 changes: 7 additions & 4 deletions lib/packages/repository/wallet_repository.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import 'package:realunit_wallet/packages/storage/database.dart';
import 'package:realunit_wallet/packages/storage/wallet_storage.dart';
import 'package:realunit_wallet/packages/wallet/wallet.dart';

class WalletRepository {
final AppDatabase _appDatabase;

const WalletRepository(this._appDatabase);

Future<int> createWallet(String name, String seed) =>
_appDatabase.insertWallet(name, seed);
Future<int> createWallet(String name, WalletType type, String seed) =>
_appDatabase.insertWallet(name, seed, "", type.index);

Future<WalletInfo?> getWalletById(int id) =>
_appDatabase.getWalletById(id);
Future<int> createViewWallet(String name, WalletType type, String address) =>
_appDatabase.insertWallet(name, "", address, type.index);

Future<WalletInfo?> getWalletById(int id) => _appDatabase.getWalletById(id);

Future<void> deleteWallet(int id) => _appDatabase.deleteWallet(id);
}
2 changes: 1 addition & 1 deletion lib/packages/service/app_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class AppStore {
_nodes = await nodeRepository.allNodes;
}

String get primaryAddress => wallet.currentAccount.primaryAddress.address.hexEip55;
String get primaryAddress => wallet.currentAccount.primaryAddress.address.hex;

web3.Web3Client getClient(int chainId) {
final node = _nodes.firstWhere(
Expand Down
32 changes: 24 additions & 8 deletions lib/packages/service/wallet_service.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:realunit_wallet/packages/hardware_wallet/bitbox.dart';
import 'package:realunit_wallet/packages/repository/settings_repository.dart';
import 'package:realunit_wallet/packages/repository/wallet_repository.dart';
import 'package:realunit_wallet/packages/wallet/wallet.dart';

class WalletService {
final WalletRepository _repository;
final SettingsRepository _settingsRepository;
final BitboxService _bitboxService;

const WalletService(this._repository, this._settingsRepository);
const WalletService(this._bitboxService, this._repository, this._settingsRepository);

Future<Wallet> createWallet(String name) {
Future<SoftwareWallet> createSeedWallet(String name) {
final mnemonic = bip39.generateMnemonic();
return restoreWallet(name, mnemonic);
}

Future<Wallet> restoreWallet(String name, String seed) async {
final walletId = await _repository.createWallet(name, seed);
Future<BitboxWallet> createBitboxWallet(String name) async {
final address = await _bitboxService.bitboxManager.getETHAddress(1, "m/44'/60'/0'/0/0");
final walletId = await _repository.createViewWallet(name, WalletType.bitbox, address);
await _settingsRepository.saveCurrentWalletId(walletId);
return Wallet(walletId, name, seed);
return BitboxWallet(walletId, name, address, _bitboxService);
}

Future<Wallet> getWalletById(int id) async {
Future<SoftwareWallet> restoreWallet(String name, String seed) async {
final walletId = await _repository.createWallet(name, WalletType.software, seed);
await _settingsRepository.saveCurrentWalletId(walletId);
return SoftwareWallet(walletId, name, seed);
}

Future<AWallet> getWalletById(int id) async {
final result = (await _repository.getWalletById(id))!;
return Wallet(result.id, result.name, result.seed);
final walletType = WalletType.values[result.type];
switch (walletType) {
case WalletType.software:
return SoftwareWallet(result.id, result.name, result.seed);
case WalletType.bitbox:
return BitboxWallet(
result.id, result.name, result.address, _bitboxService);
}
}

Future<Wallet> getCurrentWallet() async {
Future<AWallet> getCurrentWallet() async {
final id = _settingsRepository.currentWalletId!;
return getWalletById(id);
}
Expand Down
6 changes: 2 additions & 4 deletions lib/packages/storage/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,15 @@ class AppDatabase extends _$AppDatabase {
: super(_openDatabase(encryptionPassword));

@override
int get schemaVersion => 2;
int get schemaVersion => 1;

@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from < 2) {
await m.createTable(keyValueCache);
}

},
);

Expand Down
12 changes: 9 additions & 3 deletions lib/packages/storage/wallet_storage.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:realunit_wallet/packages/storage/database.dart';
import 'package:drift/drift.dart';
import 'package:realunit_wallet/packages/storage/database.dart';

extension WalletStorage on AppDatabase {
Future<int> insertWallet(String name, String seed) => into(walletInfos)
.insert(WalletInfosCompanion.insert(name: name, seed: seed));
Future<int> insertWallet(
String name, String seed, String address, int walletType) =>
into(walletInfos).insert(WalletInfosCompanion.insert(
name: name, seed: seed, address: address, type: walletType));

Future<WalletInfo?> getWalletById(int id) =>
(select(walletInfos)..where((row) => row.id.equals(id)))
Expand Down Expand Up @@ -33,6 +35,10 @@ class WalletInfos extends Table {
TextColumn get name => text()();

TextColumn get seed => text()();

TextColumn get address => text()();

IntColumn get type => integer()();
}

@DataClassName("WalletAccountInfo")
Expand Down
8 changes: 4 additions & 4 deletions lib/packages/utils/device_info.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'dart:io';
import 'package:flutter/foundation.dart';

class DeviceInfo {
DeviceInfo._();

static DeviceInfo get instance => DeviceInfo._();

bool get isMobile => Platform.isAndroid || Platform.isIOS;
bool get isMobile => [TargetPlatform.android, TargetPlatform.iOS].contains(defaultTargetPlatform);

bool get isDesktop =>
Platform.isMacOS || Platform.isWindows || Platform.isLinux;
bool get isDesktop => [TargetPlatform.macOS, TargetPlatform.windows, TargetPlatform.linux]
.contains(defaultTargetPlatform);
}
Loading