├── .github └── workflows │ └── test-package.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── NOTES.md ├── README.md ├── analysis_options.yaml ├── assets └── bip39 │ └── english.txt ├── build.yaml ├── dart_test.yaml ├── example ├── dart_example.dart ├── example.md └── flutter_example │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle │ ├── assets │ └── images │ │ ├── 2.0x │ │ └── flutter_logo.png │ │ ├── 3.0x │ │ └── flutter_logo.png │ │ └── flutter_logo.png │ ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ ├── l10n.yaml │ ├── lib │ ├── generated │ │ ├── intl │ │ │ ├── messages_all.dart │ │ │ └── messages_en.dart │ │ └── l10n.dart │ ├── l10n │ │ └── intl_en.arb │ ├── main.dart │ └── src │ │ ├── app.dart │ │ ├── localization │ │ └── app_en.arb │ │ ├── providers.dart │ │ ├── settings │ │ ├── settings_controller.dart │ │ ├── settings_service.dart │ │ └── settings_view.dart │ │ ├── wallet │ │ ├── coingecko_price_service.dart │ │ ├── wallet_details_view.dart │ │ ├── wallet_list_view.dart │ │ ├── wallet_service.dart │ │ └── wallet_state_notifier.dart │ │ └── widgets │ │ ├── ada_shape_maker.dart │ │ ├── alert_dialog.dart │ │ ├── create_or_restore_wallet_form.dart │ │ ├── create_read_only_wallet_form.dart │ │ └── send_funds_form.dart │ ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ ├── pubspec.yaml │ ├── screenshots │ ├── FlutterSDK_DarkMode_MacOS.png │ ├── FlutterSDK_Drawer_iPadPro9_7-inch.png │ ├── FlutterSDK_ListWallets_iPodTouch7thGen.png │ └── FlutterSDK_Sliders_MacOS.png │ ├── test │ └── coingecko_price_service_itest.dart │ └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── cardano_wallet_sdk.dart └── src │ ├── address │ └── shelley_address.dart │ ├── asset │ └── asset.dart │ ├── blockchain │ ├── blockchain_adapter.dart │ ├── blockchain_adapter_factory.dart │ ├── blockchain_cache.dart │ └── blockfrost │ │ ├── blockfrost_api_key_auth.dart │ │ ├── blockfrost_blockchain_adapter.dart │ │ └── dio_call.dart │ ├── crypto │ ├── key_util.dart │ ├── mnemonic.dart │ ├── mnemonic_english.dart │ ├── mnemonic_validation.dart │ ├── sha512.dart │ └── sign_ed25519.dart │ ├── hd │ ├── hd_account.dart │ ├── hd_derivation_chain.dart │ ├── hd_icarus_key_derivation.dart │ ├── hd_master_key_generation.dart │ └── hd_shelley_key_derivation.dart │ ├── network │ ├── blockchain_explorer.dart │ ├── cardano_scan.dart │ └── network_id.dart │ ├── price │ ├── coingecko_price_service.dart │ └── price_service.dart │ ├── stake │ ├── stake_account.dart │ ├── stake_pool.dart │ └── stake_pool_metadata.dart │ ├── transaction │ ├── coin_selection.dart │ ├── fee_calculation_service.dart │ ├── min_fee_function.dart │ ├── model │ │ ├── bc_abstract.dart │ │ ├── bc_auxiliary_data.dart │ │ ├── bc_certificate.dart │ │ ├── bc_exception.dart │ │ ├── bc_plutus_data.dart │ │ ├── bc_pointer.dart │ │ ├── bc_protocol_parameters.dart │ │ ├── bc_redeemer.dart │ │ ├── bc_scripts.dart │ │ ├── bc_tx.dart │ │ ├── bc_tx_body_ext.dart │ │ └── bc_tx_ext.dart │ ├── policy.dart │ ├── transaction.dart │ └── tx_builder.dart │ ├── util │ ├── ada_formatter.dart │ ├── ada_time.dart │ ├── ada_types.dart │ ├── ada_validation.dart │ ├── bech32_validation.dart │ ├── bigint_parse.dart │ ├── blake2bhash.dart │ ├── codec.dart │ ├── list_ext.dart │ └── misc.dart │ └── wallet │ ├── impl │ ├── read_only_wallet_impl.dart │ ├── wallet_cache_memory.dart │ ├── wallet_impl.dart │ └── wallet_update.dart │ ├── read_only_wallet.dart │ ├── wallet.dart │ ├── wallet_builder.dart │ └── wallet_cache.dart ├── pubspec.yaml └── test ├── address └── shelley_address_test.dart ├── asset └── asset_test.dart ├── blockchain ├── blockfrost_blockchain_adapter_itest.dart ├── blockfrost_test_auth_interceptor.dart └── price_model.json ├── crypto ├── mnemonic_test.dart ├── mnemonic_validation_test.dart └── sign_ed25519_test.dart ├── data ├── metadata.json └── plutus-data.json ├── hd ├── hd_account_test.dart └── hd_icarus_key_derivation_test.dart ├── readme_doc_test.dart ├── transaction ├── coin_selection_test.dart ├── contract_call_test.dart ├── fee_calculation_service_test.dart ├── model │ ├── bc_auxiliary_data_test.dart │ ├── bc_certificate_test.dart │ ├── bc_plutus_data_test.dart │ ├── bc_redeemer_test.dart │ ├── bc_scripts_test.dart │ └── bc_tx_test.dart ├── policy_test.dart ├── transaction_builder_mock_test.dart ├── tx_builder_mock_test.dart └── tx_contract_itest.dart ├── util ├── ada_formatter_test.dart ├── ada_time_test.dart ├── ada_validation_test.dart ├── bech32_validation_test.dart ├── bigint_parse_test.dart └── codec_test.dart └── wallet ├── mock_wallet2_adapter_test.dart ├── mock_wallet_2.dart ├── mock_wallet_2.mocks.dart ├── read_only_wallet_itest.dart └── wallet_builder_itest.dart /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | on: push 3 | 4 | env: 5 | PUB_ENVIRONMENT: bot.github 6 | 7 | jobs: 8 | # Check code formatting and static analysis on a single OS (linux) 9 | # against Dart dev. 10 | analyze: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | sdk: [dev] 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - uses: subosito/flutter-action@v1.5.3 20 | with: 21 | sdk: ${{ matrix.sdk }} 22 | - id: install 23 | name: Install dependencies 24 | run: dart pub get 25 | - name: Check formatting 26 | run: dart format --output=none --set-exit-if-changed . 27 | if: always() && steps.install.outcome == 'success' 28 | - name: Analyze code 29 | run: dart analyze --fatal-infos 30 | if: always() && steps.install.outcome == 'success' 31 | - name: Runing unit and widget tests 32 | run: flutter test --coverage 33 | - name: Making sure that code coverage is at least 60 34 | uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 35 | with: 36 | min_coverage: 60 37 | - name: Uploading coverage to Codecov 38 | uses: codecov/codecov-action@v1 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} 41 | 42 | # Run tests on a matrix consisting of two dimensions: 43 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 44 | # 2. release channel: dev 45 | test: 46 | needs: analyze 47 | runs-on: ${{ matrix.os }} 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | # Add macos-latest and/or windows-latest if relevant for this package. 52 | os: [ubuntu-latest] 53 | sdk: [dev] 54 | steps: 55 | - uses: actions/checkout@v2 56 | - uses: dart-lang/setup-dart@v0.3 57 | with: 58 | sdk: ${{ matrix.sdk }} 59 | - id: install 60 | name: Install dependencies 61 | run: dart pub get 62 | - name: Run VM tests 63 | run: dart test --platform vm 64 | if: always() && steps.install.outcome == 'success' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | test/wallet_send_test.dart 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | .vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | build/ 34 | pubspec.lock 35 | coverage/ 36 | 37 | # Android related 38 | **/android/**/gradle-wrapper.jar 39 | **/android/.gradle 40 | **/android/captures/ 41 | **/android/gradlew 42 | **/android/gradlew.bat 43 | **/android/local.properties 44 | **/android/**/GeneratedPluginRegistrant.java 45 | 46 | # iOS/XCode related 47 | **/ios/**/*.mode1v3 48 | **/ios/**/*.mode2v3 49 | **/ios/**/*.moved-aside 50 | **/ios/**/*.pbxuser 51 | **/ios/**/*.perspectivev3 52 | **/ios/**/*sync/ 53 | **/ios/**/.sconsign.dblite 54 | **/ios/**/.tags* 55 | **/ios/**/.vagrant/ 56 | **/ios/**/DerivedData/ 57 | **/ios/**/Icon? 58 | **/ios/**/Pods/ 59 | **/ios/**/.symlinks/ 60 | **/ios/**/profile 61 | **/ios/**/xcuserdata 62 | **/ios/.generated/ 63 | **/ios/Flutter/App.framework 64 | **/ios/Flutter/Flutter.framework 65 | **/ios/Flutter/Flutter.podspec 66 | **/ios/Flutter/Generated.xcconfig 67 | **/ios/Flutter/app.flx 68 | **/ios/Flutter/app.zip 69 | **/ios/Flutter/flutter_assets/ 70 | **/ios/Flutter/flutter_export_environment.sh 71 | **/ios/ServiceDefinitions.json 72 | **/ios/Runner/GeneratedPluginRegistrant.* 73 | 74 | # Exceptions to above rules. 75 | !**/ios/**/default.mode1v3 76 | !**/ios/**/default.mode2v3 77 | !**/ios/**/default.pbxuser 78 | !**/ios/**/default.perspectivev3 79 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.0-alpha.12] - 2022-04-05 2 | 3 | * fix minor bugs 4 | * prepare for release 5 | 6 | ## [0.1.0-alpha.11] - 2022-04-05 7 | 8 | * fix signing bug 9 | * F5 prototype completion 10 | 11 | ## [0.1.0-alpha.10] - 2021-12-01 12 | 13 | * add screenshots 14 | 15 | ## [0.1.0-alpha.9] - 2021-12-01 16 | 17 | * add flutter and dart examples 18 | 19 | ## [0.1.0-alpha.8] - 2021-11-16 20 | 21 | * transactions submitting! 22 | 23 | ## [0.1.0-alpha.7] - 2021-11-08 24 | 25 | * transaction validation works 26 | * switched to lint 27 | * much clean-up and better documentation 28 | 29 | ## [0.1.0-alpha.6] - 2021-10-28 30 | 31 | * add coin selection 32 | * tx balance and change calculation 33 | * TransactionBuilder.buildAndSign working! 34 | * coin selection & buildAndSign tests 35 | 36 | ## [0.1.0-alpha.5] - 2021-10-14 37 | 38 | * document use-cases in readme 39 | 40 | ## [0.1.0-alpha.4] - 2021-08-05 41 | 42 | * add spending wallet 43 | * cbor encoding 44 | * API clean-up 45 | 46 | ## [0.1.0-alpha.3] - 2021-07-27 47 | 48 | * initial stake take pool and rewards support 49 | * epoch and slot to timestamp functions 50 | * bech32 validation 51 | * handle blockfrost network errors 52 | 53 | ## [0.1.0-alpha.2] - 2021-07-16 54 | 55 | * Asset support and transaction code improvements 56 | 57 | ## [0.1.0-alpha.1] - 2021-06-27 58 | 59 | * Initial release to support read-only wallets 60 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Flutter SDK Developer Notes 2 | 3 | ## BUILD RUNNER: 4 | ``` 5 | flutter packages pub run build_runner build 6 | ``` 7 | 8 | ## COMMIT BUG FIX BY NUMBER: 9 | ``` 10 | git commit -m "fix #xxx" 11 | ``` 12 | 13 | ## PUBLISHING STEPS: 14 | * update pubspec.yaml 15 | * update CHANGELOG.md 16 | * flutter clean 17 | * dart test -P itest 18 | * dart analyze . 19 | * dart format . 20 | * git commit -m "" 21 | * git push 22 | * flutter packages pub publish --dry-run 23 | 24 | ## MISC NOTES: 25 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | analyzer: 13 | exclude: 14 | - example/** 15 | - test/** 16 | 17 | linter: 18 | # The lint rules applied to this project can be customized in the 19 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 20 | # included above or to enable additional rules. A list of all available lints 21 | # and their documentation is published at 22 | # https://dart-lang.github.io/linter/lints/index.html. 23 | # 24 | # Instead of disabling a lint rule for the entire project in the 25 | # section below, it can also be suppressed for a single line of code 26 | # or a specific dart file by using the `// ignore: name_of_lint` and 27 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 28 | # producing the lint. 29 | rules: 30 | avoid_print: false # Uncomment to disable the `avoid_print` rule 31 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 32 | # Additional information about this file can be found at 33 | # https://dart.dev/guides/language/analysis-options 34 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | # Typically the builder key is just the package name, run 5 | # `dart run build_runner doctor` to check your config. 6 | freezed: 7 | generate_for: 8 | # Example glob for only the Dart files under `lib/models` 9 | - lib/src/transaction/cbor/*.dart -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | # blockfrost tests require authorization and access to the blockfrost service. 3 | blockfrost: 4 | # coingecko tests require access to the coingecko service. 5 | coingecko: 6 | 7 | presets: 8 | # to include integration tests run: dart test -P itest 9 | itest: 10 | filename: "*test.dart" 11 | -------------------------------------------------------------------------------- /example/example.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | Two example apps and a live demo are available to get you up to speed quickly with using the Cardano Wallet SDK: 3 | * A pure [Dart example](https://github.com/reaster/cardano_wallet_sdk/blob/main/example/dart_example.dart). 4 | * A multi-platform [Flutter example](https://github.com/reaster/cardano_wallet_sdk/tree/main/example/flutter_example). 5 | * A Live [Flutter Demonstration Wallet](https://flutter-cardano-wallet.web.app/) hosted on google cloud. -------------------------------------------------------------------------------- /example/flutter_example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # Required, but unique to each developer. 13 | /assets/res/blockfrost_project_id.txt 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .packages 33 | .pub-cache/ 34 | .pub/ 35 | /build/ 36 | 37 | # Web related 38 | lib/generated_plugin_registrant.dart 39 | 40 | # Symbolication related 41 | app.*.symbols 42 | 43 | # Obfuscation related 44 | app.*.map.json 45 | 46 | # Android Studio will place build artifacts here 47 | /android/app/debug 48 | /android/app/profile 49 | /android/app/release 50 | -------------------------------------------------------------------------------- /example/flutter_example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18116933e77adc82f80866c928266a5b4f1ed645 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/flutter_example/README.md: -------------------------------------------------------------------------------- 1 | # Flutter Example App 2 | 3 | This Flutter app demonstrates using the Cardano Wallet SDK to manage wallets, 4 | send payments, handle input validation, process errors, list transactions and 5 | verify them in a blockchain browser. 6 | It is a multi-platform app that has been tested on iOS, Android, macOS and the web. 7 | 8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | ## Getting Started 17 | 18 | This project requires that a testnet policy-id key from [BlockFrost](https://blockfrost.io/) 19 | be placed in the `assets/res` project folder (the same file required to run the integration tests): 20 | 21 | ``` 22 | flutter_example/assets/res/blockfrost_project_id.txt 23 | ``` 24 | 25 | For more general help getting started with Flutter, view the 26 | [online documentation](https://flutter.dev/docs), which offers tutorials, 27 | samples, guidance on mobile development, and a full API reference. 28 | 29 | ## Implementation 30 | 31 | This project started with the Flutter 2.5 skeleton template: 32 | ``` 33 | flutter create -t skeleton flutter_example 34 | ``` 35 | Which produced a multi-platform, [internationalization-ready](https://flutter.dev/docs/development/accessibility-and-localization/internationalization), [multi-page](https://docs.flutter.dev/development/ui/navigation) master-detail starting app. This code was combined with [Riverpod](https://riverpod.dev) to create a [reactive state management template app](https://github.com/reaster/skeleton_riverpod). Finally, adding the [Cardano Wallet SDK](https://pub.dev/packages/cardano_wallet_sdk) produced a robust, multi-account Cardano wallet app. 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/flutter_example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/flutter_example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.flutter_example" 47 | minSdkVersion 16 48 | targetSdkVersion 30 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/kotlin/com/example/flutter_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/flutter_example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/flutter_example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/flutter_example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/flutter_example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/flutter_example/assets/images/2.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/assets/images/2.0x/flutter_logo.png -------------------------------------------------------------------------------- /example/flutter_example/assets/images/3.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/assets/images/3.0x/flutter_logo.png -------------------------------------------------------------------------------- /example/flutter_example/assets/images/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/assets/images/flutter_logo.png -------------------------------------------------------------------------------- /example/flutter_example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - url_launcher (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | url_launcher: 14 | :path: ".symlinks/plugins/url_launcher/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 18 | url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649 19 | 20 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 21 | 22 | COCOAPODS: 1.11.2 23 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSApplicationQueriesSchemes 6 | 7 | https 8 | http 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | flutter_example 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | $(FLUTTER_BUILD_NAME) 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | $(FLUTTER_BUILD_NUMBER) 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UIViewControllerBasedStatusBarAppearance 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/flutter_example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/flutter_example/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/src/localization 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | -------------------------------------------------------------------------------- /example/flutter_example/lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:intl/intl.dart'; 15 | import 'package:intl/message_lookup_by_library.dart'; 16 | import 'package:intl/src/intl_helpers.dart'; 17 | 18 | import 'messages_en.dart' as messages_en; 19 | 20 | typedef LibraryLoader = Future Function(); 21 | Map _deferredLibraries = { 22 | 'en': () => Future.value(null), 23 | }; 24 | 25 | MessageLookupByLibrary? _findExact(String localeName) { 26 | switch (localeName) { 27 | case 'en': 28 | return messages_en.messages; 29 | default: 30 | return null; 31 | } 32 | } 33 | 34 | /// User programs should call this before using [localeName] for messages. 35 | Future initializeMessages(String localeName) async { 36 | var availableLocale = Intl.verifiedLocale( 37 | localeName, (locale) => _deferredLibraries[locale] != null, 38 | onFailure: (_) => null); 39 | if (availableLocale == null) { 40 | return Future.value(false); 41 | } 42 | var lib = _deferredLibraries[availableLocale]; 43 | await (lib == null ? Future.value(false) : lib()); 44 | initializeInternalMessageLookup(() => CompositeMessageLookup()); 45 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 46 | return Future.value(true); 47 | } 48 | 49 | bool _messagesExistFor(String locale) { 50 | try { 51 | return _findExact(locale) != null; 52 | } catch (e) { 53 | return false; 54 | } 55 | } 56 | 57 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 58 | var actualLocale = 59 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 60 | if (actualLocale == null) return null; 61 | return _findExact(actualLocale); 62 | } 63 | -------------------------------------------------------------------------------- /example/flutter_example/lib/generated/intl/messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = MessageLookup(); 16 | 17 | typedef MessageIfAbsent = String Function( 18 | String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'en'; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static _notInlinedMessages(_) => {}; 25 | } 26 | -------------------------------------------------------------------------------- /example/flutter_example/lib/generated/l10n.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | import 'package:flutter/material.dart'; 3 | import 'package:intl/intl.dart'; 4 | import 'intl/messages_all.dart'; 5 | 6 | // ************************************************************************** 7 | // Generator: Flutter Intl IDE plugin 8 | // Made by Localizely 9 | // ************************************************************************** 10 | 11 | // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars 12 | // ignore_for_file: join_return_with_assignment, prefer_final_in_for_each 13 | // ignore_for_file: avoid_redundant_argument_values 14 | 15 | class S { 16 | S(); 17 | 18 | static S? _current; 19 | 20 | static S get current { 21 | assert(_current != null, 22 | 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); 23 | return _current!; 24 | } 25 | 26 | static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); 27 | 28 | static Future load(Locale locale) { 29 | final name = (locale.countryCode?.isEmpty ?? false) 30 | ? locale.languageCode 31 | : locale.toString(); 32 | final localeName = Intl.canonicalizedLocale(name); 33 | return initializeMessages(localeName).then((_) { 34 | Intl.defaultLocale = localeName; 35 | final instance = S(); 36 | S._current = instance; 37 | 38 | return instance; 39 | }); 40 | } 41 | 42 | static S of(BuildContext context) { 43 | final instance = S.maybeOf(context); 44 | assert(instance != null, 45 | 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); 46 | return instance!; 47 | } 48 | 49 | static S? maybeOf(BuildContext context) { 50 | return Localizations.of(context, S); 51 | } 52 | } 53 | 54 | class AppLocalizationDelegate extends LocalizationsDelegate { 55 | const AppLocalizationDelegate(); 56 | 57 | List get supportedLocales { 58 | return const [ 59 | Locale.fromSubtags(languageCode: 'en'), 60 | ]; 61 | } 62 | 63 | @override 64 | bool isSupported(Locale locale) => _isSupported(locale); 65 | @override 66 | Future load(Locale locale) => S.load(locale); 67 | @override 68 | bool shouldReload(AppLocalizationDelegate old) => false; 69 | 70 | bool _isSupported(Locale locale) { 71 | for (var supportedLocale in supportedLocales) { 72 | if (supportedLocale.languageCode == locale.languageCode) { 73 | return true; 74 | } 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /example/flutter_example/lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/flutter_example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'src/providers.dart'; 8 | import 'src/app.dart'; 9 | 10 | void main() async { 11 | Logger.root.level = Level.FINE; // defaults to Level.INFO 12 | Logger.root.onRecord.listen((record) { 13 | // ignore: avoid_print 14 | print('${record.level.name}: ${record.time}: ${record.message}'); 15 | }); 16 | 17 | // Load the user's preferred theme while the splash screen is displayed. 18 | // This prevents a sudden theme change when the app is first displayed. 19 | await settingsController.loadSettings(); 20 | 21 | // Run the app and add a ProviderScope to hold the providers. 22 | runApp(const ProviderScope( 23 | child: MyApp(), 24 | )); 25 | } 26 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/app.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 6 | import 'package:flutter_localizations/flutter_localizations.dart'; 7 | import './wallet/wallet_details_view.dart'; 8 | import './wallet/wallet_list_view.dart'; 9 | import './settings/settings_view.dart'; 10 | import './providers.dart'; 11 | 12 | /// The Widget that configures your application. 13 | class MyApp extends StatelessWidget { 14 | const MyApp({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // The AnimatedBuilder Widget listens to the SettingsController for changes. 19 | // Whenever the user updates their settings, the MaterialApp is rebuilt. 20 | return AnimatedBuilder( 21 | animation: settingsController, 22 | builder: (BuildContext context, Widget? child) { 23 | return MaterialApp( 24 | // Providing a restorationScopeId allows the Navigator built by the 25 | // MaterialApp to restore the navigation stack when a user leaves and 26 | // returns to the app after it has been killed while running in the 27 | // background. 28 | restorationScopeId: 'app', 29 | 30 | debugShowCheckedModeBanner: false, 31 | 32 | // Provide the generated AppLocalizations to the MaterialApp. This 33 | // allows descendant Widgets to display the correct translations 34 | // depending on the user's locale. 35 | localizationsDelegates: const [ 36 | AppLocalizations.delegate, 37 | GlobalMaterialLocalizations.delegate, 38 | GlobalWidgetsLocalizations.delegate, 39 | GlobalCupertinoLocalizations.delegate, 40 | ], 41 | supportedLocales: const [ 42 | Locale('en', ''), // English, no country code 43 | ], 44 | 45 | // Use AppLocalizations to configure the correct application title 46 | // depending on the user's locale. 47 | // 48 | // The appTitle is defined in .arb files found in the localization 49 | // directory. 50 | onGenerateTitle: (BuildContext context) => 51 | AppLocalizations.of(context)!.appTitle, 52 | 53 | // Define a light and dark color theme. Then, read the user's 54 | // preferred ThemeMode (light, dark, or system default) from the 55 | // SettingsController to display the correct theme. 56 | theme: ThemeData(), 57 | darkTheme: ThemeData.dark(), 58 | themeMode: settingsController.themeMode, 59 | 60 | // Define a function to handle named routes in order to support 61 | // Flutter web url navigation and deep linking. 62 | onGenerateRoute: (RouteSettings routeSettings) { 63 | return MaterialPageRoute( 64 | settings: routeSettings, 65 | builder: (BuildContext context) { 66 | switch (routeSettings.name) { 67 | case SettingsView.routeName: 68 | return const SettingsView(); 69 | case WalletDetailsView.routeName: 70 | final args = routeSettings.arguments as String; 71 | return WalletDetailsView( 72 | key: ValueKey(args), 73 | walletId: args, 74 | ); 75 | case WalletListView.routeName: 76 | default: 77 | return const WalletListView(); 78 | } 79 | }, 80 | ); 81 | }, 82 | ); 83 | }, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/localization/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": "flutter_example", 3 | "@appTitle": { 4 | "description": "The title of the application" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/providers.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:riverpod/riverpod.dart'; 5 | import 'settings/settings_controller.dart'; 6 | import 'settings/settings_service.dart'; 7 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 8 | import './wallet/wallet_state_notifier.dart'; 9 | import './wallet/wallet_service.dart'; 10 | 11 | /// 12 | /// Declare services, controllers and providers globally. 13 | /// 14 | /// Note: This is just one of many ways services, controllers and providers can be 15 | /// orginized. Having all the state management declarations in one file 16 | /// simplfies the app at the expnse of modularity and testability. 17 | /// 18 | final _settingsService = SettingsService(); 19 | final settingsController = SettingsController(_settingsService); 20 | final settingsProvider = 21 | Provider((ref) => settingsController); 22 | 23 | final walletStateNotifier = 24 | WalletStateNotifier(WalletService(settingService: _settingsService)); 25 | final walletProvider = 26 | StateNotifierProvider>( 27 | (ref) => walletStateNotifier); 28 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/settings/settings_controller.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter/material.dart'; 5 | import './settings_service.dart'; 6 | 7 | /// A class that many Widgets can interact with to read user settings, update 8 | /// user settings, or listen to user settings changes. 9 | /// 10 | /// Controllers glue Data Services to Flutter Widgets. The SettingsController 11 | /// uses the SettingsService to store and retrieve user settings. 12 | class SettingsController with ChangeNotifier { 13 | SettingsController(this._settingsService); 14 | 15 | set adapterKey(String key) => _settingsService.adapterKey = key; 16 | String get adapterKey => _settingsService.adapterKey; 17 | 18 | // Make SettingsService a private variable so it is not used directly. 19 | final SettingsService _settingsService; 20 | 21 | // Make ThemeMode a private variable so it is not updated directly without 22 | // also persisting the changes with the SettingsService. 23 | late ThemeMode _themeMode; 24 | 25 | // Allow Widgets to read the user's preferred ThemeMode. 26 | ThemeMode get themeMode => _themeMode; 27 | 28 | /// Load the user's settings from the SettingsService. It may load from a 29 | /// local database or the internet. The controller only knows it can load the 30 | /// settings from the service. 31 | Future loadSettings() async { 32 | _themeMode = await _settingsService.themeMode(); 33 | await _settingsService.loadAdapterKeyAsset(); 34 | 35 | // Important! Inform listeners a change has occurred. 36 | notifyListeners(); 37 | } 38 | 39 | /// Update and persist the ThemeMode based on the user's selection. 40 | Future updateThemeMode(ThemeMode? newThemeMode) async { 41 | if (newThemeMode == null) return; 42 | 43 | // Dot not perform any work if new and old ThemeMode are identical 44 | if (newThemeMode == _themeMode) return; 45 | 46 | // Otherwise, store the new theme mode in memory 47 | _themeMode = newThemeMode; 48 | 49 | // Important! Inform listeners a change has occurred. 50 | notifyListeners(); 51 | 52 | // Persist the changes to a local database or the internet using the 53 | // SettingService. 54 | await _settingsService.updateThemeMode(newThemeMode); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/settings/settings_service.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:async' show Future; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart' show rootBundle; 7 | 8 | /// A service that stores and retrieves user settings. 9 | /// 10 | /// By default, this class does not persist user settings. If you'd like to 11 | /// persist the user settings locally, use the shared_preferences package. If 12 | /// you'd like to store settings on a web server, use the http package. 13 | class SettingsService { 14 | String adapterKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; 15 | 16 | /// Loads the User's preferred ThemeMode from local or remote storage. 17 | Future themeMode() async => ThemeMode.system; 18 | 19 | /// Persists the user's preferred ThemeMode to local or remote storage. 20 | Future updateThemeMode(ThemeMode theme) async { 21 | // Use the shared_preferences package to persist settings locally or the 22 | // http package to persist settings over the network. 23 | } 24 | 25 | Future loadAdapterKeyAsset() async { 26 | WidgetsFlutterBinding.ensureInitialized(); 27 | adapterKey = 28 | await rootBundle.loadString('assets/res/blockfrost_project_id.txt'); 29 | return adapterKey; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/settings/settings_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter/material.dart'; 5 | import '../providers.dart'; 6 | 7 | /// Displays the various settings that can be customized by the user. 8 | /// 9 | /// When a user changes a setting, the SettingsController is updated and 10 | /// Widgets that listen to the SettingsController are rebuilt. 11 | class SettingsView extends StatelessWidget { 12 | const SettingsView({Key? key}) : super(key: key); 13 | 14 | static const routeName = '/settings'; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // final keyController = TextEditingController(text: controller.adapterKey); 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: const Text('Settings'), 22 | ), 23 | body: Padding( 24 | padding: const EdgeInsets.all(16), 25 | // Glue the SettingsController to the theme selection DropdownButton. 26 | // 27 | // When a user selects a theme from the dropdown list, the 28 | // SettingsController is updated, which rebuilds the MaterialApp. 29 | child: 30 | Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ 31 | DropdownButton( 32 | // Read the selected themeMode from the controller 33 | value: settingsController.themeMode, 34 | // Call the updateThemeMode method any time the user selects a theme. 35 | onChanged: settingsController.updateThemeMode, 36 | items: const [ 37 | DropdownMenuItem( 38 | value: ThemeMode.system, 39 | child: Text('System Theme'), 40 | ), 41 | DropdownMenuItem( 42 | value: ThemeMode.light, 43 | child: Text('Light Theme'), 44 | ), 45 | DropdownMenuItem( 46 | value: ThemeMode.dark, 47 | child: Text('Dark Theme'), 48 | ) 49 | ], 50 | ), 51 | ])), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/wallet/wallet_state_notifier.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:oxidized/oxidized.dart'; 7 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 8 | import './wallet_service.dart'; 9 | 10 | /// 11 | /// WalletStateNotifier notifies UI widgets when the list of Wallet changes. 12 | /// 13 | class WalletStateNotifier extends StateNotifier> { 14 | final WalletService walletService; 15 | 16 | WalletStateNotifier(this.walletService) : super(walletService.wallets); 17 | 18 | /// Return stored list of ReadOnlyWallets. 19 | List get items => walletService.wallets; 20 | 21 | /// Create new Wallet and add to list. 22 | Result createNewWallet( 23 | {required BuildContext context, 24 | required String walletName, 25 | required List mnemonic}) { 26 | final result = walletService.createNewWallet(walletName, mnemonic); 27 | if (result.isOk()) { 28 | state = walletService.wallets; 29 | } 30 | return result; 31 | } 32 | 33 | // Map _reloadStatus = {}; 34 | 35 | /// Refresh all wallets 36 | Future> reloadAll() async { 37 | bool updated = false; 38 | for (final wallet in walletService.wallets) { 39 | final result = await wallet.update(); 40 | if (result.isErr()) { 41 | state = walletService.wallets; //trigger view refresh to stop spinners 42 | return Err(result.unwrapErr()); 43 | } else if (result.unwrap()) { 44 | updated = true; 45 | } 46 | } 47 | state = walletService.wallets; 48 | return Ok(updated); 49 | } 50 | 51 | /// Create new ReadOnlyWallet and add to list. 52 | Future> createReadOnlyWallet( 53 | {required BuildContext context, 54 | required String walletName, 55 | required ShelleyAddress stakeAddress}) async { 56 | final result = 57 | await walletService.createReadOnlyWallet(walletName, stakeAddress); 58 | if (result.isOk()) { 59 | state = walletService.wallets; 60 | } 61 | return result; 62 | } 63 | 64 | /// Create new Wallet and add to list. 65 | Future> restoreWallet( 66 | {required BuildContext context, 67 | required String walletName, 68 | required List mnemonic}) async { 69 | final result = await walletService.restoreWallet(walletName, mnemonic); 70 | if (result.isOk()) { 71 | state = walletService.wallets; 72 | } 73 | return result; 74 | } 75 | 76 | Future> sendAda({ 77 | required Wallet wallet, 78 | required AbstractAddress toAddress, 79 | required int lovelace, 80 | }) async { 81 | final result = await wallet.sendAda( 82 | toAddress: toAddress, lovelace: lovelace, logTx: true, logTxHex: true); 83 | if (result.isOk()) { 84 | state = walletService.wallets; 85 | } 86 | return result; 87 | } 88 | 89 | /// Remove Wallet from list. 90 | Result deleteWallet( 91 | {required BuildContext context, required ReadOnlyWallet wallet}) { 92 | final result = walletService.deleteWallet(walletId: wallet.walletId); 93 | if (result.isOk()) { 94 | state = walletService.wallets; 95 | } 96 | return result; 97 | } 98 | 99 | ReadOnlyWallet? findByWalletId(String walletId) => 100 | walletService.findByWalletId(walletId); 101 | } 102 | -------------------------------------------------------------------------------- /example/flutter_example/lib/src/widgets/alert_dialog.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | Future asyncAlertDialog( 7 | BuildContext context, 8 | String title, 9 | String content, 10 | ) async { 11 | return await showDialog( 12 | context: context, 13 | builder: (dialogContext) { 14 | final titleTheme = Theme.of(dialogContext) 15 | .textTheme 16 | .subtitle1! 17 | .apply(color: Colors.red); 18 | final bodyTheme = Theme.of(dialogContext).textTheme.bodyText1; 19 | 20 | return AlertDialog( 21 | content: SingleChildScrollView( 22 | child: Column( 23 | mainAxisSize: MainAxisSize.min, 24 | crossAxisAlignment: CrossAxisAlignment.center, 25 | children: [ 26 | Text(title, style: titleTheme), 27 | Padding( 28 | padding: const EdgeInsets.all(16.0), 29 | child: Text(content, style: bodyTheme), 30 | ), 31 | ], 32 | ), 33 | ), 34 | actions: [ 35 | ElevatedButton( 36 | child: const Center(child: Text('Ok')), 37 | onPressed: () => Navigator.of(dialogContext).pop(), 38 | ), 39 | ], 40 | ); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /example/flutter_example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import url_launcher_macos 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.13' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlutterMacOS (1.0.0) 3 | - url_launcher_macos (0.0.1): 4 | - FlutterMacOS 5 | 6 | DEPENDENCIES: 7 | - FlutterMacOS (from `Flutter/ephemeral`) 8 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 9 | 10 | EXTERNAL SOURCES: 11 | FlutterMacOS: 12 | :path: Flutter/ephemeral 13 | url_launcher_macos: 14 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 15 | 16 | SPEC CHECKSUMS: 17 | FlutterMacOS: 85f90bfb3f1703249cf1539e4dfbff31e8584698 18 | url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4 19 | 20 | PODFILE CHECKSUM: a884f6dd3f7494f3892ee6c81feea3a3abbf9153 21 | 22 | COCOAPODS: 1.11.2 23 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = flutter_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/flutter_example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/flutter_example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_example 2 | description: A new Flutter project. 3 | 4 | # Prevent accidental publishing to pub.dev. 5 | publish_to: "none" 6 | 7 | version: 1.1.0 8 | 9 | environment: 10 | sdk: ">=2.17.0 <3.0.0" 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_localizations: 15 | sdk: flutter 16 | cardano_wallet_sdk: 17 | path: ../../ 18 | flutter_riverpod: ^1.0.0 19 | oxidized: ^5.1.0 20 | flutter_slidable: ^1.1.0 21 | url_launcher: ^6.0.14 22 | coingecko_dart: ^0.1.0 23 | logging: ^1.0.2 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | flutter_lints: ^1.0.0 29 | 30 | flutter: 31 | uses-material-design: true 32 | 33 | # Enable generation of localized Strings from arb files. 34 | generate: true 35 | 36 | assets: 37 | # Add assets from the images directory to the application. 38 | - assets/images/ 39 | - assets/res/ 40 | -------------------------------------------------------------------------------- /example/flutter_example/screenshots/FlutterSDK_DarkMode_MacOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/screenshots/FlutterSDK_DarkMode_MacOS.png -------------------------------------------------------------------------------- /example/flutter_example/screenshots/FlutterSDK_Drawer_iPadPro9_7-inch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/screenshots/FlutterSDK_Drawer_iPadPro9_7-inch.png -------------------------------------------------------------------------------- /example/flutter_example/screenshots/FlutterSDK_ListWallets_iPodTouch7thGen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/screenshots/FlutterSDK_ListWallets_iPodTouch7thGen.png -------------------------------------------------------------------------------- /example/flutter_example/screenshots/FlutterSDK_Sliders_MacOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/screenshots/FlutterSDK_Sliders_MacOS.png -------------------------------------------------------------------------------- /example/flutter_example/test/coingecko_price_service_itest.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | @Tags(['coingecko']) 5 | 6 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 7 | import 'package:flutter_example/src/wallet/coingecko_price_service.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | void main() { 12 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 13 | Logger.root.onRecord.listen((record) { 14 | // ignore: avoid_print 15 | print('${record.level.name}: ${record.time}: ${record.message}'); 16 | }); 17 | final logger = Logger('CoingeckoPriceServiceITest'); 18 | PriceService service = CoingeckoPriceService(); 19 | group('test CoingeckoPriceService', () { 20 | // Future> currentPrice({String from, String to}); 21 | test( 22 | 'test currentPrice - calls https://api.coingecko.com/api/v3/simple/price?ids=cardano&vs_currencies=USD', 23 | () async { 24 | final result1 = await service.currentPrice(from: 'ada', to: 'usd'); 25 | result1.when( 26 | ok: (price) { 27 | logger.info(price); 28 | expect(price.value, isNotNull); 29 | }, 30 | err: (err) => logger.info(err)); 31 | final result2 = await service.currentPrice(from: 'nexo', to: 'usd'); 32 | result2.when( 33 | ok: (price) { 34 | logger.info(price); 35 | expect(price.value, isNotNull); 36 | }, 37 | err: (err) => logger.severe(err)); 38 | }); 39 | 40 | test('test currentPrice with Tron witch is not in the _defaultSymbolToId', 41 | () async { 42 | final result1 = await service.currentPrice(from: 'trx', to: 'usd'); 43 | result1.when( 44 | ok: (price) { 45 | logger.info(price); 46 | expect(price.value, isNotNull); 47 | }, 48 | err: (err) => logger.severe(err)); 49 | }, skip: 'reduce network usage'); 50 | 51 | // Future> ping(); 52 | test('test ping - calls https://api.coingecko.com/api/v3/ping', () async { 53 | final result = await service.ping(); 54 | result.when( 55 | ok: (success) => expect(success, isTrue), 56 | err: (err) => logger.severe(err)); 57 | }, skip: 'api is broken'); 58 | 59 | // Future, String>> list(); 60 | test( 61 | 'test supported coins list - calls https://api.coingecko.com/api/v3/coins/list', 62 | () async { 63 | final result = await service.list(); 64 | result.when( 65 | ok: (map) { 66 | logger.info("cardano: ${map['ada']}"); 67 | logger.info("bitcoin: ${map['btc']}"); 68 | expect(map['ada'], isNotNull); 69 | }, 70 | err: (err) => logger.severe(err)); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /example/flutter_example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/web/favicon.png -------------------------------------------------------------------------------- /example/flutter_example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/flutter_example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/flutter_example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/flutter_example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Judecoin/flutter_libcardano/c556793cd5c5fa77e13d6d47d40aab7754d72c06/example/flutter_example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/flutter_example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_example", 3 | "short_name": "flutter_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/cardano_wallet_sdk.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | library cardano_wallet_sdk; 5 | 6 | export './src/address/shelley_address.dart'; 7 | export './src/asset/asset.dart'; 8 | export './src/blockchain/blockfrost/blockfrost_api_key_auth.dart'; 9 | export './src/blockchain/blockfrost/blockfrost_blockchain_adapter.dart'; 10 | export './src/blockchain/blockfrost/dio_call.dart'; 11 | export './src/blockchain/blockchain_adapter_factory.dart'; 12 | export './src/blockchain/blockchain_adapter.dart'; 13 | export './src/blockchain/blockchain_cache.dart'; 14 | export './src/crypto/key_util.dart'; 15 | export './src/crypto/mnemonic.dart'; 16 | export './src/crypto/mnemonic_english.dart'; 17 | export './src/crypto/mnemonic_validation.dart'; 18 | export './src/crypto/sign_ed25519.dart'; 19 | export './src/hd/hd_account.dart'; 20 | export './src/hd/hd_derivation_chain.dart'; 21 | export './src/hd/hd_icarus_key_derivation.dart'; 22 | export './src/hd/hd_master_key_generation.dart'; 23 | export './src/hd/hd_shelley_key_derivation.dart'; 24 | export './src/network/blockchain_explorer.dart'; 25 | export './src/network/cardano_scan.dart'; 26 | export './src/network/network_id.dart'; 27 | export './src/price/price_service.dart'; 28 | export './src/stake/stake_account.dart'; 29 | export './src/stake/stake_pool_metadata.dart'; 30 | export './src/stake/stake_pool.dart'; 31 | export './src/transaction/model/bc_abstract.dart'; 32 | export './src/transaction/model/bc_auxiliary_data.dart'; 33 | export './src/transaction/model/bc_certificate.dart'; 34 | export './src/transaction/model/bc_exception.dart'; 35 | export './src/transaction/model/bc_plutus_data.dart'; 36 | export './src/transaction/model/bc_protocol_parameters.dart'; 37 | export './src/transaction/model/bc_pointer.dart'; 38 | export './src/transaction/model/bc_redeemer.dart'; 39 | export './src/transaction/model/bc_scripts.dart'; 40 | export './src/transaction/model/bc_tx_ext.dart'; 41 | export './src/transaction/model/bc_tx_body_ext.dart'; 42 | export './src/transaction/model/bc_tx.dart'; 43 | export './src/transaction/coin_selection.dart'; 44 | export './src/transaction/fee_calculation_service.dart'; 45 | export './src/transaction/policy.dart'; 46 | export './src/transaction/transaction.dart'; 47 | export './src/transaction/tx_builder.dart'; 48 | export './src/util/ada_formatter.dart'; 49 | export './src/util/ada_time.dart'; 50 | export './src/util/ada_types.dart'; 51 | export './src/util/ada_validation.dart'; 52 | export './src/util/bech32_validation.dart'; 53 | export './src/util/bigint_parse.dart'; 54 | export './src/util/blake2bhash.dart'; 55 | export './src/util/codec.dart'; 56 | export './src/util/list_ext.dart'; 57 | export './src/util/misc.dart'; 58 | export './src/wallet/impl/read_only_wallet_impl.dart'; 59 | export './src/wallet/impl/wallet_cache_memory.dart'; 60 | export './src/wallet/impl/wallet_impl.dart'; 61 | export './src/wallet/impl/wallet_update.dart'; 62 | export './src/wallet/read_only_wallet.dart'; 63 | export './src/wallet/wallet_builder.dart'; 64 | export './src/wallet/wallet_cache.dart'; 65 | export './src/wallet/wallet.dart'; 66 | -------------------------------------------------------------------------------- /lib/src/blockchain/blockchain_adapter.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:typed_data'; 5 | import 'package:oxidized/oxidized.dart'; 6 | import '../network/network_id.dart'; 7 | import '../transaction/model/bc_protocol_parameters.dart'; 8 | import '../transaction/transaction.dart'; 9 | import '../wallet/impl/wallet_update.dart'; 10 | import '../address/shelley_address.dart'; 11 | import './blockchain_cache.dart'; 12 | 13 | /// 14 | /// High-level abstraction to blockchain tailored towards balences and transactions. 15 | /// 16 | abstract class BlockchainAdapter extends BlockchainCache { 17 | /// Collects the latest transactions for the wallet given it's staking address. 18 | Future> updateWallet({ 19 | required ShelleyAddress stakeAddress, 20 | CancelAction? cancelAction, 21 | TemperalSortOrder sortOrder = TemperalSortOrder.descending, 22 | }); 23 | 24 | /// Returns last latest Block instance from blockchain if successful. 25 | Future> latestBlock({CancelAction? cancelAction}); 26 | 27 | /// Submit ShelleyTransaction encoded as CBOR. Returns hex transaction ID if successful. 28 | Future> submitTransaction( 29 | {required Uint8List cborTransaction, CancelAction? cancelAction}); 30 | 31 | /// Return the fee parameters for the given epoch number or the latest epoch if no number supplied. 32 | Future> latestEpochParameters( 33 | {int epochNumber = 0, CancelAction? cancelAction}); 34 | 35 | /// Return the fee parameters for the given epoch number or the latest epoch if no number supplied. 36 | // Future> latestEpochParameters( 37 | // {int epochNumber = 0, CancelAction? cancelAction}); 38 | Networks get network; 39 | 40 | /// Return an implementation-specific instance of CancelAction. 41 | CancelAction cancelActionInstance(); 42 | } 43 | 44 | /// You can cancel a request by using a cancel token. 45 | /// One token can be shared with different requests. 46 | /// when a token's [cancel] method invoked, all requests 47 | /// with this token will be cancelled. 48 | abstract class CancelAction { 49 | /// If request have been canceled, save the cancel Error. 50 | Exception? get cancelError; 51 | 52 | /// whether cancelled 53 | bool get isCancelled => cancelError != null; 54 | 55 | /// When cancelled, this future will be resolved. 56 | Future get whenCancel; 57 | 58 | /// Cancel the request 59 | void cancel([dynamic reason]); 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/blockchain/blockchain_adapter_factory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:blockfrost/blockfrost.dart'; 6 | import 'package:logging/logging.dart'; 7 | import '../network/network_id.dart'; 8 | import './blockchain_adapter.dart'; 9 | import './blockfrost/blockfrost_api_key_auth.dart'; 10 | import './blockfrost/blockfrost_blockchain_adapter.dart'; 11 | 12 | /// 13 | /// Provides a properly configured BlockchainAdapter for the requirested network. 14 | /// Instances are cached and should be reused because they often cache 15 | /// invariant blockchain data allowing for faster updates. 16 | /// 17 | class BlockchainAdapterFactory { 18 | final Interceptor authInterceptor; 19 | final Networks network; 20 | final String projectId; 21 | Blockfrost? _blockfrost; 22 | BlockfrostBlockchainAdapter? _blockfrostAdapter; 23 | 24 | final logger = Logger('BlockchainAdapterFactory'); 25 | 26 | BlockchainAdapterFactory( 27 | {required this.authInterceptor, 28 | required this.network, 29 | required this.projectId}); 30 | 31 | /// creates an interceptor give a key 32 | static Interceptor interceptorFromKey({required String key}) => 33 | BlockfrostApiKeyAuthInterceptor(projectId: key); 34 | 35 | factory BlockchainAdapterFactory.fromKey( 36 | {required String key, required Networks network}) => 37 | BlockchainAdapterFactory( 38 | authInterceptor: interceptorFromKey(key: key), 39 | network: network, 40 | projectId: key); 41 | 42 | /// 43 | /// return cached BlockchainAdapter instance. 44 | /// 45 | BlockchainAdapter adapter() { 46 | if (_blockfrostAdapter == null) { 47 | final blockfrost = 48 | _cachedBlockfrost(network: network, authInterceptor: authInterceptor); 49 | _blockfrostAdapter = BlockfrostBlockchainAdapter( 50 | network: network, blockfrost: blockfrost, projectId: projectId); 51 | } 52 | return _blockfrostAdapter!; 53 | } 54 | 55 | /// clear cached instances 56 | void clear() { 57 | _blockfrostAdapter = null; 58 | _blockfrost = null; 59 | } 60 | 61 | /// provides a cahced Blockfrost instance. 62 | Blockfrost _cachedBlockfrost( 63 | {required Networks network, required Interceptor authInterceptor}) { 64 | if (_blockfrost == null) { 65 | final url = BlockfrostBlockchainAdapter.urlFromNetwork(network); 66 | logger.info("new Blockfrost($url)"); 67 | _blockfrost = Blockfrost( 68 | basePathOverride: url, 69 | interceptors: [authInterceptor], 70 | ); 71 | } 72 | return _blockfrost!; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/blockchain/blockchain_cache.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:blockfrost/blockfrost.dart'; 5 | import '../asset/asset.dart'; 6 | import '../transaction/transaction.dart'; 7 | import '../util/ada_types.dart'; 8 | 9 | /// 10 | /// The cache holds invariant block chain data. 11 | /// 12 | abstract class BlockchainCache { 13 | RawTransaction? cachedTransaction(TxIdHex txId); 14 | Block? cachedBlock(BlockHashHex blockId); 15 | AccountContent? cachedAccountContent( 16 | Bech32Address stakeAddress); // TODO define AccountContent locally 17 | CurrencyAsset? cachedCurrencyAsset(String assetId); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/blockchain/blockfrost/blockfrost_api_key_auth.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | class BlockfrostApiKeyAuthInterceptor extends Interceptor { 7 | final String projectId; 8 | BlockfrostApiKeyAuthInterceptor({required this.projectId}); 9 | @override 10 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 11 | options.headers['project_id'] = projectId; 12 | super.onRequest(options, handler); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/crypto/key_util.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/src/util/blake2bhash.dart'; 5 | import 'package:hex/hex.dart'; 6 | import 'package:pinenacl/ed25519.dart'; 7 | 8 | class KeyUtil { 9 | static SigningKey generateSigningKey() => SigningKey.generate(); 10 | 11 | static VerifyKey verifyKey({required SigningKey signingKey}) => 12 | signingKey.verifyKey; 13 | 14 | static String keyHash({required VerifyKey verifyKey}) => 15 | HEX.encode(blake2bHash224(verifyKey)); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/crypto/mnemonic_validation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:oxidized/oxidized.dart'; 5 | import 'mnemonic.dart'; 6 | 7 | /// 8 | /// If the mnemonic or recovery phrase has all legal characters and the 9 | /// requiredNumberOfWords, then the normalized correct form is returned. 10 | /// If it's not legal, then an explanation is returned in the error message. 11 | /// 12 | Result validMnemonic({ 13 | required String phrase, 14 | int requiredNumberOfWords = 24, 15 | required LoadMnemonicWordsFunction loadWordsFunction, 16 | MnemonicLang lang = MnemonicLang.english, 17 | }) { 18 | if (phrase.isEmpty) { 19 | return const Err("mnemonic required"); 20 | } 21 | final ValidMnemonicPhrase mnemonic = phrase.toLowerCase().trim().split(' '); 22 | if (mnemonic.length < 3) { 23 | return const Err("at least 3 mnemonic words required"); 24 | } 25 | final wordList = loadWordsFunction(lang: lang); 26 | try { 27 | mnemonicWordsToEntropy(mnemonic: mnemonic, wordList: wordList); 28 | } on ArgumentError catch (e) { 29 | //might be a bad word, see if we can find it 30 | for (String word in mnemonic) { 31 | final index = wordList.indexOf(word); 32 | if (index == -1) { 33 | return Err("invalid mnemonic word: '$word'"); 34 | } 35 | } 36 | // final validity = validMnemonicWords(words); 37 | // if (validity.isErr()) return Err(validity.unwrapErr()); 38 | //otherwise check length 39 | if (mnemonic.length != requiredNumberOfWords) { 40 | return Err("$requiredNumberOfWords words required"); 41 | } 42 | return Err(e.message); 43 | } on StateError catch (e) { 44 | if (mnemonic.length != requiredNumberOfWords) { 45 | return Err("$requiredNumberOfWords words required"); 46 | } 47 | return Err(e.message); 48 | } catch (e) { 49 | return Err(e.toString()); 50 | } 51 | if (mnemonic.length != requiredNumberOfWords) { 52 | return Err("$requiredNumberOfWords words required"); 53 | } 54 | return Ok(mnemonic); 55 | } 56 | 57 | // /// Return true if a all the provided mnemonic words are valid. 58 | // Result validMnemonicWords(List words) { 59 | // for (final word in words) { 60 | // if (!validMnemonicWord(word)) { 61 | // return Err("invalid mnemonic word: '$word'"); 62 | // } 63 | // } 64 | // return Ok(true); 65 | // } 66 | 67 | // /// Return true if a valid mnemonic word 68 | // bool validMnemonicWord(String word) { 69 | // // not a great implementation because it depends on the inner details of bip39 70 | // try { 71 | // // just need 3 words to avoid length error - append 2 valid words 72 | // mnemonicToEntropyHex( 73 | // '${word.toLowerCase().trim()} ability able'.split(' ')); 74 | // } on ArgumentError { 75 | // return false; 76 | // } on StateError { 77 | // return true; //StateError is not valid to this test 78 | // } 79 | // return true; 80 | // } 81 | 82 | // int _firstIllegalDataChar(String bech32Data) { 83 | // for (int i = 0; i < bech32Data.length; i++) { 84 | // if (!_legalBech32Char(bech32Data.codeUnitAt(i))) return i; 85 | // } 86 | // return -1; 87 | // } 88 | 89 | // bool _legalBech32Char(int ch16) => 90 | // _indexOfCodeUnit(ch16, _mnemonicLegalDataChars) > -1; 91 | 92 | // int _indexOfCodeUnit(int ch16, List codeUnits) { 93 | // for (int i = 0; i < codeUnits.length; i++) { 94 | // if (ch16 == codeUnits[i]) return i; 95 | // } 96 | // return -1; 97 | // } 98 | 99 | // final _mnemonicLegalDataChars = ' abcdefghijklmnopqrstuvwxyz' 100 | // .codeUnits; //call toLowerCase() on target string 101 | -------------------------------------------------------------------------------- /lib/src/crypto/sha512.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:typed_data'; 5 | import 'package:crypto/crypto.dart'; 6 | 7 | List hmacSha512({required List key, required List data}) { 8 | // var key = utf8.encode('p@ssw0rd'); 9 | // var bytes = utf8.encode("foobar"); 10 | 11 | final hmacSha512 = Hmac(sha512, key); 12 | final digest = hmacSha512.convert(data); 13 | 14 | print("HMAC digest as bytes: ${digest.bytes}"); 15 | print("HMAC digest as hex string: $digest"); 16 | return digest.bytes; 17 | } 18 | 19 | Uint8List hmacSha512Uint8({required Uint8List key, required Uint8List data}) { 20 | // var key = utf8.encode('p@ssw0rd'); 21 | // var bytes = utf8.encode("foobar"); 22 | 23 | final hmacSha512 = Hmac(sha512, key); 24 | final digest = hmacSha512.convert(data); 25 | 26 | print("HMAC digest as bytes: ${digest.bytes}"); 27 | print("HMAC digest as hex string: $digest"); 28 | return Uint8List.fromList(digest.bytes); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/crypto/sign_ed25519.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:bip32_ed25519/api.dart'; 5 | import 'package:pinenacl/tweetnacl.dart'; 6 | import '../util/blake2bhash.dart'; 7 | 8 | /// 9 | /// Cryptographic signature methods. 10 | /// TODO currently the code still calls Bip32VerifyKey.sign. Use it or lose it! 11 | /// 12 | 13 | ///Sign a message with a ed25519 private key and return signature 14 | Uint8List signEd25519( 15 | {required Uint8List message, required Uint8List privateKey}) { 16 | final signingKey = SigningKey(seed: privateKey); 17 | final verifyKey = signingKey.verifyKey; 18 | final signed = signingKey.sign(message); 19 | if (signed.isEmpty) { 20 | throw Exception('Signing the massage is failed'); 21 | } 22 | if (!verifyKey.verify(signature: signed.signature, message: message)) { 23 | throw Exception('verify massage failed'); 24 | } 25 | // print("signed: ${signed.length}"); 26 | return signed.prefix.asTypedList; 27 | } 28 | 29 | /// Sign a message with a ed25519 expanded private key and return signature 30 | Uint8List signEd25519Extended( 31 | {required Uint8List message, 32 | required Uint8List privateKey, 33 | required Uint8List publicKey}) { 34 | List hash = blake2bHash256(message); 35 | var sm = Uint8List(hash.length + TweetNaCl.signatureLength); 36 | final kb = Uint8List.fromList(privateKey + publicKey); 37 | final result = TweetNaCl.crypto_sign(sm, -1, message, 0, message.length, kb, 38 | extended: true); 39 | if (result != 0) { 40 | throw Exception('Signing the massage is failed'); 41 | } 42 | //print("sm: ${sm.length}, result: $result"); 43 | return sm.length > 64 ? sm.sublist(0, 64) : sm; 44 | } 45 | 46 | bool verifyEd25519( 47 | {required Uint8List signature, 48 | required Uint8List message, 49 | required Uint8List publicKey}) { 50 | if (signature.length != TweetNaCl.signatureLength) { 51 | throw Exception( 52 | 'Signature length (${signature.length}) is invalid, expected "${TweetNaCl.signatureLength}"'); 53 | } 54 | final newmessage = signature + message; 55 | if (newmessage.length < TweetNaCl.signatureLength) { 56 | throw Exception( 57 | 'Signature length (${newmessage.length}) is invalid, expected "${TweetNaCl.signatureLength}"'); 58 | } 59 | var m = Uint8List(newmessage.length); 60 | 61 | final result = TweetNaCl.crypto_sign_open( 62 | m, -1, Uint8List.fromList(newmessage), 0, newmessage.length, publicKey); 63 | if (result != 0) { 64 | throw Exception( 65 | 'The message is forged or malformed or the signature is invalid'); 66 | } 67 | return true; 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/hd/hd_icarus_key_derivation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // import 'package:pinenacl/key_derivation.dart'; 5 | import 'package:bip32_ed25519/api.dart'; 6 | import 'package:hex/hex.dart'; 7 | import '../util/codec.dart'; 8 | import 'hd_master_key_generation.dart'; 9 | 10 | // Bip32SigningKey icarusGenerateMasterKey(Uint8List entropy) { 11 | // final rawMaster = PBKDF2.hmac_sha512(Uint8List(0), entropy, 4096, 96); 12 | // return Bip32SigningKey.normalizeBytes(rawMaster); 13 | // } 14 | 15 | class IcarusKeyDerivation extends Bip32Ed25519KeyDerivation with Bip32KeyTree { 16 | IcarusKeyDerivation(Bip32Key key) { 17 | assert(key is Bip32SigningKey || key is Bip32VerifyKey); 18 | root = key; 19 | } 20 | IcarusKeyDerivation.entropy(Uint8List entropy) { 21 | root = master(entropy); 22 | } 23 | IcarusKeyDerivation.entropyHex(String entropyHex) { 24 | root = master(Uint8List.fromList(HEX.decode(entropyHex))); 25 | } 26 | IcarusKeyDerivation.bech32Key(String key) { 27 | root = doImport(key); 28 | } 29 | 30 | /// Use Icarus master key generation algo as used by Yoroi, Daedalus (Shelley era) 31 | /// https://github.com/cardano-foundation/CIPs/blob/master/CIP-0003/Icarus.md 32 | @override 33 | Bip32Key master(Uint8List seed) => icarusGenerateMasterKey(seed); 34 | 35 | /// Modify to support Cardano bech32 prefixes that make sense. 36 | /// https://cips.cardano.org/cips/cip5/ 37 | @override 38 | Bip32Key doImport(String key) { 39 | final prefixSep = key.indexOf('1'); 40 | if (prefixSep == -1 || prefixSep > 8) { 41 | throw ArgumentError( 42 | "expecting bech32 encoding with '1' as prefix seperator", key); 43 | } 44 | final prefix = key.substring(0, prefixSep); 45 | switch (prefix) { 46 | case 'root_xsk': 47 | return Bip32SigningKey.decode(key, coder: rootXskCoder); 48 | case 'acct_xsk': 49 | return Bip32SigningKey.decode(key, coder: acctXskCoder); 50 | case 'acct_xvk': 51 | return Bip32VerifyKey.decode(key, coder: acctXvkCoder); 52 | default: 53 | throw ArgumentError( 54 | "unsupported bech32 prefix: $prefix, expecting 'root_xsk', 'acct_xsk' or 'acct_xvk'", 55 | key); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/hd/hd_master_key_generation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:pinenacl/key_derivation.dart'; 5 | import 'package:bip32_ed25519/api.dart'; 6 | // import 'package:pinenacl/digests.dart'; 7 | 8 | Bip32SigningKey icarusGenerateMasterKey(Uint8List entropy) { 9 | final rawMaster = PBKDF2.hmac_sha512(Uint8List(0), entropy, 4096, 96); 10 | return Bip32SigningKey.normalizeBytes(rawMaster); 11 | } 12 | 13 | // Bip32SigningKey byronGenerateMasterKey(Uint8List entropy) => 14 | // _byronGenerateMasterKey(entropy, index: 0); 15 | 16 | // Bip32SigningKey _byronGenerateMasterKey(Uint8List entropy, {int index = 0}) { 17 | // final rawMaster = PBKDF2.hmac_sha512(Uint8List(0), entropy, 4096, 96); 18 | // return Bip32SigningKey.normalizeBytes(rawMaster); 19 | // } 20 | 21 | /// The default implementation of the original BIP32-ED25519's master key 22 | /// generation. 23 | // Bip32Key originalMaster(Uint8List masterSecret) { 24 | // final secretBytes = Hash.sha512(masterSecret); 25 | 26 | // if ((secretBytes[31] &= 0x20) != 0) { 27 | // //0b0010_0000 28 | // throw InvalidBip32Ed25519MasterSecretException(); 29 | // } 30 | 31 | // final rootChainCode = Hash.sha256([0x01, ...masterSecret].toUint8List()); 32 | 33 | // final rootKey = Bip32SigningKey.normalizeBytes( 34 | // [...secretBytes, ...rootChainCode].toUint8List()); 35 | 36 | // PineNaClUtils.listZero(masterSecret); 37 | // PineNaClUtils.listZero(rootChainCode); 38 | 39 | // return rootKey; 40 | // } 41 | 42 | // function hashRepeatedly(key, i) { 43 | // (iL, iR) = HMAC 44 | // ( hash=SHA512 45 | // , key=key 46 | // , message="Root Seed Chain " + UTF8NFKD(i) 47 | // ); 48 | 49 | // let prv = tweakBits(SHA512(iL)); 50 | 51 | // if (prv[31] & 0b0010_0000) { 52 | // return hashRepeatedly(key, i+1); 53 | // } 54 | 55 | // return (prv + iR); 56 | // } 57 | 58 | // Uint8List byronTweakBits(Uint8List bytes, {int keyLength = 96}) { 59 | // if (bytes.length != keyLength) { 60 | // throw InvalidSigningKeyError(); 61 | // } 62 | // var result = bytes.toUint8List(); 63 | // result[0] &= 0xF8; // clear the last 3 bits, result[0] &= 0b1111_1000; 64 | // result[31] &= 0x7F; // clear the 1st bit, result[31] &= 0b0111_1111; 65 | // result[31] |= 0x40; // set the 2nd bit, result[31] |= 0b0100_0000; 66 | // return result; 67 | // } 68 | 69 | // Uint8List clampKey(Uint8List bytes, {int keyLength = 96}) { 70 | // Uint8List result = byronTweakBits(bytes, keyLength: keyLength); 71 | // result[31] &= 0xDF; // clear the 3rd bit, result[31] &= 0b1101_1111; 72 | // return result; 73 | // } 74 | 75 | // Uint8List validateKeyBits(Uint8List bytes) { 76 | // bytes = ExtendedSigningKey.validateKeyBits(bytes); 77 | 78 | // if ((bytes[31] & 32) != 0) { 79 | // throw InvalidSigningKeyError(); 80 | // } 81 | // return bytes; 82 | // } 83 | -------------------------------------------------------------------------------- /lib/src/hd/hd_shelley_key_derivation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | // ignore_for_file: non_constant_identifier_names 4 | 5 | import 'package:bip32_ed25519/api.dart'; 6 | import 'package:bip32_ed25519/bip32_ed25519.dart'; 7 | import './hd_derivation_chain.dart'; 8 | import './hd_icarus_key_derivation.dart'; 9 | 10 | /// 11 | /// This class implements a hierarchical deterministic wallet that generates cryptographic keys 12 | /// given a root signing key. It also supports the creation/restoration of the root signing 13 | /// key from a set of nmemonic BIP-39 words. 14 | /// Cardano Shelley addresses are supported by default, but the code is general enough to support any 15 | /// wallet based on the BIP32-ED25519 standard. 16 | /// 17 | /// This code builds on following standards: 18 | /// 19 | /// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki - HD wallets 20 | /// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki - mnemonic words 21 | /// https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki - Bitcoin purpose 22 | /// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki - multi-acct wallets 23 | /// https://cips.cardano.org/cips/cip3/ - key generation 24 | /// https://cips.cardano.org/cips/cip5/ - Bech32 prefixes 25 | /// https://cips.cardano.org/cips/cip11/ - staking key 26 | /// https://cips.cardano.org/cips/cip16/ - key serialisation 27 | /// https://cips.cardano.org/cips/cip19/ - address structure 28 | /// https://cips.cardano.org/cips/cip1852/ - 1852 purpose field 29 | /// https://cips.cardano.org/cips/cip1855/ - forging keys 30 | /// https://github.com/LedgerHQ/orakolo/blob/master/papers/Ed25519_BIP%20Final.pdf 31 | /// 32 | /// 33 | /// BIP-44 path: 34 | /// m / purpose' / coin_type' / account_ix' / change_chain / address_ix 35 | /// 36 | /// Cardano adoption: 37 | /// m / 1852' / 1851' / account' / role / index 38 | /// 39 | /// 40 | class HdKeyDerivation { 41 | final IcarusKeyDerivation derivation; 42 | 43 | HdKeyDerivation(Bip32Key key) : derivation = IcarusKeyDerivation(key); 44 | 45 | HdKeyDerivation.entropy(Uint8List entropy) 46 | : derivation = IcarusKeyDerivation.entropy(entropy); 47 | 48 | HdKeyDerivation.entropyHex(String entropyHex) 49 | : derivation = IcarusKeyDerivation.entropyHex(entropyHex); 50 | 51 | // HdKeyDerivation.bech32(String root_sk) 52 | // : derivation = IcarusKeyDerivation(codec.decode(root_sk)); 53 | // HdKeyDerivation.rootKey(Bip32SigningKey rootKey) 54 | // : derivation = IcarusKeyDerivation.import(codec.encode(rootKey)); 55 | 56 | HdKeyDerivation.rootX(String root_xsk) 57 | : derivation = IcarusKeyDerivation.bech32Key(root_xsk); 58 | 59 | HdKeyDerivation.privateAcctX(String acct_xsk) 60 | : derivation = IcarusKeyDerivation.bech32Key(acct_xsk); 61 | 62 | HdKeyDerivation.pubicAcctX(String acct_xvk) 63 | : derivation = IcarusKeyDerivation.bech32Key(acct_xvk); 64 | 65 | Bip32Key get root => derivation.root; 66 | 67 | /// Derive key from root key and DerivationChain. 68 | Bip32Key fromChain(HdDerivationChain chain) { 69 | //print("HdKeyDerivation.fromChain: ${chain.toString()}"); 70 | return derivation.pathToKey(chain.toString()); 71 | } 72 | 73 | /// Derive key from root key and path. 74 | Bip32Key fromPath(String path) { 75 | //print("HdKeyDerivation.fromPath: ${path.toString()}"); 76 | return derivation.pathToKey(path.toString()); 77 | } 78 | 79 | //static const codec = Bech32Encoder(hrp: 'root_sk'); 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/network/blockchain_explorer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './network_id.dart'; 5 | 6 | /// 7 | /// Cardano Blockchain Explorer url generator for common blockchain web pages. 8 | /// Supports both mainnet and testnet networks and i18n language translations. 9 | /// 10 | class CardanoBlockchainExplorer { 11 | final String baseUrl; 12 | final String lang; 13 | 14 | static const mainnetUrl = 'https://explorer.cardano.org/'; 15 | static const testnetUrl = 'https://explorer.cardano-testnet.iohkdev.io/'; 16 | 17 | CardanoBlockchainExplorer({this.baseUrl = mainnetUrl, this.lang = 'en'}); 18 | 19 | factory CardanoBlockchainExplorer.fromNetwork(Networks network) => 20 | CardanoBlockchainExplorer( 21 | baseUrl: network == Networks.mainnet ? mainnetUrl : testnetUrl); 22 | 23 | /// result example: https://explorer.cardano.org/en/epoch?number=268 24 | String epicUrl({required int epicNumber}) => 25 | "$baseUrl/$lang/epoch?number=$epicNumber"; 26 | 27 | /// result example: https://explorer.cardano.org/en/block?id=67f5e3c6094c7c3e5a3ec82d75b6cb0e5009f9bb3b7f8bd9eab6cd248b2f5f54 28 | String blockUrl({required String blockIdHex32}) => 29 | "$baseUrl/$lang/block?id=$blockIdHex32"; 30 | 31 | // result example: https://explorer.cardano.org/en/transaction?id=4602417d2786f6e315b320275a54432b935f770cf1b43811f676fd352645c158 32 | String transactionUrl({required String transactionIdHex32}) => 33 | "$baseUrl/$lang/transaction?id=$transactionIdHex32"; 34 | 35 | /// result example: https://explorer.cardano.org/en/address?address=addr1qyvadph9dewhm8nn0lm2telukv9egd3sxayzextnrr9ephyhky2d9jpqrr83rzvpyum7gty3slchf0u9lnczfqsh2n3q8ukd37 36 | String addressUrl({required String addressBech32}) => 37 | "$baseUrl/$lang/address?address=$addressBech32"; 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/network/cardano_scan.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './network_id.dart'; 5 | 6 | /// 7 | /// Cardano Scan Blockchain Explorer url generator for common blockchain web pages. 8 | /// Supports both mainnet and testnet networks. 9 | /// 10 | class CardanoScanBlockchainExplorer { 11 | final String baseUrl; 12 | 13 | static const mainnetUrl = 'https://cardanoscan.io'; 14 | static const testnetUrl = 'https://testnet.cardanoscan.io'; 15 | 16 | CardanoScanBlockchainExplorer({this.baseUrl = mainnetUrl}); 17 | 18 | factory CardanoScanBlockchainExplorer.fromNetwork(Networks network) => 19 | CardanoScanBlockchainExplorer( 20 | baseUrl: network == Networks.mainnet ? mainnetUrl : testnetUrl); 21 | 22 | /// result example: https://cardanoscan.io/epoch/269 23 | String epicUrl({required int epicNumber}) => "$baseUrl/epoch/$epicNumber"; 24 | 25 | /// result example: https://cardanoscan.io/block/5805954 26 | String blockUrl({required int blockNumber}) => "$baseUrl/block/$blockNumber"; 27 | 28 | // result example: https://cardanoscan.io/transaction/811f7323ad7866cb4093ebbe7d98006f43303a0b7d8654391b571f3f9a952011 29 | String transactionUrl({required String transactionIdHex32}) => 30 | "$baseUrl/transaction/$transactionIdHex32"; 31 | 32 | /// result example: https://cardanoscan.io/address/addr1v95sf69jcfhnmknvffwmfvlvnccatqwfjcyh0nlfc6gh5scta2yzg 33 | String addressUrl({required String addressBech32}) => 34 | "$baseUrl/address/$addressBech32"; 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/network/network_id.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | /// The Cardano testnet and mainnet are two independent blockchains and this value 6 | /// determines which one your code will run on. 7 | /// 8 | enum Networks { 9 | testnet(0, 1097911063), 10 | mainnet(1, 764824073); 11 | 12 | const Networks(this.networkId, this.protocolMagic); 13 | final int networkId; 14 | final int protocolMagic; 15 | } 16 | 17 | //rust: 18 | // pub fn testnet() -> NetworkInfo { 19 | // NetworkInfo { 20 | // network_id: 0b0000, 21 | // protocol_magic: 1097911063 22 | // } 23 | // } 24 | // pub fn mainnet() -> NetworkInfo { 25 | // NetworkInfo { 26 | // network_id: 0b0001, 27 | // protocol_magic: 764824073 28 | -------------------------------------------------------------------------------- /lib/src/price/price_service.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:oxidized/oxidized.dart'; 5 | 6 | /// 7 | /// Used for tracking currency trading pair prices (i.e. ADA to USD). 8 | /// 9 | /// TODO simple ticker names won't work with Cardano Native tokens which use a policyId and coin name. 10 | /// 11 | class Price { 12 | final String fromTicker; 13 | final String toTicker; 14 | final int timestamp; 15 | final double value; 16 | 17 | Price( 18 | {this.fromTicker = 'ADA', 19 | this.toTicker = 'USD', 20 | this.timestamp = 0, 21 | this.value = 0.0}); 22 | 23 | DateTime get dateTime => 24 | DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true); 25 | 26 | @override 27 | String toString() { 28 | return 'Price(from: $fromTicker, to: $toTicker, value: $value, timestamp: $dateTime'; 29 | } 30 | 31 | Duration get lastUpdated => DateTime.now().difference(dateTime); 32 | 33 | String get briefLastUpdated { 34 | final update = lastUpdated; 35 | if (update.inDays > 1) { 36 | return "${update.inDays}d ago"; 37 | } else if (update.inHours > 1) { 38 | return "${update.inHours}h ago"; 39 | } else if (update.inMinutes > 1) { 40 | return "${update.inMinutes}m ago"; 41 | } else { 42 | return "${update.inSeconds}s ago"; 43 | } 44 | } 45 | } 46 | 47 | /// not being used at the moment. see price_polling_service 48 | abstract class PriceService { 49 | /// 50 | /// retrieve current ratio of to currency to from currency. 51 | /// example: priceService(from:'BTC', to:'USD') -> 55234.654 52 | /// 53 | Future> currentPrice({String from, String to}); 54 | 55 | /// 56 | /// check coingecko service 57 | /// 58 | Future> ping(); 59 | 60 | /// 61 | /// list all coins supported by price service 62 | /// returns map where the key is the ticker and the value is the crypto's name 63 | Future, String>> list(); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/stake/stake_account.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './stake_pool.dart'; 5 | import './stake_pool_metadata.dart'; 6 | 7 | // '["active",true,"active_epoch",135,"controlled_amount","398515694","rewards_sum","690831","withdrawals_sum","0","reserves_sum","0","treasury_sum","0","withdrawable_amount","690831","pool_id","pool14pdhhugxlqp9vta49pyfu5e2d5s82zmtukcy9x5ylukpkekqk8l"]', 8 | 9 | class StakeAccount { 10 | /// Registration state of an account 11 | final bool active; 12 | 13 | /// Epoch of the most recent action - registration or deregistration 14 | final int? activeEpoch; 15 | 16 | /// Balance of the account in Lovelaces 17 | final BigInt controlledAmount; 18 | 19 | /// Sum of all rewards for the account in the Lovelaces 20 | final BigInt rewardsSum; 21 | 22 | /// Sum of all the withdrawals for the account in Lovelaces 23 | final BigInt withdrawalsSum; 24 | 25 | /// Sum of all funds from reserves for the account in the Lovelaces 26 | final BigInt reservesSum; 27 | 28 | /// Sum of all funds from treasury for the account in the Lovelaces 29 | final BigInt treasurySum; 30 | 31 | /// Sum of available rewards that haven't been withdrawn yet for the account in the Lovelaces 32 | final BigInt withdrawableAmount; 33 | 34 | /// Bech32 pool ID that owns the account 35 | final String? poolId; 36 | 37 | /// name, url, ticker, etc. 38 | final StakePoolMetadata? poolMetadata; 39 | 40 | /// stake pool details 41 | final StakePool? stakePool; 42 | 43 | final List rewards; 44 | 45 | StakeAccount({ 46 | required this.active, 47 | this.activeEpoch, 48 | required this.controlledAmount, 49 | required this.rewardsSum, 50 | required this.withdrawalsSum, 51 | required this.reservesSum, 52 | required this.treasurySum, 53 | required this.withdrawableAmount, 54 | this.poolId, 55 | this.poolMetadata, 56 | this.stakePool, 57 | required this.rewards, 58 | }); 59 | 60 | // Account({ 61 | // required this.policyId, 62 | // required this.assetName, 63 | // String? fingerprint, 64 | // required this.quantity, 65 | // required this.initialMintTxHash, 66 | // this.metadata, 67 | // }) : this.assetId = '$policyId$assetName', 68 | // this.name = hex2str.encode(assetName), //if assetName is not hex, this will usualy fail 69 | // this.fingerprint = fingerprint ?? calculateFingerlogger.i(policyId: policyId, assetNameHex: assetName); 70 | } 71 | 72 | class StakeReward { 73 | /// epoch reward was paid in 74 | final int epoch; 75 | 76 | /// amount of reward in lovelace 77 | final BigInt amount; 78 | 79 | /// stake pool ID 80 | final String poolId; 81 | 82 | StakeReward( 83 | {required this.epoch, required this.amount, required this.poolId}); 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/stake/stake_pool.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class StakePool { 5 | /// VRF key hash 6 | final String vrfKey; 7 | 8 | /// Total minted blocks 9 | final int blocksMinted; 10 | 11 | final String liveStake; 12 | 13 | final num liveSize; 14 | 15 | final num liveSaturation; 16 | 17 | final num liveDelegators; 18 | 19 | final String activeStake; 20 | 21 | final num activeSize; 22 | 23 | /// Stake pool certificate pledge 24 | final String declaredPledge; 25 | 26 | /// Stake pool current pledge 27 | final String livePledge; 28 | 29 | /// Margin tax cost of the stake pool 30 | final num marginCost; 31 | 32 | /// Fixed tax cost of the stake pool 33 | final String fixedCost; 34 | 35 | /// Bech32 reward account of the stake pool 36 | final String rewardAccount; 37 | 38 | final List owners; 39 | 40 | final List registration; 41 | 42 | final List retirement; 43 | 44 | StakePool({ 45 | required this.vrfKey, 46 | required this.blocksMinted, 47 | required this.liveStake, 48 | required this.liveSize, 49 | required this.liveSaturation, 50 | required this.liveDelegators, 51 | required this.activeStake, 52 | required this.activeSize, 53 | required this.declaredPledge, 54 | required this.livePledge, 55 | required this.marginCost, 56 | required this.fixedCost, 57 | required this.rewardAccount, 58 | required this.owners, 59 | required this.registration, 60 | required this.retirement, 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/stake/stake_pool_metadata.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class StakePoolMetadata { 5 | /// URL to the stake pool metadata 6 | final String? url; 7 | 8 | /// Hash of the metadata file 9 | final String? hash; 10 | 11 | /// Ticker of the stake pool 12 | final String? ticker; 13 | 14 | /// Name of the stake pool 15 | final String? name; 16 | 17 | /// Description of the stake pool 18 | final String? description; 19 | 20 | /// Home page of the stake pool 21 | final String? homepage; 22 | 23 | StakePoolMetadata({ 24 | this.url, 25 | this.hash, 26 | this.ticker, 27 | this.name, 28 | this.description, 29 | this.homepage, 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/transaction/fee_calculation_service.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:cbor/cbor.dart'; 7 | 8 | class FeeCalculationService { 9 | final logger = Logger('FeeCalculationService'); 10 | final ProtocolParameters protocolParameters; 11 | 12 | FeeCalculationService(this.protocolParameters); 13 | 14 | /// 15 | /// calculate transaction fee based on transaction length and minimum constants 16 | /// 17 | BigInt calculateMinFee({required BcTransaction transaction}) { 18 | return calculateMinFeeFromBytes(cbor.encode(transaction.toCborList())); 19 | } 20 | 21 | /// 22 | /// calculate transaction fee based on bytes length and minimum constants 23 | /// 24 | BigInt calculateMinFeeFromBytes(List bytes) { 25 | final result = BigInt.from( 26 | protocolParameters.minFeeA * bytes.length + protocolParameters.minFeeB); 27 | logger.info( 28 | "calculateMinFeeFromBytes(minFeeA * tx.len + minFeeB) = ${protocolParameters.minFeeA} * ${bytes.length} + ${protocolParameters.minFeeB} = $result"); 29 | return result; 30 | } 31 | 32 | /// 33 | /// Calculate fee needed to submit script. 34 | /// TODO uses double because Dart doesn't support BigDecimal, use 3rd party lib? 35 | /// 36 | BigInt calculateScriptFee(List exUnitsList) { 37 | num priceMem = protocolParameters.priceMem ?? 0.0; 38 | num priceSteps = protocolParameters.priceStep ?? 0.0; 39 | double scriptFee = 0.0; 40 | String? log = logger.isLoggable(Level.INFO) ? "" : null; 41 | for (BcExUnits exUnits in exUnitsList) { 42 | double memCost = priceMem * exUnits.mem.toDouble(); 43 | double stepCost = priceSteps * exUnits.steps.toDouble(); 44 | scriptFee = scriptFee + memCost + stepCost; 45 | if (log != null) { 46 | log = "($memCost + $stepCost),$log"; 47 | } 48 | } 49 | //round 50 | final result = BigInt.from(scriptFee.ceil().toInt()); 51 | logger.info( 52 | "calculateScriptFee ∑ ExUnits(memCost + stepCost) = $log = $result"); 53 | return result; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/transaction/model/bc_abstract.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:typed_data'; 5 | import 'package:hex/hex.dart'; 6 | import 'package:cbor/cbor.dart'; 7 | import '../../util/blake2bhash.dart'; 8 | 9 | /// 10 | /// Base class for mapping binary data structures used on the Cardano blockchain. 11 | /// 12 | /// Subclasses should minimally implement a cborValue getter and a fromCbor factory memthod. 13 | /// Polymorphic subclasses should implement an abstract base class (see BcNativeScript for an 14 | /// example) to build the hierarchy. 15 | /// 16 | abstract class BcAbstractCbor { 17 | CborValue get cborValue; 18 | Uint8List get serialize => _toUint8List(cborValue); 19 | String get cborJson => _toCborJson(cborValue); 20 | String get hex => HEX.encode(serialize); 21 | Uint8List get hash => Uint8List.fromList(blake2bHash256(serialize)); 22 | String get hashHex => HEX.encode(hash); 23 | 24 | @override 25 | String toString() => hex; 26 | 27 | @override 28 | int get hashCode => hex.hashCode; 29 | 30 | /// does a byte-by-byte comparison of the serialized representations. 31 | @override 32 | bool operator ==(Object other) { 33 | bool isEq = identical(this, other) || 34 | other is BcAbstractCbor && runtimeType == other.runtimeType; 35 | if (!isEq) return false; 36 | final Uint8List bytes1 = serialize; 37 | final Uint8List bytes2 = (other as BcAbstractCbor).serialize; 38 | return _equalBytes(bytes1, bytes2); 39 | } 40 | 41 | bool _equalBytes(Uint8List a, Uint8List b) { 42 | for (var i = 0; i < a.length; i++) { 43 | if (a[i] != b[i]) return false; 44 | } 45 | return true; 46 | } 47 | 48 | Uint8List _toUint8List(CborValue value) => 49 | Uint8List.fromList(cbor.encode(value)); 50 | 51 | String _toCborJson(CborValue value) => const CborJsonEncoder().convert(value); 52 | } 53 | 54 | class CborError extends Error { 55 | final String message; 56 | CborError(this.message); 57 | @override 58 | String toString() => message; 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/transaction/model/bc_auxiliary_data.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cbor/cbor.dart'; 5 | import './bc_abstract.dart'; 6 | import './bc_scripts.dart'; 7 | import './bc_tx.dart'; 8 | 9 | class BcAuxiliaryData extends BcAbstractCbor { 10 | final BcMetadata? metadata; 11 | final List nativeScripts; 12 | final List plutusV1Scripts; 13 | final List plutusV2Scripts; 14 | 15 | BcAuxiliaryData({ 16 | this.metadata, 17 | this.nativeScripts = const [], 18 | this.plutusV1Scripts = const [], 19 | this.plutusV2Scripts = const [], 20 | }); 21 | 22 | factory BcAuxiliaryData.fromCbor(CborMap cborMap) => _fromCborMap(cborMap); 23 | 24 | bool get isEmpty => 25 | (metadata == null || metadata!.isEmpty) && 26 | nativeScripts.isEmpty && 27 | plutusV1Scripts.isEmpty && 28 | plutusV2Scripts.isEmpty; 29 | 30 | @override 31 | CborValue get cborValue => toCborMap(); 32 | 33 | CborMap toCborMap() => 34 | //TODO metadata may not be a map 35 | 36 | (metadata != null && 37 | nativeScripts.isEmpty && 38 | plutusV1Scripts.isEmpty && 39 | plutusV2Scripts.isEmpty) 40 | //Shelley-mary format 41 | ? metadata!.value as CborMap 42 | //Alonzo format -> tag 259 43 | : CborMap( 44 | { 45 | if (metadata != null) _metaKey: metadata!.value, 46 | if (nativeScripts.isNotEmpty) 47 | _nativeKey: CborList( 48 | nativeScripts.map((s) => s.toCborList()).toList()), 49 | if (plutusV1Scripts.isNotEmpty) 50 | _v1Key: CborList( 51 | plutusV1Scripts.map((s) => s.cborBytes).toList()), 52 | if (plutusV2Scripts.isNotEmpty) 53 | _v2Key: CborList( 54 | plutusV2Scripts.map((s) => s.cborBytes).toList()), 55 | }, 56 | tags: [_alonzoTag], 57 | ); 58 | 59 | static BcAuxiliaryData _fromCborMap(CborMap cborMap) => 60 | (cborMap.tags.contains(_alonzoTag)) 61 | ? BcAuxiliaryData( 62 | metadata: cborMap.containsKey(_metaKey) 63 | ? BcMetadata.fromCbor(map: cborMap[_metaKey]!) 64 | : null, 65 | nativeScripts: cborMap.containsKey(_nativeKey) 66 | ? (cborMap[_nativeKey]! as CborList) 67 | .map((s) => BcNativeScript.fromCbor(list: s as CborList)) 68 | .toList() 69 | : [], 70 | plutusV1Scripts: cborMap.containsKey(_v1Key) 71 | ? (cborMap[_v1Key]! as CborList) 72 | .map((s) => BcPlutusScript.fromCbor(s as CborBytes, 73 | type: BcScriptType.plutusV1) as BcPlutusScriptV1) 74 | .toList() 75 | : [], 76 | plutusV2Scripts: cborMap.containsKey(_v2Key) 77 | ? (cborMap[_v2Key]! as CborList) 78 | .map((s) => BcPlutusScript.fromCbor(s as CborBytes, 79 | type: BcScriptType.plutusV2) as BcPlutusScriptV2) 80 | .toList() 81 | : [], 82 | ) 83 | : BcAuxiliaryData(metadata: BcMetadata.fromCbor(map: cborMap)); 84 | 85 | static const _alonzoTag = 259; 86 | static const _metaKey = CborSmallInt(0); 87 | static const _nativeKey = CborSmallInt(1); 88 | static const _v1Key = CborSmallInt(2); 89 | static const _v2Key = CborSmallInt(3); 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/transaction/model/bc_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class BcCborDeserializationException implements Exception { 5 | final String? message; 6 | BcCborDeserializationException([this.message]); 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/transaction/model/bc_pointer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class BcPointer { 4 | final int slot; //long 5 | final int txIndex; //int 6 | final int certIndex; //int 7 | 8 | const BcPointer( 9 | {required this.slot, required this.txIndex, required this.certIndex}); 10 | 11 | Uint8List get hash => Uint8List.fromList( 12 | _natEncode(slot) + _natEncode(txIndex) + _natEncode(certIndex)); 13 | 14 | List _natEncode(int num) { 15 | List result = []; 16 | result.add(num & 0x7f); 17 | num = num ~/ 128; 18 | while (num > 0) { 19 | result.add(num & 0x7f | 0x80); 20 | num = num ~/ 128; 21 | } 22 | return result.reversed.toList(); 23 | } 24 | // private byte[] variableNatEncode(long num) { 25 | // List output = new ArrayList<>(); 26 | // output.add((byte)(num & 0x7F)); 27 | 28 | // num /= 128; 29 | // while(num > 0) { 30 | // output.add((byte)((num & 0x7F) | 0x80)); 31 | // num /= 128; 32 | // } 33 | // Collections.reverse(output); 34 | 35 | // return Bytes.toArray(output); 36 | // } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/transaction/model/bc_redeemer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:bip32_ed25519/bip32_ed25519.dart'; 5 | import 'package:cbor/cbor.dart'; 6 | import 'package:hex/hex.dart'; 7 | import './bc_abstract.dart'; 8 | import './bc_plutus_data.dart'; 9 | 10 | enum BcRedeemerTag { 11 | spend(0), 12 | mint(1), 13 | cert(2), 14 | reward(3); 15 | 16 | final int value; 17 | const BcRedeemerTag(this.value); 18 | 19 | static BcRedeemerTag fromCbor(CborValue value) { 20 | if (value is CborInt && value.toInt() >= 0 && value.toInt() < 5) { 21 | return BcRedeemerTag.values[value.toInt()]; 22 | } else { 23 | throw CborError( 24 | "BcRedeemerTag expecting CborInt with value in [0..3], not $value"); 25 | } 26 | } 27 | } 28 | 29 | class BcRedeemer extends BcAbstractCbor { 30 | final BcRedeemerTag tag; 31 | final BigInt index; 32 | final BcPlutusData data; 33 | final BcExUnits exUnits; 34 | 35 | BcRedeemer( 36 | {required this.tag, 37 | required this.index, 38 | required this.data, 39 | required this.exUnits}); 40 | 41 | static BcRedeemer deserialize(Uint8List bytes) => 42 | fromCbor(cbor.decode(bytes)); 43 | 44 | static BcRedeemer fromCbor(CborValue item) { 45 | if (item is CborList) { 46 | if (item.length == 4) { 47 | return BcRedeemer( 48 | tag: BcRedeemerTag.fromCbor(item[0]), 49 | index: (item[1] as CborInt).toBigInt(), 50 | data: BcPlutusData.fromCbor(item[2]), 51 | exUnits: BcExUnits.fromCbor(item[3]), 52 | ); 53 | } else { 54 | throw CborError( 55 | "Redeemer list must contain 4 properties, not ${item.length}"); 56 | } 57 | } else { 58 | throw CborError("Redeemer expecting CborList, not $item"); 59 | } 60 | } 61 | 62 | factory BcRedeemer.fromHex(String hex) => 63 | BcRedeemer.fromCbor(cbor.decode(HEX.decode(hex))); 64 | 65 | @override 66 | CborValue get cborValue => CborList([ 67 | CborSmallInt(tag.value), 68 | CborInt(index), 69 | data.cborValue, 70 | exUnits.cborValue, 71 | ]); 72 | } 73 | 74 | class BcExUnits { 75 | final BigInt mem; 76 | final BigInt steps; 77 | 78 | BcExUnits(this.mem, this.steps); 79 | 80 | CborValue get cborValue => CborList([ 81 | CborInt(mem), 82 | CborInt(steps), 83 | ]); 84 | 85 | static BcExUnits fromCbor(CborValue value) { 86 | if (value is CborList && 87 | value.length == 2 && 88 | value[0] is CborInt && 89 | value[1] is CborInt) { 90 | return BcExUnits( 91 | (value[0] as CborInt).toBigInt(), (value[1] as CborInt).toBigInt()); 92 | } else { 93 | throw CborError( 94 | "BcExUnits.fromCbor expecting CborArray of two CborInt's, not $value"); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/transaction/policy.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:bip32_ed25519/bip32_ed25519.dart'; 5 | import 'package:cardano_wallet_sdk/src/crypto/key_util.dart'; 6 | import 'package:collection/collection.dart'; 7 | import 'package:hex/hex.dart'; 8 | 9 | import './model/bc_scripts.dart'; 10 | 11 | class Policy { 12 | final String? name; 13 | final BcNativeScript policyScript; 14 | final List policyKeys; 15 | 16 | Policy({this.name, required this.policyScript, required this.policyKeys}); 17 | 18 | String get policyId => policyScript.policyId; 19 | 20 | static const int slotsPerEpoch = 5 * 24 * 60 * 60; 21 | 22 | factory Policy.createEpochBasedTimeLocked( 23 | String? name, int currentSlot, int epochs) { 24 | final signingKey = KeyUtil.generateSigningKey(); 25 | final scriptPubkey = BcScriptPubkey( 26 | keyHash: KeyUtil.keyHash(verifyKey: signingKey.verifyKey)); 27 | final requireTimeBefore = 28 | BcRequireTimeBefore(slot: currentSlot + slotsPerEpoch * epochs); 29 | final scriptAll = BcScriptAll(scripts: [requireTimeBefore, scriptPubkey]); 30 | return Policy( 31 | name: name, policyScript: scriptAll, policyKeys: [signingKey]); 32 | } 33 | 34 | factory Policy.createMultiSigScriptAll(String? name, int numOfSigners) { 35 | if (numOfSigners < 1) { 36 | throw ArgumentError( 37 | "Number of policy signers must be larger or equal to 1"); 38 | } 39 | final policyKeys = List.generate( 40 | numOfSigners, (_) => KeyUtil.generateSigningKey(), 41 | growable: false); 42 | final policyAll = BcScriptAll( 43 | scripts: policyKeys 44 | .map((k) => BcScriptPubkey( 45 | keyHash: KeyUtil.keyHash(verifyKey: k.verifyKey))) 46 | .toList()); 47 | return Policy(name: name, policyScript: policyAll, policyKeys: policyKeys); 48 | } 49 | 50 | factory Policy.createMultiSigScriptAtLeast( 51 | String? name, 52 | int numOfSigners, 53 | int atLeast, 54 | ) { 55 | if (atLeast > numOfSigners) { 56 | throw ArgumentError( 57 | "Number of required signers cannot be higher than overall signers amount"); 58 | } 59 | final p = Policy.createMultiSigScriptAll(name, numOfSigners); 60 | final scriptAtLeast = BcScriptAtLeast( 61 | amount: atLeast, scripts: (p.policyScript as BcScriptAll).scripts); 62 | return Policy( 63 | name: name, policyScript: scriptAtLeast, policyKeys: p.policyKeys); 64 | } 65 | 66 | Map get toJson => { 67 | if (name != null) 'name': name, 68 | 'policyScript': policyScript.toJson, 69 | 'policyKeys': [ 70 | for (SigningKey key in policyKeys) HEX.encode(key), 71 | ], 72 | }; 73 | 74 | factory Policy.fromJson(Map json) => Policy( 75 | name: json['name'] as String?, 76 | policyScript: BcNativeScript.fromJson( 77 | json['policyScript'] as Map), 78 | policyKeys: (json['policyKeys'] as List) 79 | .map((e) => SigningKey.fromValidBytes( 80 | Uint8List.fromList(HEX.decode(e as String)))) 81 | .toList(), 82 | ); 83 | 84 | @override 85 | int get hashCode => 86 | Object.hash(name, policyScript, Object.hashAll(policyKeys)); 87 | 88 | @override 89 | bool operator ==(Object other) => 90 | identical(this, other) || 91 | (other is Policy && 92 | runtimeType == other.runtimeType && 93 | policyScript == other.policyScript && 94 | const ListEquality().equals(policyKeys, other.policyKeys)); 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/util/ada_types.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | /// Represents ADA amount in lovelace. 6 | /// 7 | /// TODO migrate to BigInt in future release. 8 | /// 9 | typedef Coin = BigInt; //int; 10 | 11 | /// placeholder for future BigInt.zero 12 | final Coin coinZero = BigInt.zero; // 0; 13 | 14 | /// Native Token policyId appended to hex encoded coin name. ADA has no policyId 15 | /// so its assetId is just 'lovelace' in hex: '6c6f76656c616365'. Simalur to 'unit' 16 | /// but 'lovelace' is not hex encoded. 17 | typedef AssetId = String; 18 | 19 | /// String representation of bech32 address 20 | typedef Bech32Address = String; 21 | 22 | /// Hex encoded transaction hash ID 23 | typedef TxIdHex = String; 24 | 25 | /// Hex encoded block hash ID 26 | typedef BlockHashHex = String; 27 | 28 | /// Wallet ID - stakingPublicKey for Shelley wallets 29 | typedef WalletId = String; 30 | -------------------------------------------------------------------------------- /lib/src/util/ada_validation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:oxidized/oxidized.dart'; 5 | 6 | /// 7 | /// If ADA string is all digits, not negative and the correct decimal precision, then the normalized 8 | /// correct form is returned. 9 | /// If it's not legal, then an explanation is returned in the error message. 10 | /// 11 | Result validAda({ 12 | required String ada, 13 | int decimalPrecision = 6, 14 | bool allowNegative = false, 15 | bool zeroAllowed = false, 16 | }) { 17 | ada = ada.trim(); 18 | int invalidCharIndex = _firstIllegalDataChar(ada, allowNegative); 19 | if (invalidCharIndex > -1) { 20 | return Err( 21 | "invalid character: ${ada.substring(invalidCharIndex, invalidCharIndex + 1)}"); 22 | } 23 | final amount = double.tryParse(ada) ?? 0.0; 24 | if (!zeroAllowed && amount == 0.0) return const Err("can't be zero"); 25 | final index = ada.lastIndexOf('.'); 26 | final fraction = 27 | index >= 0 && index < ada.length ? ada.substring(index + 1) : ''; 28 | if (fraction.length > decimalPrecision) { 29 | return Err("only $decimalPrecision decimal places allowed"); 30 | } 31 | return Ok('$amount'); 32 | } 33 | 34 | int _firstIllegalDataChar(String ada, bool allowNegative) { 35 | for (int i = 0; i < ada.length; i++) { 36 | if (!_legalAdaChar(ada.codeUnitAt(i), allowNegative)) return i; 37 | } 38 | return -1; 39 | } 40 | 41 | bool _legalAdaChar(int ch16, bool allowNegative) => 42 | _indexOfCodeUnit( 43 | ch16, allowNegative ? _adaLegalDataCharsNegative : _adaLegalDataChars) > 44 | -1; 45 | 46 | int _indexOfCodeUnit(int ch16, List codeUnits) { 47 | for (int i = 0; i < codeUnits.length; i++) { 48 | if (ch16 == codeUnits[i]) return i; 49 | } 50 | return -1; 51 | } 52 | 53 | final _adaLegalDataChars = '.0123456789'.codeUnits; 54 | final _adaLegalDataCharsNegative = '-.0123456789'.codeUnits; 55 | -------------------------------------------------------------------------------- /lib/src/util/bech32_validation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:logging/logging.dart'; 5 | import 'package:oxidized/oxidized.dart'; 6 | import 'package:quiver/strings.dart'; 7 | 8 | final _logger = Logger('bech32_validation'); 9 | 10 | /// 11 | /// if bech32 string has the correct prefix, '1' seperator, legal data characters and optionaly correct length, 12 | /// the normalized correct form is returned. If it's not legal, then an explanation is returned in the error message. 13 | /// 14 | Result validBech32( 15 | {required String bech32, 16 | required List hrpPrefixes, 17 | int? dataPartRequiredLength}) { 18 | if (hrpPrefixes.isEmpty) { 19 | throw Exception("validBech32 hrpPrefixes array must not be empty"); 20 | } 21 | if (isBlank(bech32)) return const Err("address missing"); 22 | final lowerCase = bech32.toLowerCase(); 23 | if (hrpPrefixes.length > 1) { 24 | hrpPrefixes.sort((a, b) => b.compareTo(a)); 25 | } //avoid matching 'addr' for 'addr_test' 26 | _logger.info(hrpPrefixes); 27 | final prefix = hrpPrefixes 28 | .firstWhere((prefix) => lowerCase.startsWith(prefix), orElse: () => ''); 29 | if (isBlank(prefix)) { 30 | return Err("must start with ${hrpPrefixes.join(' or ')}"); 31 | } 32 | if (lowerCase.length > prefix.length && 33 | lowerCase.codeUnitAt(prefix.length) != '1'.codeUnitAt(0)) { 34 | return const Err("missing '1' after prefix"); 35 | } 36 | final data = lowerCase.length > prefix.length 37 | ? lowerCase.substring(prefix.length + 1) 38 | : ''; 39 | int invalidCharIndex = _firstIllegalDataChar(data); 40 | if (invalidCharIndex > -1) { 41 | return Err( 42 | "invalid character: ${data.substring(invalidCharIndex, invalidCharIndex + 1)}"); 43 | } 44 | if (dataPartRequiredLength != null && data.length != dataPartRequiredLength) { 45 | return Err( 46 | "data length is ${data.length}, requires $dataPartRequiredLength"); 47 | } 48 | return Ok(lowerCase); 49 | } 50 | 51 | int _firstIllegalDataChar(String bech32Data) { 52 | for (int i = 0; i < bech32Data.length; i++) { 53 | if (!_legalBech32Char(bech32Data.codeUnitAt(i))) return i; 54 | } 55 | return -1; 56 | } 57 | 58 | bool _legalBech32Char(int ch16) => 59 | _indexOfCodeUnit(ch16, _bech32LegalDataChars) > -1; 60 | 61 | int _indexOfCodeUnit(int ch16, List codeUnits) { 62 | for (int i = 0; i < codeUnits.length; i++) { 63 | if (ch16 == codeUnits[i]) return i; 64 | } 65 | return -1; 66 | } 67 | 68 | final _bech32LegalDataChars = 69 | '023456789acdefghjklmnpqrstuvwxyz'.codeUnits; //exlucdes '1bio' 70 | -------------------------------------------------------------------------------- /lib/src/util/blake2bhash.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:pinenacl/digests.dart'; 5 | import 'package:pinenacl/encoding.dart'; 6 | 7 | /// 8 | /// Base blake2b hash function can produce hashes of arbirary size. 9 | /// 10 | List blake2bHash(List stringBytes, {required int digestSize}) => 11 | Hash.blake2b(Uint8List.fromList(stringBytes), digestSize: digestSize); 12 | 13 | List blake2bHash160(List stringBytes) => 14 | blake2bHash(stringBytes, digestSize: 20); 15 | 16 | List blake2bHash224(List stringBytes) => 17 | blake2bHash(stringBytes, digestSize: 28); 18 | 19 | List blake2bHash256(List stringBytes) => 20 | blake2bHash(stringBytes, digestSize: 32); 21 | -------------------------------------------------------------------------------- /lib/src/util/list_ext.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:typed_data'; 5 | 6 | /// Converts a `List` to a [Uint8List]. 7 | /// 8 | /// Attempts to cast to a [Uint8List] first to avoid creating an unnecessary 9 | /// copy. 10 | extension AsUint8List on List { 11 | Uint8List asUint8List() { 12 | final self = this; // Local variable to allow automatic type promotion. 13 | return (self is Uint8List) ? self : Uint8List.fromList(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/util/misc.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// Dump byte array. Example: bytes[20]: 244,155,227,187,150,186,199,61,202,241,76,208,46,192,219,56,241,103,253,67 5 | String b2s(List bytes, {String prefix = 'bytes'}) => 6 | "$prefix[${bytes.length}]: ${bytes.join(',')}"; 7 | -------------------------------------------------------------------------------- /lib/src/wallet/impl/wallet_cache_memory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import '../../util/ada_types.dart'; 5 | import '../wallet_cache.dart'; 6 | 7 | /// 8 | /// Implements an in-memory version of WalletCache. 9 | /// 10 | class WalletCacheMemory implements WalletCache { 11 | final Map> _cache = {}; 12 | 13 | ///lookup cached WalletValue by ID 14 | @override 15 | WalletValue? cachedWalletById(WalletId walletId) => _cache[walletId]; 16 | 17 | ///cache WalletValue. 18 | @override 19 | void cacheWallet(WalletValue walletValue) => 20 | _cache[walletValue.wallet.walletId] = walletValue; 21 | 22 | ///clear cache, returning number of wallets removed. 23 | @override 24 | int clearCachedWallets() { 25 | final length = _cache.length; 26 | _cache.clear(); 27 | return length; 28 | } 29 | 30 | Map> get cache => _cache; 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/wallet/impl/wallet_update.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // import 'dart:convert'; 5 | 6 | import '../../address/shelley_address.dart'; 7 | import '../../stake/stake_account.dart'; 8 | import '../../transaction/transaction.dart'; 9 | import '../../asset/asset.dart'; 10 | import '../../util/ada_types.dart'; 11 | 12 | /// 13 | /// Pass-back object used to update existing or new wallets. 14 | /// 15 | class WalletUpdate { 16 | final Coin balance; 17 | final List transactions; 18 | final List addresses; 19 | final Map assets; 20 | // final List utxos; 21 | final List stakeAccounts; 22 | WalletUpdate({ 23 | required this.balance, 24 | required this.transactions, 25 | required this.addresses, 26 | required this.assets, 27 | // required this.utxos, 28 | required this.stakeAccounts, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/wallet/read_only_wallet.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:oxidized/oxidized.dart'; 5 | import '../address/shelley_address.dart'; 6 | import '../asset/asset.dart'; 7 | import '../blockchain/blockchain_adapter.dart'; 8 | import '../network/network_id.dart'; 9 | import '../stake/stake_account.dart'; 10 | import '../transaction/transaction.dart'; 11 | import '../util/ada_types.dart'; 12 | 13 | /// 14 | /// Cardano read-only wallet that holds transactions, staking rewards and their associated 15 | /// addresses. A public, read-ony wallet can be built given a stakingAddress and a network. 16 | /// All blockchain data retrieval is delegated to the BlockchainAdapter. 17 | /// 18 | abstract class ReadOnlyWallet { 19 | /// Return walletId. ID is public staking address for Shelley wallets. 20 | WalletId get walletId; 21 | 22 | /// network is either mainnet or nestnet 23 | Networks get network; 24 | 25 | /// return true if this is read-only wallet that can't sign transactions and send funds. 26 | bool get readOnly; 27 | 28 | /// name of wallet 29 | String get walletName; 30 | 31 | /// balance of wallet in lovelace 32 | Coin get balance; 33 | 34 | /// calculate balance from transactions and rewards 35 | Coin get calculatedBalance; 36 | 37 | /// balances of native tokens indexed by assetId 38 | Map get currencies; 39 | 40 | /// optional stake pool details 41 | List get stakeAccounts; 42 | 43 | /// staking address 44 | ShelleyAddress get stakeAddress; 45 | 46 | /// access to the bockchain 47 | BlockchainAdapter get blockchainAdapter; 48 | 49 | /// assets present in this wallet indexed by assetId 50 | Map get assets; 51 | List get transactions; 52 | List get unspentTransactions; 53 | List filterTransactions({required AssetId assetId}); 54 | List get addresses; 55 | bool refresh( 56 | {required Coin balance, 57 | required List transactions, 58 | required List usedAddresses, 59 | required Map assets, 60 | required List stakeAccounts}); 61 | 62 | CurrencyAsset? findAssetWhere(bool Function(CurrencyAsset asset) matcher); 63 | CurrencyAsset? findAssetByTicker(String ticker); 64 | 65 | /// Duration since update was called. Set to zero when update completes. 66 | Duration get loadingTime; 67 | 68 | /// Update or sync wallet transactions with blockchain. Return true if data changed. 69 | /// Is ignored if loadingTime is not zero. 70 | Future> update(); 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/wallet/wallet.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // import 'package:bip32_ed25519/bip32_ed25519.dart'; 5 | // import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 6 | import 'package:oxidized/oxidized.dart'; 7 | // import '../address/hd_wallet.dart'; 8 | import '../address/shelley_address.dart'; 9 | // import '../transaction/spec/shelley_spec.dart'; 10 | import '../transaction/model/bc_tx.dart'; 11 | import '../util/ada_types.dart'; 12 | import './read_only_wallet.dart'; 13 | import '../hd/hd_account.dart'; 14 | 15 | /// 16 | /// Extend ReadOnlyWallet with signing and transactional capabilities. Signing (private), 17 | /// key, verify (public) key and address generation is handled by the HdWallet instance. 18 | /// 19 | /// This is currently a prototype wallet and only supports sending simple ADA transactions. 20 | /// 21 | abstract class Wallet extends ReadOnlyWallet { 22 | /// Hierarchical deterministic wallet 23 | HdAccount get account; 24 | 25 | // /// Account index of wallet, defaults to 0. 26 | // int get accountIndex; 27 | 28 | // /// Root private and public key 29 | // Bip32KeyPair get rootKeyPair; 30 | 31 | // /// Base address key pair used for signing transactions 32 | // Bip32KeyPair get addressKeyPair; 33 | 34 | /// Returns first unused receive address, used by others to send money to this account. 35 | ShelleyReceiveKit get firstUnusedReceiveAddress; 36 | 37 | /// Returns first unused change address, used to return unspent change to this wallet. 38 | ShelleyReceiveKit get firstUnusedChangeAddress; 39 | 40 | /// Find signing key for spend or change address. 41 | Map findSigningKeyForUtxos( 42 | {required Set utxos}); 43 | 44 | /// Send ADA to another address. 45 | Future> sendAda({ 46 | required AbstractAddress toAddress, 47 | required Coin lovelace, 48 | int ttl = 0, 49 | Coin? fee, 50 | bool logTxHex = false, 51 | bool logTx = false, 52 | }); 53 | 54 | /// Send a transaction. 55 | Future> submitTransaction({ 56 | required BcTransaction tx, 57 | }); 58 | 59 | /// Build a simple spend transaction. 60 | Future> buildSpendTransaction({ 61 | required ShelleyAddress toAddress, 62 | required Coin lovelace, 63 | int ttl = 0, 64 | Coin? fee, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/wallet/wallet_cache.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import '../util/ada_types.dart'; 5 | import './read_only_wallet.dart'; 6 | 7 | /// 8 | /// Wallet cache interface. 9 | /// 10 | abstract class WalletCache { 11 | ///lookup cached wallet by ID 12 | WalletValue? cachedWalletById(WalletId walletId); 13 | 14 | ///cache wallet. 15 | void cacheWallet(WalletValue walletValue); 16 | 17 | ///clear cache 18 | int clearCachedWallets(); 19 | } 20 | 21 | class WalletValue { 22 | final ReadOnlyWallet wallet; 23 | final T metadata; 24 | WalletValue({required this.wallet, required this.metadata}); 25 | } 26 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cardano_wallet_sdk 2 | description: Targeting Flutter apps, the Cardano Wallet SDK is a high-level Dart library for managing cryptocurrency accounts & executing transactions on the blockchain. 3 | version: 0.1.0-alpha.12 4 | homepage: https://github.com/reaster/cardano_wallet_sdk 5 | 6 | environment: 7 | sdk: ">=2.17.0 <3.4.0" 8 | 9 | dependencies: 10 | analyzer: ^5.4.0 11 | blockfrost: ^1.1.0-dev.1 12 | # blockfrost: 13 | # path: ../blockfrost_api 14 | bip32_ed25519: 0.5.0 15 | pinenacl: 0.5.1 16 | crypto: ^3.0.2 17 | base_x: ^2.0.0 18 | built_collection: ^5.1.0 19 | built_value: ^8.1.0 20 | cbor: ^5.0.1 21 | collection: ^1.15.0 22 | convert: ^3.0.0 23 | dio: ^4.0.3 24 | hex: ^0.2.0 25 | intl: ^0.18.0 26 | json_annotation: ^4.5.0 27 | logging: ^1.0.2 28 | oxidized: ^5.1.0 29 | quiver: ^3.0.1+1 30 | typed_data: ^1.3.0 31 | 32 | dev_dependencies: 33 | build_runner: ^2.1.5 34 | flutter_lints: ^2.0.0 35 | http: ^0.13.4 36 | json_serializable: 37 | lints: ^2.0.0 38 | mockito: ^5.3.1 39 | test: any 40 | # dependency_overrides: 41 | # pinenacl: 42 | # path: ../pinenacl-dart 43 | # bip32_ed25519: 44 | # path: ../bip32-ed25519-dart 45 | # blockfrost: 46 | # git: git@github.com:reaster/blockfrost_api.git 47 | # path: ../blockfrost_api 48 | 49 | flutter: 50 | assets: 51 | - assets/bip39/english.txt 52 | -------------------------------------------------------------------------------- /test/blockchain/blockfrost_blockchain_adapter_itest.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:cbor/cbor.dart'; 7 | import 'package:blockfrost/blockfrost.dart'; 8 | import 'dart:convert' as convert; 9 | import 'dart:io' as io; 10 | import 'package:test/test.dart'; 11 | import 'package:hex/hex.dart'; 12 | 13 | const apiKeyFilePath = '../blockfrost_project_id.txt'; 14 | 15 | String _readApiKey() { 16 | final file = io.File(apiKeyFilePath).absolute; 17 | return file.readAsStringSync(); 18 | } 19 | 20 | void main() { 21 | // Logger.root.level = Level.WARNING; // defaults to Level.INFO 22 | Logger.root.onRecord.listen((record) { 23 | print('${record.level.name}: ${record.time}: ${record.message}'); 24 | }); 25 | final logger = Logger('BlockfrostBlockchainAdapterTest'); 26 | final network = Networks.testnet; 27 | final interceptor = BlockfrostApiKeyAuthInterceptor(projectId: _readApiKey()); 28 | final blockfrost = Blockfrost( 29 | basePathOverride: BlockfrostBlockchainAdapter.urlFromNetwork(network), 30 | interceptors: [interceptor], 31 | ); 32 | final blockfrostAdapter = BlockfrostBlockchainAdapter( 33 | network: network, blockfrost: blockfrost, projectId: _readApiKey()); 34 | final mnemonic = 35 | 'company coast prison denial unknown design paper engage sadness employ phone cherry thunder chimney vapor cake lock afraid frequent myself engage lumber between tip' 36 | .split(' '); 37 | final HdAccount sender = 38 | HdMaster.mnemonic(mnemonic, network: network).account(); 39 | 40 | group('BlockfrostBlockchainAdapter -', () { 41 | test('latestEpochParameters', () async { 42 | final result = await blockfrostAdapter.latestEpochParameters(); 43 | expect(result.isOk(), isTrue); 44 | final ProtocolParameters params = result.unwrap(); 45 | expect(params.epoch, greaterThan(243)); 46 | expect(params.minFeeA, greaterThanOrEqualTo(40)); 47 | expect( 48 | params.coinsPerUtxoSize, greaterThanOrEqualTo(BigInt.parse('4310'))); 49 | expect(params.costModels.length, equals(2)); 50 | final Map? plutusV1 = 51 | params.costModels[BcScriptType.plutusV1]; 52 | expect(plutusV1, isNotNull); 53 | expect(plutusV1!.isNotEmpty, isTrue); 54 | // logger.info( 55 | // "plutusV1!['addInteger-cpu-arguments-intercept']:${plutusV1!['addInteger-cpu-arguments-intercept']}"); 56 | }); 57 | 58 | test('latestBlock', () async { 59 | final result = await blockfrostAdapter.latestBlock(); 60 | expect(result.isOk(), isTrue); 61 | final Block block = result.unwrap(); 62 | expect(block.epoch, greaterThan(243)); 63 | //print("block.time: ${block.time}"); 64 | expect( 65 | block.time.millisecondsSinceEpoch, 66 | greaterThanOrEqualTo(DateTime.now() 67 | .subtract(Duration(days: 1)) 68 | .millisecondsSinceEpoch)); 69 | }); 70 | }, skip: "upgrade to Prepod"); 71 | } 72 | -------------------------------------------------------------------------------- /test/blockchain/blockfrost_test_auth_interceptor.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'dart:io'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:blockfrost/src/auth/auth.dart'; 7 | 8 | /// 9 | /// This is the AuthInterceptor used to access the Cardano blockchain via blockfrost.io. 10 | /// 11 | /// You'll need to obtian a free apiKey from https://blockfrost.io to run these tests. 12 | /// 13 | /// Once you have a key, place it in a text file in the parent directory of this project, 14 | /// in a file named: blockfrost_project_id.txt 15 | /// 16 | class BlockfrostTestAuthInterceptor extends AuthInterceptor { 17 | late final String apiKey; 18 | 19 | BlockfrostTestAuthInterceptor() : apiKey = _readApiKey(); 20 | 21 | @override 22 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 23 | options.headers['project_id'] = apiKey; 24 | super.onRequest(options, handler); 25 | } 26 | 27 | static String _readApiKey() { 28 | final file = File(apiKeyFilePath); 29 | return file.readAsStringSync(); 30 | } 31 | 32 | static const apiKeyFilePath = '../blockfrost_project_id.txt'; 33 | } 34 | 35 | /// 36 | /// https://itnexplorer.cardano.org/en/ 37 | /// 38 | /// Address: 39 | /// Addresses are 59-character, case-sensitive alphanumerical strings. 40 | /// Transaction 41 | /// Transaction hashes are 64-character, case-sensitive hexadecimal strings. 42 | /// Block 43 | /// Block hashes are 64-character, case-sensitive hexadecimal strings. 44 | /// Epoch 45 | /// Epoch numbers are numerical strings of no fixed length. Searches for future epochs are also valid and will return information about the time and date when the future epoch will commence. 46 | /// Stake pool 47 | /// Stake pool hashes are 64-character, case-sensitive hexadecimal strings. 48 | /// 49 | /// examples: 50 | /// addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp 51 | /// stake_test1uqevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqp8n5xl 52 | /// 53 | /// 54 | /// 55 | 56 | const mainnet = 'https://cardano-mainnet.blockfrost.io/api/v0'; 57 | const testnet = 'https://cardano-testnet.blockfrost.io/api/v0'; 58 | const ipfs = 59 | 'https://ipfs.blockfrost.io/api/v0'; //InterPlanetary File System, 100MB max upload size 60 | 61 | const asc = 'asc'; //?order=asc, oldest first (default) 62 | const desc = 'desc'; //?order=desc, newest first 63 | const lovelacePerAda = 1000000; 64 | -------------------------------------------------------------------------------- /test/crypto/sign_ed25519_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:test/test.dart'; 6 | import 'package:pinenacl/ed25519.dart'; 7 | import 'dart:convert'; 8 | import 'package:hex/hex.dart'; 9 | import 'dart:typed_data'; 10 | 11 | /// Standalone signing functions. 12 | /// TODO currently the code still calls Bip32VerifyKey.sign. Use it or lose it! 13 | void main() { 14 | group('Ed25519 - ', () { 15 | test('signEd25519Extended', () { 16 | final msg = Uint8List.fromList(utf8.encode('hello')); 17 | final pvtKey = Uint8List.fromList(HEX.decode( 18 | '78bfcc962ce4138fba00ea6e46d4eca6ae9457a058566709b52941aaf026fe53dede3f2ddde7762821c2f957aac77b80a3c36beab75881cc83c600695806f1dd')); 19 | final pubKey = Uint8List.fromList(HEX.decode( 20 | '9518c18103cbdab9c6e60b58ecc3e2eb439fef6519bb22570f391327381900a8')); 21 | final expectedSignatureHex = 22 | 'f13fa9acffb108114ec060561b58005fb2d69184de0a2d7400b2ea1f111c0794831cc832c92daf4807820dd9458324935e90bec855e8bf076bbbc4e42b727b07'; 23 | final expectResult = Uint8List.fromList(HEX.decode(expectedSignatureHex)); 24 | //print("expectResult.length: ${expectResult.length}"); 25 | 26 | Uint8List signature = signEd25519Extended( 27 | message: msg, privateKey: pvtKey, publicKey: pubKey); 28 | final signatureHex = HEX.encode(signature); 29 | 30 | expect(signatureHex, expectedSignatureHex); 31 | 32 | final verified = 33 | verifyEd25519(signature: signature, message: msg, publicKey: pubKey); 34 | expect(verified, isTrue); 35 | }); 36 | 37 | test('signEd25519', () { 38 | final privateKey = Uint8List.fromList(HEX.decode( 39 | '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60')); 40 | final publicKey = Uint8List.fromList(HEX.decode( 41 | 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a')); 42 | final msg = Uint8List.fromList(utf8 43 | .encode('eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc')); 44 | final signature = signEd25519(message: msg, privateKey: privateKey); 45 | final expectedSignature = Uint8List.fromList(HEX.decode( 46 | '860c98d2297f3060a33f42739672d61b53cf3adefed3d3c672f320dc021b411e9d59b8628dc351e248b88b29468e0e41855b0fb7d83bb15be902bfccb8cd0a02')); 47 | expect(signature, expectedSignature); 48 | 49 | final verified = verifyEd25519( 50 | signature: signature, message: msg, publicKey: publicKey); 51 | expect(verified, isTrue); 52 | }); 53 | 54 | test('signEd25519ZeroSeed', () { 55 | final seed = Uint8List.fromList(HEX.decode( 56 | '0000000000000000000000000000000000000000000000000000000000000000')); 57 | final msg = Uint8List.fromList(utf8.encode('This is a secret message')); 58 | final signature = signEd25519(message: msg, privateKey: seed); 59 | final expectedSignature = Uint8List.fromList(HEX.decode( 60 | '94825896c7075c31bcb81f06dba2bdcd9dcf16e79288d4b9f87c248215c8468d475f429f3de3b4a2cf67fe17077ae19686020364d6d4fa7a0174bab4a123ba0f')); 61 | expect(signature, expectedSignature); 62 | 63 | final verifyKey = SigningKey(seed: seed).verifyKey; 64 | final verified = verifyEd25519( 65 | signature: signature, message: msg, publicKey: verifyKey.asTypedList); 66 | expect(verified, isTrue); 67 | }); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /test/data/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "json-1": { 3 | "17802948329108123211": {}, 4 | "945845007538436815": "0x4ebc4ea3b43bb0cc76bb326f17a30d8f", 5 | "1351859328329939190": { 6 | "0x0e": "0x3bdefda92265", 7 | "0x14ff8d": "-1.3139667629422286119e19" 8 | }, 9 | "7505166164059511819": "rceHlUxXlWmZJcxYd", 10 | "7274669146951118819": "-1.4814972676680046432e19", 11 | "1302243434517352162": [ 12 | "UJB3", 13 | "-1.6236436627090480302e19" 14 | ] 15 | }, 16 | "json-2": { 17 | "17802948329108123211": {}, 18 | "945845007538436815": "0x4ebc4ea3b43bb0cc76bb326f17a30d8f", 19 | "1351859328329939190": { 20 | "0x0e": "0x3bdefda92265", 21 | "0x14ff8d": "-1.3139667629422286119e19", 22 | "nested-map": { 23 | "0x0aaa": "0x3bdefda92265", 24 | "0x11ff8d": 1234, 25 | "nested-list": [ 26 | "N1", 27 | 123 28 | ] 29 | } 30 | }, 31 | "7505166164059511819": "rceHlUxXlWmZJcxYd", 32 | "7274669146951118819": "-1.4814972676680046432e19", 33 | "1302243434517352162": [ 34 | "UJB3", 35 | "-1.6236436627090480302e19", 36 | { 37 | "key1": "another-netsted-value", 38 | "key2": 4566 39 | } 40 | ] 41 | }, 42 | "json-3": { 43 | "17802948329108123211": {}, 44 | "945845007538436815": "0x4ebc4ea3b43bb0cc76bb326f17a30d8f", 45 | "7505166164059511819": "rceHlUxXlWmZJcxYd", 46 | "1351859328329939190": { 47 | "0x0e": "0x3bdefda92265", 48 | "0x14ff8d": "-1.3139667629422286119e19" 49 | }, 50 | "7274669146951118819": "-1.4814972676680046432e19", 51 | "1302243434517352162": [ 52 | "UJB3", 53 | "-1.6236436627090480302e19" 54 | ] 55 | }, 56 | "json-4": { 57 | "17802948329108123211": {}, 58 | "945845007538436815": "0x4ebc4ea3b43bb0cc76bb326f17a30d8f", 59 | "1351859328329939190": { 60 | "0x0e": "0x3bdefda92265", 61 | "0x14ff8d": -13139667 62 | }, 63 | "7505166164059511819": "rceHlUxXlWmZJcxYd", 64 | "7274669146951118819": -1481497267, 65 | "1302243434517352162": [ 66 | "UJB3", 67 | -16236436 68 | ] 69 | } 70 | } -------------------------------------------------------------------------------- /test/data/plutus-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MultiSigPolicy", 3 | "policyScript": { 4 | "type": "all", 5 | "scripts": [ 6 | { 7 | "type": "sig", 8 | "keyHash": "2e5adb21a00882b43e7b9c16d457cfab78699d27cb7833b7a8bd11b6" 9 | }, 10 | { 11 | "type": "sig", 12 | "keyHash": "786645049d724ace01dbb397646fa4de5d936c770f02c5eb1b89456a" 13 | }, 14 | { 15 | "type": "sig", 16 | "keyHash": "e5df1f1439c7bf1c265db00d46ea07fe2708af720fafcef560bded27" 17 | } 18 | ] 19 | }, 20 | "policyKeys": [ 21 | { 22 | "type": "PaymentVerificationKeyShelley_ed25519", 23 | "description": "Payment Signing Key", 24 | "cborHex": "582088dfc434edf14d3e72ba518c2aad3132e51d721a183662dc9c42156caebee48c" 25 | }, 26 | { 27 | "type": "PaymentVerificationKeyShelley_ed25519", 28 | "description": "Payment Signing Key", 29 | "cborHex": "5820389f6de926f003c648f9184ad7d74cea8951ef3c6fa875da2ab4626febc6542d" 30 | }, 31 | { 32 | "type": "PaymentVerificationKeyShelley_ed25519", 33 | "description": "Payment Signing Key", 34 | "cborHex": "58201d74e9f2e6bfa76afd60a5a7851df601f607d5c7a01351ee43fa9253975dda16" 35 | } 36 | ], 37 | "policyId": "403687b05f2c8f8f8e7a5c860c0b489fc041bf75f8404c409d9a3b80" 38 | } -------------------------------------------------------------------------------- /test/transaction/fee_calculation_service_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:collection/collection.dart'; 6 | import 'package:hex/hex.dart'; 7 | import 'package:logging/logging.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | //Logger.root.level = Level.WARNING; // defaults to Level.INFO 12 | Logger.root.onRecord.listen((record) { 13 | print('${record.level.name}: ${record.time}: ${record.message}'); 14 | }); 15 | final logger = Logger('FeeCalculationServiceTest'); 16 | final protocolParameters = protocolParametersEpoch243; 17 | final service = FeeCalculationService(protocolParameters); 18 | 19 | group('fee calculation -', () { 20 | test('tx fee', () async { 21 | final txHex = 22 | '84a5008182582064fd185ae2760fe89651d06ea9a1dbacd0529f18532daa70ae1deed13b36f0f801018282583900fe02378e3e22e64ff864a68e7ec2d7300ac20a768eadc0c67ce249a63e61daf0df57f1cc6fdb15cea66150d63fa3db71c90f8f337960243b1a001e848082583900b0270066e3821d63ba1ed5cbebe2fec46c341f0f67786c332dee637554beac4fe00ebcdc9d39b80b4b5bb554493afbdbccf8e2b017b5dc351a05b3a956021a00029755031a0474f38e075820bdaa99eb158414dea0a91d6c727e2268574b23efe6e08ab3b841abe8059a030ca100818258205b8392e8bced75e8c217dc57c907a79304685d3508ba0aacff8bc388351ad2e95840313a221cac2e1f3ae5ccfdf4054b6728d0c175895698f96d041db0a740d45e8f8a8a10939c2ec2922aff273cc58990ee4e7a3c90a0124af87098283faef8ca0ff5d90103a0'; 23 | final buff = HEX.decode(txHex); 24 | expect(buff.length, equals(328)); 25 | final BigInt fee0 = service.calculateMinFeeFromBytes(buff); 26 | expect(fee0, equals(BigInt.from(169813))); 27 | final tx = BcTransaction.fromHex(txHex); 28 | final BigInt fee = service.calculateMinFee(transaction: tx); 29 | expect(fee, equals(BigInt.from(169813)), 30 | skip: "length is too short 290 vs 328"); 31 | }); 32 | 33 | test('script fee', () async { 34 | final redeemer = BcRedeemer( 35 | tag: BcRedeemerTag.spend, 36 | index: BigInt.zero, 37 | data: BcBigIntPlutusData(BigInt.from(42)), 38 | exUnits: BcExUnits( 39 | BigInt.from(458438), 40 | BigInt.from(234081144), 41 | )); 42 | BigInt fee = service.calculateScriptFee([redeemer.exUnits]); 43 | expect(fee.toInt(), equals(43330)); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test/transaction/model/bc_certificate_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:bip32_ed25519/bip32_ed25519.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'dart:convert'; 8 | import 'package:cbor/cbor.dart'; 9 | import 'package:test/test.dart'; 10 | import 'package:hex/hex.dart'; 11 | 12 | /// 13 | /// CBOR output can be validated here: http://cbor.me 14 | /// CBOR encoding reference: https://www.rfc-editor.org/rfc/rfc7049.html#appendix-B 15 | /// 16 | /// Current CBOR spec is rfc8949: https://www.rfc-editor.org/rfc/rfc8949.html 17 | /// 18 | /// tests and results taken from: https://github.com/bloxbean/cardano-client-lib. Thank you! 19 | /// 20 | class DummyTransactionBody extends BcAbstractCbor { 21 | final List certs; 22 | DummyTransactionBody(this.certs); 23 | factory DummyTransactionBody.fromCbor({required CborMap map}) { 24 | final certs = map[const CborSmallInt(4)] == null 25 | ? [] 26 | : (map[const CborSmallInt(4)] as CborList) 27 | .map((list) => BcCertificate.fromCbor(list: list as CborList)) 28 | .toList(); 29 | return DummyTransactionBody(certs); 30 | } 31 | 32 | @override 33 | CborValue get cborValue => toCborMap(); 34 | 35 | CborMap toCborMap() { 36 | return CborMap({ 37 | if (certs.isNotEmpty) 38 | const CborSmallInt(4): CborList(certs.map((m) => m.cborValue).toList()), 39 | }); 40 | } 41 | } 42 | 43 | void main() { 44 | // Logger.root.level = Level.WARNING; // defaults to Level.INFO 45 | Logger.root.onRecord.listen((record) { 46 | print('${record.level.name}: ${record.time}: ${record.message}'); 47 | }); 48 | final logger = Logger('BcCertificateTest'); 49 | group('certificate -', () { 50 | final dummyChainCode = csvToUint8List( 51 | '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'); 52 | final verifyKey1 = Bip32VerifyKey.fromKeyBytes( 53 | addrVkCoder.decode( 54 | 'addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd'), 55 | dummyChainCode); 56 | final verifyKey2 = Bip32VerifyKey.fromKeyBytes( 57 | stakeVkCoder.decode( 58 | 'stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu'), 59 | dummyChainCode); 60 | 61 | test('BcCertificate serialize', () { 62 | BcCertificate c1 = BcStakeRegistration( 63 | credential: BcStakeCredential.fromKey(verifyKey: verifyKey1)); 64 | BcCertificate c2 = BcStakeDeregistration( 65 | credential: BcStakeCredential.fromKey(verifyKey: verifyKey2)); 66 | BcCertificate c3 = BcStakeDelegation( 67 | credential: BcStakeCredential.fromKey(verifyKey: verifyKey2), 68 | poolId: 'pool14pdhhugxlqp9vta49pyfu5e2d5s82zmtukcy9x5ylukpkekqk8l', 69 | ); 70 | final body1 = DummyTransactionBody([c1, c2, c3]); 71 | final cbor1 = body1.toCborMap(); 72 | final body2 = DummyTransactionBody.fromCbor(map: cbor1); 73 | expect(body2, equals(body1)); 74 | }); 75 | test('poolId bech32', () { 76 | final pool1 = 'pool14pdhhugxlqp9vta49pyfu5e2d5s82zmtukcy9x5ylukpkekqk8l'; 77 | final bytes1 = stakePoolBytesFromBech32(pool1); 78 | final pool2 = bech32FromStakePoolBytes(bytes1); 79 | expect(pool1, equals(pool2)); 80 | }); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/transaction/model/bc_redeemer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'dart:convert' as convert; 7 | import 'package:cbor/cbor.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:hex/hex.dart'; 10 | import 'dart:typed_data'; 11 | 12 | void main() { 13 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 14 | Logger.root.onRecord.listen((record) { 15 | print('${record.level.name}: ${record.time}: ${record.message}'); 16 | }); 17 | final logger = Logger('BcScriptsTest'); 18 | group('Redeemer -', () { 19 | final fortyTwo = CborBigInt(BigInt.from(42)); 20 | final hello = CborBytes('hello'.codeUnits); 21 | final list1 = CborList([fortyTwo, hello]); 22 | final map1 = CborMap({fortyTwo: hello}); 23 | 24 | test('cbor', () { 25 | final redeemer1 = BcRedeemer( 26 | tag: BcRedeemerTag.spend, 27 | index: BigInt.from(99), 28 | data: BcPlutusData.fromCbor(map1), 29 | exUnits: BcExUnits(BigInt.from(1024), BigInt.from(6)), 30 | ); 31 | final cbor = redeemer1.cborValue; 32 | logger.info(cbor); 33 | final hex1 = redeemer1.hex; 34 | logger.info(hex1); 35 | final redeemer2 = BcRedeemer.fromHex(hex1); 36 | expect(redeemer2, equals(redeemer1)); 37 | }); 38 | test('cbor2', () { 39 | final redeemer1 = BcRedeemer( 40 | tag: BcRedeemerTag.spend, 41 | index: BigInt.from(0), 42 | data: BcBigIntPlutusData(BigInt.from(2021)), 43 | exUnits: BcExUnits(BigInt.from(1700), BigInt.from(476468)), 44 | ); 45 | final cbor = redeemer1.cborValue; 46 | logger.info(cbor); 47 | final hex1 = redeemer1.hex; 48 | logger.info(hex1); 49 | expect(hex1, equals('8400001907e5821906a41a00074534')); 50 | }); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /test/transaction/policy_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'dart:convert' as convert; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 11 | Logger.root.onRecord.listen((record) { 12 | print('${record.level.name}: ${record.time}: ${record.message}'); 13 | }); 14 | final logger = Logger('PolicyTest'); 15 | convert.JsonEncoder ppEncoder = convert.JsonEncoder.withIndent(' '); 16 | 17 | group('serialize -', () { 18 | final currentSlot = Policy.slotsPerEpoch * 42; 19 | 20 | test('createEpochBasedTimeLocked', () async { 21 | final p = Policy.createEpochBasedTimeLocked('locked', currentSlot, 3); 22 | logger.info(ppEncoder.convert(p.toJson)); 23 | final p2 = Policy.fromJson(p.toJson); 24 | logger.info(ppEncoder.convert(p2.toJson)); 25 | expect(p2, equals(p)); 26 | }); 27 | 28 | test('createMultiSigScriptAll', () async { 29 | final p = Policy.createMultiSigScriptAll('locked', 3); 30 | logger.info(ppEncoder.convert(p.toJson)); 31 | final p2 = Policy.fromJson(p.toJson); 32 | logger.info(ppEncoder.convert(p2.toJson)); 33 | expect(p2, equals(p)); 34 | }); 35 | 36 | test('createMultiSigScriptAtLeast', () async { 37 | final p = Policy.createMultiSigScriptAtLeast('locked', 3, 2); 38 | logger.info(ppEncoder.convert(p.toJson)); 39 | final p2 = Policy.fromJson(p.toJson); 40 | logger.info(ppEncoder.convert(p2.toJson)); 41 | expect(p2, equals(p)); 42 | }); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /test/util/ada_time_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | //Logger.root.level = Level.WARNING; // defaults to Level.INFO 10 | Logger.root.onRecord.listen((record) { 11 | print('${record.level.name}: ${record.time}: ${record.message}'); 12 | }); 13 | final logger = Logger('AdaTimeTest'); 14 | group('AdaDateTime -', () { 15 | test('codec', () { 16 | final now = DateTime.utc(2017, 9, 7, 17, 30, 59); 17 | final timestamp = adaDateTime.decode(now); 18 | final now2 = adaDateTime.encode(timestamp); 19 | logger.info("$now -> secondsSinceEpoch: $timestamp -> $now2"); 20 | expect(now, equals(now2)); 21 | }); 22 | }); 23 | group('EpochDateTime -', () { 24 | // test('codec', () { 25 | // final slot0DateTime = DateTime(2017, 9, 23, 21, 44, 51).toUtc(); 26 | // final slot0EpochMsUtc = slot0DateTime.millisecondsSinceEpoch; 27 | // final now0 = DateTime.fromMillisecondsSinceEpoch(137 * 432000 * 1000 + 1506203091, isUtc: true); 28 | // final now1 = epochDateTime.encode(137); 29 | // final epoch = epochDateTime.decode(now1); 30 | // final now2 = epochDateTime.encode(epoch); 31 | // logger.info("$now1 -> epochDateTime: -> $epoch"); 32 | // expect(now1, equals(now2)); 33 | // }); 34 | test('epoch to unix time milliseconds', () { 35 | final _epoch0Slot0Utc = epochToDateTime(epoch: 0); 36 | expect(_epoch0Slot0Utc, epoch0Slot0Utc); 37 | 38 | //last slot of epoch 207, slot 4492799, block 4490510, 2020/07/29 21:44:31 39 | final epoch207UtcBase = DateTime(2020, 7, 24, 21, 44, 51).toUtc(); 40 | final epoch207Ut = epochToDateTime(epoch: 207); 41 | expect(epoch207Ut, epoch207UtcBase); 42 | 43 | //TODO past 207 epochs are off by 1 hour 44 | //Epoch Start Date Stake Snapshot for Epoch Rewards Paid for Epoch 45 | //239 Thu 31 Dec 2020 (21:45:00 UTC) 240 237, 2020/12/31 21:44:51 46 | final epoch239UtcBase = DateTime(2020, 12, 31, 20, 44, 51).toUtc(); 47 | final epoch239Utc = epochToDateTime(epoch: 239); 48 | expect(epoch239Utc, epoch239UtcBase); 49 | 50 | //Epoch Start Date Stake Snapshot for Epoch Rewards Paid for Epoch 51 | //309 Thu 16 Dec 2021 (21:45:00 UTC) 310 307 52 | final epoch309UtcBase = DateTime(2021, 12, 16, 20, 44, 51).toUtc(); 53 | final epoch309Utc = epochToDateTime(epoch: 309); 54 | expect(epoch309Utc, epoch309UtcBase); 55 | }, skip: 'this test is off by 1 hour when run on github linux box'); 56 | test('slot to unix time milliseconds', () { 57 | final epoch0Slot9UtcBase = DateTime(2017, 9, 23, 21, 47, 51).toUtc(); 58 | final epoch0Slot9Utc = slotToDateTime(slot: 9); 59 | expect(epoch0Slot9Utc, epoch0Slot9UtcBase); 60 | 61 | // https://explorer.cardano.org/en/epoch.html?number=136&page=0&perPage=10 62 | // 136, 2937600, 2936067, 2019/08/04 21:44:51 63 | final slot2937600UtcBase = DateTime(2019, 8, 4, 21, 44, 51).toUtc(); 64 | final slot2937600Utc = slotToDateTime(slot: 2937600); 65 | expect(slot2937600Utc, slot2937600UtcBase); 66 | 67 | // 278, 35164786, 6001557, 2021/07/19 21:44:37 68 | final slot35164786UtcBase = DateTime(2021, 7, 19, 21, 44, 37).toUtc(); 69 | final slot35164786Utc = slotToDateTime(slot: 35164786); 70 | expect(slot35164786Utc, slot35164786UtcBase); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /test/util/ada_validation_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 10 | Logger.root.onRecord.listen((record) { 11 | print('${record.level.name}: ${record.time}: ${record.message}'); 12 | }); 13 | final logger = Logger('AdaValidationTest'); 14 | group('ada validation - ', () { 15 | test('valid', () { 16 | final result = validAda(ada: '1'); 17 | logger.info(result.unwrap()); 18 | expect(result.unwrap(), '1.0'); 19 | expect(validAda(ada: '01').isOk(), isTrue); 20 | expect(validAda(ada: '0123456789000').isOk(), isTrue); 21 | expect(validAda(ada: '.777777').isOk(), isTrue); 22 | expect(validAda(ada: '0.123456').isOk(), isTrue); 23 | expect(validAda(ada: '12345.678900').isOk(), isTrue); 24 | expect(validAda(ada: '12345.').isOk(), isTrue); 25 | expect(validAda(ada: '00000.000', zeroAllowed: true).isOk(), isTrue); 26 | expect( 27 | validAda(ada: '-00000.000', zeroAllowed: true, allowNegative: true) 28 | .isOk(), 29 | isTrue); 30 | expect(validAda(ada: '-1', allowNegative: true).isOk(), isTrue); 31 | expect(validAda(ada: '.', zeroAllowed: true).isOk(), isTrue); 32 | }); 33 | test('normalization', () { 34 | final result = validAda(ada: '1'); 35 | logger.info(result.unwrap()); 36 | expect(result.unwrap(), '1.0'); 37 | expect(validAda(ada: '01').unwrap(), '1.0'); 38 | expect(validAda(ada: '0123456789000').unwrap(), '123456789000.0'); 39 | expect(validAda(ada: '.777777').unwrap(), '0.777777'); 40 | expect(validAda(ada: '0.123456').unwrap(), '0.123456'); 41 | expect(validAda(ada: '12345.678900').unwrap(), '12345.6789'); 42 | expect(validAda(ada: '12345.').unwrap(), '12345.0'); 43 | expect(validAda(ada: '00000.000', zeroAllowed: true).unwrap(), '0.0'); 44 | expect( 45 | validAda(ada: '-00000.000', zeroAllowed: true, allowNegative: true) 46 | .unwrap(), 47 | '-0.0'); 48 | expect(validAda(ada: '-1', allowNegative: true).unwrap(), '-1.0'); 49 | expect(validAda(ada: '.', zeroAllowed: true).unwrap(), '0.0'); 50 | }); 51 | test('invalid precision length', () { 52 | final result = validAda(ada: '.1234567'); 53 | expect(result.isErr(), isTrue); 54 | logger.info(result.unwrapErr()); 55 | }); 56 | test('zero', () { 57 | expect(validAda(ada: '.').isErr(), isTrue); 58 | expect(validAda(ada: '0', zeroAllowed: false).isErr(), isTrue); 59 | expect(validAda(ada: '0', zeroAllowed: true).isOk(), isTrue); 60 | }); 61 | test('invalid data char', () { 62 | final result = validAda(ada: 'a'); 63 | expect(result.isErr(), isTrue); 64 | logger.info(result.unwrapErr()); 65 | expect(validAda(ada: '-.1').isErr(), isTrue); 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/util/bech32_validation_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 10 | Logger.root.onRecord.listen((record) { 11 | print('${record.level.name}: ${record.time}: ${record.message}'); 12 | }); 13 | final logger = Logger('Bech32ValidationTest'); 14 | group('bech32 validation - ', () { 15 | test('valid', () { 16 | final result = validBech32( 17 | bech32: 'addr_test1234567890acdefghjklmnpqrstuvwxyz', 18 | hrpPrefixes: ['addr', 'addr_test'], 19 | dataPartRequiredLength: 32); 20 | expect(result.unwrap(), 'addr_test1234567890acdefghjklmnpqrstuvwxyz'); 21 | }); 22 | test('fix range bug', () { 23 | final result = validBech32( 24 | bech32: 'addr', 25 | hrpPrefixes: ['addr', 'addr_test'], 26 | dataPartRequiredLength: 32); 27 | expect(result.isErr(), isTrue); 28 | logger.info(result.unwrapErr()); 29 | }); 30 | test('return lower case alphas', () { 31 | final result = validBech32( 32 | bech32: 'addr_test1234567890ACDEFGHJKLMNPQRSTUVWXYZ', 33 | hrpPrefixes: ['addr_test', 'addr'], 34 | dataPartRequiredLength: 32); 35 | expect(result.unwrap(), 'addr_test1234567890acdefghjklmnpqrstuvwxyz'); 36 | }); 37 | test('invalid length', () { 38 | final result = validBech32( 39 | bech32: 'addr_test1234567890acdefghjklmnpqrstuvwxyz', 40 | hrpPrefixes: ['addr_test', 'addr'], 41 | dataPartRequiredLength: 31); 42 | expect(result.isErr(), isTrue); 43 | logger.info(result.unwrapErr()); 44 | }); 45 | test('missing', () { 46 | final result = 47 | validBech32(bech32: '', hrpPrefixes: ['addr_test', 'addr']); 48 | expect(result.isErr(), isTrue); 49 | logger.info(result.unwrapErr()); 50 | }); 51 | test('invalid data char', () { 52 | final result = validBech32( 53 | bech32: 'addr1234567890abcdefghjklmnpqrstuvwxyz', 54 | hrpPrefixes: ['addr_test', 'addr']); 55 | expect(result.isErr(), isTrue); 56 | logger.info(result.unwrapErr()); 57 | }); 58 | test('invalid prefix', () { 59 | final result = validBech32( 60 | bech32: 'dude_test1234567890acdefghjklmnpqrstuvwxyz', 61 | hrpPrefixes: ['addr_test', 'addr']); 62 | expect(result.isErr(), isTrue); 63 | logger.info(result.unwrapErr()); 64 | }); 65 | test('missing 1 seperator', () { 66 | final result = validBech32( 67 | bech32: 'addr234567890acdefghjklmnpqrstuvwxyz', 68 | hrpPrefixes: ['addr_test', 'addr']); 69 | expect(result.isErr(), isTrue); 70 | logger.info(result.unwrapErr()); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /test/util/bigint_parse_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | import 'dart:math'; 8 | 9 | void main() { 10 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 11 | Logger.root.onRecord.listen((record) { 12 | print('${record.level.name}: ${record.time}: ${record.message}'); 13 | }); 14 | final logger = Logger('BigIntParseTest'); 15 | 16 | // note: Native Dart int values can only handle numbers up to the 9 Quintillion 17 | // range [-9,223,372,036,854,775,808..9,223,372,036,854,775,807]. 18 | 19 | group('parseBigInt -', () { 20 | test('valid formats', () { 21 | final success = { 22 | '9.9e2': BigInt.from(990), 23 | '0.0e2': BigInt.zero, 24 | '7e5': BigInt.from(700000), 25 | '7E1': BigInt.from(70), 26 | '1.222e2': BigInt.from(122), 27 | '1.2': BigInt.one, 28 | '1.99999999999': BigInt.one, 29 | '1.': BigInt.one, 30 | '.2e1': BigInt.two, 31 | '333.2e1': BigInt.from(3332), 32 | '+1.2e+2': BigInt.from(120), 33 | '-1.2': BigInt.from(-1), 34 | '1.000e1': BigInt.from(10), 35 | '1.000e0': BigInt.one, 36 | '+00001.20000e+0002': BigInt.from(120), 37 | '+1.234500e5': BigInt.from(123450), 38 | '9.223372036854775807e18': BigInt.from(9223372036854775807), 39 | '-9.223372036854775808e18': BigInt.from(-9223372036854775808), 40 | '-1.3139667629422286119e19': BigInt.parse('-13139667629422286119'), 41 | //'-1.3139667629422286119e19': BigInt.parse('-13139667629422286118'); 42 | '0x3bdefda92265': BigInt.parse('0x3bdefda92265'), 43 | }; 44 | for (MapEntry entry in success.entries) { 45 | try { 46 | final i = parseBigInt(entry.key); 47 | logger.info("${entry.key} -> $i"); 48 | expect(i, equals(entry.value)); 49 | } on FormatException catch (e) { 50 | logger.info("ERROR: ${entry.key} -> ${e.message}"); 51 | } 52 | } 53 | }); 54 | 55 | test('invalid formats', () { 56 | final failure = [ 57 | '9.9e-2', 58 | 'ffee', 59 | '.0', 60 | '0xfh', 61 | 'one', 62 | ]; 63 | for (String invalid in failure) { 64 | try { 65 | final i = parseBigInt(invalid); 66 | logger.info("${invalid} -> $i"); 67 | fail( 68 | "Expected test failure: '${invalid}' is not a valid BigInt format"); 69 | } on FormatException catch (e) { 70 | logger.info("ERROR: ${invalid} -> ${e.message}: ${e.source}"); 71 | } on TestFailure { 72 | rethrow; 73 | } catch (e) { 74 | logger.info("${invalid} -> $e"); 75 | fail("'${invalid}' unexpected exception: $e"); 76 | } 77 | } 78 | expect(tryParseBigInt('0xff', allowHex: false), isNull); 79 | }); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/util/codec_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | /// 8 | /// various encoders, decoders and type converters. 9 | /// 10 | void main() { 11 | test('hexFromShelleyAddress', () { 12 | const addr = 13 | 'addr_test1qqy3df0763vfmygxjxu94h0kprwwaexe6cx5exjd92f9qfkry2djz2a8a7ry8nv00cudvfunxmtp5sxj9zcrdaq0amtqmflh6v'; 14 | const addrHexExpected = 15 | '000916A5FED4589D910691B85ADDF608DCEEE4D9D60D4C9A4D2A925026C3229B212BA7EF8643CD8F7E38D6279336D61A40D228B036F40FEED6'; 16 | final addrHex = hexFromShelleyAddress(addr, uppercase: true); 17 | //print(addrHex); 18 | expect(addrHex, addrHexExpected); 19 | }); 20 | test('bech32ShelleyAddressFromBytes', () { 21 | const hex = 22 | '000916A5FED4589D910691B85ADDF608DCEEE4D9D60D4C9A4D2A925026C3229B212BA7EF8643CD8F7E38D6279336D61A40D228B036F40FEED6'; 23 | final bytes = uint8BufferFromHex(hex); 24 | final addr = bech32ShelleyAddressFromBytes(bytes); 25 | const addrExpected = 26 | 'addr_test1qqy3df0763vfmygxjxu94h0kprwwaexe6cx5exjd92f9qfkry2djz2a8a7ry8nv00cudvfunxmtp5sxj9zcrdaq0amtqmflh6v'; 27 | //print(addr); 28 | expect(addr, addrExpected); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/wallet/mock_wallet2_adapter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Richard Easterling 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import 'package:cardano_wallet_sdk/cardano_wallet_sdk.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | import 'mock_wallet_2.dart'; 8 | 9 | void main() { 10 | Logger.root.level = Level.WARNING; // defaults to Level.INFO 11 | Logger.root.onRecord.listen((record) { 12 | print('${record.level.name}: ${record.time}: ${record.message}'); 13 | }); 14 | final logger = Logger('MockWallet2AdapterTest'); 15 | final formatter = AdaFormattter.compactCurrency(); 16 | final mockAdapter = BlockfrostBlockchainAdapter( 17 | blockfrost: buildMockBlockfrostWallet2(), 18 | network: Networks.testnet, 19 | projectId: 'mock-id'); 20 | group('MockPublicWallet -', () { 21 | test('create testnet wallet 2', () async { 22 | final address = ShelleyAddress.fromBech32(stakeAddr2); 23 | final wallet = ReadOnlyWalletImpl( 24 | blockchainAdapter: mockAdapter, 25 | stakeAddress: address, 26 | walletName: 'mock wallet', 27 | ); 28 | final latestBlockResult = await mockAdapter.latestBlock(); 29 | latestBlockResult.when( 30 | ok: (block) { 31 | logger.info("Block(time: ${block.time}, slot: ${block.slot})"); 32 | expect(block.slot, greaterThanOrEqualTo(39241175)); 33 | }, 34 | err: (err) => logger.info(err)); 35 | final updateResult = 36 | await mockAdapter.updateWallet(stakeAddress: address); 37 | updateResult.when( 38 | ok: (update) { 39 | logger.info( 40 | "Wallet(balance: ${update.balance}, formatted: ${formatter.format(update.balance)})"); 41 | wallet.refresh( 42 | balance: update.balance, 43 | usedAddresses: update.addresses, 44 | transactions: update.transactions, 45 | assets: update.assets, 46 | stakeAccounts: []); 47 | 48 | //addresses 49 | for (var addr in update.addresses) { 50 | logger.info(addr.toString()); 51 | } 52 | expect(wallet.addresses.length, equals(3)); 53 | 54 | //assets 55 | update.assets.forEach( 56 | (key, value) => logger.info("Asset($key: $key, value: $value")); 57 | expect(wallet.findAssetByTicker('ADA'), isNotNull); 58 | expect( 59 | wallet.findAssetByTicker('ADA')?.assetId, equals(lovelaceHex)); 60 | expect(wallet.findAssetByTicker('TEST'), isNotNull); 61 | final testcoinHex = wallet.findAssetByTicker('TEST')!.assetId; 62 | 63 | //transactions 64 | final Set addressSet = update.addresses.toSet(); 65 | for (var tx in update.transactions) { 66 | final w = WalletTransactionImpl( 67 | rawTransaction: tx, addressSet: addressSet); 68 | logger.info( 69 | "${tx.toString()} - ${w.currencyBalancesByTicker(assetByAssetId: update.assets)} fees:${w.fees}"); 70 | } 71 | expect(wallet.filterTransactions(assetId: lovelaceHex).length, 72 | equals(4)); 73 | expect(wallet.filterTransactions(assetId: testcoinHex).length, 74 | equals(2)); 75 | 76 | //balances 77 | expect(wallet.currencies[lovelaceHex], equals(update.balance)); 78 | expect(wallet.currencies[testcoinHex], equals(BigInt.one)); 79 | }, 80 | err: (err) => logger.info(err)); 81 | }); //, skip: 'not worth the effort to setup and maintain' 82 | }); 83 | } 84 | --------------------------------------------------------------------------------