├── .github └── workflows │ └── precompile_binaries.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── LICENSE-APACHE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── settings.gradle └── src │ └── main │ └── AndroidManifest.xml ├── cargokit ├── .github │ └── workflows │ │ ├── check_and_lint.yml │ │ └── test_example_plugin_build.yml ├── .gitignore ├── LICENSE ├── README ├── build_pod.sh ├── build_tool │ ├── README.md │ ├── analysis_options.yaml │ ├── bin │ │ └── build_tool.dart │ ├── lib │ │ ├── build_tool.dart │ │ └── src │ │ │ ├── android_environment.dart │ │ │ ├── artifacts_provider.dart │ │ │ ├── build_cmake.dart │ │ │ ├── build_gradle.dart │ │ │ ├── build_pod.dart │ │ │ ├── build_tool.dart │ │ │ ├── builder.dart │ │ │ ├── cargo.dart │ │ │ ├── crate_hash.dart │ │ │ ├── environment.dart │ │ │ ├── logging.dart │ │ │ ├── options.dart │ │ │ ├── precompile_binaries.dart │ │ │ ├── rustup.dart │ │ │ ├── target.dart │ │ │ ├── util.dart │ │ │ └── verify_binaries.dart │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ ├── builder_test.dart │ │ ├── cargo_test.dart │ │ ├── options_test.dart │ │ └── rustup_test.dart ├── cmake │ ├── cargokit.cmake │ └── resolve_symlinks.ps1 ├── docs │ ├── architecture.md │ └── precompiled_binaries.md ├── gradle │ └── plugin.gradle ├── run_build_tool.cmd └── run_build_tool.sh ├── check_precompiled.sh ├── example ├── .gitignore ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── io │ │ │ │ │ └── ldk │ │ │ │ │ └── f │ │ │ │ │ └── ldk_node_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 ├── cargokit_options.yaml ├── integration_test │ ├── bolt11_test.dart │ └── bolt12_test.dart ├── 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 │ │ │ └── dev.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 ├── lib │ └── main.dart ├── pubspec.lock └── pubspec.yaml ├── flutter_rust_bridge.yaml ├── ios ├── .gitignore ├── Classes │ ├── EnforceBundling.swift │ ├── frb_generated.h │ └── ldk_node.c └── ldk_node.podspec ├── lefthook.yml ├── lib ├── ldk_node.dart └── src │ ├── generated │ ├── api │ │ ├── bolt11.dart │ │ ├── bolt12.dart │ │ ├── builder.dart │ │ ├── graph.dart │ │ ├── node.dart │ │ ├── on_chain.dart │ │ ├── spontaneous.dart │ │ ├── types.dart │ │ └── types.freezed.dart │ ├── frb_generated.dart │ ├── frb_generated.io.dart │ ├── lib.dart │ └── utils │ │ ├── error.dart │ │ └── error.freezed.dart │ ├── root.dart │ └── utils │ ├── default_services.dart │ ├── exceptions.dart │ └── utils.dart ├── makefile ├── pubspec.yaml └── rust ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSE.md ├── cargokit.yaml └── src ├── api ├── bolt11.rs ├── bolt12.rs ├── builder.rs ├── graph.rs ├── mod.rs ├── node.rs ├── on_chain.rs ├── spontaneous.rs └── types.rs ├── frb_generated.io.rs ├── frb_generated.rs ├── lib.rs └── utils ├── error.rs └── mod.rs /.github/workflows/precompile_binaries.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, v0.3.0] 4 | 5 | name: Precompile Binaries 6 | 7 | jobs: 8 | Precompile: 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | os: [ubuntu-20.04, macOS-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: stable 20 | override: true 21 | - name: Configure Cargo.toml optimizations 22 | run: | 23 | mkdir -p .cargo 24 | echo "[profile.release]" >> .cargo/config.toml 25 | echo "opt-level = 3" >> .cargo/config.toml 26 | echo "lto = true" >> .cargo/config.toml 27 | echo "codegen-units = 1" >> .cargo/config.toml 28 | echo "panic = 'abort'" >> .cargo/config.toml 29 | - uses: dart-lang/setup-dart@v1 30 | - uses: subosito/flutter-action@v2 31 | with: 32 | channel: 'stable' 33 | - name: Precompile (with iOS) 34 | if: matrix.os == 'macOS-latest' 35 | run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=LtbLightning/ldk-node-flutter 36 | working-directory: cargokit/build_tool 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 39 | PRIVATE_KEY: ${{ secrets.CARGOKIT_PRIVATE_KEY }} 40 | - name: Precompile (with Android) 41 | if: matrix.os == 'ubuntu-20.04' 42 | run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=LtbLightning/ldk-node-flutter --android-sdk-location=/usr/local/lib/android/sdk --android-ndk-version=24.0.8215888 --android-min-sdk-version=23 43 | working-directory: cargokit/build_tool 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 46 | PRIVATE_KEY: ${{ secrets.CARGOKIT_PRIVATE_KEY }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | rust/target/ 32 | rust/wallets/ 33 | rust/ldk.0.2.1/ 34 | -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 17 | base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 18 | - platform: android 19 | create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 20 | base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 21 | - platform: ios 22 | create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 23 | base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.3.0] 2 | Updated `flutter_rust_bridge` to `2.0.0`. 3 | #### APIs added 4 | - `buildWithFsStore` method is added in the `Builder` class allowing to use the filesystem storage backend. 5 | - Exposed `setLiquiditySourceLsps2` method to `Builder` to configure the `Node` instance to source the inbound liquidity. 6 | - `nextEventAsync` method is added to `Node` class that allows polling the event queue asynchronously. 7 | - `status` method has been added to `Node` allowing to retrieve information about the Node's status. 8 | - `config` method added to get the config with which the `Node` was initialized. 9 | - The payment APIs have been restructured to use per-type (bolt11, onchain, bolt12) payment handlers which can be accessed via `node.{type}Payment`. 10 | - Support for sourcing inbound liquidity via LSPS2 just-in-time (JIT) channels has been added. 11 | - Support for creating and paying BOLT12 offers and refunds has been added. 12 | - Added `networkGraph` handler method allowing to query the network graph. 13 | - Added `forceCloseChannel` to `Node` class. 14 | #### API changed 15 | - All available balances outside of channel balances are now exposed via a unified `listBalances` interface method. 16 | #### Fixed 17 | Persisted peers are now correctly reconnected after startup. 18 | 19 | ## [0.2.2-dev] 20 | Updated Rust and Flutter dependencies. 21 | #### APIs added 22 | - Expose `isRunning()` in `Node` class. 23 | #### API changed 24 | - Renamed `waitUntilNextHandled()` to `waitNextHandled`. 25 | - Renamed `listeningAddress()` to `listeningAddresses`. 26 | - Upgraded `BuilderException` to handle invalid `socketAddress` & `trustedPeers. 27 | - Upgraded `NodeException` to handle invalid `txid`. 28 | #### Fixed 29 | - Functions hang indefinitely on iOs devices 30 | - Android support bug resolved 31 | - Thread `frb_workerpool` panic on `SocketAddress`, `PublicKey`, `Address` `Bolt11Invoice`, `Config` and `Txid`. 32 | 33 | ## [0.2.0] 34 | Updated `Rust` and `Flutter` dependencies. 35 | 36 | ## [0.1.3] 37 | Updated `Rust` and `Flutter` dependencies. 38 | Invalid `BuilderException` bug resolved 39 | #### APIs added 40 | - Expose `generate()` in `Mnemonic` class. 41 | #### API changed 42 | - Remove `generateEntropyMnemonic()`. 43 | 44 | ## [0.1.2] 45 | #### APIs added 46 | - Expose `generateEntropyMnemonic` function - a utility method for generating a BIP39 mnemonic. 47 | - Expose `Node` class's `updateChannelConfig`, `verifySignature`, `signMessage`, `sendPaymentProbe`, `sendSpontaneousPaymentProbe` methods. 48 | - Add `ChannelConfig?` to node.connectOpenChannel() params - a `ChannelConfig` may now be specified on channel open or updated afterwards. 49 | - Expose `counterpartyNodeId`, `funding_txo` & `channelValueSats` in `ChannelDetails`. 50 | - Expose `trustedPeers0Conf` `probingLiquidityLimitMultiplier`, `logDirPath`, `onchainWalletSyncInterval_secs`, `walletSyncIntervalSecs`, & 51 | `feeRateCacheUpdateIntervalSecs` in `Config` - allowing inbound trusted 0conf channels. 52 | - Non-permanently connected peers are now included in node.listPeers(). 53 | 54 | #### API changed 55 | - node.newFundingAddress renamed to `newOnchainAddress`. 56 | - node.sendToOnChainAddress renamed to `sendToOnchainAddress`. 57 | - node.sendAllToOnChainAddress renamed to `sendAllToOnchainAddress`. 58 | - Remove node.onChainBalance. 59 | 60 | ## [0.1.1-alpha] 61 | Support `Dart 3` and `Flutter 3.10` 62 | 63 | ## [0.1.1] 64 | Support `Dart 3` and `Flutter 3.10` 65 | 66 | ### Fixed 67 | - `setEntropyBip39Mnemonic` issue resolved 68 | ## [0.1.0] 69 | 70 | This is the first release of `ldk_node`. It features support for sourcing chain data via an `Esplora` server, filesystem persistence, gossip sourcing via the `Lightning` peer-to-peer network, and configurble entropy sources for the integrated LDK & BDK-based wallets. 71 | 72 | ### Functionality Added 73 | 74 | `build`- Builds an `Node` instance according to the options previously configured. 75 | 76 | `fromConfig`- Creates a new `Builder` instance from an Config. 77 | 78 | `setEntropyBip39Mnemonic`- Configures the `Node` instance to source its wallet entropy from a BIP 39 mnemonic. 79 | 80 | `setEntropySeedBytes`- Configures the `Node` instance to source its wallet entropy from the given seed bytes. 81 | 82 | `setEntropySeedPath`- Configures the `Node` instance to source its wallet entropy from a seed file on disk. 83 | 84 | `setEsploraServerUrl`- Sets the `Esplora` server URL. Default: https://blockstream.info/api 85 | 86 | `setListeningAddress`- Sets the IP address and TCP port on which Node will listen for incoming network connections. Default: 0.0.0.0:9735 87 | 88 | `setNetwork`- Sets the `Bitcoin` network used. 89 | 90 | `setStorageDirPath`- Sets the used storage directory path. 91 | 92 | `closeChannel`- Close a previously opened channel. 93 | 94 | `connect`- Connect to a node on the peer-to-peer network. If permanently is set to true, we’ll remember the peer and reconnect to it on restart 95 | 96 | `connectOpenChannel`- Connect to a node and open a new channel. Disconnects and re-connects are handled automatically 97 | 98 | `disconnect`- Disconnects the peer with the given `node id`. 99 | 100 | `eventHandled`- Confirm the last retrieved event handled. 101 | 102 | `listChannels`- Retrieve a list of known channels. 103 | 104 | `listPaymentsWithFilter`- Retrieves all payments that match the given predicate. 105 | 106 | `listeningAddress`- Returns our own listening address. 107 | 108 | `newFundingAddress`- Retrieve a new on-chain/funding address. 109 | 110 | `nextEvent`- Blocks until the next event is available. 111 | 112 | `nodeId`- Returns our own `node id`. 113 | 114 | `onchainBalance`- Retrieve the current on-chain balance. 115 | 116 | `payment`- Retrieve the details of a specific payment with the given hash. 117 | 118 | `receivePayment`- Returns a payable invoice that can be used to request and receive a payment of the amount given. 119 | 120 | `receiveVariableAmountPayment`- Returns a payable invoice that can be used to request and receive a payment for which the amount is to be determined by the user,also known as a `zero-amount` invoice. 121 | 122 | `removePayment`- Remove the payment with the given hash from the store. 123 | 124 | `sendAllToOnchainAddress`- Send an on-chain payment to the given address, draining all the available funds. 125 | 126 | `sendPayment`- Send a payement given an invoice. 127 | 128 | `sendPaymentUsingAmount`- Send a payment given an invoice and an amount in millisatoshi. 129 | 130 | `sendSpontaneousPayment`- Send a spontaneous, aka. `keysend`, payment. 131 | 132 | `sendToOnchainAddress`- Send an on-chain payment to the given address. 133 | 134 | `start`- Starts the necessary background tasks, such as handling events coming from user input, `LDK/BDK`, and the peer-to-peer network. 135 | 136 | `stop`- Disconnects all peers, stops all running background tasks, and shuts down Node. 137 | 138 | `syncWallets`- Sync the `LDK & BDK` wallets with the current chain state. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## LDK_Node 2 | 3 |

4 | 5 | LDK Node is released under the MIT license. 6 | 7 | 8 | Docs 9 | 10 | 11 | Current pub package version. 12 | 13 | 14 | Issues 15 | 16 | 17 | Stars 18 | 19 | 20 | Forks 21 | 22 | 23 | Demo App 24 | 25 |

26 | 27 | A Flutter library for [LDK Node](https://github.com/lightningdevkit/ldk-node), a ready-to-go Lightning node library built using [LDK](https://lightningdevkit.org) and [BDK](https://bitcoindevkit.org). 28 | 29 | LDK Node is a non-custodial Lightning node. Its central goal is to provide a small, simple, and straightforward interface that enables users to easily set up and run a Lightning node with an integrated on-chain wallet. While minimalism is at its core, LDK Node aims to be sufficiently modular and configurable to be useful for a variety of use cases. 30 | 31 | The primary abstraction of the library is the Node, which can be retrieved by setting up and configuring a Builder to your liking and calling build(). Node can then be controlled via commands such as start, stop, connectOpenChannel, sendPayment, etc.: 32 | 33 | This release covers the same API from LDK Node 0.1.0 Rust. It has support for sourcing chain data via an Esplora server, filesystem persistence, gossip sourcing via the Lightning peer-to-peer network, and configurable entropy sources for the integrated LDK and BDK-based wallets. 34 | 35 | Please note: This release is considered experimental, and should not be run in production 36 | ### How to use ldk_node 37 | 38 | To use the `ldk_node` package in your project, add it as a dependency in your project's pubspec.yaml: 39 | 40 | 41 | ```dart 42 | dependencies: 43 | ldk_node: ^0.3.0 44 | ``` 45 | or add from pub.dev using `pub add` command 46 | 47 | ``` 48 | flutter pub add ldk_node 49 | ``` 50 | 51 | ### Examples 52 | 53 | ### Build, Start & Sync the local node 54 | 55 | ```dart 56 | import 'package:ldk_node/ldk_node.dart'; 57 | 58 | // .... 59 | 60 | // Path to a directory where the application may place data that is user-generated 61 | final path = "${directory.path}/alice's_node"; 62 | 63 | // Your preferred `Esplora` url 64 | final esploraUrl = "https://mempool.space/testnet/api"; 65 | 66 | // configuration options for the node 67 | final config = Config( 68 | probingLiquidityLimitMultiplier: 3, 69 | trustedPeers0Conf: [], 70 | storageDirPath: path, 71 | network: Network.Testnet, 72 | listeningAddresses: [ 73 | SocketAddress.hostname(addr: "0.0.0.0", port: 3003) 74 | ], 75 | onchainWalletSyncIntervalSecs: 60, 76 | walletSyncIntervalSecs: 20, 77 | feeRateCacheUpdateIntervalSecs: 600, 78 | logLevel: ldk.LogLevel.Debug, 79 | defaultCltvExpiryDelta: 144 80 | ); 81 | Builder builder = Builder.fromConfig(config); 82 | final node = await builder 83 | .setEntropyBip39Mnemonic( 84 | mnemonic: ldk.Mnemonic( 85 | 'certain sense kiss guide crumble hint transfer crime much stereo warm coral')) 86 | .setEsploraServer(esploraUrl) 87 | .build(); 88 | 89 | // Starting the node 90 | await node.start(); 91 | 92 | // Syncing the node 93 | await node.syncWallets(); 94 | 95 | ``` 96 | 97 | ### References: 98 | - Setting up a local Esplora instance for testing: 99 | https://bitcoin.stackexchange.com/questions/116937/how-do-i-setup-an-esplora-instance-for-local-testing/116938#116938 100 | 101 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | linter: 3 | rules: 4 | prefer_single_quotes: false 5 | avoid_void_async: false 6 | prefer_interpolation_to_compose_strings: false 7 | avoid_print: false 8 | use_build_context_synchronously: false 9 | unnecessary_string_escapes: false 10 | avoid_dynamic_calls: false 11 | non_constant_identifier_names: false 12 | 13 | analyzer: 14 | exclude: 15 | - lib/src/generated/frb_generated.dart 16 | - lib/src/generated/frb_generated.io.dart 17 | - lib/src/generated/frb_generated.dart 18 | - lib/src/generated/api/types.freezed.dart 19 | - README.md 20 | - cargokit/ -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'io.ldk.f.ldk_node' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 31 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | minSdkVersion 16 45 | } 46 | } 47 | apply from: "../cargokit/gradle/plugin.gradle" 48 | 49 | cargokit { 50 | manifestDir = "../rust" 51 | libname = "ldk_node" 52 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ldk_node' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /cargokit/.github/workflows/check_and_lint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Check and Lint 8 | 9 | jobs: 10 | Flutter: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 #v2.7.0 14 | - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d #1.6.0 15 | - name: Pub Get 16 | run: dart pub get --no-precompile 17 | working-directory: build_tool 18 | - name: Dart Format 19 | run: dart format . --output=none --set-exit-if-changed 20 | working-directory: build_tool 21 | - name: Analyze 22 | run: dart analyze 23 | working-directory: build_tool 24 | - name: Test 25 | run: dart test 26 | working-directory: build_tool 27 | -------------------------------------------------------------------------------- /cargokit/.github/workflows/test_example_plugin_build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Test Example Plugin 8 | 9 | jobs: 10 | Build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macOS-latest 18 | - windows-latest 19 | build_mode: 20 | - debug 21 | - profile 22 | - release 23 | env: 24 | EXAMPLE_DIR: "a b/hello_rust_ffi_plugin/example" 25 | CARGOKIT_VERBOSE: 1 26 | steps: 27 | - name: Extract branch name 28 | shell: bash 29 | run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT 30 | id: extract_branch 31 | - name: Setup Repository 32 | shell: bash 33 | run: | 34 | mkdir "a b" # Space is intentional 35 | cd "a b" 36 | git config --global user.email "you@example.com" 37 | git config --global user.name "Your Name" 38 | # "advanced" branch has extra iOS flavor and uses rust nightly for release builds 39 | git clone -b advanced https://github.com/irondash/hello_rust_ffi_plugin 40 | cd hello_rust_ffi_plugin 41 | git subtree pull --prefix cargokit https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} ${{ steps.extract_branch.outputs.branch }} --squash 42 | - uses: subosito/flutter-action@cc97e1648fff6ca5cc647fa67f47e70f7895510b # 2.11.0 43 | with: 44 | channel: "stable" 45 | - name: Install GTK 46 | if: (matrix.os == 'ubuntu-latest') 47 | run: sudo apt-get update && sudo apt-get install libgtk-3-dev 48 | - name: Install ninja-build 49 | if: (matrix.os == 'ubuntu-latest') 50 | run: sudo apt-get update && sudo apt-get install ninja-build 51 | - name: Build Linux (${{ matrix.build_mode }}) 52 | if: matrix.os == 'ubuntu-latest' 53 | shell: bash 54 | working-directory: ${{ env.EXAMPLE_DIR }} 55 | run: flutter build linux --${{ matrix.build_mode }} -v 56 | - name: Build macOS (${{ matrix.build_mode }}) 57 | if: matrix.os == 'macos-latest' 58 | shell: bash 59 | working-directory: ${{ env.EXAMPLE_DIR }} 60 | run: flutter build macos --${{ matrix.build_mode }} -v 61 | - name: Build iOS (${{ matrix.build_mode }}) 62 | if: matrix.os == 'macos-latest' 63 | shell: bash 64 | working-directory: ${{ env.EXAMPLE_DIR }} 65 | run: flutter build ios --${{ matrix.build_mode }} --no-codesign -v 66 | - name: Build iOS (${{ matrix.build_mode }}) - flavor1 67 | if: matrix.os == 'macos-latest' 68 | shell: bash 69 | working-directory: ${{ env.EXAMPLE_DIR }} 70 | run: flutter build ios --flavor flavor1 --${{ matrix.build_mode }} --no-codesign -v 71 | - name: Build Windows (${{ matrix.build_mode }}) 72 | if: matrix.os == 'windows-latest' 73 | shell: bash 74 | working-directory: ${{ env.EXAMPLE_DIR }} 75 | run: flutter build windows --${{ matrix.build_mode }} -v 76 | - name: Build Android (${{ matrix.build_mode }}) 77 | shell: bash 78 | working-directory: ${{ env.EXAMPLE_DIR }} 79 | run: | 80 | export JAVA_HOME=$JAVA_HOME_11_X64 81 | flutter build apk --${{ matrix.build_mode }} -v 82 | 83 | -------------------------------------------------------------------------------- /cargokit/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .dart_tool 3 | *.iml 4 | !pubspec.lock 5 | -------------------------------------------------------------------------------- /cargokit/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Matej Knopp 2 | 3 | ================================================================================ 4 | 5 | MIT LICENSE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 20 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | ================================================================================ 25 | 26 | APACHE LICENSE, VERSION 2.0 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | -------------------------------------------------------------------------------- /cargokit/README: -------------------------------------------------------------------------------- 1 | Experimental repository to provide glue for seamlessly integrating cargo build 2 | with flutter plugins and packages. 3 | 4 | See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ 5 | for a tutorial on how to use Cargokit. 6 | 7 | Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. 8 | 9 | -------------------------------------------------------------------------------- /cargokit/build_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASEDIR=$(dirname "$0") 5 | 6 | # Workaround for https://github.com/dart-lang/pub/issues/4010 7 | BASEDIR=$(cd "$BASEDIR" ; pwd -P) 8 | 9 | # Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project 10 | NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` 11 | 12 | export PATH=${NEW_PATH%?} # remove trailing : 13 | 14 | env 15 | 16 | # Platform name (macosx, iphoneos, iphonesimulator) 17 | export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME 18 | 19 | # Arctive architectures (arm64, armv7, x86_64), space separated. 20 | export CARGOKIT_DARWIN_ARCHS=$ARCHS 21 | 22 | # Current build configuration (Debug, Release) 23 | export CARGOKIT_CONFIGURATION=$CONFIGURATION 24 | 25 | # Path to directory containing Cargo.toml. 26 | export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 27 | 28 | # Temporary directory for build artifacts. 29 | export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR 30 | 31 | # Output directory for final artifacts. 32 | export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME 33 | 34 | # Directory to store built tool artifacts. 35 | export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool 36 | 37 | # Directory inside root project. Not necessarily the top level directory of root project. 38 | export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT 39 | 40 | FLUTTER_EXPORT_BUILD_ENVIRONMENT=( 41 | "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS 42 | "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS 43 | ) 44 | 45 | for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" 46 | do 47 | if [[ -f "$path" ]]; then 48 | source "$path" 49 | fi 50 | done 51 | 52 | "$BASEDIR/run_build_tool.sh" build-pod "$@" 53 | 54 | # Make a symlink from built framework to phony file, which will be used as input to 55 | # build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate 56 | # attribute on custom build phase) 57 | ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" 58 | ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" 59 | -------------------------------------------------------------------------------- /cargokit/build_tool/README.md: -------------------------------------------------------------------------------- 1 | A sample command-line application with an entrypoint in `bin/`, library code 2 | in `lib/`, and example unit test in `test/`. 3 | -------------------------------------------------------------------------------- /cargokit/build_tool/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | linter: 19 | rules: 20 | - prefer_relative_imports 21 | - directives_ordering 22 | 23 | # analyzer: 24 | # exclude: 25 | # - path/to/excluded/files/** 26 | 27 | # For more information about the core and recommended set of lints, see 28 | # https://dart.dev/go/core-lints 29 | 30 | # For additional information about configuring this file, see 31 | # https://dart.dev/guides/language/analysis-options 32 | -------------------------------------------------------------------------------- /cargokit/build_tool/bin/build_tool.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_tool/build_tool.dart' as build_tool; 2 | 3 | void main(List arguments) { 4 | build_tool.runMain(arguments); 5 | } 6 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/build_tool.dart: -------------------------------------------------------------------------------- 1 | import 'src/build_tool.dart' as build_tool; 2 | 3 | Future runMain(List args) async { 4 | return build_tool.runMain(args); 5 | } 6 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/android_environment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:isolate'; 3 | import 'dart:math' as math; 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:path/path.dart' as path; 7 | import 'package:version/version.dart'; 8 | 9 | import 'target.dart'; 10 | import 'util.dart'; 11 | 12 | class AndroidEnvironment { 13 | AndroidEnvironment({ 14 | required this.sdkPath, 15 | required this.ndkVersion, 16 | required this.minSdkVersion, 17 | required this.targetTempDir, 18 | required this.target, 19 | }); 20 | 21 | static void clangLinkerWrapper(List args) { 22 | final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; 23 | if (clang == null) { 24 | throw Exception( 25 | "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); 26 | } 27 | final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; 28 | if (target == null) { 29 | throw Exception( 30 | "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); 31 | } 32 | 33 | runCommand(clang, [ 34 | target, 35 | ...args, 36 | ]); 37 | } 38 | 39 | /// Full path to Android SDK. 40 | final String sdkPath; 41 | 42 | /// Full version of Android NDK. 43 | final String ndkVersion; 44 | 45 | /// Minimum supported SDK version. 46 | final int minSdkVersion; 47 | 48 | /// Target directory for build artifacts. 49 | final String targetTempDir; 50 | 51 | /// Target being built. 52 | final Target target; 53 | 54 | bool ndkIsInstalled() { 55 | final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); 56 | final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); 57 | return ndkPackageXml.existsSync(); 58 | } 59 | 60 | void installNdk({ 61 | required String javaHome, 62 | }) { 63 | final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; 64 | final sdkManager = path.join( 65 | sdkPath, 66 | 'cmdline-tools', 67 | 'latest', 68 | 'bin', 69 | 'sdkmanager$sdkManagerExtension', 70 | ); 71 | 72 | log.info('Installing NDK $ndkVersion'); 73 | runCommand(sdkManager, [ 74 | '--install', 75 | 'ndk;$ndkVersion', 76 | ], environment: { 77 | 'JAVA_HOME': javaHome, 78 | }); 79 | } 80 | 81 | Future> buildEnvironment() async { 82 | final hostArch = Platform.isMacOS 83 | ? "darwin-x86_64" 84 | : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); 85 | 86 | final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); 87 | final toolchainPath = path.join( 88 | ndkPath, 89 | 'toolchains', 90 | 'llvm', 91 | 'prebuilt', 92 | hostArch, 93 | 'bin', 94 | ); 95 | 96 | final minSdkVersion = 97 | math.max(target.androidMinSdkVersion!, this.minSdkVersion); 98 | 99 | final exe = Platform.isWindows ? '.exe' : ''; 100 | 101 | final arKey = 'AR_${target.rust}'; 102 | final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] 103 | .map((e) => path.join(toolchainPath, e)) 104 | .firstWhereOrNull((element) => File(element).existsSync()); 105 | if (arValue == null) { 106 | throw Exception('Failed to find ar for $target in $toolchainPath'); 107 | } 108 | 109 | final targetArg = '--target=${target.rust}$minSdkVersion'; 110 | 111 | final ccKey = 'CC_${target.rust}'; 112 | final ccValue = path.join(toolchainPath, 'clang$exe'); 113 | final cfFlagsKey = 'CFLAGS_${target.rust}'; 114 | final cFlagsValue = targetArg; 115 | 116 | final cxxKey = 'CXX_${target.rust}'; 117 | final cxxValue = path.join(toolchainPath, 'clang++$exe'); 118 | final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; 119 | final cxxFlagsValue = targetArg; 120 | 121 | final linkerKey = 122 | 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); 123 | 124 | final ranlibKey = 'RANLIB_${target.rust}'; 125 | final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); 126 | 127 | final ndkVersionParsed = Version.parse(ndkVersion); 128 | final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; 129 | final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); 130 | 131 | final runRustTool = 132 | Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; 133 | 134 | final packagePath = (await Isolate.resolvePackageUri( 135 | Uri.parse('package:build_tool/buildtool.dart')))! 136 | .toFilePath(); 137 | final selfPath = path.canonicalize(path.join( 138 | packagePath, 139 | '..', 140 | '..', 141 | '..', 142 | runRustTool, 143 | )); 144 | 145 | // Make sure that run_build_tool is working properly even initially launched directly 146 | // through dart run. 147 | final toolTempDir = 148 | Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; 149 | 150 | return { 151 | arKey: arValue, 152 | ccKey: ccValue, 153 | cfFlagsKey: cFlagsValue, 154 | cxxKey: cxxValue, 155 | cxxFlagsKey: cxxFlagsValue, 156 | ranlibKey: ranlibValue, 157 | rustFlagsKey: rustFlagsValue, 158 | linkerKey: selfPath, 159 | // Recognized by main() so we know when we're acting as a wrapper 160 | '_CARGOKIT_NDK_LINK_TARGET': targetArg, 161 | '_CARGOKIT_NDK_LINK_CLANG': ccValue, 162 | 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, 163 | }; 164 | } 165 | 166 | // Workaround for libgcc missing in NDK23, inspired by cargo-ndk 167 | String _libGccWorkaround(String buildDir, Version ndkVersion) { 168 | final workaroundDir = path.join( 169 | buildDir, 170 | 'cargokit', 171 | 'libgcc_workaround', 172 | '${ndkVersion.major}', 173 | ); 174 | Directory(workaroundDir).createSync(recursive: true); 175 | if (ndkVersion.major >= 23) { 176 | File(path.join(workaroundDir, 'libgcc.a')) 177 | .writeAsStringSync('INPUT(-lunwind)'); 178 | } else { 179 | // Other way around, untested, forward libgcc.a from libunwind once Rust 180 | // gets updated for NDK23+. 181 | File(path.join(workaroundDir, 'libunwind.a')) 182 | .writeAsStringSync('INPUT(-lgcc)'); 183 | } 184 | 185 | var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; 186 | if (rustFlags.isNotEmpty) { 187 | rustFlags = '$rustFlags\x1f'; 188 | } 189 | rustFlags = '$rustFlags-L\x1f$workaroundDir'; 190 | return rustFlags; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/build_cmake.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart' as path; 4 | 5 | import 'artifacts_provider.dart'; 6 | import 'builder.dart'; 7 | import 'environment.dart'; 8 | import 'options.dart'; 9 | import 'target.dart'; 10 | 11 | class BuildCMake { 12 | final CargokitUserOptions userOptions; 13 | 14 | BuildCMake({required this.userOptions}); 15 | 16 | Future build() async { 17 | final targetPlatform = Environment.targetPlatform; 18 | final target = Target.forFlutterName(Environment.targetPlatform); 19 | if (target == null) { 20 | throw Exception("Unknown target platform: $targetPlatform"); 21 | } 22 | 23 | final environment = BuildEnvironment.fromEnvironment(isAndroid: false); 24 | final provider = 25 | ArtifactProvider(environment: environment, userOptions: userOptions); 26 | final artifacts = await provider.getArtifacts([target]); 27 | 28 | final libs = artifacts[target]!; 29 | 30 | for (final lib in libs) { 31 | if (lib.type == AritifactType.dylib) { 32 | File(lib.path) 33 | .copySync(path.join(Environment.outputDir, lib.finalFileName)); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/build_gradle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:logging/logging.dart'; 4 | import 'package:path/path.dart' as path; 5 | 6 | import 'artifacts_provider.dart'; 7 | import 'builder.dart'; 8 | import 'environment.dart'; 9 | import 'options.dart'; 10 | import 'target.dart'; 11 | 12 | final log = Logger('build_gradle'); 13 | 14 | class BuildGradle { 15 | BuildGradle({required this.userOptions}); 16 | 17 | final CargokitUserOptions userOptions; 18 | 19 | Future build() async { 20 | final targets = Environment.targetPlatforms.map((arch) { 21 | final target = Target.forFlutterName(arch); 22 | if (target == null) { 23 | throw Exception( 24 | "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); 25 | } 26 | return target; 27 | }).toList(); 28 | 29 | final environment = BuildEnvironment.fromEnvironment(isAndroid: true); 30 | final provider = 31 | ArtifactProvider(environment: environment, userOptions: userOptions); 32 | final artifacts = await provider.getArtifacts(targets); 33 | 34 | for (final target in targets) { 35 | final libs = artifacts[target]!; 36 | final outputDir = path.join(Environment.outputDir, target.android!); 37 | Directory(outputDir).createSync(recursive: true); 38 | 39 | for (final lib in libs) { 40 | if (lib.type == AritifactType.dylib) { 41 | File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/build_pod.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart' as path; 4 | 5 | import 'artifacts_provider.dart'; 6 | import 'builder.dart'; 7 | import 'environment.dart'; 8 | import 'options.dart'; 9 | import 'target.dart'; 10 | import 'util.dart'; 11 | 12 | class BuildPod { 13 | BuildPod({required this.userOptions}); 14 | 15 | final CargokitUserOptions userOptions; 16 | 17 | Future build() async { 18 | final targets = Environment.darwinArchs.map((arch) { 19 | final target = Target.forDarwin( 20 | platformName: Environment.darwinPlatformName, darwinAarch: arch); 21 | if (target == null) { 22 | throw Exception( 23 | "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); 24 | } 25 | return target; 26 | }).toList(); 27 | 28 | final environment = BuildEnvironment.fromEnvironment(isAndroid: false); 29 | final provider = 30 | ArtifactProvider(environment: environment, userOptions: userOptions); 31 | final artifacts = await provider.getArtifacts(targets); 32 | 33 | void performLipo(String targetFile, Iterable sourceFiles) { 34 | runCommand("lipo", [ 35 | '-create', 36 | ...sourceFiles, 37 | '-output', 38 | targetFile, 39 | ]); 40 | } 41 | 42 | final outputDir = Environment.outputDir; 43 | 44 | Directory(outputDir).createSync(recursive: true); 45 | 46 | final staticLibs = artifacts.values 47 | .expand((element) => element) 48 | .where((element) => element.type == AritifactType.staticlib) 49 | .toList(); 50 | final dynamicLibs = artifacts.values 51 | .expand((element) => element) 52 | .where((element) => element.type == AritifactType.dylib) 53 | .toList(); 54 | 55 | final libName = environment.crateInfo.packageName; 56 | 57 | // If there is static lib, use it and link it with pod 58 | if (staticLibs.isNotEmpty) { 59 | final finalTargetFile = path.join(outputDir, "lib$libName.a"); 60 | performLipo(finalTargetFile, staticLibs.map((e) => e.path)); 61 | } else { 62 | // Otherwise try to replace bundle dylib with our dylib 63 | final bundlePaths = [ 64 | '$libName.framework/Versions/A/$libName', 65 | '$libName.framework/$libName', 66 | ]; 67 | 68 | for (final bundlePath in bundlePaths) { 69 | final targetFile = path.join(outputDir, bundlePath); 70 | if (File(targetFile).existsSync()) { 71 | performLipo(targetFile, dynamicLibs.map((e) => e.path)); 72 | 73 | // Replace absolute id with @rpath one so that it works properly 74 | // when moved to Frameworks. 75 | runCommand("install_name_tool", [ 76 | '-id', 77 | '@rpath/$bundlePath', 78 | targetFile, 79 | ]); 80 | return; 81 | } 82 | } 83 | throw Exception('Unable to find bundle for dynamic library'); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:logging/logging.dart'; 3 | import 'package:path/path.dart' as path; 4 | 5 | import 'android_environment.dart'; 6 | import 'cargo.dart'; 7 | import 'environment.dart'; 8 | import 'options.dart'; 9 | import 'rustup.dart'; 10 | import 'target.dart'; 11 | import 'util.dart'; 12 | 13 | final _log = Logger('builder'); 14 | 15 | enum BuildConfiguration { 16 | debug, 17 | release, 18 | profile, 19 | } 20 | 21 | extension on BuildConfiguration { 22 | bool get isDebug => this == BuildConfiguration.debug; 23 | String get rustName => switch (this) { 24 | BuildConfiguration.debug => 'debug', 25 | BuildConfiguration.release => 'release', 26 | BuildConfiguration.profile => 'release', 27 | }; 28 | } 29 | 30 | class BuildException implements Exception { 31 | final String message; 32 | 33 | BuildException(this.message); 34 | 35 | @override 36 | String toString() { 37 | return 'BuildException: $message'; 38 | } 39 | } 40 | 41 | class BuildEnvironment { 42 | final BuildConfiguration configuration; 43 | final CargokitCrateOptions crateOptions; 44 | final String targetTempDir; 45 | final String manifestDir; 46 | final CrateInfo crateInfo; 47 | 48 | final bool isAndroid; 49 | final String? androidSdkPath; 50 | final String? androidNdkVersion; 51 | final int? androidMinSdkVersion; 52 | final String? javaHome; 53 | 54 | BuildEnvironment({ 55 | required this.configuration, 56 | required this.crateOptions, 57 | required this.targetTempDir, 58 | required this.manifestDir, 59 | required this.crateInfo, 60 | required this.isAndroid, 61 | this.androidSdkPath, 62 | this.androidNdkVersion, 63 | this.androidMinSdkVersion, 64 | this.javaHome, 65 | }); 66 | 67 | static BuildConfiguration parseBuildConfiguration(String value) { 68 | // XCode configuration adds the flavor to configuration name. 69 | final firstSegment = value.split('-').first; 70 | final buildConfiguration = BuildConfiguration.values.firstWhereOrNull( 71 | (e) => e.name == firstSegment, 72 | ); 73 | if (buildConfiguration == null) { 74 | _log.warning('Unknown build configuraiton $value, will assume release'); 75 | return BuildConfiguration.release; 76 | } 77 | return buildConfiguration; 78 | } 79 | 80 | static BuildEnvironment fromEnvironment({ 81 | required bool isAndroid, 82 | }) { 83 | final buildConfiguration = 84 | parseBuildConfiguration(Environment.configuration); 85 | final manifestDir = Environment.manifestDir; 86 | final crateOptions = CargokitCrateOptions.load( 87 | manifestDir: manifestDir, 88 | ); 89 | final crateInfo = CrateInfo.load(manifestDir); 90 | return BuildEnvironment( 91 | configuration: buildConfiguration, 92 | crateOptions: crateOptions, 93 | targetTempDir: Environment.targetTempDir, 94 | manifestDir: manifestDir, 95 | crateInfo: crateInfo, 96 | isAndroid: isAndroid, 97 | androidSdkPath: isAndroid ? Environment.sdkPath : null, 98 | androidNdkVersion: isAndroid ? Environment.ndkVersion : null, 99 | androidMinSdkVersion: 100 | isAndroid ? int.parse(Environment.minSdkVersion) : null, 101 | javaHome: isAndroid ? Environment.javaHome : null, 102 | ); 103 | } 104 | } 105 | 106 | class RustBuilder { 107 | final Target target; 108 | final BuildEnvironment environment; 109 | 110 | RustBuilder({ 111 | required this.target, 112 | required this.environment, 113 | }); 114 | 115 | void prepare( 116 | Rustup rustup, 117 | ) { 118 | final toolchain = _toolchain; 119 | if (rustup.installedTargets(toolchain) == null) { 120 | rustup.installToolchain(toolchain); 121 | } 122 | if (toolchain == 'nightly') { 123 | rustup.installRustSrcForNightly(); 124 | } 125 | if (!rustup.installedTargets(toolchain)!.contains(target.rust)) { 126 | rustup.installTarget(target.rust, toolchain: toolchain); 127 | } 128 | } 129 | 130 | CargoBuildOptions? get _buildOptions => 131 | environment.crateOptions.cargo[environment.configuration]; 132 | 133 | String get _toolchain => _buildOptions?.toolchain.name ?? 'stable'; 134 | 135 | /// Returns the path of directory containing build artifacts. 136 | Future build() async { 137 | final extraArgs = _buildOptions?.flags ?? []; 138 | final manifestPath = path.join(environment.manifestDir, 'Cargo.toml'); 139 | runCommand( 140 | 'rustup', 141 | [ 142 | 'run', 143 | _toolchain, 144 | 'cargo', 145 | 'build', 146 | ...extraArgs, 147 | '--manifest-path', 148 | manifestPath, 149 | '-p', 150 | environment.crateInfo.packageName, 151 | if (!environment.configuration.isDebug) '--release', 152 | '--target', 153 | target.rust, 154 | '--target-dir', 155 | environment.targetTempDir, 156 | ], 157 | environment: await _buildEnvironment(), 158 | ); 159 | return path.join( 160 | environment.targetTempDir, 161 | target.rust, 162 | environment.configuration.rustName, 163 | ); 164 | } 165 | 166 | Future> _buildEnvironment() async { 167 | if (target.android == null) { 168 | return {}; 169 | } else { 170 | final sdkPath = environment.androidSdkPath; 171 | final ndkVersion = environment.androidNdkVersion; 172 | final minSdkVersion = environment.androidMinSdkVersion; 173 | if (sdkPath == null) { 174 | throw BuildException('androidSdkPath is not set'); 175 | } 176 | if (ndkVersion == null) { 177 | throw BuildException('androidNdkVersion is not set'); 178 | } 179 | if (minSdkVersion == null) { 180 | throw BuildException('androidMinSdkVersion is not set'); 181 | } 182 | final env = AndroidEnvironment( 183 | sdkPath: sdkPath, 184 | ndkVersion: ndkVersion, 185 | minSdkVersion: minSdkVersion, 186 | targetTempDir: environment.targetTempDir, 187 | target: target, 188 | ); 189 | if (!env.ndkIsInstalled() && environment.javaHome != null) { 190 | env.installNdk(javaHome: environment.javaHome!); 191 | } 192 | return env.buildEnvironment(); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/cargo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart' as path; 4 | import 'package:toml/toml.dart'; 5 | 6 | class ManifestException { 7 | ManifestException(this.message, {required this.fileName}); 8 | 9 | final String? fileName; 10 | final String message; 11 | 12 | @override 13 | String toString() { 14 | if (fileName != null) { 15 | return 'Failed to parse package manifest at $fileName: $message'; 16 | } else { 17 | return 'Failed to parse package manifest: $message'; 18 | } 19 | } 20 | } 21 | 22 | class CrateInfo { 23 | CrateInfo({required this.packageName}); 24 | 25 | final String packageName; 26 | 27 | static CrateInfo parseManifest(String manifest, {final String? fileName}) { 28 | final toml = TomlDocument.parse(manifest); 29 | final package = toml.toMap()['package']; 30 | if (package == null) { 31 | throw ManifestException('Missing package section', fileName: fileName); 32 | } 33 | final name = package['name']; 34 | if (name == null) { 35 | throw ManifestException('Missing package name', fileName: fileName); 36 | } 37 | return CrateInfo(packageName: name); 38 | } 39 | 40 | static CrateInfo load(String manifestDir) { 41 | final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); 42 | final manifest = manifestFile.readAsStringSync(); 43 | return parseManifest(manifest, fileName: manifestFile.path); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/crate_hash.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:convert/convert.dart'; 7 | import 'package:crypto/crypto.dart'; 8 | import 'package:path/path.dart' as path; 9 | 10 | class CrateHash { 11 | /// Computes a hash uniquely identifying crate content. This takes into account 12 | /// content all all .rs files inside the src directory, as well as Cargo.toml, 13 | /// Cargo.lock, build.rs and cargokit.yaml. 14 | /// 15 | /// If [tempStorage] is provided, computed hash is stored in a file in that directory 16 | /// and reused on subsequent calls if the crate content hasn't changed. 17 | static String compute(String manifestDir, {String? tempStorage}) { 18 | return CrateHash._( 19 | manifestDir: manifestDir, 20 | tempStorage: tempStorage, 21 | )._compute(); 22 | } 23 | 24 | CrateHash._({ 25 | required this.manifestDir, 26 | required this.tempStorage, 27 | }); 28 | 29 | String _compute() { 30 | final files = getFiles(); 31 | final tempStorage = this.tempStorage; 32 | if (tempStorage != null) { 33 | final quickHash = _computeQuickHash(files); 34 | final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); 35 | quickHashFolder.createSync(recursive: true); 36 | final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); 37 | if (quickHashFile.existsSync()) { 38 | return quickHashFile.readAsStringSync(); 39 | } 40 | final hash = _computeHash(files); 41 | quickHashFile.writeAsStringSync(hash); 42 | return hash; 43 | } else { 44 | return _computeHash(files); 45 | } 46 | } 47 | 48 | /// Computes a quick hash based on files stat (without reading contents). This 49 | /// is used to cache the real hash, which is slower to compute since it involves 50 | /// reading every single file. 51 | String _computeQuickHash(List files) { 52 | final output = AccumulatorSink(); 53 | final input = sha256.startChunkedConversion(output); 54 | 55 | final data = ByteData(8); 56 | for (final file in files) { 57 | input.add(utf8.encode(file.path)); 58 | final stat = file.statSync(); 59 | data.setUint64(0, stat.size); 60 | input.add(data.buffer.asUint8List()); 61 | data.setUint64(0, stat.modified.millisecondsSinceEpoch); 62 | input.add(data.buffer.asUint8List()); 63 | } 64 | 65 | input.close(); 66 | return base64Url.encode(output.events.single.bytes); 67 | } 68 | 69 | String _computeHash(List files) { 70 | final output = AccumulatorSink(); 71 | final input = sha256.startChunkedConversion(output); 72 | 73 | void addTextFile(File file) { 74 | // text Files are hashed by lines in case we're dealing with github checkout 75 | // that auto-converts line endings. 76 | final splitter = LineSplitter(); 77 | if (file.existsSync()) { 78 | final data = file.readAsStringSync(); 79 | final lines = splitter.convert(data); 80 | for (final line in lines) { 81 | input.add(utf8.encode(line)); 82 | } 83 | } 84 | } 85 | 86 | for (final file in files) { 87 | addTextFile(file); 88 | } 89 | 90 | input.close(); 91 | final res = output.events.single; 92 | 93 | // Truncate to 128bits. 94 | final hash = res.bytes.sublist(0, 16); 95 | return hex.encode(hash); 96 | } 97 | 98 | List getFiles() { 99 | final src = Directory(path.join(manifestDir, 'src')); 100 | final files = src 101 | .listSync(recursive: true, followLinks: false) 102 | .whereType() 103 | .toList(); 104 | files.sortBy((element) => element.path); 105 | void addFile(String relative) { 106 | final file = File(path.join(manifestDir, relative)); 107 | if (file.existsSync()) { 108 | files.add(file); 109 | } 110 | } 111 | 112 | addFile('Cargo.toml'); 113 | addFile('Cargo.lock'); 114 | addFile('build.rs'); 115 | addFile('cargokit.yaml'); 116 | return files; 117 | } 118 | 119 | final String manifestDir; 120 | final String? tempStorage; 121 | } 122 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/environment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | extension on String { 4 | String resolveSymlink() => File(this).resolveSymbolicLinksSync(); 5 | } 6 | 7 | class Environment { 8 | /// Current build configuration (debug or release). 9 | static String get configuration => 10 | _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); 11 | 12 | static bool get isDebug => configuration == 'debug'; 13 | static bool get isRelease => configuration == 'release'; 14 | 15 | /// Temporary directory where Rust build artifacts are placed. 16 | static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); 17 | 18 | /// Final output directory where the build artifacts are placed. 19 | static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); 20 | 21 | /// Path to the crate manifest (containing Cargo.toml). 22 | static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); 23 | 24 | /// Directory inside root project. Not necessarily root folder. Symlinks are 25 | /// not resolved on purpose. 26 | static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); 27 | 28 | // Pod 29 | 30 | /// Platform name (macosx, iphoneos, iphonesimulator). 31 | static String get darwinPlatformName => 32 | _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); 33 | 34 | /// List of architectures to build for (arm64, armv7, x86_64). 35 | static List get darwinArchs => 36 | _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); 37 | 38 | // Gradle 39 | static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); 40 | static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); 41 | static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); 42 | static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); 43 | static List get targetPlatforms => 44 | _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); 45 | 46 | // CMAKE 47 | static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); 48 | 49 | static String _getEnv(String key) { 50 | final res = Platform.environment[key]; 51 | if (res == null) { 52 | throw Exception("Missing environment variable $key"); 53 | } 54 | return res; 55 | } 56 | 57 | static String _getEnvPath(String key) { 58 | final res = _getEnv(key); 59 | if (Directory(res).existsSync()) { 60 | return res.resolveSymlink(); 61 | } else { 62 | return res; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/logging.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:logging/logging.dart'; 4 | 5 | const String kSeparator = "--"; 6 | const String kDoubleSeparator = "=="; 7 | 8 | bool _lastMessageWasSeparator = false; 9 | 10 | void _log(LogRecord rec) { 11 | final prefix = '${rec.level.name}: '; 12 | final out = rec.level == Level.SEVERE ? stderr : stdout; 13 | if (rec.message == kSeparator) { 14 | if (!_lastMessageWasSeparator) { 15 | out.write(prefix); 16 | out.writeln('-' * 80); 17 | _lastMessageWasSeparator = true; 18 | } 19 | return; 20 | } else if (rec.message == kDoubleSeparator) { 21 | out.write(prefix); 22 | out.writeln('=' * 80); 23 | _lastMessageWasSeparator = true; 24 | return; 25 | } 26 | out.write(prefix); 27 | out.writeln(rec.message); 28 | _lastMessageWasSeparator = false; 29 | } 30 | 31 | void initLogging() { 32 | Logger.root.level = Level.INFO; 33 | Logger.root.onRecord.listen((LogRecord rec) { 34 | final lines = rec.message.split('\n'); 35 | for (final line in lines) { 36 | if (line.isNotEmpty || lines.length == 1 || line != lines.last) { 37 | _log(LogRecord( 38 | rec.level, 39 | line, 40 | rec.loggerName, 41 | )); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | void enableVerboseLogging() { 48 | Logger.root.level = Level.ALL; 49 | } 50 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/precompile_binaries.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:ed25519_edwards/ed25519_edwards.dart'; 4 | import 'package:github/github.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:path/path.dart' as path; 7 | 8 | import 'artifacts_provider.dart'; 9 | import 'builder.dart'; 10 | import 'cargo.dart'; 11 | import 'crate_hash.dart'; 12 | import 'options.dart'; 13 | import 'rustup.dart'; 14 | import 'target.dart'; 15 | 16 | final _log = Logger('precompile_binaries'); 17 | 18 | class PrecompileBinaries { 19 | PrecompileBinaries({ 20 | required this.privateKey, 21 | required this.githubToken, 22 | required this.repositorySlug, 23 | required this.manifestDir, 24 | required this.targets, 25 | this.androidSdkLocation, 26 | this.androidNdkVersion, 27 | this.androidMinSdkVersion, 28 | this.tempDir, 29 | }); 30 | 31 | final PrivateKey privateKey; 32 | final String githubToken; 33 | final RepositorySlug repositorySlug; 34 | final String manifestDir; 35 | final List targets; 36 | final String? androidSdkLocation; 37 | final String? androidNdkVersion; 38 | final int? androidMinSdkVersion; 39 | final String? tempDir; 40 | 41 | static String fileName(Target target, String name) { 42 | return '${target.rust}_$name'; 43 | } 44 | 45 | static String signatureFileName(Target target, String name) { 46 | return '${target.rust}_$name.sig'; 47 | } 48 | 49 | Future run() async { 50 | final crateInfo = CrateInfo.load(manifestDir); 51 | 52 | final targets = List.of(this.targets); 53 | if (targets.isEmpty) { 54 | targets.addAll([ 55 | ...Target.buildableTargets(), 56 | if (androidSdkLocation != null) ...Target.androidTargets(), 57 | ]); 58 | } 59 | 60 | _log.info('Precompiling binaries for $targets'); 61 | 62 | final hash = CrateHash.compute(manifestDir); 63 | _log.info('Computed crate hash: $hash'); 64 | 65 | final String tagName = 'precompiled_$hash'; 66 | 67 | final github = GitHub(auth: Authentication.withToken(githubToken)); 68 | final repo = github.repositories; 69 | final release = await _getOrCreateRelease( 70 | repo: repo, 71 | tagName: tagName, 72 | packageName: crateInfo.packageName, 73 | hash: hash, 74 | ); 75 | 76 | final tempDir = this.tempDir != null 77 | ? Directory(this.tempDir!) 78 | : Directory.systemTemp.createTempSync('precompiled_'); 79 | 80 | tempDir.createSync(recursive: true); 81 | 82 | final crateOptions = CargokitCrateOptions.load( 83 | manifestDir: manifestDir, 84 | ); 85 | 86 | final buildEnvironment = BuildEnvironment( 87 | configuration: BuildConfiguration.release, 88 | crateOptions: crateOptions, 89 | targetTempDir: tempDir.path, 90 | manifestDir: manifestDir, 91 | crateInfo: crateInfo, 92 | isAndroid: androidSdkLocation != null, 93 | androidSdkPath: androidSdkLocation, 94 | androidNdkVersion: androidNdkVersion, 95 | androidMinSdkVersion: androidMinSdkVersion, 96 | ); 97 | 98 | final rustup = Rustup(); 99 | 100 | for (final target in targets) { 101 | final artifactNames = getArtifactNames( 102 | target: target, 103 | libraryName: crateInfo.packageName, 104 | remote: true, 105 | ); 106 | 107 | if (artifactNames.every((name) { 108 | final fileName = PrecompileBinaries.fileName(target, name); 109 | return (release.assets ?? []).any((e) => e.name == fileName); 110 | })) { 111 | _log.info("All artifacts for $target already exist - skipping"); 112 | continue; 113 | } 114 | 115 | _log.info('Building for $target'); 116 | 117 | final builder = 118 | RustBuilder(target: target, environment: buildEnvironment); 119 | builder.prepare(rustup); 120 | final res = await builder.build(); 121 | 122 | final assets = []; 123 | for (final name in artifactNames) { 124 | final file = File(path.join(res, name)); 125 | if (!file.existsSync()) { 126 | throw Exception('Missing artifact: ${file.path}'); 127 | } 128 | 129 | final data = file.readAsBytesSync(); 130 | final create = CreateReleaseAsset( 131 | name: PrecompileBinaries.fileName(target, name), 132 | contentType: "application/octet-stream", 133 | assetData: data, 134 | ); 135 | final signature = sign(privateKey, data); 136 | final signatureCreate = CreateReleaseAsset( 137 | name: signatureFileName(target, name), 138 | contentType: "application/octet-stream", 139 | assetData: signature, 140 | ); 141 | bool verified = verify(public(privateKey), data, signature); 142 | if (!verified) { 143 | throw Exception('Signature verification failed'); 144 | } 145 | assets.add(create); 146 | assets.add(signatureCreate); 147 | } 148 | _log.info('Uploading assets: ${assets.map((e) => e.name)}'); 149 | for (final asset in assets) { 150 | // This seems to be failing on CI so do it one by one 151 | int retryCount = 0; 152 | while (true) { 153 | try { 154 | await repo.uploadReleaseAssets(release, [asset]); 155 | break; 156 | } on Exception catch (e) { 157 | if (retryCount == 10) { 158 | rethrow; 159 | } 160 | ++retryCount; 161 | _log.shout( 162 | 'Upload failed (attempt $retryCount, will retry): ${e.toString()}'); 163 | await Future.delayed(Duration(seconds: 2)); 164 | } 165 | } 166 | } 167 | } 168 | 169 | _log.info('Cleaning up'); 170 | tempDir.deleteSync(recursive: true); 171 | } 172 | 173 | Future _getOrCreateRelease({ 174 | required RepositoriesService repo, 175 | required String tagName, 176 | required String packageName, 177 | required String hash, 178 | }) async { 179 | Release release; 180 | try { 181 | _log.info('Fetching release $tagName'); 182 | release = await repo.getReleaseByTagName(repositorySlug, tagName); 183 | } on ReleaseNotFound { 184 | _log.info('Release not found - creating release $tagName'); 185 | release = await repo.createRelease( 186 | repositorySlug, 187 | CreateRelease.from( 188 | tagName: tagName, 189 | name: 'Precompiled binaries ${hash.substring(0, 8)}', 190 | targetCommitish: null, 191 | isDraft: false, 192 | isPrerelease: false, 193 | body: 'Precompiled binaries for crate $packageName, ' 194 | 'crate hash $hash.', 195 | )); 196 | } 197 | return release; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/rustup.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:collection/collection.dart'; 4 | import 'package:path/path.dart' as path; 5 | 6 | import 'util.dart'; 7 | 8 | class _Toolchain { 9 | _Toolchain( 10 | this.name, 11 | this.targets, 12 | ); 13 | 14 | final String name; 15 | final List targets; 16 | } 17 | 18 | class Rustup { 19 | List? installedTargets(String toolchain) { 20 | final targets = _installedTargets(toolchain); 21 | return targets != null ? List.unmodifiable(targets) : null; 22 | } 23 | 24 | void installToolchain(String toolchain) { 25 | log.info("Installing Rust toolchain: $toolchain"); 26 | runCommand("rustup", ['toolchain', 'install', toolchain]); 27 | _installedToolchains 28 | .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); 29 | } 30 | 31 | void installTarget( 32 | String target, { 33 | required String toolchain, 34 | }) { 35 | log.info("Installing Rust target: $target"); 36 | runCommand("rustup", [ 37 | 'target', 38 | 'add', 39 | '--toolchain', 40 | toolchain, 41 | target, 42 | ]); 43 | _installedTargets(toolchain)?.add(target); 44 | } 45 | 46 | final List<_Toolchain> _installedToolchains; 47 | 48 | Rustup() : _installedToolchains = _getInstalledToolchains(); 49 | 50 | List? _installedTargets(String toolchain) => _installedToolchains 51 | .firstWhereOrNull( 52 | (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) 53 | ?.targets; 54 | 55 | static List<_Toolchain> _getInstalledToolchains() { 56 | String extractToolchainName(String line) { 57 | // ignore (default) after toolchain name 58 | final parts = line.split(' '); 59 | return parts[0]; 60 | } 61 | 62 | final res = runCommand("rustup", ['toolchain', 'list']); 63 | 64 | // To list all non-custom toolchains, we need to filter out lines that 65 | // don't start with "stable", "beta", or "nightly". 66 | Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); 67 | final lines = res.stdout 68 | .toString() 69 | .split('\n') 70 | .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) 71 | .map(extractToolchainName) 72 | .toList(growable: true); 73 | 74 | return lines 75 | .map( 76 | (name) => _Toolchain( 77 | name, 78 | _getInstalledTargets(name), 79 | ), 80 | ) 81 | .toList(growable: true); 82 | } 83 | 84 | static List _getInstalledTargets(String toolchain) { 85 | final res = runCommand("rustup", [ 86 | 'target', 87 | 'list', 88 | '--toolchain', 89 | toolchain, 90 | '--installed', 91 | ]); 92 | final lines = res.stdout 93 | .toString() 94 | .split('\n') 95 | .where((e) => e.isNotEmpty) 96 | .toList(growable: true); 97 | return lines; 98 | } 99 | 100 | bool _didInstallRustSrcForNightly = false; 101 | 102 | void installRustSrcForNightly() { 103 | if (_didInstallRustSrcForNightly) { 104 | return; 105 | } 106 | // Useful for -Z build-std 107 | runCommand( 108 | "rustup", 109 | ['component', 'add', 'rust-src', '--toolchain', 'nightly'], 110 | ); 111 | _didInstallRustSrcForNightly = true; 112 | } 113 | 114 | static String? executablePath() { 115 | final envPath = Platform.environment['PATH']; 116 | final envPathSeparator = Platform.isWindows ? ';' : ':'; 117 | final home = Platform.isWindows 118 | ? Platform.environment['USERPROFILE'] 119 | : Platform.environment['HOME']; 120 | final paths = [ 121 | if (home != null) path.join(home, '.cargo', 'bin'), 122 | if (envPath != null) ...envPath.split(envPathSeparator), 123 | ]; 124 | for (final p in paths) { 125 | final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; 126 | final rustupPath = path.join(p, rustup); 127 | if (File(rustupPath).existsSync()) { 128 | return rustupPath; 129 | } 130 | } 131 | return null; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/target.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:collection/collection.dart'; 4 | 5 | import 'util.dart'; 6 | 7 | class Target { 8 | Target({ 9 | required this.rust, 10 | this.flutter, 11 | this.android, 12 | this.androidMinSdkVersion, 13 | this.darwinPlatform, 14 | this.darwinArch, 15 | }); 16 | 17 | static final all = [ 18 | Target( 19 | rust: 'armv7-linux-androideabi', 20 | flutter: 'android-arm', 21 | android: 'armeabi-v7a', 22 | androidMinSdkVersion: 16, 23 | ), 24 | Target( 25 | rust: 'aarch64-linux-android', 26 | flutter: 'android-arm64', 27 | android: 'arm64-v8a', 28 | androidMinSdkVersion: 21, 29 | ), 30 | Target( 31 | rust: 'i686-linux-android', 32 | flutter: 'android-x86', 33 | android: 'x86', 34 | androidMinSdkVersion: 16, 35 | ), 36 | Target( 37 | rust: 'x86_64-linux-android', 38 | flutter: 'android-x64', 39 | android: 'x86_64', 40 | androidMinSdkVersion: 21, 41 | ), 42 | Target( 43 | rust: 'x86_64-pc-windows-msvc', 44 | flutter: 'windows-x64', 45 | ), 46 | Target( 47 | rust: 'x86_64-unknown-linux-gnu', 48 | flutter: 'linux-x64', 49 | ), 50 | Target( 51 | rust: 'aarch64-unknown-linux-gnu', 52 | flutter: 'linux-arm64', 53 | ), 54 | Target( 55 | rust: 'x86_64-apple-darwin', 56 | darwinPlatform: 'macosx', 57 | darwinArch: 'x86_64', 58 | ), 59 | Target( 60 | rust: 'aarch64-apple-darwin', 61 | darwinPlatform: 'macosx', 62 | darwinArch: 'arm64', 63 | ), 64 | Target( 65 | rust: 'aarch64-apple-ios', 66 | darwinPlatform: 'iphoneos', 67 | darwinArch: 'arm64', 68 | ), 69 | Target( 70 | rust: 'aarch64-apple-ios-sim', 71 | darwinPlatform: 'iphonesimulator', 72 | darwinArch: 'arm64', 73 | ), 74 | Target( 75 | rust: 'x86_64-apple-ios', 76 | darwinPlatform: 'iphonesimulator', 77 | darwinArch: 'x86_64', 78 | ), 79 | ]; 80 | 81 | static Target? forFlutterName(String flutterName) { 82 | return all.firstWhereOrNull((element) => element.flutter == flutterName); 83 | } 84 | 85 | static Target? forDarwin({ 86 | required String platformName, 87 | required String darwinAarch, 88 | }) { 89 | return all.firstWhereOrNull((element) => // 90 | element.darwinPlatform == platformName && 91 | element.darwinArch == darwinAarch); 92 | } 93 | 94 | static Target? forRustTriple(String triple) { 95 | return all.firstWhereOrNull((element) => element.rust == triple); 96 | } 97 | 98 | static List androidTargets() { 99 | return all 100 | .where((element) => element.android != null) 101 | .toList(growable: false); 102 | } 103 | 104 | /// Returns buildable targets on current host platform ignoring Android targets. 105 | static List buildableTargets() { 106 | if (Platform.isLinux) { 107 | // Right now we don't support cross-compiling on Linux. So we just return 108 | // the host target. 109 | final arch = runCommand('arch', []).stdout as String; 110 | if (arch.trim() == 'aarch64') { 111 | return [Target.forRustTriple('aarch64-unknown-linux-gnu')!]; 112 | } else { 113 | return [Target.forRustTriple('x86_64-unknown-linux-gnu')!]; 114 | } 115 | } 116 | return all.where((target) { 117 | if (Platform.isWindows) { 118 | return target.rust.contains('-windows-'); 119 | } else if (Platform.isMacOS) { 120 | return target.darwinPlatform != null; 121 | } 122 | return false; 123 | }).toList(growable: false); 124 | } 125 | 126 | @override 127 | String toString() { 128 | return rust; 129 | } 130 | 131 | final String? flutter; 132 | final String rust; 133 | final String? android; 134 | final int? androidMinSdkVersion; 135 | final String? darwinPlatform; 136 | final String? darwinArch; 137 | } 138 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:logging/logging.dart'; 5 | import 'package:path/path.dart' as path; 6 | 7 | import 'logging.dart'; 8 | import 'rustup.dart'; 9 | 10 | final log = Logger("process"); 11 | 12 | class CommandFailedException implements Exception { 13 | final String executable; 14 | final List arguments; 15 | final ProcessResult result; 16 | 17 | CommandFailedException({ 18 | required this.executable, 19 | required this.arguments, 20 | required this.result, 21 | }); 22 | 23 | @override 24 | String toString() { 25 | final stdout = result.stdout.toString().trim(); 26 | final stderr = result.stderr.toString().trim(); 27 | return [ 28 | "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", 29 | "Returned Exit Code: ${result.exitCode}", 30 | kSeparator, 31 | "STDOUT:", 32 | if (stdout.isNotEmpty) stdout, 33 | kSeparator, 34 | "STDERR:", 35 | if (stderr.isNotEmpty) stderr, 36 | ].join('\n'); 37 | } 38 | } 39 | 40 | class TestRunCommandArgs { 41 | final String executable; 42 | final List arguments; 43 | final String? workingDirectory; 44 | final Map? environment; 45 | final bool includeParentEnvironment; 46 | final bool runInShell; 47 | final Encoding? stdoutEncoding; 48 | final Encoding? stderrEncoding; 49 | 50 | TestRunCommandArgs({ 51 | required this.executable, 52 | required this.arguments, 53 | this.workingDirectory, 54 | this.environment, 55 | this.includeParentEnvironment = true, 56 | this.runInShell = false, 57 | this.stdoutEncoding, 58 | this.stderrEncoding, 59 | }); 60 | } 61 | 62 | class TestRunCommandResult { 63 | TestRunCommandResult({ 64 | this.pid = 1, 65 | this.exitCode = 0, 66 | this.stdout = '', 67 | this.stderr = '', 68 | }); 69 | 70 | final int pid; 71 | final int exitCode; 72 | final String stdout; 73 | final String stderr; 74 | } 75 | 76 | TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride; 77 | 78 | ProcessResult runCommand( 79 | String executable, 80 | List arguments, { 81 | String? workingDirectory, 82 | Map? environment, 83 | bool includeParentEnvironment = true, 84 | bool runInShell = false, 85 | Encoding? stdoutEncoding = systemEncoding, 86 | Encoding? stderrEncoding = systemEncoding, 87 | }) { 88 | if (testRunCommandOverride != null) { 89 | final result = testRunCommandOverride!(TestRunCommandArgs( 90 | executable: executable, 91 | arguments: arguments, 92 | workingDirectory: workingDirectory, 93 | environment: environment, 94 | includeParentEnvironment: includeParentEnvironment, 95 | runInShell: runInShell, 96 | stdoutEncoding: stdoutEncoding, 97 | stderrEncoding: stderrEncoding, 98 | )); 99 | return ProcessResult( 100 | result.pid, 101 | result.exitCode, 102 | result.stdout, 103 | result.stderr, 104 | ); 105 | } 106 | log.finer('Running command $executable ${arguments.join(' ')}'); 107 | final res = Process.runSync( 108 | _resolveExecutable(executable), 109 | arguments, 110 | workingDirectory: workingDirectory, 111 | environment: environment, 112 | includeParentEnvironment: includeParentEnvironment, 113 | runInShell: runInShell, 114 | stderrEncoding: stderrEncoding, 115 | stdoutEncoding: stdoutEncoding, 116 | ); 117 | if (res.exitCode != 0) { 118 | throw CommandFailedException( 119 | executable: executable, 120 | arguments: arguments, 121 | result: res, 122 | ); 123 | } else { 124 | return res; 125 | } 126 | } 127 | 128 | class RustupNotFoundException implements Exception { 129 | @override 130 | String toString() { 131 | return [ 132 | ' ', 133 | 'rustup not found in PATH.', 134 | ' ', 135 | 'Maybe you need to install Rust? It only takes a minute:', 136 | ' ', 137 | if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', 138 | if (hasHomebrewRustInPath()) ...[ 139 | '\$ brew unlink rust # Unlink homebrew Rust from PATH', 140 | ], 141 | if (!Platform.isWindows) 142 | "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", 143 | ' ', 144 | ].join('\n'); 145 | } 146 | 147 | static bool hasHomebrewRustInPath() { 148 | if (!Platform.isMacOS) { 149 | return false; 150 | } 151 | final envPath = Platform.environment['PATH'] ?? ''; 152 | final paths = envPath.split(':'); 153 | return paths.any((p) { 154 | return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); 155 | }); 156 | } 157 | } 158 | 159 | String _resolveExecutable(String executable) { 160 | if (executable == 'rustup') { 161 | final resolved = Rustup.executablePath(); 162 | if (resolved != null) { 163 | return resolved; 164 | } 165 | throw RustupNotFoundException(); 166 | } else { 167 | return executable; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /cargokit/build_tool/lib/src/verify_binaries.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:ed25519_edwards/ed25519_edwards.dart'; 4 | import 'package:http/http.dart'; 5 | 6 | import 'artifacts_provider.dart'; 7 | import 'cargo.dart'; 8 | import 'crate_hash.dart'; 9 | import 'options.dart'; 10 | import 'precompile_binaries.dart'; 11 | import 'target.dart'; 12 | 13 | class VerifyBinaries { 14 | VerifyBinaries({ 15 | required this.manifestDir, 16 | }); 17 | 18 | final String manifestDir; 19 | 20 | Future run() async { 21 | final crateInfo = CrateInfo.load(manifestDir); 22 | 23 | final config = CargokitCrateOptions.load(manifestDir: manifestDir); 24 | final precompiledBinaries = config.precompiledBinaries; 25 | if (precompiledBinaries == null) { 26 | stdout.writeln('Crate does not support precompiled binaries.'); 27 | } else { 28 | final crateHash = CrateHash.compute(manifestDir); 29 | stdout.writeln('Crate hash: $crateHash'); 30 | 31 | for (final target in Target.all) { 32 | final message = 'Checking ${target.rust}...'; 33 | stdout.write(message.padRight(40)); 34 | stdout.flush(); 35 | 36 | final artifacts = getArtifactNames( 37 | target: target, 38 | libraryName: crateInfo.packageName, 39 | remote: true, 40 | ); 41 | 42 | final prefix = precompiledBinaries.uriPrefix; 43 | 44 | bool ok = true; 45 | 46 | for (final artifact in artifacts) { 47 | final fileName = PrecompileBinaries.fileName(target, artifact); 48 | final signatureFileName = 49 | PrecompileBinaries.signatureFileName(target, artifact); 50 | 51 | final url = Uri.parse('$prefix$crateHash/$fileName'); 52 | final signatureUrl = 53 | Uri.parse('$prefix$crateHash/$signatureFileName'); 54 | 55 | final signature = await get(signatureUrl); 56 | if (signature.statusCode != 200) { 57 | stdout.writeln('MISSING'); 58 | ok = false; 59 | break; 60 | } 61 | final asset = await get(url); 62 | if (asset.statusCode != 200) { 63 | stdout.writeln('MISSING'); 64 | ok = false; 65 | break; 66 | } 67 | 68 | if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, 69 | signature.bodyBytes)) { 70 | stdout.writeln('INVALID SIGNATURE'); 71 | ok = false; 72 | } 73 | } 74 | 75 | if (ok) { 76 | stdout.writeln('OK'); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /cargokit/build_tool/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: build_tool 2 | description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. 3 | publish_to: none 4 | version: 1.0.0 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | 9 | # Add regular dependencies here. 10 | dependencies: 11 | # these are pinned on purpose because the bundle_tool_runner doesn't have 12 | # pubspec.lock. See run_build_tool.sh 13 | logging: 1.2.0 14 | path: 1.8.0 15 | version: 3.0.0 16 | collection: 1.18.0 17 | ed25519_edwards: 0.3.1 18 | hex: 0.2.0 19 | yaml: 3.1.2 20 | source_span: 1.10.0 21 | github: 9.17.0 22 | args: 2.4.2 23 | crypto: 3.0.3 24 | convert: 3.1.1 25 | http: 1.1.0 26 | toml: 0.14.0 27 | 28 | dev_dependencies: 29 | lints: ^2.1.0 30 | test: ^1.24.0 31 | -------------------------------------------------------------------------------- /cargokit/build_tool/test/builder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_tool/src/builder.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('parseBuildConfiguration', () { 6 | var b = BuildEnvironment.parseBuildConfiguration('debug'); 7 | expect(b, BuildConfiguration.debug); 8 | 9 | b = BuildEnvironment.parseBuildConfiguration('profile'); 10 | expect(b, BuildConfiguration.profile); 11 | 12 | b = BuildEnvironment.parseBuildConfiguration('release'); 13 | expect(b, BuildConfiguration.release); 14 | 15 | b = BuildEnvironment.parseBuildConfiguration('debug-dev'); 16 | expect(b, BuildConfiguration.debug); 17 | 18 | b = BuildEnvironment.parseBuildConfiguration('profile'); 19 | expect(b, BuildConfiguration.profile); 20 | 21 | b = BuildEnvironment.parseBuildConfiguration('profile-prod'); 22 | expect(b, BuildConfiguration.profile); 23 | 24 | // fallback to release 25 | b = BuildEnvironment.parseBuildConfiguration('unknown'); 26 | expect(b, BuildConfiguration.release); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /cargokit/build_tool/test/cargo_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_tool/src/cargo.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | final _cargoToml = """ 5 | [workspace] 6 | 7 | [profile.release] 8 | lto = true 9 | panic = "abort" 10 | opt-level = "z" 11 | # strip = "symbols" 12 | 13 | [package] 14 | name = "super_native_extensions" 15 | version = "0.1.0" 16 | edition = "2021" 17 | resolver = "2" 18 | 19 | [lib] 20 | crate-type = ["cdylib", "staticlib"] 21 | """; 22 | 23 | void main() { 24 | test('parseCargoToml', () { 25 | final info = CrateInfo.parseManifest(_cargoToml); 26 | expect(info.packageName, 'super_native_extensions'); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /cargokit/build_tool/test/options_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_tool/src/builder.dart'; 2 | import 'package:build_tool/src/options.dart'; 3 | import 'package:hex/hex.dart'; 4 | import 'package:test/test.dart'; 5 | import 'package:yaml/yaml.dart'; 6 | 7 | void main() { 8 | test('parseCargoBuildOptions', () { 9 | final yaml = """ 10 | toolchain: nightly 11 | extra_flags: 12 | - -Z 13 | # Comment here 14 | - build-std=panic_abort,std 15 | """; 16 | final node = loadYamlNode(yaml); 17 | final options = CargoBuildOptions.parse(node); 18 | expect(options.toolchain, Toolchain.nightly); 19 | expect(options.flags, ['-Z', 'build-std=panic_abort,std']); 20 | }); 21 | 22 | test('parsePrecompiledBinaries', () { 23 | final yaml = """ 24 | url_prefix: https://url-prefix 25 | public_key: a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445 26 | """; 27 | final precompiledBinaries = PrecompiledBinaries.parse(loadYamlNode(yaml)); 28 | final key = HEX.decode( 29 | 'a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445'); 30 | expect(precompiledBinaries.uriPrefix, 'https://url-prefix'); 31 | expect(precompiledBinaries.publicKey.bytes, key); 32 | }); 33 | 34 | test('parseCargokitOptions', () { 35 | const yaml = ''' 36 | cargo: 37 | # For smalles binaries rebuilt the standard library with panic=abort 38 | debug: 39 | toolchain: nightly 40 | extra_flags: 41 | - -Z 42 | # Comment here 43 | - build-std=panic_abort,std 44 | release: 45 | toolchain: beta 46 | 47 | precompiled_binaries: 48 | url_prefix: https://url-prefix 49 | public_key: a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445 50 | '''; 51 | final options = CargokitCrateOptions.parse(loadYamlNode(yaml)); 52 | expect(options.precompiledBinaries?.uriPrefix, 'https://url-prefix'); 53 | final key = HEX.decode( 54 | 'a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445'); 55 | expect(options.precompiledBinaries?.publicKey.bytes, key); 56 | 57 | final debugOptions = options.cargo[BuildConfiguration.debug]!; 58 | expect(debugOptions.toolchain, Toolchain.nightly); 59 | expect(debugOptions.flags, ['-Z', 'build-std=panic_abort,std']); 60 | 61 | final releaseOptions = options.cargo[BuildConfiguration.release]!; 62 | expect(releaseOptions.toolchain, Toolchain.beta); 63 | expect(releaseOptions.flags, []); 64 | }); 65 | 66 | test('parseCargokitUserOptions', () { 67 | const yaml = ''' 68 | use_precompiled_binaries: false 69 | verbose_logging: true 70 | '''; 71 | final options = CargokitUserOptions.parse(loadYamlNode(yaml)); 72 | expect(options.usePrecompiledBinaries, false); 73 | expect(options.verboseLogging, true); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /cargokit/build_tool/test/rustup_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_tool/src/rustup.dart'; 2 | import 'package:build_tool/src/util.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('rustup with no toolchains', () { 7 | bool didListToolchains = false; 8 | bool didInstallStable = false; 9 | bool didListTargets = false; 10 | testRunCommandOverride = (args) { 11 | expect(args.executable, 'rustup'); 12 | switch (args.arguments) { 13 | case ['toolchain', 'list']: 14 | didListToolchains = true; 15 | return TestRunCommandResult(stdout: 'no installed toolchains\n'); 16 | case ['toolchain', 'install', 'stable']: 17 | didInstallStable = true; 18 | return TestRunCommandResult(); 19 | case ['target', 'list', '--toolchain', 'stable', '--installed']: 20 | didListTargets = true; 21 | return TestRunCommandResult( 22 | stdout: 'x86_64-unknown-linux-gnu\nx86_64-apple-darwin\n'); 23 | default: 24 | throw Exception('Unexpected call: ${args.arguments}'); 25 | } 26 | }; 27 | final rustup = Rustup(); 28 | rustup.installToolchain('stable'); 29 | expect(didInstallStable, true); 30 | expect(didListToolchains, true); 31 | expect(didListTargets, true); 32 | expect(rustup.installedTargets('stable'), [ 33 | 'x86_64-unknown-linux-gnu', 34 | 'x86_64-apple-darwin', 35 | ]); 36 | testRunCommandOverride = null; 37 | }); 38 | 39 | test('rustup with esp toolchain', () { 40 | final targetsQueried = []; 41 | testRunCommandOverride = (args) { 42 | expect(args.executable, 'rustup'); 43 | switch (args.arguments) { 44 | case ['toolchain', 'list']: 45 | return TestRunCommandResult( 46 | stdout: 'stable-aarch64-apple-darwin (default)\n' 47 | 'nightly-aarch64-apple-darwin\n' 48 | 'esp\n'); 49 | case ['target', 'list', '--toolchain', String toolchain, '--installed']: 50 | targetsQueried.add(toolchain); 51 | return TestRunCommandResult(stdout: '$toolchain:target\n'); 52 | default: 53 | throw Exception('Unexpected call: ${args.arguments}'); 54 | } 55 | }; 56 | final rustup = Rustup(); 57 | expect(targetsQueried, [ 58 | 'stable-aarch64-apple-darwin', 59 | 'nightly-aarch64-apple-darwin', 60 | ]); 61 | expect(rustup.installedTargets('stable'), 62 | ['stable-aarch64-apple-darwin:target']); 63 | expect(rustup.installedTargets('nightly'), 64 | ['nightly-aarch64-apple-darwin:target']); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /cargokit/cmake/cargokit.cmake: -------------------------------------------------------------------------------- 1 | SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..") 2 | 3 | # Workaround for https://github.com/dart-lang/pub/issues/4010 4 | get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH) 5 | 6 | if(WIN32) 7 | # REALPATH does not properly resolve symlinks on windows :-/ 8 | execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE) 9 | endif() 10 | 11 | # Arguments 12 | # - target: CMAKE target to which rust library is linked 13 | # - manifest_dir: relative path from current folder to directory containing cargo manifest 14 | # - lib_name: cargo package name 15 | # - any_symbol_name: name of any exported symbol from the library. 16 | # used on windows to force linking with library. 17 | function(apply_cargokit target manifest_dir lib_name any_symbol_name) 18 | 19 | set(CARGOKIT_LIB_NAME "${lib_name}") 20 | set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}") 21 | if (CMAKE_CONFIGURATION_TYPES) 22 | set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$") 23 | set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${CARGOKIT_LIB_FULL_NAME}") 24 | else() 25 | set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") 26 | set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}") 27 | endif() 28 | set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build") 29 | 30 | if (FLUTTER_TARGET_PLATFORM) 31 | set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}") 32 | else() 33 | set(CARGOKIT_TARGET_PLATFORM "windows-x64") 34 | endif() 35 | 36 | set(CARGOKIT_ENV 37 | "CARGOKIT_CMAKE=${CMAKE_COMMAND}" 38 | "CARGOKIT_CONFIGURATION=$" 39 | "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" 40 | "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" 41 | "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" 42 | "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" 43 | "CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool" 44 | "CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}" 45 | ) 46 | 47 | if (WIN32) 48 | set(SCRIPT_EXTENSION ".cmd") 49 | set(IMPORT_LIB_EXTENSION ".lib") 50 | else() 51 | set(SCRIPT_EXTENSION ".sh") 52 | set(IMPORT_LIB_EXTENSION "") 53 | endif() 54 | 55 | # Using generators in custom command is only supported in CMake 3.20+ 56 | if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0") 57 | foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) 58 | add_custom_command( 59 | OUTPUT 60 | "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}" 61 | "${CMAKE_CURRENT_BINARY_DIR}/_phony_" 62 | COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} 63 | "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake 64 | VERBATIM 65 | ) 66 | endforeach() 67 | else() 68 | add_custom_command( 69 | OUTPUT 70 | ${OUTPUT_LIB} 71 | "${CMAKE_CURRENT_BINARY_DIR}/_phony_" 72 | COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} 73 | "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake 74 | VERBATIM 75 | ) 76 | endif() 77 | 78 | set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE) 79 | 80 | if (TARGET ${target}) 81 | # If we have actual cmake target provided create target and make existing 82 | # target depend on it 83 | add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB}) 84 | add_dependencies("${target}" "${target}_cargokit") 85 | target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}") 86 | if(WIN32) 87 | target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}") 88 | endif() 89 | else() 90 | # Otherwise (FFI) just use ALL to force building always 91 | add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB}) 92 | endif() 93 | 94 | # Allow adding the output library to plugin bundled libraries 95 | set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE) 96 | 97 | endfunction() -------------------------------------------------------------------------------- /cargokit/cmake/resolve_symlinks.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-Symlinks { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | param( 5 | [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 6 | [string] $Path 7 | ) 8 | 9 | [string] $separator = '/' 10 | [string[]] $parts = $Path.Split($separator) 11 | 12 | [string] $realPath = '' 13 | foreach ($part in $parts) { 14 | if ($realPath -and !$realPath.EndsWith($separator)) { 15 | $realPath += $separator 16 | } 17 | $realPath += $part 18 | $item = Get-Item $realPath 19 | if ($item.Target) { 20 | $realPath = $item.Target.Replace('\', '/') 21 | } 22 | } 23 | $realPath 24 | } 25 | 26 | $path=Resolve-Symlinks -Path $args[0] 27 | Write-Host $path 28 | -------------------------------------------------------------------------------- /cargokit/docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Cargokit Architecture 2 | 3 | Note: This is mostly relevant for plugins authors that want to see a bit under the hood rather then just following a tutorial. 4 | 5 | In ideal conditions the end-developer using the plugin should not even be aware of Cargokit existence. 6 | 7 | ## Integration 8 | 9 | Cargokit is meant to be included in Flutter plugin (or application) that contains the Rust crate to be built during the Flutter build process. 10 | 11 | Cargokit can be either incuded as git submodule or git subtree (required for plugins - as pub does not support submodules for git dependencies). 12 | 13 | For a step by step tutorial on integrating Cargokit with a Flutter plugin see https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/. 14 | 15 | ## build_tool 16 | 17 | Build tool is the core of cargokit. It is a Dart command line package that facilitates the build of Rust crate. It is invoked during the Flutter build process to build (or download) Rust artifacts, but it can be also used as a standalone tool. 18 | 19 | It handles the following commands: 20 | 21 | ### build-cmake 22 | 23 | This is invoked from `cargokit.cmake` and it is used to build the Rust crate into a dynamic library on Linux and Windows (which use CMake as build system). 24 | 25 | The command takes no additional arguments, everything is controlled during environment variables set by `cargokit.cmake`. 26 | 27 | ### build-gradle 28 | 29 | This is invoked from `plugin.gradle` and it is used to build the Rust crate into a dynamic library on Android. The command takes no additional arguments, everything is controlled during environment variables set by `plugin.gradle`. 30 | 31 | The build_tool installs NDK if needed, configures the Rust environment for cross compilation and then invokes `cargo build` with appropriate arguments and environment variables. 32 | 33 | The build-tool also acts a linker driver. 34 | 35 | ### build-pod 36 | 37 | This is invoked from plugin's podspec `script_phase` through `build_pod.sh`. Bundle tool will build the Rust crate into a static library that gets linked into the plugin Framework. In this case must have `:execution_position` set to `:before_compile`. 38 | 39 | Cargokit will build binaries for all active architectures from XCode build and lipo them togherer. 40 | 41 | When using Cargokit to integrate Rust code with an application (not a plugin) you can also configure the `Cargo.toml` to just build a dynamic library. When Cargokit finds that the crate only built a dylib and no static lib, it will attempt to replace the Cocoapod framework binary with the dylib. In this case the script `:execution_position` must be set to `:after_compile`. This is *not* recommended for plugins and it's quite experimental. 42 | 43 | ### gen-key, precompile-binaries, verify-binaries 44 | 45 | These are used as when providing precompiled binaries for Plugin. See [precompiled_binaries.md](precompiled_binaries.md) for more information. 46 | 47 | ## Launching the build_tool during build. 48 | 49 | During Flutter build, the build tool can not be launched directly using `dart run`. Rather it is launched through `run_build_tool.sh` and `run_build_tool.cmd`. Because the `build_tool` is shipped as part of plugin, we generally don't want to write into the plugin directory during build, which would happen if the `build_tool` was simply invoked through `dart run` (For example the `.dart_tool/package_config.json` file would get written inside the `build_tool` directory). 50 | 51 | Instead the `run_build_tool` script creates a minimal Dart command line package in the build directory and references the `build_tool` as package. That way the `.dart_tool/package_config.json` file is created in the temporary build folder and not in the plugin itself. The script also precompiles the Dart code to speed up subsequent invocations. 52 | 53 | ## Configuring Cargokit 54 | 55 | ### Configuration for the Rust crate 56 | 57 | Cargokit can be configured through a `cargokit.yaml` file, which can be used to control the build of the Rust package and is placed into the Rust crate next to `Cargo.toml`. 58 | 59 | Here is an example `cargokit.yaml` with comments: 60 | ```yaml 61 | cargo: 62 | debug: # Configuration of cargo execution during debug builds 63 | toolchain: stable # default 64 | release: # Configuration of cargo execution for release builds 65 | toolchain: nightly # rustup will be invoked with nightly toolchain 66 | extra_flags: # extra arguments passed to cargo build 67 | - -Z 68 | - build-std=panic_abort,std 69 | 70 | # If crate ships with precompiled binaries, they can be configured here. 71 | precompiled_binaries: 72 | # Uri prefix used when downloading precompiled binaries. 73 | url_prefix: https://github.com/superlistapp/super_native_extensions/releases/download/precompiled_ 74 | 75 | # Public key for verifying downloaded precompiled binaries. 76 | public_key: 3a257ef1c7d72d84225ac4658d24812ada50a7a7a8a2138c2a91353389fdc514 77 | ``` 78 | 79 | ### Configuration for the application consuming the plugin 80 | 81 | A `cargokit_options.yaml` file can also be placed by developer using plugin to the root of the application package. In which case the file can be used to specify following options: 82 | 83 | ```yaml 84 | # Enables verbose logging of Cargokit during build 85 | verbose_logging: true 86 | 87 | # Opts out of using precompiled binaries. If crate has configured 88 | # and deployed precompiled binaries, these will be by default used whenever Rustup 89 | # is not installed. With `use_precompiled_binaries` set to false, the build will 90 | # instead be aborted prompting user to install Rustup. 91 | use_precompiled_binaries: false 92 | ``` 93 | 94 | ## Detecting Rustup 95 | 96 | When the plugin doesn't come with precompiled libraries (or user opt-out), `build_tool` will need to invoke Rustup during build to ensure that required Rust targets and toolchain are installed for current build and to build the Rust crate. 97 | 98 | Cargokit will attempt to detect Rustup in the default Rustup installation location (`~/.cargo/rustup`) as well as in PATH. This is done so that if user install Rustup but doesn't properly configure PATH, Cargokit will still work. 99 | 100 | If `build_tool` doesn't find Rustup, it will about the build with a message showing instructions to install Rustup specific to current platform. 101 | 102 | On macOS it will also detect a homebrew Rust installation in PATH and will prompt user to call `brew unlink rust` first to remove homebrew Rust installation from PATH, because it may interfere with Rustup. 103 | 104 | Homebrew Rust installation can not be used by Cargokit, because it can only build for host platform. Cargokit needs to be able to cross compile the Rust crate for iOS and Android and thus needs full Rustup installation. 105 | -------------------------------------------------------------------------------- /cargokit/docs/precompiled_binaries.md: -------------------------------------------------------------------------------- 1 | # Precompiled Binaries 2 | 3 | Because Cargokit builds the Rust crate during Flutter build, it is inherently 4 | dependend on the Rust toolchain being installed on the developer's machine. 5 | 6 | To decrease the friction, it is possible for Cargokit to use precompiled binaries instead. 7 | 8 | This is how the process of using precompiled binaries looks from the perspective of the build on developer machine: 9 | 10 | 1. Cargokit checks if there is `cargokit_options.yaml` file in the root folder of target application. If there is one, it will be checked for `use_precompiled_binaries` options to see if user opted out of using precompiled binaries. In which case Cargokit will insist on building from source. Cargokit will also build from source if the configuration file is absent, but user has Rustup installed. 11 | 12 | 2. Cargokit checks if there is `cargokit.yaml` file placed in the Rust crate. If there is one, it will be checked for `precompiled_binaries` section to see if crate supports precompiled binaries. The configuration section must contain a public key and URL prefix. 13 | 14 | 3. Cargokit computes a `crate-hash`. This is a SHA256 hash value computed from all Rust files inside crate, `Cargo.toml`, `Cargo.lock` and `cargokit.yaml`. This uniquely identifies the crate and it is used to find the correct precompiled binaries. 15 | 16 | 4. Cargokit will attempt to download the precompiled binaries for target platform and `crate_hash` combination and a signature file for each downloaded binary. If download succeeds, the binary content will be verified against the signature and public key included in `cargokit.yaml` (which is part of Rust crate and thus part of published Flutter package). 17 | 18 | 5. If the verification succeeds, the precompiled binaries will be used. Otherwise the binary will be discarded and Cargokit will insist on building from source. 19 | 20 | ## Providing precompiled binaries 21 | 22 | Note that this assumes that precompiled binaries will be generated during github actions and deployed as github releases. 23 | 24 | ### Use `build_tool` to generate a key-pair: 25 | 26 | ``` 27 | dart run build_tool gen-key 28 | ``` 29 | 30 | This will print the private key and public key. Store the private key securely. It needs to be provided as a secret to github action. 31 | 32 | The public key should be included in `cargokit.yaml` file in the Rust crate. 33 | 34 | ### Provide a `cargokit.yaml` file in the Rust crate 35 | 36 | The file must be placed alongside Cargo.toml. 37 | 38 | ```yaml 39 | precompiled_binaries: 40 | # Uri prefix used when downloading precompiled binaries. 41 | url_prefix: https://github.com///releases/download/precompiled_ 42 | 43 | # Public key for verifying downloaded precompiled binaries. 44 | public_key: 45 | ``` 46 | 47 | ### Configure a github action to build and upload precompiled binaries. 48 | 49 | The github action should be run at every commit to main branch (and possibly other branches). 50 | 51 | The action needs two secrets - private key for signing binaries and GitHub token for uploading binaries as releases. Here is example action that precompiles and uploads binaries for all supported targets. 52 | 53 | ```yaml 54 | on: 55 | push: 56 | branches: [ main ] 57 | 58 | name: Precompile Binaries 59 | 60 | jobs: 61 | Precompile: 62 | runs-on: ${{ matrix.os }} 63 | strategy: 64 | fail-fast: false 65 | matrix: 66 | os: 67 | - ubuntu-latest 68 | - macOS-latest 69 | - windows-latest 70 | steps: 71 | - uses: actions/checkout@v2 72 | - uses: dart-lang/setup-dart@v1 73 | - name: Install GTK 74 | if: (matrix.os == 'ubuntu-latest') 75 | run: sudo apt-get update && sudo apt-get install libgtk-3-dev 76 | - name: Precompile 77 | if: (matrix.os == 'macOS-latest') || (matrix.os == 'windows-latest') 78 | run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=superlistapp/super_native_extensions 79 | working-directory: super_native_extensions/cargokit/build_tool 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 82 | PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }} 83 | - name: Precompile (with Android) 84 | if: (matrix.os == 'ubuntu-latest') 85 | run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=superlistapp/super_native_extensions --android-sdk-location=/usr/local/lib/android/sdk --android-ndk-version=24.0.8215888 --android-min-sdk-version=23 86 | working-directory: super_native_extensions/cargokit/build_tool 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 89 | PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }} 90 | ``` 91 | 92 | By default the `built_tool precompile-binaries` commands build and uploads the binaries for all targets buildable from current host. This can be overriden using the `--target ` argument. 93 | 94 | Android binaries will be built when `--android-sdk-location` and `--android-ndk-version` arguments are provided. 95 | 96 | -------------------------------------------------------------------------------- /cargokit/gradle/plugin.gradle: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | import org.apache.tools.ant.taskdefs.condition.Os 3 | 4 | CargoKitPlugin.file = buildscript.sourceFile 5 | 6 | apply plugin: CargoKitPlugin 7 | 8 | class CargoKitExtension { 9 | String manifestDir; // Relative path to folder containing Cargo.toml 10 | String libname; // Library name within Cargo.toml. Must be a cdylib 11 | } 12 | 13 | abstract class CargoKitBuildTask extends DefaultTask { 14 | 15 | @Input 16 | String buildMode 17 | 18 | @Input 19 | String buildDir 20 | 21 | @Input 22 | String outputDir 23 | 24 | @Input 25 | String ndkVersion 26 | 27 | @Input 28 | String sdkDirectory 29 | 30 | @Input 31 | int compileSdkVersion; 32 | 33 | @Input 34 | int minSdkVersion; 35 | 36 | @Input 37 | String pluginFile 38 | 39 | @Input 40 | List targetPlatforms 41 | 42 | @TaskAction 43 | def build() { 44 | if (project.cargokit.manifestDir == null) { 45 | throw new GradleException("Property 'manifestDir' must be set on cargokit extension"); 46 | } 47 | 48 | if (project.cargokit.libname == null) { 49 | throw new GradleException("Property 'libname' must be set on cargokit extension"); 50 | } 51 | 52 | def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" 53 | def path = Paths.get(new File(pluginFile).parent, "..", executableName); 54 | 55 | def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) 56 | 57 | def rootProjectDir = project.rootProject.projectDir 58 | 59 | project.exec { 60 | executable path 61 | args "build-gradle" 62 | environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir 63 | environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool" 64 | environment "CARGOKIT_MANIFEST_DIR", manifestDir 65 | environment "CARGOKIT_CONFIGURATION", buildMode 66 | environment "CARGOKIT_TARGET_TEMP_DIR", buildDir 67 | environment "CARGOKIT_OUTPUT_DIR", outputDir 68 | environment "CARGOKIT_NDK_VERSION", ndkVersion 69 | environment "CARGOKIT_SDK_DIR", sdkDirectory 70 | environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion 71 | environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion 72 | environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",") 73 | environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] 74 | } 75 | } 76 | } 77 | 78 | class CargoKitPlugin implements Plugin { 79 | 80 | static String file; 81 | 82 | private Plugin findFlutterPlugin(Project rootProject) { 83 | _findFlutterPlugin(rootProject.childProjects) 84 | } 85 | 86 | private Plugin _findFlutterPlugin(Map projects) { 87 | for (project in projects) { 88 | for (plugin in project.value.getPlugins()) { 89 | if (plugin.class.name == "FlutterPlugin") { 90 | return plugin; 91 | } 92 | } 93 | def plugin = _findFlutterPlugin(project.value.childProjects); 94 | if (plugin != null) { 95 | return plugin; 96 | } 97 | } 98 | return null; 99 | } 100 | 101 | @Override 102 | void apply(Project project) { 103 | def plugin = findFlutterPlugin(project.rootProject); 104 | 105 | project.extensions.create("cargokit", CargoKitExtension) 106 | 107 | if (plugin == null) { 108 | print("Flutter plugin not found, CargoKit plugin will not be applied.") 109 | return; 110 | } 111 | 112 | def cargoBuildDir = "${project.buildDir}/build" 113 | 114 | plugin.project.android.applicationVariants.all { variant -> 115 | 116 | final buildType = variant.buildType.name 117 | 118 | def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; 119 | def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; 120 | jniLibs.srcDir(new File(cargoOutputDir)) 121 | 122 | def platforms = plugin.getTargetPlatforms().collect() 123 | 124 | // Same thing addFlutterDependencies does in flutter.gradle 125 | if (buildType == "debug") { 126 | platforms.add("android-x86") 127 | platforms.add("android-x64") 128 | } 129 | 130 | // The task name depends on plugin properties, which are not available 131 | // at this point 132 | project.getGradle().afterProject { 133 | def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}"; 134 | 135 | if (project.tasks.findByName(taskName)) { 136 | return 137 | } 138 | 139 | if (plugin.project.android.ndkVersion == null) { 140 | throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") 141 | } 142 | 143 | def task = project.tasks.create(taskName, CargoKitBuildTask.class) { 144 | buildMode = variant.buildType.name 145 | buildDir = cargoBuildDir 146 | outputDir = cargoOutputDir 147 | ndkVersion = plugin.project.android.ndkVersion 148 | sdkDirectory = plugin.project.android.sdkDirectory 149 | minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int 150 | compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int 151 | targetPlatforms = platforms 152 | pluginFile = CargoKitPlugin.file 153 | } 154 | def onTask = { newTask -> 155 | if (newTask.name == "merge${buildType.capitalize()}NativeLibs") { 156 | newTask.dependsOn task 157 | // Fix gradle 7.4.2 not picking up JNI library changes 158 | newTask.outputs.upToDateWhen { false } 159 | } 160 | } 161 | project.tasks.each onTask 162 | project.tasks.whenTaskAdded onTask 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /cargokit/run_build_tool.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | setlocal ENABLEDELAYEDEXPANSION 5 | 6 | SET BASEDIR=%~dp0 7 | 8 | if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( 9 | mkdir "%CARGOKIT_TOOL_TEMP_DIR%" 10 | ) 11 | cd /D "%CARGOKIT_TOOL_TEMP_DIR%" 12 | 13 | SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool 14 | SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart 15 | 16 | set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% 17 | 18 | ( 19 | echo name: build_tool_runner 20 | echo version: 1.0.0 21 | echo publish_to: none 22 | echo. 23 | echo environment: 24 | echo sdk: '^>=3.0.0 ^<4.0.0' 25 | echo. 26 | echo dependencies: 27 | echo build_tool: 28 | echo path: %BUILD_TOOL_PKG_DIR_POSIX% 29 | ) >pubspec.yaml 30 | 31 | if not exist bin ( 32 | mkdir bin 33 | ) 34 | 35 | ( 36 | echo import 'package:build_tool/build_tool.dart' as build_tool; 37 | echo void main^(List^ args^) ^{ 38 | echo build_tool.runMain^(args^); 39 | echo ^} 40 | ) >bin\build_tool_runner.dart 41 | 42 | SET PRECOMPILED=bin\build_tool_runner.dill 43 | 44 | REM To detect changes in package we compare output of DIR /s (recursive) 45 | set PREV_PACKAGE_INFO=.dart_tool\package_info.prev 46 | set CUR_PACKAGE_INFO=.dart_tool\package_info.cur 47 | 48 | DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" 49 | 50 | REM Last line in dir output is free space on harddrive. That is bound to 51 | REM change between invocation so we need to remove it 52 | ( 53 | Set "Line=" 54 | For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( 55 | SetLocal EnableDelayedExpansion 56 | If Defined Line Echo !Line! 57 | EndLocal 58 | Set "Line=%%A") 59 | ) >"%CUR_PACKAGE_INFO%" 60 | DEL "%CUR_PACKAGE_INFO%_orig" 61 | 62 | REM Compare current directory listing with previous 63 | FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 64 | 65 | If %ERRORLEVEL% neq 0 ( 66 | REM Changed - copy current to previous and remove precompiled kernel 67 | if exist "%PREV_PACKAGE_INFO%" ( 68 | DEL "%PREV_PACKAGE_INFO%" 69 | ) 70 | MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" 71 | if exist "%PRECOMPILED%" ( 72 | DEL "%PRECOMPILED%" 73 | ) 74 | ) 75 | 76 | REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% 77 | REM which means we need to do pub get and precompile 78 | if not exist "%PRECOMPILED%" ( 79 | echo Running pub get in "%cd%" 80 | "%DART%" pub get --no-precompile 81 | "%DART%" compile kernel bin/build_tool_runner.dart 82 | ) 83 | 84 | "%DART%" "%PRECOMPILED%" %* 85 | 86 | REM 253 means invalid snapshot version. 87 | If %ERRORLEVEL% equ 253 ( 88 | "%DART%" pub get --no-precompile 89 | "%DART%" compile kernel bin/build_tool_runner.dart 90 | "%DART%" "%PRECOMPILED%" %* 91 | ) 92 | -------------------------------------------------------------------------------- /cargokit/run_build_tool.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | BASEDIR=$(dirname "$0") 6 | 7 | mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" 8 | 9 | cd "$CARGOKIT_TOOL_TEMP_DIR" 10 | 11 | # Write a very simple bin package in temp folder that depends on build_tool package 12 | # from Cargokit. This is done to ensure that we don't pollute Cargokit folder 13 | # with .dart_tool contents. 14 | 15 | BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" 16 | 17 | if [[ -z $FLUTTER_ROOT ]]; then # not defined 18 | DART=dart 19 | else 20 | DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" 21 | fi 22 | 23 | cat << EOF > "pubspec.yaml" 24 | name: build_tool_runner 25 | version: 1.0.0 26 | publish_to: none 27 | 28 | environment: 29 | sdk: '>=3.0.0 <4.0.0' 30 | 31 | dependencies: 32 | build_tool: 33 | path: "$BUILD_TOOL_PKG_DIR" 34 | EOF 35 | 36 | mkdir -p "bin" 37 | 38 | cat << EOF > "bin/build_tool_runner.dart" 39 | import 'package:build_tool/build_tool.dart' as build_tool; 40 | void main(List args) { 41 | build_tool.runMain(args); 42 | } 43 | EOF 44 | 45 | # Create alias for `shasum` if it does not exist and `sha1sum` exists 46 | if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then 47 | shopt -s expand_aliases 48 | alias shasum="sha1sum" 49 | fi 50 | 51 | # Dart run will not cache any package that has a path dependency, which 52 | # is the case for our build_tool_runner. So instead we precompile the package 53 | # ourselves. 54 | # To invalidate the cached kernel we use the hash of ls -LR of the build_tool 55 | # package directory. This should be good enough, as the build_tool package 56 | # itself is not meant to have any path dependencies. 57 | 58 | if [[ "$OSTYPE" == "darwin"* ]]; then 59 | PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) 60 | else 61 | PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) 62 | fi 63 | 64 | PACKAGE_HASH_FILE=".package_hash" 65 | 66 | if [ -f "$PACKAGE_HASH_FILE" ]; then 67 | EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") 68 | if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then 69 | rm "$PACKAGE_HASH_FILE" 70 | fi 71 | fi 72 | 73 | # Run pub get if needed. 74 | if [ ! -f "$PACKAGE_HASH_FILE" ]; then 75 | "$DART" pub get --no-precompile 76 | "$DART" compile kernel bin/build_tool_runner.dart 77 | echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" 78 | fi 79 | 80 | set +e 81 | 82 | "$DART" bin/build_tool_runner.dill "$@" 83 | 84 | exit_code=$? 85 | 86 | # 253 means invalid snapshot version. 87 | if [ $exit_code == 253 ]; then 88 | "$DART" pub get --no-precompile 89 | "$DART" compile kernel bin/build_tool_runner.dart 90 | "$DART" bin/build_tool_runner.dill "$@" 91 | exit_code=$? 92 | fi 93 | 94 | exit $exit_code 95 | -------------------------------------------------------------------------------- /check_precompiled.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BASEDIR=$(dirname "$0") 4 | 5 | cd $BASEDIR/cargokit/build_tool 6 | 7 | # Check whether the precompiled binaries ara available for each architecture. 8 | # Note: aaarch64-unknown-linux-gnu binary is missing as there is no 9 | # cross-compilation available currently. 10 | 11 | dart run build_tool verify-binaries --manifest-dir=../../rust -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # ldk_node_example 2 | 3 | Demonstrates how to use the ldk_node plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /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/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 flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "io.ldk.f.ldk_node_example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/io/ldk/f/ldk_node_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.ldk.f.ldk_node_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 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 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 6 | -------------------------------------------------------------------------------- /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/cargokit_options.yaml: -------------------------------------------------------------------------------- 1 | verbose_logging: false 2 | use_precompiled_binaries: true -------------------------------------------------------------------------------- /example/integration_test/bolt11_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:integration_test/integration_test.dart'; 4 | import 'package:ldk_node/ldk_node.dart' as ldk; 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | void main() { 8 | BigInt satsToMsats(int sats) => BigInt.from(sats * 1000); 9 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | group('bolt11_integration', () { 12 | setUp(() async {}); 13 | testWidgets('full_cycle', (WidgetTester tester) async { 14 | final directory = await getApplicationDocumentsDirectory(); 15 | final ldkCache = "${directory.path}/ldk_cache"; 16 | final aliceStoragePath = "$ldkCache/integration/alice"; 17 | debugPrint('Alice Storage Path: $aliceStoragePath'); 18 | final alice_builder = ldk.Builder.mutinynet() 19 | .setEntropyBip39Mnemonic( 20 | mnemonic: ldk.Mnemonic( 21 | seedPhrase: 22 | "replace force spring cruise nothing select glass erupt medal raise consider pull")) 23 | .setStorageDirPath(aliceStoragePath); 24 | final aliceNode = await alice_builder.build(); 25 | await aliceNode.start(); 26 | final bobStoragePath = "$ldkCache/integration/bob"; 27 | final bob_builder = ldk.Builder.mutinynet() 28 | .setEntropyBip39Mnemonic( 29 | mnemonic: ldk.Mnemonic( 30 | seedPhrase: 31 | "skin hospital fee risk health theory actor kiwi solution desert unhappy hello")) 32 | .setStorageDirPath(bobStoragePath); 33 | debugPrint('Bob Storage Path: $bobStoragePath'); 34 | final bobNode = await bob_builder.build(); 35 | await bobNode.start(); 36 | debugPrint("Manually syncing Alice's node"); 37 | await aliceNode.syncWallets(); 38 | debugPrint("Manually syncing Bob's node"); 39 | final aliceBalance = 40 | (await aliceNode.listBalances()).spendableOnchainBalanceSats; 41 | await bobNode.syncWallets(); 42 | expect(aliceBalance, 0); 43 | debugPrint("Alice's onChain balance ${aliceBalance.toString()}"); 44 | final bobBalance = 45 | (await bobNode.listBalances()).spendableOnchainBalanceSats; 46 | debugPrint("Bob's onChain balance ${bobBalance.toString()}"); 47 | 48 | final bobBolt11PaymentHandler = await bobNode.bolt11Payment(); 49 | final bobJitInvoice = await bobBolt11PaymentHandler.receiveViaJitChannel( 50 | amountMsat: satsToMsats(10000), 51 | description: 'test', 52 | expirySecs: 9000, 53 | ); 54 | debugPrint("Bob's onChain balance ${bobBalance.toString()}"); 55 | final aliceBolt11PaymentHandler = await aliceNode.bolt11Payment(); 56 | final aliceJitInvoice = 57 | await aliceBolt11PaymentHandler.receiveViaJitChannel( 58 | amountMsat: satsToMsats(10000), 59 | description: 'test', 60 | expirySecs: 9000, 61 | ); 62 | final aliceLBalance = 63 | (await aliceNode.listBalances()).totalLightningBalanceSats; 64 | debugPrint("Alice's Lightning balance ${aliceLBalance.toString()}"); 65 | final bobInvoice = await bobBolt11PaymentHandler.receive( 66 | amountMsat: satsToMsats(10000), 67 | description: 'test', 68 | expirySecs: 9000); 69 | final paymentId = 70 | await aliceBolt11PaymentHandler.send(invoice: bobInvoice); 71 | debugPrint("Alice's payment id ${paymentId.field0}"); 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /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/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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.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/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - integration_test (0.0.1): 4 | - Flutter 5 | - ldk_node (0.3.0): 6 | - Flutter 7 | - path_provider_foundation (0.0.1): 8 | - Flutter 9 | - FlutterMacOS 10 | 11 | DEPENDENCIES: 12 | - Flutter (from `Flutter`) 13 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 14 | - ldk_node (from `.symlinks/plugins/ldk_node/ios`) 15 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 16 | 17 | EXTERNAL SOURCES: 18 | Flutter: 19 | :path: Flutter 20 | integration_test: 21 | :path: ".symlinks/plugins/integration_test/ios" 22 | ldk_node: 23 | :path: ".symlinks/plugins/ldk_node/ios" 24 | path_provider_foundation: 25 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 26 | 27 | SPEC CHECKSUMS: 28 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 29 | integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 30 | ldk_node: fffac5d5afd55ed30034da12cdaca29f41b3fdd2 31 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 32 | 33 | PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 34 | 35 | COCOAPODS: 1.14.3 36 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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/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/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/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Ldk Node Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ldk_node_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ldk_node_example 2 | description: Demonstrates how to use the ldk_node plugin. 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | 5 | environment: 6 | sdk: ">=2.18.5 <3.0.0" 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | ldk_node: 12 | path: ../ 13 | cupertino_icons: ^1.0.2 14 | path_provider: ^2.0.14 15 | google_fonts: ^4.0.4 16 | dio: ^5.5.0+1 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | integration_test: 21 | sdk: flutter 22 | flutter_driver: 23 | sdk: flutter 24 | flutter_lints: ^2.0.0 25 | flutter: 26 | uses-material-design: true -------------------------------------------------------------------------------- /flutter_rust_bridge.yaml: -------------------------------------------------------------------------------- 1 | rust_input: crate::api 2 | rust_root: rust/ 3 | dart_output: lib/src/generated/ 4 | full_dep: true 5 | web: false 6 | dart3: true 7 | c_output: ios/Classes/frb_generated.h 8 | dart_entrypoint_class_name: core -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Classes/EnforceBundling.swift: -------------------------------------------------------------------------------- 1 | public func dummyMethodToEnforceBundling() -> Int64 { 2 | return dummy_method_to_enforce_bundling() 3 | } 4 | let dummyVar = dummyMethodToEnforceBundling(); 5 | -------------------------------------------------------------------------------- /ios/Classes/ldk_node.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LtbLightning/ldk-node-flutter/254063317e47127fb27bb35d44663d1f81e1f058/ios/Classes/ldk_node.c -------------------------------------------------------------------------------- /ios/ldk_node.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ldk_node' 3 | s.version = '0.3.0' 4 | s.summary = 'A ready-to-go Lightning node library built using LDK and BDK.' 5 | s.homepage = 'https://www.LtbL.io' 6 | s.license = { :file => '../LICENSE' } 7 | s.author = { 'Let there be Lightning, Inc' => 'hello@LtbL.io' } 8 | s.source = { :path => '.' } 9 | s.source_files = 'Classes/**/*' 10 | s.dependency 'Flutter' 11 | s.platform = :ios, '12.0' 12 | s.script_phase = { 13 | :name => 'Build Rust library', 14 | # First argument is relative path to the `rust` folder, second is name of rust library 15 | :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust ldk_node', 16 | :execution_position => :before_compile, 17 | :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], 18 | # Let XCode know that the static library referenced in -force_load below is 19 | # created by this build step. 20 | :output_files => ["${BUILT_PRODUCTS_DIR}/libldk_node.a"], 21 | } 22 | s.pod_target_xcconfig = { 23 | 'DEFINES_MODULE' => 'YES', 24 | # Flutter.framework does not contain a i386 slice. 25 | 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 26 | 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libldk_node.a', 27 | } 28 | end 29 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | lint_code: 4 | glob: '*.dart' 5 | run: dart fix lib --apply && git add . 6 | format_code: 7 | glob: '*.dart' 8 | run: dart format {staged_files} && git add {staged_files} 9 | 10 | pre-push: 11 | parallel: true 12 | commands: 13 | static_code_analysis: 14 | run: flutter analyze -------------------------------------------------------------------------------- /lib/ldk_node.dart: -------------------------------------------------------------------------------- 1 | export './src/generated/api/bolt11.dart'; 2 | export './src/generated/api/bolt12.dart'; 3 | export './src/generated/api/on_chain.dart'; 4 | export './src/generated/api/spontaneous.dart'; 5 | export './src/generated/api/types.dart' 6 | hide 7 | Event_ChannelClosed, 8 | Event_ChannelReady, 9 | Event_PaymentFailed, 10 | Event_PaymentReceived, 11 | Event_PaymentSuccessful, 12 | Event_ChannelPending, 13 | MaxDustHTLCExposure, 14 | SocketAddress_Hostname, 15 | SocketAddress_OnionV2, 16 | EntropySourceConfig, 17 | SocketAddress_OnionV3, 18 | $ChainDataSourceConfigCopyWith, 19 | SocketAddress_TcpIpV4, 20 | SocketAddress_TcpIpV6, 21 | MaxDustHTLCExposure_FeeRateMultiplier, 22 | MaxDustHTLCExposure_FixedLimitMsat, 23 | EntropySourceConfig_SeedBytes, 24 | EntropySourceConfig_Bip39Mnemonic, 25 | ChainDataSourceConfig_Esplora, 26 | GossipSourceConfig_P2PNetwork, 27 | GossipSourceConfig_RapidGossipSync, 28 | ChainDataSourceConfig, 29 | GossipSourceConfig, 30 | EntropySourceConfig_SeedFile; 31 | export 'src/root.dart'; 32 | export 'src/generated/lib.dart' show U8Array4, U8Array12, U8Array64, U8Array32; 33 | export 'src/utils/utils.dart' 34 | hide ExceptionBase, mapLdkBuilderError, mapLdkNodeError, Frb; 35 | -------------------------------------------------------------------------------- /lib/src/generated/api/bolt11.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import '../lib.dart'; 8 | import '../utils/error.dart'; 9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 10 | import 'types.dart'; 11 | 12 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `clone`, `eq`, `fmt`, `from`, `from`, `try_from` 13 | 14 | ///Represents a syntactically and semantically correct lightning BOLT11 invoice. 15 | /// 16 | class Bolt11Invoice { 17 | final String signedRawInvoice; 18 | 19 | const Bolt11Invoice({ 20 | required this.signedRawInvoice, 21 | }); 22 | 23 | @override 24 | int get hashCode => signedRawInvoice.hashCode; 25 | 26 | @override 27 | bool operator ==(Object other) => 28 | identical(this, other) || 29 | other is Bolt11Invoice && 30 | runtimeType == other.runtimeType && 31 | signedRawInvoice == other.signedRawInvoice; 32 | } 33 | 34 | class LdkBolt11Payment { 35 | final Bolt11Payment ptr; 36 | 37 | const LdkBolt11Payment({ 38 | required this.ptr, 39 | }); 40 | 41 | Future claimForHash( 42 | {required PaymentHash paymentHash, 43 | required BigInt claimableAmountMsat, 44 | required PaymentPreimage preimage}) => 45 | core.instance.api.crateApiBolt11LdkBolt11PaymentClaimForHash( 46 | that: this, 47 | paymentHash: paymentHash, 48 | claimableAmountMsat: claimableAmountMsat, 49 | preimage: preimage); 50 | 51 | Future failForHash({required PaymentHash paymentHash}) => 52 | core.instance.api.crateApiBolt11LdkBolt11PaymentFailForHash( 53 | that: this, paymentHash: paymentHash); 54 | 55 | Future receive( 56 | {required BigInt amountMsat, 57 | required String description, 58 | required int expirySecs}) => 59 | core.instance.api.crateApiBolt11LdkBolt11PaymentReceive( 60 | that: this, 61 | amountMsat: amountMsat, 62 | description: description, 63 | expirySecs: expirySecs); 64 | 65 | Future receiveForHash( 66 | {required PaymentHash paymentHash, 67 | required BigInt amountMsat, 68 | required String description, 69 | required int expirySecs}) => 70 | core.instance.api.crateApiBolt11LdkBolt11PaymentReceiveForHash( 71 | that: this, 72 | paymentHash: paymentHash, 73 | amountMsat: amountMsat, 74 | description: description, 75 | expirySecs: expirySecs); 76 | 77 | Future receiveVariableAmount( 78 | {required String description, required int expirySecs}) => 79 | core.instance.api.crateApiBolt11LdkBolt11PaymentReceiveVariableAmount( 80 | that: this, description: description, expirySecs: expirySecs); 81 | 82 | Future receiveVariableAmountForHash( 83 | {required String description, 84 | required int expirySecs, 85 | required PaymentHash paymentHash}) => 86 | core.instance.api 87 | .crateApiBolt11LdkBolt11PaymentReceiveVariableAmountForHash( 88 | that: this, 89 | description: description, 90 | expirySecs: expirySecs, 91 | paymentHash: paymentHash); 92 | 93 | Future receiveVariableAmountViaJitChannel( 94 | {required String description, 95 | required int expirySecs, 96 | BigInt? maxProportionalLspFeeLimitPpmMsat}) => 97 | core.instance.api 98 | .crateApiBolt11LdkBolt11PaymentReceiveVariableAmountViaJitChannel( 99 | that: this, 100 | description: description, 101 | expirySecs: expirySecs, 102 | maxProportionalLspFeeLimitPpmMsat: 103 | maxProportionalLspFeeLimitPpmMsat); 104 | 105 | Future receiveViaJitChannel( 106 | {required BigInt amountMsat, 107 | required String description, 108 | required int expirySecs, 109 | BigInt? maxTotalLspFeeLimitMsat}) => 110 | core.instance.api.crateApiBolt11LdkBolt11PaymentReceiveViaJitChannel( 111 | that: this, 112 | amountMsat: amountMsat, 113 | description: description, 114 | expirySecs: expirySecs, 115 | maxTotalLspFeeLimitMsat: maxTotalLspFeeLimitMsat); 116 | 117 | Future send({required Bolt11Invoice invoice}) => core.instance.api 118 | .crateApiBolt11LdkBolt11PaymentSend(that: this, invoice: invoice); 119 | 120 | Future sendProbes({required Bolt11Invoice invoice}) => core.instance.api 121 | .crateApiBolt11LdkBolt11PaymentSendProbes(that: this, invoice: invoice); 122 | 123 | Future sendProbesUsingAmount( 124 | {required Bolt11Invoice invoice, required BigInt amountMsat}) => 125 | core.instance.api.crateApiBolt11LdkBolt11PaymentSendProbesUsingAmount( 126 | that: this, invoice: invoice, amountMsat: amountMsat); 127 | 128 | Future sendUsingAmount( 129 | {required Bolt11Invoice invoice, required BigInt amountMsat}) => 130 | core.instance.api.crateApiBolt11LdkBolt11PaymentSendUsingAmount( 131 | that: this, invoice: invoice, amountMsat: amountMsat); 132 | 133 | @override 134 | int get hashCode => ptr.hashCode; 135 | 136 | @override 137 | bool operator ==(Object other) => 138 | identical(this, other) || 139 | other is LdkBolt11Payment && 140 | runtimeType == other.runtimeType && 141 | ptr == other.ptr; 142 | } 143 | -------------------------------------------------------------------------------- /lib/src/generated/api/bolt12.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import '../lib.dart'; 8 | import '../utils/error.dart'; 9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 10 | import 'types.dart'; 11 | 12 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `clone`, `eq`, `fmt`, `from`, `from`, `from`, `try_from`, `try_from`, `try_from` 13 | 14 | // Rust type: RustOpaqueNom> 15 | abstract class ArcBolt12Payment implements RustOpaqueInterface {} 16 | 17 | ///A Bolt12Invoice is a payment request, typically corresponding to an Offer or a Refund. 18 | class Bolt12Invoice { 19 | final Uint8List data; 20 | 21 | const Bolt12Invoice({ 22 | required this.data, 23 | }); 24 | 25 | @override 26 | int get hashCode => data.hashCode; 27 | 28 | @override 29 | bool operator ==(Object other) => 30 | identical(this, other) || 31 | other is Bolt12Invoice && 32 | runtimeType == other.runtimeType && 33 | data == other.data; 34 | } 35 | 36 | class LdkBolt12Payment { 37 | final ArcBolt12Payment ptr; 38 | 39 | const LdkBolt12Payment({ 40 | required this.ptr, 41 | }); 42 | 43 | Future initiateRefund( 44 | {required BigInt amountMsat, required int expirySecs}) => 45 | core.instance.api.crateApiBolt12LdkBolt12PaymentInitiateRefund( 46 | that: this, amountMsat: amountMsat, expirySecs: expirySecs); 47 | 48 | Future receive( 49 | {required BigInt amountMsat, required String description}) => 50 | core.instance.api.crateApiBolt12LdkBolt12PaymentReceive( 51 | that: this, amountMsat: amountMsat, description: description); 52 | 53 | Future receiveVariableAmount({required String description}) => 54 | core.instance.api.crateApiBolt12LdkBolt12PaymentReceiveVariableAmount( 55 | that: this, description: description); 56 | 57 | Future requestRefundPayment({required Refund refund}) => 58 | core.instance.api.crateApiBolt12LdkBolt12PaymentRequestRefundPayment( 59 | that: this, refund: refund); 60 | 61 | Future send({required Offer offer, String? payerNote}) => 62 | core.instance.api.crateApiBolt12LdkBolt12PaymentSend( 63 | that: this, offer: offer, payerNote: payerNote); 64 | 65 | Future sendUsingAmount( 66 | {required Offer offer, 67 | String? payerNote, 68 | required BigInt amountMsat}) => 69 | core.instance.api.crateApiBolt12LdkBolt12PaymentSendUsingAmount( 70 | that: this, 71 | offer: offer, 72 | payerNote: payerNote, 73 | amountMsat: amountMsat); 74 | 75 | @override 76 | int get hashCode => ptr.hashCode; 77 | 78 | @override 79 | bool operator ==(Object other) => 80 | identical(this, other) || 81 | other is LdkBolt12Payment && 82 | runtimeType == other.runtimeType && 83 | ptr == other.ptr; 84 | } 85 | 86 | /// An `Offer` is a potentially long-lived proposal for payment of a good or service. 87 | /// 88 | /// An offer is a precursor to an [InvoiceRequest]. A merchant publishes an offer from which a 89 | /// customer may request an [Bolt12Invoice] for a specific quantity and using an amount sufficient 90 | /// to cover that quantity (i.e., at least `quantity * amount`). 91 | /// 92 | /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the 93 | /// latter. 94 | /// 95 | class Offer { 96 | final String s; 97 | 98 | const Offer({ 99 | required this.s, 100 | }); 101 | 102 | @override 103 | int get hashCode => s.hashCode; 104 | 105 | @override 106 | bool operator ==(Object other) => 107 | identical(this, other) || 108 | other is Offer && runtimeType == other.runtimeType && s == other.s; 109 | } 110 | 111 | ///A Refund is a request to send an `Bolt12Invoice` without a preceding `Offer`. 112 | class Refund { 113 | final String s; 114 | 115 | const Refund({ 116 | required this.s, 117 | }); 118 | 119 | @override 120 | int get hashCode => s.hashCode; 121 | 122 | @override 123 | bool operator ==(Object other) => 124 | identical(this, other) || 125 | other is Refund && runtimeType == other.runtimeType && s == other.s; 126 | } 127 | -------------------------------------------------------------------------------- /lib/src/generated/api/builder.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import '../lib.dart'; 8 | import '../utils/error.dart'; 9 | import 'node.dart'; 10 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 11 | import 'types.dart'; 12 | 13 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`, `from`, `try_from` 14 | 15 | // Rust type: RustOpaqueNom> 16 | abstract class NodeBuilder implements RustOpaqueInterface { 17 | Builder get builder; 18 | 19 | void set builder(Builder builder); 20 | 21 | Future build(); 22 | 23 | Future buildWithFsStore(); 24 | 25 | static Future createBuilder( 26 | {required Config config, 27 | ChainDataSourceConfig? chainDataSourceConfig, 28 | EntropySourceConfig? entropySourceConfig, 29 | GossipSourceConfig? gossipSourceConfig, 30 | LiquiditySourceConfig? liquiditySourceConfig}) => 31 | core.instance.api.crateApiBuilderNodeBuilderCreateBuilder( 32 | config: config, 33 | chainDataSourceConfig: chainDataSourceConfig, 34 | entropySourceConfig: entropySourceConfig, 35 | gossipSourceConfig: gossipSourceConfig, 36 | liquiditySourceConfig: liquiditySourceConfig); 37 | } 38 | 39 | // Rust type: RustOpaqueNom 40 | abstract class Builder implements RustOpaqueInterface {} 41 | 42 | class LdkMnemonic { 43 | final String seedPhrase; 44 | 45 | const LdkMnemonic({ 46 | required this.seedPhrase, 47 | }); 48 | 49 | static Future generate() => 50 | core.instance.api.crateApiBuilderLdkMnemonicGenerate(); 51 | 52 | @override 53 | int get hashCode => seedPhrase.hashCode; 54 | 55 | @override 56 | bool operator ==(Object other) => 57 | identical(this, other) || 58 | other is LdkMnemonic && 59 | runtimeType == other.runtimeType && 60 | seedPhrase == other.seedPhrase; 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/generated/api/on_chain.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import '../lib.dart'; 8 | import '../utils/error.dart'; 9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 10 | import 'types.dart'; 11 | 12 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `from` 13 | 14 | class LdkOnChainPayment { 15 | final OnchainPayment ptr; 16 | 17 | const LdkOnChainPayment({ 18 | required this.ptr, 19 | }); 20 | 21 | Future
newAddress() => 22 | core.instance.api.crateApiOnChainLdkOnChainPaymentNewAddress( 23 | that: this, 24 | ); 25 | 26 | Future sendAllToAddress({required Address address}) => 27 | core.instance.api.crateApiOnChainLdkOnChainPaymentSendAllToAddress( 28 | that: this, address: address); 29 | 30 | Future sendToAddress( 31 | {required Address address, required BigInt amountSats}) => 32 | core.instance.api.crateApiOnChainLdkOnChainPaymentSendToAddress( 33 | that: this, address: address, amountSats: amountSats); 34 | 35 | @override 36 | int get hashCode => ptr.hashCode; 37 | 38 | @override 39 | bool operator ==(Object other) => 40 | identical(this, other) || 41 | other is LdkOnChainPayment && 42 | runtimeType == other.runtimeType && 43 | ptr == other.ptr; 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/generated/api/spontaneous.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import '../lib.dart'; 8 | import '../utils/error.dart'; 9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 10 | import 'types.dart'; 11 | 12 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `from` 13 | 14 | class LdkSpontaneousPayment { 15 | final SpontaneousPayment ptr; 16 | 17 | const LdkSpontaneousPayment({ 18 | required this.ptr, 19 | }); 20 | 21 | Future send( 22 | {required BigInt amountMsat, required PublicKey nodeId}) => 23 | core.instance.api.crateApiSpontaneousLdkSpontaneousPaymentSend( 24 | that: this, amountMsat: amountMsat, nodeId: nodeId); 25 | 26 | Future sendProbes( 27 | {required BigInt amountMsat, required PublicKey nodeId}) => 28 | core.instance.api.crateApiSpontaneousLdkSpontaneousPaymentSendProbes( 29 | that: this, amountMsat: amountMsat, nodeId: nodeId); 30 | 31 | @override 32 | int get hashCode => ptr.hashCode; 33 | 34 | @override 35 | bool operator ==(Object other) => 36 | identical(this, other) || 37 | other is LdkSpontaneousPayment && 38 | runtimeType == other.runtimeType && 39 | ptr == other.ptr; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/generated/lib.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import 'frb_generated.dart'; 7 | import 'package:collection/collection.dart'; 8 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 9 | 10 | // Rust type: RustOpaqueNom 11 | abstract class NetworkGraph implements RustOpaqueInterface {} 12 | 13 | // Rust type: RustOpaqueNom 14 | abstract class Bolt11Payment implements RustOpaqueInterface {} 15 | 16 | // Rust type: RustOpaqueNom 17 | abstract class OnchainPayment implements RustOpaqueInterface {} 18 | 19 | // Rust type: RustOpaqueNom 20 | abstract class SpontaneousPayment implements RustOpaqueInterface {} 21 | 22 | class U8Array12 extends NonGrowableListView { 23 | static const arraySize = 12; 24 | 25 | @internal 26 | Uint8List get inner => _inner; 27 | final Uint8List _inner; 28 | 29 | U8Array12(this._inner) 30 | : assert(_inner.length == arraySize), 31 | super(_inner); 32 | 33 | U8Array12.init() : this(Uint8List(arraySize)); 34 | } 35 | 36 | class U8Array16 extends NonGrowableListView { 37 | static const arraySize = 16; 38 | 39 | @internal 40 | Uint8List get inner => _inner; 41 | final Uint8List _inner; 42 | 43 | U8Array16(this._inner) 44 | : assert(_inner.length == arraySize), 45 | super(_inner); 46 | 47 | U8Array16.init() : this(Uint8List(arraySize)); 48 | } 49 | 50 | class U8Array32 extends NonGrowableListView { 51 | static const arraySize = 32; 52 | 53 | @internal 54 | Uint8List get inner => _inner; 55 | final Uint8List _inner; 56 | 57 | U8Array32(this._inner) 58 | : assert(_inner.length == arraySize), 59 | super(_inner); 60 | 61 | U8Array32.init() : this(Uint8List(arraySize)); 62 | } 63 | 64 | class U8Array4 extends NonGrowableListView { 65 | static const arraySize = 4; 66 | 67 | @internal 68 | Uint8List get inner => _inner; 69 | final Uint8List _inner; 70 | 71 | U8Array4(this._inner) 72 | : assert(_inner.length == arraySize), 73 | super(_inner); 74 | 75 | U8Array4.init() : this(Uint8List(arraySize)); 76 | } 77 | 78 | class U8Array64 extends NonGrowableListView { 79 | static const arraySize = 64; 80 | 81 | @internal 82 | Uint8List get inner => _inner; 83 | final Uint8List _inner; 84 | 85 | U8Array64(this._inner) 86 | : assert(_inner.length == arraySize), 87 | super(_inner); 88 | 89 | U8Array64.init() : this(Uint8List(arraySize)); 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/utils/default_services.dart: -------------------------------------------------------------------------------- 1 | class DefaultServicesTestnet { 2 | static const String esploraServerUrl = 'https://testnet.ltbl.io/api'; 3 | static const String rgsServerUrl = 'https://testnet.ltbl.io/snapshot'; 4 | } 5 | 6 | class DefaultServicesMutinynet { 7 | static const String esploraServerUrl = 'https://mutinynet.ltbl.io/api'; 8 | static const String rgsServerUrl = 'https://mutinynet.ltbl.io/snapshot'; 9 | static const String lsps2SourceAddress = '44.219.111.31'; 10 | static const int lsps2SourcePort = 39735; 11 | static const String lsps2SourcePublicKey = 12 | '0371d6fd7d75de2d0372d03ea00e8bacdacb50c27d0eaea0a76a0622eff1f5ef2b'; 13 | static const String lsps2SourceToken = 'JZWN9YLW'; 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import '../generated/frb_generated.dart'; 4 | 5 | export 'exceptions.dart'; 6 | 7 | class Frb { 8 | static Future verifyInit() async { 9 | try { 10 | if (!core.instance.initialized) { 11 | await core.init(); 12 | } 13 | } catch (e) { 14 | debugPrint(e.toString()); 15 | throw Exception("Failed to initialize ldk-node"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | PROJECTNAME=$(shell basename "$(PWD)") 3 | 4 | .PHONY: help 5 | help: makefile 6 | @echo 7 | @echo " Available actions in "$(PROJECTNAME)":" 8 | @echo 9 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' 10 | @echo 11 | 12 | ## init: Install missing dependencies. 13 | init: 14 | cargo install flutter_rust_bridge_codegen --version 2.0.0 15 | ## : 16 | 17 | all: init generate-bindings 18 | 19 | generate-bindings: 20 | @echo "[GENERATING FRB CODE] $@" 21 | flutter_rust_bridge_codegen generate 22 | @echo "[Done ✅]" 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ldk_node 2 | description: A ready-to-go Lightning node library built using LDK and BDK. 3 | version: 0.3.0 4 | homepage: https://github.com/LtbLightning/ldk-node-flutter 5 | 6 | environment: 7 | sdk: ">=3.1.0 <4.0.0" 8 | flutter: ">=2.5.0" 9 | 10 | dependencies: 11 | collection: ^1.18.0 12 | ffi: ^2.1.0 13 | flutter: 14 | sdk: flutter 15 | flutter_rust_bridge: ^2.0.0 16 | freezed_annotation: ^2.2.0 17 | meta: ^1.12.0 18 | uuid: ^4.3.3 19 | path_provider: ^2.1.2 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | ffigen: ^12.0.0 24 | freezed: ^2.5.2 25 | build_runner: ^2.4.8 26 | lints: ^4.0.0 27 | 28 | flutter: 29 | plugin: 30 | platforms: 31 | android: 32 | ffiPlugin: true 33 | ios: 34 | ffiPlugin: true 35 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ldk_node" 3 | version = "0.3.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["staticlib", "cdylib"] 8 | 9 | 10 | [build-dependencies] 11 | anyhow = "1.0.68" 12 | [dependencies] 13 | flutter_rust_bridge = "=2.0.0" 14 | anyhow = { version = "1.0.71"} 15 | ldk-node = { version = "= 0.3.0" } 16 | # ldk-node = {git = 'https://github.com/lightningdevkit/ldk-node.git', rev = "246775d04dbb2e99528a6a1aa0bc04ad7378e900"} 17 | 18 | 19 | [profile.release] 20 | opt-level = 3 # Optimize for size. 21 | lto = true # Enable Link Time Optimization 22 | codegen-units = 1 # Reduce number of codegen units to increase optimizations. 23 | panic = 'abort' 24 | 25 | -------------------------------------------------------------------------------- /rust/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 5 | the Software, and to permit persons to whom the Software is furnished to do so, 6 | subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /rust/LICENSE.md: -------------------------------------------------------------------------------- 1 | This software is licensed under [Apache 2.0](LICENSE-APACHE) or 2 | [MIT](LICENSE-MIT), at your option. 3 | 4 | Some files retain their own copyright notice, however, for full authorship 5 | information, see version control history. 6 | 7 | Except as otherwise noted in individual files, all files in this repository are 8 | licensed under the Apache License, Version 2.0 or the MIT license , at your option. 11 | 12 | You may not use, copy, modify, merge, publish, distribute, sublicense, and/or 13 | sell copies of this software or any files in this repository except in 14 | accordance with one or both of these licenses. 15 | -------------------------------------------------------------------------------- /rust/cargokit.yaml: -------------------------------------------------------------------------------- 1 | cargo: 2 | release: 3 | toolchain: stable 4 | 5 | precompiled_binaries: 6 | url_prefix: https://github.com/LtbLightning/ldk-node-flutter/releases/download/precompiled_ 7 | public_key: 0e43d5e8452d00db7f3000c18fb1ba796babfcb5dc6306bb0629eff24f8be85b 8 | -------------------------------------------------------------------------------- /rust/src/api/bolt11.rs: -------------------------------------------------------------------------------- 1 | use crate::api::types::{PaymentHash, PaymentId, PaymentPreimage}; 2 | use crate::frb_generated::RustOpaque; 3 | use crate::utils::error::LdkNodeError; 4 | use std::str::FromStr; 5 | 6 | pub struct LdkBolt11Payment { 7 | pub ptr: RustOpaque, 8 | } 9 | impl From for LdkBolt11Payment { 10 | fn from(value: ldk_node::payment::Bolt11Payment) -> Self { 11 | LdkBolt11Payment { 12 | ptr: RustOpaque::new(value), 13 | } 14 | } 15 | } 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | ///Represents a syntactically and semantically correct lightning BOLT11 invoice. 18 | /// 19 | pub struct Bolt11Invoice { 20 | pub signed_raw_invoice: String, 21 | } 22 | 23 | impl TryFrom for ldk_node::lightning_invoice::Bolt11Invoice { 24 | type Error = LdkNodeError; 25 | 26 | fn try_from(value: Bolt11Invoice) -> Result { 27 | ldk_node::lightning_invoice::Bolt11Invoice::from_str(value.signed_raw_invoice.as_str()) 28 | .map_err(|_| LdkNodeError::InvalidInvoice) 29 | } 30 | } 31 | impl From for Bolt11Invoice { 32 | fn from(value: ldk_node::lightning_invoice::Bolt11Invoice) -> Self { 33 | Bolt11Invoice { 34 | signed_raw_invoice: value.to_string(), 35 | } 36 | } 37 | } 38 | 39 | impl LdkBolt11Payment { 40 | pub fn send(&self, invoice: Bolt11Invoice) -> Result { 41 | self.ptr 42 | .send(&(invoice.try_into()?)) 43 | .map_err(|e| e.into()) 44 | .map(|e| e.into()) 45 | } 46 | pub fn send_using_amount( 47 | &self, 48 | invoice: Bolt11Invoice, 49 | amount_msat: u64, 50 | ) -> anyhow::Result { 51 | self.ptr 52 | .send_using_amount(&(invoice.try_into()?), amount_msat) 53 | .map_err(|e| e.into()) 54 | .map(|e| e.into()) 55 | } 56 | 57 | pub fn send_probes(&self, invoice: Bolt11Invoice) -> anyhow::Result<(), LdkNodeError> { 58 | self.ptr 59 | .send_probes(&(invoice.try_into()?)) 60 | .map_err(|e| e.into()) 61 | } 62 | 63 | pub fn send_probes_using_amount( 64 | &self, 65 | invoice: Bolt11Invoice, 66 | amount_msat: u64, 67 | ) -> Result<(), LdkNodeError> { 68 | self.ptr 69 | .send_probes_using_amount(&(invoice.try_into()?), amount_msat) 70 | .map_err(|e| e.into()) 71 | } 72 | pub fn claim_for_hash( 73 | &self, 74 | payment_hash: PaymentHash, 75 | claimable_amount_msat: u64, 76 | preimage: PaymentPreimage, 77 | ) -> anyhow::Result<(), LdkNodeError> { 78 | self.ptr 79 | .claim_for_hash(payment_hash.into(), claimable_amount_msat, preimage.into()) 80 | .map_err(|e| e.into()) 81 | } 82 | pub fn fail_for_hash(&self, payment_hash: PaymentHash) -> anyhow::Result<(), LdkNodeError> { 83 | self.ptr 84 | .fail_for_hash(payment_hash.into()) 85 | .map_err(|e| e.into()) 86 | } 87 | pub fn receive( 88 | &self, 89 | amount_msat: u64, 90 | description: String, 91 | expiry_secs: u32, 92 | ) -> anyhow::Result { 93 | self.ptr 94 | .receive(amount_msat, description.as_str(), expiry_secs) 95 | .map_err(|e| e.into()) 96 | .map(|e| e.into()) 97 | } 98 | 99 | pub fn receive_for_hash( 100 | &self, 101 | payment_hash: PaymentHash, 102 | amount_msat: u64, 103 | description: String, 104 | expiry_secs: u32, 105 | ) -> anyhow::Result { 106 | self.ptr 107 | .receive_for_hash( 108 | amount_msat, 109 | description.as_str(), 110 | expiry_secs, 111 | payment_hash.into(), 112 | ) 113 | .map_err(|e| e.into()) 114 | .map(|e| e.into()) 115 | } 116 | pub fn receive_variable_amount( 117 | &self, 118 | description: String, 119 | expiry_secs: u32, 120 | ) -> anyhow::Result { 121 | self.ptr 122 | .receive_variable_amount(description.as_str(), expiry_secs) 123 | .map_err(|e| e.into()) 124 | .map(|e| e.into()) 125 | } 126 | pub fn receive_variable_amount_via_jit_channel( 127 | &self, 128 | description: String, 129 | expiry_secs: u32, 130 | max_proportional_lsp_fee_limit_ppm_msat: Option, 131 | ) -> anyhow::Result { 132 | match self.ptr.receive_variable_amount_via_jit_channel( 133 | description.as_str(), 134 | expiry_secs, 135 | max_proportional_lsp_fee_limit_ppm_msat, 136 | ) { 137 | Ok(e) => Ok(e.into()), 138 | Err(e) => Err(e.into()), 139 | } 140 | } 141 | 142 | pub fn receive_variable_amount_for_hash( 143 | &self, 144 | description: String, 145 | expiry_secs: u32, 146 | payment_hash: PaymentHash 147 | ) -> anyhow::Result { 148 | match self.ptr.receive_variable_amount_for_hash( 149 | description.as_str(), 150 | expiry_secs, 151 | payment_hash.into() 152 | ) { 153 | Ok(e) => Ok(e.into()), 154 | Err(e) => Err(e.into()), 155 | } 156 | } 157 | 158 | pub fn receive_via_jit_channel( 159 | &self, 160 | amount_msat: u64, 161 | description: String, 162 | expiry_secs: u32, 163 | max_total_lsp_fee_limit_msat: Option, 164 | ) -> anyhow::Result { 165 | match self.ptr.receive_via_jit_channel( 166 | amount_msat, 167 | description.as_str(), 168 | expiry_secs, 169 | max_total_lsp_fee_limit_msat, 170 | ) { 171 | Ok(e) => Ok(e.into()), 172 | Err(e) => Err(e.into()), 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /rust/src/api/bolt12.rs: -------------------------------------------------------------------------------- 1 | use crate::api::types::PaymentId; 2 | use ldk_node::lightning::util::ser::Writeable; 3 | use std::str::FromStr; 4 | use std::sync::Arc; 5 | 6 | use crate::frb_generated::RustOpaque; 7 | use crate::utils::error::LdkNodeError; 8 | /// An `Offer` is a potentially long-lived proposal for payment of a good or service. 9 | /// 10 | /// An offer is a precursor to an [InvoiceRequest]. A merchant publishes an offer from which a 11 | /// customer may request an [Bolt12Invoice] for a specific quantity and using an amount sufficient 12 | /// to cover that quantity (i.e., at least `quantity * amount`). 13 | /// 14 | /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the 15 | /// latter. 16 | /// 17 | 18 | pub struct Offer { 19 | pub s: String, 20 | } 21 | impl TryFrom for ldk_node::lightning::offers::offer::Offer { 22 | type Error = LdkNodeError; 23 | 24 | fn try_from(value: Offer) -> Result { 25 | ldk_node::lightning::offers::offer::Offer::from_str(value.s.as_str()).map_err(|e| e.into()) 26 | } 27 | } 28 | impl From for Offer { 29 | fn from(value: ldk_node::lightning::offers::offer::Offer) -> Self { 30 | Offer { 31 | s: value.to_string(), 32 | } 33 | } 34 | } 35 | ///A Refund is a request to send an `Bolt12Invoice` without a preceding `Offer`. 36 | // 37 | // Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to recoup their funds. 38 | // A refund may be used more generally as an “offer for money”, such as with a bitcoin ATM. 39 | 40 | pub struct Refund { 41 | pub s: String, 42 | } 43 | 44 | impl TryFrom for ldk_node::lightning::offers::refund::Refund { 45 | type Error = LdkNodeError; 46 | 47 | fn try_from(value: Refund) -> Result { 48 | ldk_node::lightning::offers::refund::Refund::from_str(value.s.as_str()) 49 | .map_err(|e| e.into()) 50 | } 51 | } 52 | impl From for Refund { 53 | fn from(value: ldk_node::lightning::offers::refund::Refund) -> Self { 54 | Refund { 55 | s: value.to_string(), 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug, Clone, PartialEq, Eq)] 61 | ///A Bolt12Invoice is a payment request, typically corresponding to an Offer or a Refund. 62 | // An invoice may be sent in response to an InvoiceRequest in the case of an offer or sent directly after scanning a refund. It includes all the information needed to pay a recipient. 63 | pub struct Bolt12Invoice { 64 | pub data: Vec, 65 | } 66 | 67 | impl TryFrom for ldk_node::lightning::offers::invoice::Bolt12Invoice { 68 | type Error = LdkNodeError; 69 | 70 | fn try_from(value: Bolt12Invoice) -> Result { 71 | ldk_node::lightning::offers::invoice::Bolt12Invoice::try_from(value.data) 72 | .map_err(|_| LdkNodeError::InvalidInvoice) 73 | } 74 | } 75 | impl From for Bolt12Invoice { 76 | fn from(value: ldk_node::lightning::offers::invoice::Bolt12Invoice) -> Self { 77 | Bolt12Invoice { 78 | data: value.encode(), 79 | } 80 | } 81 | } 82 | pub struct LdkBolt12Payment { 83 | pub ptr: RustOpaque>, 84 | } 85 | impl LdkBolt12Payment { 86 | pub fn send( 87 | &self, 88 | offer: Offer, 89 | payer_note: Option, 90 | ) -> Result { 91 | self.ptr 92 | .send(&(offer.try_into()?), payer_note) 93 | .map_err(|e| e.into()) 94 | .map(|e| e.into()) 95 | } 96 | pub fn send_using_amount( 97 | &self, 98 | offer: Offer, 99 | payer_note: Option, 100 | amount_msat: u64, 101 | ) -> anyhow::Result { 102 | self.ptr 103 | .send_using_amount(&(offer.try_into()?), payer_note, amount_msat) 104 | .map_err(|e| e.into()) 105 | .map(|e| e.into()) 106 | } 107 | pub fn receive( 108 | &self, 109 | amount_msat: u64, 110 | description: String, 111 | ) -> anyhow::Result { 112 | self.ptr 113 | .receive(amount_msat, description.as_str()) 114 | .map_err(|e| e.into()) 115 | .map(|e| e.into()) 116 | } 117 | pub fn receive_variable_amount( 118 | &self, 119 | description: String, 120 | ) -> anyhow::Result { 121 | self.ptr 122 | .receive_variable_amount(description.as_str()) 123 | .map_err(|e| e.into()) 124 | .map(|e| e.into()) 125 | } 126 | 127 | pub fn request_refund_payment( 128 | &self, 129 | refund: Refund, 130 | ) -> anyhow::Result { 131 | self.ptr 132 | .request_refund_payment(&(refund.try_into()?)) 133 | .map_err(|e| e.into()) 134 | .map(|e| e.into()) 135 | } 136 | 137 | pub fn initiate_refund( 138 | &self, 139 | amount_msat: u64, 140 | expiry_secs: u32, 141 | ) -> anyhow::Result { 142 | self.ptr 143 | .initiate_refund(amount_msat, expiry_secs) 144 | .map_err(|e| e.into()) 145 | .map(|e| e.into()) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /rust/src/api/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::api::node::LdkNode; 2 | use crate::api::types::{ 3 | ChainDataSourceConfig, Config, EntropySourceConfig, GossipSourceConfig, LiquiditySourceConfig, 4 | }; 5 | use crate::frb_generated::RustOpaque; 6 | use crate::utils::error::LdkBuilderError; 7 | use flutter_rust_bridge::frb; 8 | use ldk_node::lightning::util::ser::Writeable; 9 | use std::str::FromStr; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct LdkMnemonic { 13 | pub seed_phrase: String, 14 | } 15 | 16 | impl TryFrom for ldk_node::bip39::Mnemonic { 17 | type Error = LdkBuilderError; 18 | 19 | fn try_from(value: LdkMnemonic) -> Result { 20 | ldk_node::bip39::Mnemonic::from_str(&value.seed_phrase).map_err(|e| e.into()) 21 | } 22 | } 23 | impl From for LdkMnemonic { 24 | fn from(value: ldk_node::bip39::Mnemonic) -> Self { 25 | LdkMnemonic { 26 | seed_phrase: value.to_string(), 27 | } 28 | } 29 | } 30 | impl LdkMnemonic { 31 | pub fn generate() -> LdkMnemonic { 32 | ldk_node::generate_entropy_mnemonic().into() 33 | } 34 | } 35 | 36 | #[frb(opaque)] 37 | pub struct NodeBuilder { 38 | pub builder: RustOpaque, 39 | } 40 | 41 | impl NodeBuilder { 42 | pub fn create_builder( 43 | config: Config, 44 | chain_data_source_config: Option, 45 | entropy_source_config: Option, 46 | gossip_source_config: Option, 47 | liquidity_source_config: Option, 48 | ) -> Result { 49 | let mut builder = ldk_node::Builder::from_config(config.try_into()?); 50 | if let Some(source) = entropy_source_config { 51 | match source { 52 | EntropySourceConfig::SeedFile(e) => builder.set_entropy_seed_path(e), 53 | EntropySourceConfig::SeedBytes(e) => builder.set_entropy_seed_bytes(e.encode())?, 54 | EntropySourceConfig::Bip39Mnemonic { 55 | mnemonic, 56 | passphrase, 57 | } => builder.set_entropy_bip39_mnemonic( 58 | >::try_into(mnemonic)?, 59 | passphrase, 60 | ), 61 | }; 62 | } 63 | if let Some(source) = chain_data_source_config { 64 | match source { 65 | ChainDataSourceConfig::Esplora(e) => builder.set_esplora_server(e), 66 | }; 67 | } 68 | if let Some(source) = gossip_source_config { 69 | match source { 70 | GossipSourceConfig::P2PNetwork => builder.set_gossip_source_p2p(), 71 | GossipSourceConfig::RapidGossipSync(e) => builder.set_gossip_source_rgs(e), 72 | }; 73 | } 74 | if let Some(liquidity) = liquidity_source_config { 75 | builder.set_liquidity_source_lsps2( 76 | liquidity.lsps2_service.0.try_into()?, 77 | liquidity 78 | .lsps2_service 79 | .1 80 | .try_into() 81 | .map_err(|_| LdkBuilderError::InvalidPublicKey)?, 82 | liquidity.lsps2_service.2, 83 | ); 84 | } 85 | Ok(NodeBuilder { 86 | builder: RustOpaque::new(builder), 87 | }) 88 | } 89 | pub fn build(self) -> anyhow::Result { 90 | match self.builder.build() { 91 | Ok(e) => Ok(LdkNode { 92 | ptr: RustOpaque::new(e), 93 | }), 94 | Err(e) => Err(e.into()), 95 | } 96 | } 97 | pub fn build_with_fs_store(self) -> anyhow::Result { 98 | match self.builder.build_with_fs_store() { 99 | Ok(e) => Ok(LdkNode { 100 | ptr: RustOpaque::new(e), 101 | }), 102 | Err(e) => Err(e.into()), 103 | } 104 | } 105 | // fn build_with_store( 106 | // self 107 | // ) -> anyhow::Result { 108 | // match self.builder.build_with_store(Arc::new(())) { 109 | // Ok(e) => Ok(LdkNode { 110 | // ptr: RustOpaque::new(e), 111 | // }), 112 | // Err(e) => Err(e.into()), 113 | // } 114 | // } 115 | } 116 | 117 | // pub fn build_with_vss_store( 118 | // self, url: String, store_id: String 119 | // ) -> anyhow::Result { 120 | // match self.builder.build_with_vss_store(url, store_id) { 121 | // Ok(e) => Ok(LdkNode { 122 | // ptr: RustOpaque::new(e), 123 | // }), 124 | // Err(e) => Err(e.into()), 125 | // } 126 | // } 127 | -------------------------------------------------------------------------------- /rust/src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bolt11; 2 | pub mod bolt12; 3 | pub mod builder; 4 | pub mod graph; 5 | pub mod node; 6 | pub mod on_chain; 7 | pub mod spontaneous; 8 | pub mod types; 9 | -------------------------------------------------------------------------------- /rust/src/api/on_chain.rs: -------------------------------------------------------------------------------- 1 | use crate::api::types::{Address, Txid}; 2 | use crate::frb_generated::RustOpaque; 3 | use crate::utils::error::LdkNodeError; 4 | 5 | pub struct LdkOnChainPayment { 6 | pub ptr: RustOpaque, 7 | } 8 | impl From for LdkOnChainPayment { 9 | fn from(value: ldk_node::payment::OnchainPayment) -> Self { 10 | LdkOnChainPayment { 11 | ptr: RustOpaque::new(value), 12 | } 13 | } 14 | } 15 | 16 | impl LdkOnChainPayment { 17 | pub fn new_address(&self) -> Result { 18 | self.ptr 19 | .new_address() 20 | .map_err(|e| e.into()) 21 | .map(|e| e.into()) 22 | } 23 | pub fn send_to_address( 24 | &self, 25 | address: Address, 26 | amount_sats: u64, 27 | ) -> Result { 28 | self.ptr 29 | .send_to_address(&(address.try_into()?), amount_sats) 30 | .map_err(|e| e.into()) 31 | .map(|e| e.into()) 32 | } 33 | pub fn send_all_to_address(&self, address: Address) -> Result { 34 | self.ptr 35 | .send_all_to_address(&(address.try_into()?)) 36 | .map_err(|e| e.into()) 37 | .map(|e| e.into()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rust/src/api/spontaneous.rs: -------------------------------------------------------------------------------- 1 | use crate::api::types::{PaymentId, PublicKey}; 2 | use crate::frb_generated::RustOpaque; 3 | use crate::utils::error::LdkNodeError; 4 | 5 | pub struct LdkSpontaneousPayment { 6 | pub ptr: RustOpaque, 7 | } 8 | impl From for LdkSpontaneousPayment { 9 | fn from(value: ldk_node::payment::SpontaneousPayment) -> Self { 10 | Self { 11 | ptr: RustOpaque::new(value), 12 | } 13 | } 14 | } 15 | impl LdkSpontaneousPayment { 16 | pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result { 17 | self.ptr 18 | .send(amount_msat, node_id.try_into()?) 19 | .map_err(|e| e.into()) 20 | .map(|e| e.into()) 21 | } 22 | pub fn send_probes(&self, amount_msat: u64, node_id: PublicKey) -> Result<(), LdkNodeError> { 23 | self.ptr 24 | .send_probes(amount_msat, node_id.try_into()?) 25 | .map_err(|e| e.into()) 26 | .map(|e| e.into()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod frb_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ 2 | 3 | pub mod api; 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /rust/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | --------------------------------------------------------------------------------