Nekoton bridge for flutter

License: AGPLv3 Build & Test

This package based on flutter_rust_bridge generator.

Gettind Started as user ๐Ÿš€

Installation โš™๏ธ

Add flutter_nekoton_bridge to your pubspec.yaml:

dependencies:
  flutter_nekoton_bridge:

Logger ๐Ÿชต

The first thing you need to do is to setup logger. You can use any logger you want, but we recommend to use logging-based package. For example, you can check fancy_logger from Sparx wallet.

To setup logger you need to call setupLogger function from flutter_nekoton_bridge package. It's a good idea to provide mapping from LogLevel to your logger levels. For example, you can use LogLevel from logger package:

  /// The main log level map
  static final Map<Level, fnb.LogLevel?> _logMap = {
    Level.ALL: fnb.LogLevel.Trace,
    Level.FINEST: fnb.LogLevel.Trace,
    Level.FINER: fnb.LogLevel.Trace,
    Level.FINE: fnb.LogLevel.Debug,
    Level.CONFIG: fnb.LogLevel.Debug,
    Level.INFO: fnb.LogLevel.Info,
    Level.WARNING: fnb.LogLevel.Warn,
    Level.SEVERE: fnb.LogLevel.Error,
    Level.SHOUT: fnb.LogLevel.Error,
    Level.OFF: null,
  };

  Level _toLogLevel(fnb.LogLevel level) {
    return _logMap.keys.firstWhere((key) => _logMap[key] == level);
  }

Then you can use it in _logHandler callback:

  void _logHandler(fnb.LogEntry logEntry) {
    final logLevel = _toLogLevel(logEntry.level);

    _log.log(logLevel, '${logEntry.tag}: ${logEntry.msg}');
  }

And finally, you can setup logger in your app using this code:

   fnb.setupLogger(
      level: logLevel,
      mobileLogger: mobileLogger,
      logHandler: _logHandler,
   );

Where level is your default log level, mobileLogger is just a flag that you want to use mobile logger (if you want to use your own logger, you can set it to false) and ```logHandler` is a callback that will be called for each log entry.

Please note: you should setup logger before any other calls to bridge.

Bridge initialization ๐ŸŽฌ

To initialize bridge you need to call initRustToDartCaller function from the package. It will register all callbacks and initialize bridge. You should call it before any other calls to bridge (just call it right after logger setup).

  fnb.initRustToDartCaller();

It's a good idea to check how we do it in Sparx wallet in NekotonRepository.

Bridge usage ๐Ÿ“ˆ

Fixed point numbers ๐Ÿ”

We don't used Fixed package in this package because in the most cases we don't have scale digits in our amounts ยฏ_(ใƒ„)_/ยฏ.

But! We highly recommend to use it package for fixed point numbers in your app because you have to have scale digits in your amounts for correct calculations and representation. So, tt's a good idea to use it in your app!

Fixed allows you to store and manipulate fixed point numbers. Fixed point numbers are numbers that have a fixed number of digits after the decimal point. You shouldn't use floating point numbers for financial calculations because they are not precise enough. Using strings ๐Ÿฉผ for storing fixed point is also not a good idea because it's not efficient and can produce bugs due to decimal separator difference (, or .) and can't be used in calculations. Fixed point numbers are precise and can be used for financial calculations.

For example:

Fixed.fromInt(1234, scale: 3); // == 1.234

Fixed.fromBigInt(BigInt.from(1234), scale: 3); // == 1.234

final t1 = Fixed.fromDecimal(Decimal.fromInt(1), scale: 2); // == 1.00

final t3 = Fixed.parse('1.234', scale: 2); // == 1.23, scale: 2

Please note: this is the least desireable method as it can introduce rounding errors:

final t2 = Fixed.fromNum(1.234, scale: 3); // == 1.234

Money ๐Ÿ’ฐ

Also we recommend to use money2 package for money calculations. It's a good idea to use it for all money calculations in your app.

Please note that due to found bug we highly recommend to use money2_improver for parsing amounts from strings. Also this package contains some useful extensions for money2.

Getting Started as hacker ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป

You should start installing the main things:

Then your adventure continues by installing dependencies:

cargo install flutter_rust_bridge_codegen
cargo install cargo-xcode
rustup target add \
    aarch64-linux-android \
    armv7-linux-androideabi \
    x86_64-linux-android \
    i686-linux-android
cargo install cargo-ndk --version 2.6.0
rustup target add aarch64-apple-ios x86_64-apple-ios
rustup target add aarch64-apple-ios-sim
dart pub global activate melos

At this point, all preparations should be completed and we can start compiling the library.

Melos magic ๐Ÿช„

Using melos makes it very easy to work with the project, so enjoy.

You can run any job interactively run running melos run and selecting needed case or directly (e.g. melos run test).

Bootstrap ๐Ÿ

Melos takes care about dependencies of all packages, including managing of local-generated library version. So, just run:

melos bs

Codegen ๐Ÿฆพ

This thing will run all code generators for all packages:

$ melos run codegen

Build ๐Ÿช›

The library consists of dart files and platform-specific binaries. pub.dev has a hard 100 MB upload limit, so we can't distribute platform-specific (they can be really huge!) this way. So, we should use something like Github releases for distribute it. Commands below will build everything for two platforms:

melos run build:apple
melos run build:android

# or just
melos build

You can find platform-build/NekotonBridge.xcframework.zip and platform-build/android.tar.gz in case of success. Rust build cache will be in target/ directory.

Clean up ๐Ÿงน

Just run commands below to clean all, including build directories and flutter projects.

melos clean

Example ๐Ÿคก

You can find dumbest example in packages/flutter_nekoton_bridge/example. During build scripts will copy platform-specific binaries from platform-build/ folder, or download prebuild binaries from github (if you don't build binaries locally).

You can open the project in Android studio or Visual studio code, or even build and run it using flutter run.

iOS build ๏ฃฟ

During build scripts copies platform-specific binaries to packages/flutter_nekoton_bridge/ios/Frameworks/netokon_bridge*.zip and unpack it to packages/flutter_nekoton_bridge/ios/Frameworks/NekotonBridge.xcframework/. It's a good idea to check this things if something went wrong.

Android build ๐Ÿค–

During build scripts copies platform-specific binaries to packages/flutter_nekoton_bridge/android/netokon_bridge.tar.gz and unpack it to packages/flutter_nekoton_bridge/android/src/main/jniLibs/. It's also a good idea to check this things if something went wrong.

Tests โœ”๏ธ

You can run dart, flutter and integration tests: melos run test:dart, melos run test:flutter or melos run test:integration. You can also run all tests at one by running melos run test.

You need to run emulator/simulator to complete melos run test:integration

Disabling flaky tests ๐Ÿ’ฉ

Yu can disable test or even test group by adding

skip: skipBecauseFlaky()

It detects current OS and skips test depending on FLUTTER_TEST_SKIP_FLAKY_* environment variable that can be set in CI. Currently it supports:

  • Android: FLUTTER_TEST_SKIP_FLAKY_ANDROID
  • iOS: FLUTTER_TEST_SKIP_FLAKY_IOS

Code ๐Ÿ“Š

You can run code analysis: melos run analyze. It will analyze all dart code, including subpackages.

Code format ๐Ÿ—ƒ๏ธ

melos run check-format will check, melos run format will fix dart code formatting.

Rust code format and analysis ๐Ÿฆ€

melos run check-rust will ckeck and analyze rust code.

Prepare to commit ๐Ÿค๐Ÿป

melos run check-all will ckeck, analyze and run all tests. In future this thing will be in git pre-comit hook.

You need to run emulator/simulator to complete this command

Conventional Commits โค๏ธ

This magic will update version and build our library automatically using commit messages and tags. Conventional Commits is a lightweight convention on top of commit messages.

Version ๐Ÿท๏ธ

Package version control is done by melos. It runs by gh action 'create-release' melos version -a --yes.

HOW to write rust code for nekoton_bridge ๐Ÿฆ€

The first thing you need understand is because of frb ability to generate Dart code correctly only from single rust file, we must merge all necessary source code into single file.

Thanks to bin/merger.dart we can write code in separated modules as a usual code written in rust. But here's some nuances you should understand:

  1. All code that should be generated to Dart side must be located inside file with name pattern *api.rs, for example, logger_api.rs or models_api.rs. Code in this files will be merged into merged.rs file that is target for flutter_rust_bridge.
  2. You don't need to import local crates as pub. frb can look through local imports itself.
  3. Use pub for external structures as described in official doc of frb, but in bin/merger.dart whole hierarchy of crate will be converted to public if there is at least one pub import in any file.
  4. DO NOT PUT any structures inside *api.rs files because it will be copied to merged.rs file and you will see duplicate. Better to put it inside other files of module and import it inside *api.rs file.
  5. USE local imports starting with crate: crate::nekoton_wrapper::...
  6. If method can throw Error, then it should return anyhow::Result<T, anyhow::Error> where T is return type and error can be got by calling handle_error function.
  7. If you have problems with implementing dyn AnyTrait of nekoton, then you need to make a hack with self-wrapped trait with implemented UnwindSafe + RefUnwindSafe, see UnsignedMessageBoxTrait

Libraries

dynamic_value
example_related/abstract_caller
example_related/caller_test_class_wrapper
example_related/caller_test_class_wrapper.reflectable
example_related/caller_wrapper
example_related/caller_wrapper.reflectable
example_related/mega_struct
flutter_nekoton_bridge
log_entry
nekoton/core/accounts_storage/accounts_storage
nekoton/core/accounts_storage/accounts_storage_lib
nekoton/core/accounts_storage/models/account_to_add
nekoton/core/accounts_storage/models/additional_assets
nekoton/core/accounts_storage/models/assets_list
nekoton/core/accounts_storage/models/constants
nekoton/core/accounts_storage/models/depool_asset
nekoton/core/accounts_storage/models/multisig_type
nekoton/core/accounts_storage/models/token_wallet_asset
nekoton/core/accounts_storage/models/ton_wallet_asset
nekoton/core/accounts_storage/models/wallet_type
nekoton/core/core_lib
nekoton/core/generic_contract/generic_contract
nekoton/core/generic_contract/generic_contract.reflectable
nekoton/core/generic_contract/generic_contract_lib
nekoton/core/keystore/constants
nekoton/core/keystore/keystore
nekoton/core/keystore/keystore_lib
nekoton/core/keystore/models/key_store_entry
nekoton/core/models/account_status
nekoton/core/models/accounts_list
nekoton/core/models/address
nekoton/core/models/blockchain_config
nekoton/core/models/contract_state
nekoton/core/models/existing_contract
nekoton/core/models/expiration
nekoton/core/models/full_contract_state
nekoton/core/models/gen_timings
nekoton/core/models/internal_message
nekoton/core/models/last_transaction_id
nekoton/core/models/message
nekoton/core/models/models_lib
nekoton/core/models/on_message_expired_payload
nekoton/core/models/on_message_sent_payload
nekoton/core/models/on_state_changed_payload
nekoton/core/models/on_transactions_found_payload
nekoton/core/models/pending_transaction
nekoton/core/models/public_key
nekoton/core/models/raw_contract_state
nekoton/core/models/raw_transaction
nekoton/core/models/storage_fee_info
nekoton/core/models/subscription_handler_message
nekoton/core/models/transaction
nekoton/core/models/transaction_id
nekoton/core/models/transaction_with_data
nekoton/core/models/transactions_batch_info
nekoton/core/models/transactions_batch_type
nekoton/core/models/transactions_list
nekoton/core/refreshing_interface
nekoton/core/token_wallet/models/on_balance_changed_payload
nekoton/core/token_wallet/models/root_token_contract_details
nekoton/core/token_wallet/models/symbol
nekoton/core/token_wallet/models/token_incoming_transfer
nekoton/core/token_wallet/models/token_outgoing_transfer
nekoton/core/token_wallet/models/token_swap_back
nekoton/core/token_wallet/models/token_wallet_details
nekoton/core/token_wallet/models/token_wallet_transaction
nekoton/core/token_wallet/models/token_wallet_transaction_with_data
nekoton/core/token_wallet/models/token_wallet_version
nekoton/core/token_wallet/models/transfer_recipient
nekoton/core/token_wallet/token_wallet
nekoton/core/token_wallet/token_wallet.reflectable
nekoton/core/token_wallet/token_wallet_lib
nekoton/core/ton_wallet/models/de_pool_on_round_complete_notification
nekoton/core/ton_wallet/models/de_pool_receive_answer_notification
nekoton/core/ton_wallet/models/existing_wallet_info
nekoton/core/ton_wallet/models/known_payload
nekoton/core/ton_wallet/models/multisig_confirm_transaction
nekoton/core/ton_wallet/models/multisig_pending_transaction
nekoton/core/ton_wallet/models/multisig_send_transaction
nekoton/core/ton_wallet/models/multisig_submit_transaction
nekoton/core/ton_wallet/models/multisig_transaction
nekoton/core/ton_wallet/models/token_wallet_deployed_notification
nekoton/core/ton_wallet/models/ton_wallet_details
nekoton/core/ton_wallet/models/ton_wallet_transaction_with_data
nekoton/core/ton_wallet/models/transaction_additional_info
nekoton/core/ton_wallet/models/wallet_interaction_info
nekoton/core/ton_wallet/models/wallet_interaction_method
nekoton/core/ton_wallet/ton_wallet
nekoton/core/ton_wallet/ton_wallet.reflectable
nekoton/core/ton_wallet/ton_wallet_lib
nekoton/crypto/constants
nekoton/crypto/crypto_lib
nekoton/crypto/derived_key/derived_key_create_input
nekoton/crypto/derived_key/derived_key_create_input_derive
nekoton/crypto/derived_key/derived_key_create_input_import
nekoton/crypto/derived_key/derived_key_export_output
nekoton/crypto/derived_key/derived_key_export_params
nekoton/crypto/derived_key/derived_key_get_public_keys
nekoton/crypto/derived_key/derived_key_lib
nekoton/crypto/derived_key/derived_key_sign_params
nekoton/crypto/derived_key/derived_key_sign_params_by_account_id
nekoton/crypto/derived_key/derived_key_sign_params_by_public_key
nekoton/crypto/derived_key/derived_key_update_params
nekoton/crypto/derived_key/derived_key_update_params_change_password
nekoton/crypto/derived_key/derived_key_update_params_rename_key
nekoton/crypto/encrypted_key/encrypted_key_create_input
nekoton/crypto/encrypted_key/encrypted_key_export_output
nekoton/crypto/encrypted_key/encrypted_key_get_public_keys
nekoton/crypto/encrypted_key/encrypted_key_lib
nekoton/crypto/encrypted_key/encrypted_key_password
nekoton/crypto/encrypted_key/encrypted_key_update_params
nekoton/crypto/encrypted_key/encrypted_key_update_params_change_password
nekoton/crypto/encrypted_key/encrypted_key_update_params_rename
nekoton/crypto/ledger_key/ledger_key_create_input
nekoton/crypto/ledger_key/ledger_key_get_public_keys
nekoton/crypto/ledger_key/ledger_key_lib
nekoton/crypto/ledger_key/ledger_sign_input
nekoton/crypto/ledger_key/ledger_update_key_input
nekoton/crypto/ledger_key/ledger_update_key_input_rename
nekoton/crypto/mnemonic/mnemonic
nekoton/crypto/models/create_key_input
nekoton/crypto/models/encrypted_data
nekoton/crypto/models/encryption_algorithm
nekoton/crypto/models/export_key_input
nekoton/crypto/models/export_key_output
nekoton/crypto/models/get_public_keys
nekoton/crypto/models/keypair
nekoton/crypto/models/models_lib
nekoton/crypto/models/sign_input
nekoton/crypto/models/signed_message
nekoton/crypto/models/update_key_input
nekoton/crypto/password_cache/password
nekoton/crypto/password_cache/password_cache_behavior
nekoton/crypto/password_cache/password_cache_lib
nekoton/crypto/password_cache/password_explicit
nekoton/crypto/unsigned_message
nekoton/external/external_lib
nekoton/external/gql_connection
nekoton/external/gql_connection.reflectable
nekoton/external/jrpc_connection
nekoton/external/jrpc_connection.reflectable
nekoton/external/ledger_connection
nekoton/external/ledger_connection.reflectable
nekoton/external/models/gql_network_settings
nekoton/external/models/jrpc_network_settings
nekoton/external/models/ledger_signature_context
nekoton/external/models/proto_network_settings
nekoton/external/proto_connection
nekoton/external/proto_connection.reflectable
nekoton/external/storage
nekoton/external/storage.reflectable
nekoton/helpers/abi
nekoton/helpers/helpers_lib
nekoton/helpers/models/abi_param
nekoton/helpers/models/decoded_event
nekoton/helpers/models/decoded_input
nekoton/helpers/models/decoded_output
nekoton/helpers/models/decoded_transaction
nekoton/helpers/models/decoded_transaction_event
nekoton/helpers/models/execution_output
nekoton/helpers/models/function_call
nekoton/helpers/models/method_name
nekoton/helpers/models/tokens_object
nekoton/nekoton_lib
nekoton/transport/gql_transport
nekoton/transport/jrpc_transport
nekoton/transport/models/transport_type
nekoton/transport/models/tx_tree_simulation_error
nekoton/transport/models/tx_tree_simulation_error_item
nekoton/transport/models/tx_tree_simulation_error_type
nekoton/transport/proto_transport
nekoton/transport/transport
nekoton/transport/transport_lib
nekoton/utils
rust_to_dart/reflector
rust_to_dart/rust_to_dart_caller
tests_related/test_caller
tests_related/test_logger