From b7fe4507fe7a41d4fae92bb20377f723ecbc7162 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 13 Jan 2026 09:00:00 -0500 Subject: [PATCH 01/42] Add core utility functions for hex encoding and HTTP retry --- lib/src/precompiled/util.dart | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 lib/src/precompiled/util.dart diff --git a/lib/src/precompiled/util.dart b/lib/src/precompiled/util.dart new file mode 100644 index 0000000..7548ef7 --- /dev/null +++ b/lib/src/precompiled/util.dart @@ -0,0 +1,79 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:http/http.dart' as http; + +// Parse hex-encoded bytes from configuration/secrets. +List decodeHex(String hex) { + final normalized = hex.trim().toLowerCase(); + if (normalized.length.isOdd) { + throw FormatException('Invalid hex length'); + } + final out = []; + for (var i = 0; i < normalized.length; i += 2) { + final byte = int.parse(normalized.substring(i, i + 2), radix: 16); + out.add(byte); + } + return out; +} + +// Encode raw bytes as lowercase hex. +String hexEncode(List bytes) { + final b = StringBuffer(); + for (final v in bytes) { + b.write(v.toRadixString(16).padLeft(2, '0')); + } + return b.toString(); +} + +// Retry GET requests on transient connection resets. +Future httpGetWithRetry( + Uri url, { + Map? headers, + int maxAttempts = 4, + Duration timeout = const Duration(seconds: 30), + Duration retryDelay = const Duration(seconds: 1), +}) async { + final client = http.Client(); + var attempt = 0; + try { + while (true) { + attempt++; + try { + return await client.get(url, headers: headers).timeout(timeout); + } on TimeoutException { + if (attempt >= maxAttempts) rethrow; + } on SocketException { + if (attempt >= maxAttempts) rethrow; + } on http.ClientException { + if (attempt >= maxAttempts) rethrow; + } on HttpException { + if (attempt >= maxAttempts) rethrow; + } + await Future.delayed(retryDelay); + } + } finally { + client.close(); + } +} + +Future writeBytesAtomically(File file, List bytes) async { + final directory = file.parent; + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + + final tempPath = '${file.path}.${DateTime.now().microsecondsSinceEpoch}.tmp'; + final tempFile = File(tempPath); + + await tempFile.writeAsBytes(bytes, flush: true); + + try { + await tempFile.rename(file.path); + } on FileSystemException { + if (file.existsSync()) { + await file.delete(); + } + await tempFile.rename(file.path); + } +} From 85dfab4bd2acbf8157af4bff42e36f844e48dffc Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 13 Jan 2026 09:00:00 -0500 Subject: [PATCH 02/42] Add utility functions and target triple mapping --- lib/src/precompiled/target.dart | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 lib/src/precompiled/target.dart diff --git a/lib/src/precompiled/target.dart b/lib/src/precompiled/target.dart new file mode 100644 index 0000000..7d8f489 --- /dev/null +++ b/lib/src/precompiled/target.dart @@ -0,0 +1,42 @@ +import 'package:code_assets/code_assets.dart'; + +// Maps Dart build config to Rust target triples and link modes. +extension CodeConfigTargetTriple on CodeConfig { + // Derive Rust target triple from OS/arch. + String get targetTriple { + return switch ((targetOS, targetArchitecture)) { + (OS.android, Architecture.arm64) => 'aarch64-linux-android', + (OS.android, Architecture.arm) => 'armv7-linux-androideabi', + (OS.android, Architecture.x64) => 'x86_64-linux-android', + (OS.iOS, Architecture.arm64) + when iOS.targetSdk == IOSSdk.iPhoneSimulator => + 'aarch64-apple-ios-sim', + (OS.iOS, Architecture.arm64) when iOS.targetSdk == IOSSdk.iPhoneOS => + 'aarch64-apple-ios', + (OS.iOS, Architecture.x64) => 'x86_64-apple-ios', + (OS.windows, Architecture.arm64) => 'aarch64-pc-windows-msvc', + (OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc', + (OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu', + (OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu', + (OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin', + (OS.macOS, Architecture.x64) => 'x86_64-apple-darwin', + (_, _) => throw UnsupportedError( + 'Unsupported target: $targetOS on $targetArchitecture', + ), + }; + } + + // Resolve link mode from code asset preferences. + LinkMode get linkMode { + return switch (linkModePreference) { + LinkModePreference.dynamic || + LinkModePreference.preferDynamic => DynamicLoadingBundled(), + LinkModePreference.static || + LinkModePreference.preferStatic => StaticLinking(), + _ => throw UnsupportedError( + 'Unsupported LinkModePreference: $linkModePreference', + ), + }; + } +} + From feb7f856ed11ef1d9523025a8ec2ba96f6680422 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 13 Jan 2026 11:00:00 -0500 Subject: [PATCH 03/42] Add options and configuration parsing --- lib/src/precompiled/options.dart | 264 +++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 lib/src/precompiled/options.dart diff --git a/lib/src/precompiled/options.dart b/lib/src/precompiled/options.dart new file mode 100644 index 0000000..62a231d --- /dev/null +++ b/lib/src/precompiled/options.dart @@ -0,0 +1,264 @@ +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +import 'util.dart'; + +enum PrecompiledBinaryMode { auto, always, never } + +class PrecompiledBinariesConfig { + PrecompiledBinariesConfig({ + required this.artifactHost, + required this.mode, + required this.publicKey, + this.urlPrefix, + }); + + final String artifactHost; + final PrecompiledBinaryMode mode; + final String? urlPrefix; + final PublicKey publicKey; + + Uri fileUrl({required String crateHash, required String fileName}) { + final prefix = urlPrefix; + if (prefix != null && prefix.isNotEmpty) { + return Uri.parse('$prefix$crateHash/$fileName'); + } + final tag = 'precompiled_$crateHash'; + return Uri.parse( + 'https://github.com/$artifactHost/releases/download/$tag/$fileName', + ); + } + + static PrecompiledBinariesConfig parse(YamlNode node) { + if (node is! YamlMap) { + throw FormatException('precompiled_binaries must be a map'); + } + + String? urlPrefix; + final urlPrefixNode = node.nodes['url_prefix']; + if (urlPrefixNode != null) { + if (urlPrefixNode is! YamlScalar || urlPrefixNode.value is! String) { + throw FormatException( + 'precompiled_binaries.url_prefix must be a string', + ); + } + urlPrefix = urlPrefixNode.value as String; + } + + PrecompiledBinaryMode mode = PrecompiledBinaryMode.auto; + final modeNode = node.nodes['mode']; + if (modeNode != null) { + if (modeNode is! YamlScalar || modeNode.value is! String) { + throw FormatException('precompiled_binaries.mode must be a string'); + } + final m = (modeNode.value as String).trim(); + final parsed = _parsePrecompiledBinaryMode(m); + if (parsed == null) { + throw FormatException( + 'precompiled_binaries.mode must be one of: auto, always, never (aliases: download->always, build->never)', + ); + } + mode = parsed; + } + + final artifactHostNode = node.nodes['artifact_host']; + final releaseRepoNode = node.nodes['release_repo']; + + final publicKeyNode = node.nodes['public_key']; + + String? artifactHost; + if (artifactHostNode != null) { + if (artifactHostNode is! YamlScalar || + artifactHostNode.value is! String) { + throw FormatException( + 'precompiled_binaries.artifact_host must be a string', + ); + } + artifactHost = (artifactHostNode.value as String).trim(); + } + if (artifactHost == null && releaseRepoNode != null) { + if (releaseRepoNode is! YamlScalar || releaseRepoNode.value is! String) { + throw FormatException( + 'precompiled_binaries.release_repo must be a string', + ); + } + artifactHost = (releaseRepoNode.value as String).trim(); + } + + if ((urlPrefix == null || urlPrefix.isEmpty) && + (artifactHost == null || artifactHost.isEmpty)) { + throw FormatException( + 'precompiled_binaries must specify either url_prefix or artifact_host', + ); + } + + artifactHost ??= ''; + final normalizedArtifactHost = artifactHost.isEmpty + ? '' + : _normalizeOwnerRepo(artifactHost); + if (artifactHost.isNotEmpty && normalizedArtifactHost == null) { + throw FormatException( + 'precompiled_binaries.artifact_host must be in owner/repo format (or github.com/owner/repo)', + ); + } + + if (publicKeyNode is! YamlScalar || publicKeyNode.value is! String) { + throw FormatException('precompiled_binaries.public_key must be a string'); + } + final keyBytes = decodeHex(publicKeyNode.value as String); + if (keyBytes.length != 32) { + throw FormatException('public_key must be 32 bytes'); + } + return PrecompiledBinariesConfig( + artifactHost: normalizedArtifactHost ?? '', + mode: mode, + publicKey: PublicKey(keyBytes), + urlPrefix: urlPrefix, + ); + } +} + +String? _normalizeOwnerRepo(String raw) { + var v = raw.trim(); + v = v.replaceFirst(RegExp(r'^https?://'), ''); + v = v.replaceFirst(RegExp(r'^github\.com/'), ''); + v = v.replaceAll(RegExp(r'/+$'), ''); + final parts = v.split('/'); + if (parts.length != 2) { + return null; + } + if (parts[0].isEmpty || parts[1].isEmpty) { + return null; + } + return '${parts[0]}/${parts[1]}'; +} + +class PubspecOptions { + PubspecOptions({required this.precompiledBinaries}); + + final PrecompiledBinariesConfig? precompiledBinaries; + + static YamlNode? _findPrecompiledBinariesNode( + YamlMap root, { + required String configKey, + }) { + final key = configKey.trim(); + if (key.isEmpty) { + return null; + } + final packageNode = root.nodes[key]; + if (packageNode is! YamlMap) { + return null; + } + return packageNode.nodes['precompiled_binaries']; + } + + static PrecompiledBinaryMode? loadModeOverride({ + required String packageRoot, + required String packageName, + }) { + final file = File(path.join(packageRoot, 'pubspec.yaml')); + if (!file.existsSync()) { + return null; + } + final root = loadYamlNode(file.readAsStringSync(), sourceUrl: file.uri); + if (root is! YamlMap) { + throw FormatException('pubspec.yaml must be a map'); + } + + final node = _findPrecompiledBinariesNode(root, configKey: packageName); + if (node == null) { + return null; + } + if (node is! YamlMap) { + throw FormatException('precompiled_binaries must be a map'); + } + + final modeNode = node.nodes['mode']; + if (modeNode == null) { + return null; + } + if (modeNode is! YamlScalar || modeNode.value is! String) { + throw FormatException('precompiled_binaries.mode must be a string'); + } + + final m = (modeNode.value as String).trim(); + final parsed = _parsePrecompiledBinaryMode(m); + if (parsed == null) { + throw FormatException( + 'precompiled_binaries.mode must be one of: auto, always, never (aliases: download->always, build->never)', + ); + } + return parsed; + } + + static PubspecOptions load({ + required String packageRoot, + required String pluginConfigKey, + }) { + final file = File(path.join(packageRoot, 'pubspec.yaml')); + if (!file.existsSync()) { + return PubspecOptions(precompiledBinaries: null); + } + final root = loadYamlNode(file.readAsStringSync(), sourceUrl: file.uri); + if (root is! YamlMap) { + throw FormatException('pubspec.yaml must be a map'); + } + final node = _findPrecompiledBinariesNode(root, configKey: pluginConfigKey); + + if (node == null) { + return PubspecOptions(precompiledBinaries: null); + } + + return PubspecOptions( + precompiledBinaries: PrecompiledBinariesConfig.parse(node), + ); + } +} + +class UserOptions { + UserOptions({required this.usePrecompiledBinaries}); + + final bool usePrecompiledBinaries; + + static bool _rustupExists() { + final envPath = Platform.environment['PATH']; + final envPathSeparator = Platform.isWindows ? ';' : ':'; + final home = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + final paths = [ + if (home != null) path.join(home, '.cargo', 'bin'), + if (envPath != null) ...envPath.split(envPathSeparator), + ]; + for (final p in paths) { + final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; + if (File(path.join(p, rustup)).existsSync()) { + return true; + } + } + return false; + } + + static bool defaultUsePrecompiledBinaries() => !_rustupExists(); + + static UserOptions load({required bool hasConfig}) { + if (!hasConfig) { + return UserOptions(usePrecompiledBinaries: false); + } + return UserOptions(usePrecompiledBinaries: defaultUsePrecompiledBinaries()); + } +} + +PrecompiledBinaryMode? _parsePrecompiledBinaryMode(String raw) { + final v = raw.trim().toLowerCase(); + return switch (v) { + 'auto' => PrecompiledBinaryMode.auto, + 'always' || 'download' => PrecompiledBinaryMode.always, + 'never' || 'build' || 'off' || 'disabled' => PrecompiledBinaryMode.never, + _ => null, + }; +} From e126921f1c4b49ff4bedfd90cdf86565126b0c20 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 13 Jan 2026 14:00:00 -0500 Subject: [PATCH 04/42] Add crate hash calculation --- lib/src/precompiled/crate_hash.dart | 171 ++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 lib/src/precompiled/crate_hash.dart diff --git a/lib/src/precompiled/crate_hash.dart b/lib/src/precompiled/crate_hash.dart new file mode 100644 index 0000000..54547c8 --- /dev/null +++ b/lib/src/precompiled/crate_hash.dart @@ -0,0 +1,171 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +// Computes a stable hash for crate inputs used in precompiled releases. +class CrateHash { + // Compute (and optionally cache) the crate hash. + static String compute(String manifestDir, {String? tempStorage}) { + return CrateHash._(manifestDir: manifestDir, tempStorage: tempStorage) + ._compute(); + } + + CrateHash._({required this.manifestDir, required this.tempStorage}); + + final String manifestDir; + final String? tempStorage; + + // Collect all files that participate in the crate hash. + static List collectFiles(String manifestDir) { + return CrateHash._(manifestDir: manifestDir, tempStorage: null)._getFiles(); + } + + String _compute() { + final files = _getFiles(); + final tempStorage = this.tempStorage; + if (tempStorage != null) { + // Quick hash keyed by file path + size + mtime to reuse cached full hash. + final quickHash = _computeQuickHash(files); + final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); + quickHashFolder.createSync(recursive: true); + final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); + if (quickHashFile.existsSync()) { + return quickHashFile.readAsStringSync(); + } + final hash = _computeHash(files); + quickHashFile.writeAsStringSync(hash); + return hash; + } + return _computeHash(files); + } + + // Fast hash over file metadata to key the cache. + String _computeQuickHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + final data = ByteData(8); + for (final file in files) { + input.add(utf8.encode(file.path)); + final stat = file.statSync(); + data.setUint64(0, stat.size); + input.add(data.buffer.asUint8List()); + data.setUint64(0, stat.modified.millisecondsSinceEpoch); + input.add(data.buffer.asUint8List()); + } + + input.close(); + return base64Url.encode(output.events.single.bytes); + } + + // Deterministic content hash; normalizes pubspec precompiled config. + String _computeHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + void addTextFile(File file) { + final splitter = const LineSplitter(); + if (file.existsSync()) { + final data = file.readAsStringSync(); + final lines = splitter.convert(data); + for (final line in lines) { + input.add(utf8.encode(line)); + } + } + } + + // Include precompiled_binaries config so it invalidates releases. + void addPrecompiledBinariesFromPubspec(File file) { + if (!file.existsSync()) { + return; + } + final yamlContent = file.readAsStringSync(); + final doc = loadYaml(yamlContent); + final extensionSection = doc is YamlMap ? doc['bdk_dart'] : null; + final precompiled = + extensionSection is YamlMap ? extensionSection['precompiled_binaries'] : null; + final normalized = _normalizeYaml(precompiled ?? {}); + input.add(utf8.encode('pubspec.yaml:bdk_dart.precompiled_binaries:')); + input.add(utf8.encode(jsonEncode(normalized))); + } + + // Find pubspec.yaml at package root (one level up from crate). + final rootDir = path.normalize(path.join(manifestDir, '../')); + final pubspecFile = File(path.join(rootDir, 'pubspec.yaml')); + addPrecompiledBinariesFromPubspec(pubspecFile); + + for (final file in files) { + addTextFile(file); + } + + input.close(); + final res = output.events.single; + final hash = res.bytes.sublist(0, 16); + return _hexEncode(hash); + } + + String _hexEncode(List bytes) { + final b = StringBuffer(); + for (final v in bytes) { + b.write(v.toRadixString(16).padLeft(2, '0')); + } + return b.toString(); + } + + // Normalize maps/lists for deterministic serialization. + Object? _normalizeYaml(Object? value) { + if (value is YamlMap) { + final keys = value.keys.map((key) => key.toString()).toList()..sort(); + final result = {}; + for (final key in keys) { + result[key] = _normalizeYaml(value[key]); + } + return result; + } + if (value is YamlList) { + return value.map(_normalizeYaml).toList(); + } + if (value is Map) { + final keys = value.keys.map((key) => key.toString()).toList()..sort(); + final result = {}; + for (final key in keys) { + result[key] = _normalizeYaml(value[key]); + } + return result; + } + if (value is List) { + return value.map(_normalizeYaml).toList(); + } + return value; + } + + // Collect all source and manifest files that affect the build. + List _getFiles() { + final src = Directory(path.join(manifestDir, 'src')); + final files = src.existsSync() + ? src + .listSync(recursive: true, followLinks: false) + .whereType() + .toList() + : []; + files.sort((a, b) => a.path.compareTo(b.path)); + + void addFileInCrate(String relative) { + final file = File(path.join(manifestDir, relative)); + if (file.existsSync()) { + files.add(file); + } + } + + addFileInCrate('Cargo.toml'); + addFileInCrate('Cargo.lock'); + addFileInCrate('build.rs'); + return files; + } +} + From 9d582c6cb4382e4fadcb586b77e13a9af4e02c47 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 13 Jan 2026 16:00:00 -0500 Subject: [PATCH 05/42] Add Rust toolchain detection --- lib/src/precompiled/rust_toolchain.dart | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 lib/src/precompiled/rust_toolchain.dart diff --git a/lib/src/precompiled/rust_toolchain.dart b/lib/src/precompiled/rust_toolchain.dart new file mode 100644 index 0000000..8209e3b --- /dev/null +++ b/lib/src/precompiled/rust_toolchain.dart @@ -0,0 +1,82 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:toml/toml.dart'; + +// Parses rust-toolchain.toml and exposes channel/targets. +class RustToolchain { + RustToolchain._({ + required this.channel, + required this.targets, + }); + + final String channel; + final List targets; + + // Load rust-toolchain.toml from a crate directory. + static RustToolchain load(String manifestDir) { + final file = File(path.join(manifestDir, 'rust-toolchain.toml')); + if (!file.existsSync()) { + throw StateError('rust-toolchain.toml not found: ${file.path}'); + } + return _parseToolchain(file.path); + } + + // Parse the toolchain TOML into a RustToolchain instance. + static RustToolchain _parseToolchain(String toolchainTomlPath) { + final doc = TomlDocument.loadSync(toolchainTomlPath).toMap(); + final toolchain = doc['toolchain']; + if (toolchain is! Map) { + throw FormatException('Missing [toolchain] table in $toolchainTomlPath'); + } + final channel = toolchain['channel']; + if (channel is! String || channel.trim().isEmpty) { + throw FormatException('Missing toolchain.channel in $toolchainTomlPath'); + } + final targetsRaw = toolchain['targets']; + final targets = targetsRaw is List + ? targetsRaw.whereType().toList(growable: false) + : const []; + return RustToolchain._( + channel: channel.trim(), + targets: targets, + ); + } + + // Filter targets for a given OS label. + List targetsForOs(String os) { + final normalized = _normalizeOs(os); + if (normalized == null) { + return targets; + } + return _filterTargets(targets, normalized); + } + + // Normalize OS aliases used by CLI/workflows. + static String? _normalizeOs(String raw) { + final v = raw.trim().toLowerCase(); + return switch (v) { + 'linux' || 'ubuntu-latest' => 'linux', + 'macos' || 'darwin' || 'macos-latest' => 'macos', + 'windows' || 'windows-latest' => 'windows', + 'android' => 'android', + 'ios' => 'ios', + 'all' => null, + _ => v.isEmpty ? null : v, + }; + } + + // Apply OS-specific target filtering. + static List _filterTargets(List targets, String os) { + bool include(String t) => switch (os) { + 'macos' => t.endsWith('apple-darwin') || t.contains('apple-ios'), + 'ios' => t.contains('apple-ios'), + 'linux' => t.endsWith('unknown-linux-gnu'), + 'windows' => t.endsWith('pc-windows-msvc'), + 'android' => t.endsWith('linux-android') || t.endsWith('linux-androideabi'), + _ => false, + }; + return targets.where(include).toList(growable: false); + } +} + From 1e2df777c19378b6419b98f6711d6bab3c203754 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 14 Jan 2026 09:00:00 -0500 Subject: [PATCH 06/42] Add Cargo build integration --- lib/src/precompiled/cargo.dart | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/src/precompiled/cargo.dart diff --git a/lib/src/precompiled/cargo.dart b/lib/src/precompiled/cargo.dart new file mode 100644 index 0000000..1db3f6f --- /dev/null +++ b/lib/src/precompiled/cargo.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; + +class CrateInfo { + CrateInfo({required this.packageName}); + + final String packageName; + + // Extract crate package name from Cargo.toml. + static CrateInfo load(String manifestDir) { + final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); + final manifest = manifestFile.readAsStringSync(); + final lines = manifest.split('\n'); + var inPackage = false; + for (final raw in lines) { + final line = raw.trim(); + if (line.startsWith('[') && line.endsWith(']')) { + inPackage = line == '[package]'; + continue; + } + if (!inPackage) continue; + if (line.startsWith('name')) { + final parts = line.split('='); + if (parts.length >= 2) { + final value = parts.sublist(1).join('=').trim(); + final m = RegExp(r'^"(.+)"$').firstMatch(value); + if (m != null) { + return CrateInfo(packageName: m.group(1)!); + } + } + } + } + throw StateError('Failed to determine crate name from Cargo.toml'); + } +} From 6c6f97492bb58a30d7384bf78503c179e7abb58a Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 14 Jan 2026 11:00:00 -0500 Subject: [PATCH 07/42] Add artifacts provider for download and verification --- lib/src/precompiled/artifacts_provider.dart | 178 ++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 lib/src/precompiled/artifacts_provider.dart diff --git a/lib/src/precompiled/artifacts_provider.dart b/lib/src/precompiled/artifacts_provider.dart new file mode 100644 index 0000000..54d59f1 --- /dev/null +++ b/lib/src/precompiled/artifacts_provider.dart @@ -0,0 +1,178 @@ +import 'dart:io'; + +import 'package:code_assets/code_assets.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:hooks/hooks.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'target.dart'; +import 'util.dart'; + +// Handles locating, downloading, and verifying precompiled artifacts. +final _log = Logger('bdk_dart.artifacts_provider'); + +// Infer the invoking package root from the build output directory. +String? _invokerRootFromOutputDirectory(Uri outputDirectory) { + final parts = path.split(path.fromUri(outputDirectory)); + final dartToolIndex = parts.lastIndexOf('.dart_tool'); + if (dartToolIndex <= 0) { + return null; + } + return path.joinAll(parts.take(dartToolIndex)); +} + +// Result of a successful download + verification. +class DownloadedArtifact { + DownloadedArtifact({required this.filePath, required this.dependencies}); + + final String filePath; + final List dependencies; +} + +// Resolves config, downloads binary + signature, and verifies them. +class PrecompiledArtifactProvider { + PrecompiledArtifactProvider({ + required this.input, + required this.buildModeName, + required this.crateDir, + }); + + final BuildInput input; + final String buildModeName; + final Directory crateDir; + + Future tryGetPrecompiledArtifact() async { + // Read precompiled_binaries configuration from pubspec.yaml. + final pubspecOptions = PubspecOptions.load( + packageRoot: path.fromUri(input.packageRoot), + pluginConfigKey: 'bdk_dart', + ); + final baseConfig = pubspecOptions.precompiledBinaries; + if (baseConfig == null) { + return null; + } + + // Allow invoker package to override mode for this build. + final invokerRoot = _invokerRootFromOutputDirectory(input.outputDirectory); + final invokerMode = invokerRoot == null + ? null + : PubspecOptions.loadModeOverride( + packageRoot: invokerRoot, + packageName: input.packageName, + ); + + final precompiled = invokerMode == null + ? baseConfig + : PrecompiledBinariesConfig( + artifactHost: baseConfig.artifactHost, + mode: invokerMode, + publicKey: baseConfig.publicKey, + urlPrefix: baseConfig.urlPrefix, + ); + + if (invokerMode != null) { + _log.info( + 'Using invoker override precompiled_binaries.mode=${invokerMode.name} from $invokerRoot', + ); + } + + if (precompiled.mode == PrecompiledBinaryMode.never) { + _log.info('Precompiled binaries disabled by mode=never'); + return null; + } + + if (precompiled.mode == PrecompiledBinaryMode.auto) { + final userOptions = UserOptions.load(hasConfig: true); + if (!userOptions.usePrecompiledBinaries) { + _log.info( + 'Precompiled binaries disabled (auto mode + local build preferred)', + ); + return null; + } + } + + if (!input.config.buildCodeAssets) { + return null; + } + + final codeConfig = input.config.code; + // Derive crate name from Cargo.toml for file naming. + final crateInfo = CrateInfo.load(crateDir.path); + final targetTriple = codeConfig.targetTriple; + final linkMode = codeConfig.linkMode; + + // Write into the build output target directory. + final outDir = path.join(path.fromUri(input.outputDirectory), 'target'); + final libFileName = codeConfig.targetOS + .libraryFileName(crateInfo.packageName, linkMode) + .replaceAll('-', '_'); + final finalLibPath = path.join( + outDir, + targetTriple, + buildModeName, + libFileName, + ); + + Directory(path.dirname(finalLibPath)).createSync(recursive: true); + + // Crate hash keys the release tag and artifact path. + final crateHash = CrateHash.compute(crateDir.path, tempStorage: outDir); + + final remoteFileName = '${targetTriple}_$libFileName'; + final remoteSignatureName = '$remoteFileName.sig'; + + final binaryUrl = precompiled.fileUrl( + crateHash: crateHash, + fileName: remoteFileName, + ); + final signatureUrl = precompiled.fileUrl( + crateHash: crateHash, + fileName: remoteSignatureName, + ); + + _log.info('Downloading signature from $signatureUrl'); + final signatureRes = await httpGetWithRetry(signatureUrl); + if (signatureRes.statusCode == 404) { + _log.info('No precompiled binaries for crate hash $crateHash'); + return null; + } + if (signatureRes.statusCode != 200) { + _log.warning( + 'Failed to download signature: status ${signatureRes.statusCode}', + ); + return null; + } + + _log.info('Downloading binary from $binaryUrl'); + final binaryRes = await httpGetWithRetry(binaryUrl); + if (binaryRes.statusCode != 200) { + _log.warning('Failed to download binary: status ${binaryRes.statusCode}'); + return null; + } + + // Verify binary integrity before writing it to disk. + final ok = verify( + precompiled.publicKey, + binaryRes.bodyBytes, + signatureRes.bodyBytes, + ); + if (!ok) { + _log.warning('Signature verification failed; ignoring binary'); + return null; + } + + await writeBytesAtomically(File(finalLibPath), binaryRes.bodyBytes); + _log.info('Verified and wrote precompiled binary to $finalLibPath'); + + // Dependencies track local crate inputs for rebuild invalidation. + final deps = CrateHash.collectFiles( + crateDir.path, + ).map((f) => f.absolute.uri).toList(growable: false); + + return DownloadedArtifact(filePath: finalLibPath, dependencies: deps); + } +} From c19cef811d6d11bda376345b066ee07131ef37fa Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 14 Jan 2026 14:00:00 -0500 Subject: [PATCH 08/42] Add precompiled builder main integration --- lib/src/precompiled/precompiled_builder.dart | 133 +++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 lib/src/precompiled/precompiled_builder.dart diff --git a/lib/src/precompiled/precompiled_builder.dart b/lib/src/precompiled/precompiled_builder.dart new file mode 100644 index 0000000..2520602 --- /dev/null +++ b/lib/src/precompiled/precompiled_builder.dart @@ -0,0 +1,133 @@ +import 'dart:io'; + +import 'package:code_assets/code_assets.dart'; +import 'package:hooks/hooks.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'target.dart'; + +// Build hook that prefers signed precompiled binaries, with local fallback. +final _log = Logger('bdk_dart.precompiled_builder'); + +// Callback used when no precompiled artifact is available. +typedef FallbackBuilder = + Future Function( + BuildInput input, + BuildOutputBuilder output, + List assetRouting, + Logger? logger, + ); + +// Builder that downloads verified artifacts when available. +final class PrecompiledBuilder implements Builder { + const PrecompiledBuilder({ + required this.assetName, + required this.fallback, + this.cratePath, + this.buildModeName = 'release', + }); + + final String assetName; + final FallbackBuilder fallback; + final String? cratePath; + final String buildModeName; + + @override + Future run({ + required BuildInput input, + required BuildOutputBuilder output, + List assetRouting = const [ToAppBundle()], + Logger? logger, + }) async { + // Configure logging once for the build process. + _initLogging(); + if (!input.config.buildCodeAssets) { + return; + } + + logger ??= Logger('bdk_dart.PrecompiledBuilder'); + + // Resolve crate directory from the package root. + final crateDirectory = _resolveCrateDirectory( + rootPath: path.fromUri(input.packageRoot), + cratePathOptions: cratePath != null ? [cratePath!] : ['native', 'rust'], + ); + + // Provider handles download + signature verification. + final provider = PrecompiledArtifactProvider( + input: input, + buildModeName: buildModeName, + crateDir: crateDirectory, + ); + + final downloaded = await provider.tryGetPrecompiledArtifact(); + if (downloaded != null) { + for (final dep in downloaded.dependencies) { + output.dependencies.add(dep); + } + + final codeConfig = input.config.code; + final linkMode = codeConfig.linkMode; + + // Register the verified asset under each routing. + for (final routing in assetRouting) { + output.assets.code.add( + CodeAsset( + package: input.packageName, + name: assetName, + linkMode: linkMode, + file: File(downloaded.filePath).absolute.uri, + ), + routing: routing, + ); + } + _log.info('Using precompiled binary for ${codeConfig.targetTriple}'); + return; + } + + _log.info( + 'Falling back to local build for ${input.config.code.targetTriple}', + ); + await fallback(input, output, assetRouting, logger); + } + + // Locate the Rust crate folder based on common paths or override. + Directory _resolveCrateDirectory({ + required String rootPath, + required List cratePathOptions, + }) { + for (final option in cratePathOptions) { + final dir = Directory(path.join(rootPath, option)); + if (dir.existsSync()) { + return dir; + } + } + throw StateError( + 'Could not find crate directory. Checked: $cratePathOptions at $rootPath', + ); + } +} + +// Prevent duplicate log handlers across multiple invocations. +bool _loggingInitialized = false; + +void _initLogging() { + if (_loggingInitialized) return; + _loggingInitialized = true; + + // Verbose mode is opt-in via env var to avoid noisy builds. + final verbose = Platform.environment['BDK_DART_PRECOMPILED_VERBOSE'] == '1'; + Logger.root.level = verbose ? Level.ALL : Level.INFO; + Logger.root.onRecord.listen((rec) { + final out = rec.level >= Level.WARNING ? stderr : stdout; + out.writeln('${rec.level.name}: ${rec.message}'); + if (rec.error != null) { + out.writeln(rec.error); + } + if (rec.stackTrace != null && verbose) { + out.writeln(rec.stackTrace); + } + }); +} From 7dab3210fd1150c85a308a81b9f6653a9d72114c Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 14 Jan 2026 16:00:00 -0500 Subject: [PATCH 09/42] Add CLI support utilities --- lib/src/precompiled/cli/support/os.dart | 14 +++++++++++ lib/src/precompiled/cli/support/process.dart | 25 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 lib/src/precompiled/cli/support/os.dart create mode 100644 lib/src/precompiled/cli/support/process.dart diff --git a/lib/src/precompiled/cli/support/os.dart b/lib/src/precompiled/cli/support/os.dart new file mode 100644 index 0000000..58089fe --- /dev/null +++ b/lib/src/precompiled/cli/support/os.dart @@ -0,0 +1,14 @@ +// Normalize OS inputs for CLI filtering. +String? normalizeOs(String raw) { + final v = raw.trim().toLowerCase(); + return switch (v) { + 'linux' || 'ubuntu-latest' => 'linux', + 'macos' || 'darwin' || 'macos-latest' => 'macos', + 'windows' || 'windows-latest' => 'windows', + 'android' => 'android', + 'ios' => 'ios', + 'all' => null, + _ => v.isEmpty ? null : v, + }; +} + diff --git a/lib/src/precompiled/cli/support/process.dart b/lib/src/precompiled/cli/support/process.dart new file mode 100644 index 0000000..9273c99 --- /dev/null +++ b/lib/src/precompiled/cli/support/process.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +// Run a process, echoing output, and throw on non-zero exit. +Future runOrThrow( + String exe, + List args, { + required bool verbose, + Map? environment, + String? workingDirectory, +}) async { + if (verbose) stderr.writeln('> $exe ${args.join(' ')}'); + final res = await Process.run( + exe, + args, + environment: environment, + workingDirectory: workingDirectory, + ); + stdout.write(res.stdout); + stderr.write(res.stderr); + if (res.exitCode != 0) { + exitCode = res.exitCode; + throw StateError('Command failed: $exe ${args.join(' ')}'); + } +} + From 224d288b86e4e12b6a4b913171ab756b264a0bac Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 15 Jan 2026 09:00:00 -0500 Subject: [PATCH 10/42] Add CLI framework --- lib/src/precompiled/cli/cli.dart | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/src/precompiled/cli/cli.dart diff --git a/lib/src/precompiled/cli/cli.dart b/lib/src/precompiled/cli/cli.dart new file mode 100644 index 0000000..116a915 --- /dev/null +++ b/lib/src/precompiled/cli/cli.dart @@ -0,0 +1,57 @@ +import 'dart:io'; + +import 'commands/gen_key.dart' as gen_key; +import 'commands/hash.dart' as hash; +import 'commands/precompile_binaries.dart' as precompile_binaries; +import 'commands/sign.dart' as sign_cmd; +import 'commands/targets.dart' as targets; + +// Entry point for the precompiled tooling CLI. +Future runCli(List args) async { + if (args.isEmpty) { + _printUsage(); + exitCode = 2; + return; + } + + final command = args[0]; + final commandArgs = args.sublist(1); + + switch (command) { + case 'precompile-binaries': + await precompile_binaries.run(commandArgs); + return; + case 'hash': + await hash.run(commandArgs); + return; + case 'targets': + await targets.run(commandArgs); + return; + case 'sign': + await sign_cmd.run(commandArgs); + return; + case 'gen-key': + await gen_key.run(commandArgs); + return; + default: + stderr.writeln('Unknown command: $command'); + _printUsage(); + exitCode = 2; + } +} + +void _printUsage() { + stdout.writeln(''' +Usage: dart run bdk_dart [options] + +Commands: + precompile-binaries Precompile binaries for all targets + hash Compute crate hash + targets Resolve toolchain targets + sign Sign a file + gen-key Generate Ed25519 keypair + +Run 'dart run bdk_dart --help' for command-specific help. +'''); +} + From bb8ca56453ed9d645bd4e807a8253bb1f409b898 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 15 Jan 2026 11:00:00 -0500 Subject: [PATCH 11/42] Add CLI commands: targets and hash --- lib/src/precompiled/cli/commands/hash.dart | 56 +++++++++++++++ lib/src/precompiled/cli/commands/targets.dart | 69 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 lib/src/precompiled/cli/commands/hash.dart create mode 100644 lib/src/precompiled/cli/commands/targets.dart diff --git a/lib/src/precompiled/cli/commands/hash.dart b/lib/src/precompiled/cli/commands/hash.dart new file mode 100644 index 0000000..a94ff0a --- /dev/null +++ b/lib/src/precompiled/cli/commands/hash.dart @@ -0,0 +1,56 @@ +import 'dart:io'; + +import 'package:bdk_dart/src/precompiled/crate_hash.dart'; + +// Compute and print the crate hash for release tagging. +Future run(List args) async { + String? manifestDir; + var debug = false; + var list = false; + + for (final arg in args) { + if (arg == '--debug') { + debug = true; + continue; + } + if (arg == '--list') { + list = true; + continue; + } + if (arg.startsWith('--manifest-dir=')) { + manifestDir = arg.substring('--manifest-dir='.length).trim(); + continue; + } + if (arg == '--help' || arg == '-h') { + stdout.writeln('Usage: hash --manifest-dir= [--debug] [--list]'); + return; + } + if (arg.trim().isEmpty) continue; + stderr.writeln('Unknown argument: $arg'); + exitCode = 2; + return; + } + + if (manifestDir == null || manifestDir.trim().isEmpty) { + stderr.writeln('Missing --manifest-dir'); + exitCode = 2; + return; + } + if (!Directory(manifestDir).existsSync()) { + stderr.writeln('Manifest directory not found: $manifestDir'); + exitCode = 1; + return; + } + + if (debug || list) { + final files = CrateHash.collectFiles(manifestDir); + for (final file in files) { + if (list) { + stderr.writeln(file.path); + } else { + stderr.writeln('${file.path}'); + } + } + } + stdout.write(CrateHash.compute(manifestDir)); +} diff --git a/lib/src/precompiled/cli/commands/targets.dart b/lib/src/precompiled/cli/commands/targets.dart new file mode 100644 index 0000000..4cdbb2d --- /dev/null +++ b/lib/src/precompiled/cli/commands/targets.dart @@ -0,0 +1,69 @@ +import 'dart:io'; + +import 'package:bdk_dart/src/precompiled/rust_toolchain.dart'; + +import '../support/os.dart'; + +Future run(List args) async { + String? os; + String? manifestDir; + var printChannel = false; + + for (final arg in args) { + if (arg.startsWith('--os=')) { + os = arg.substring('--os='.length).trim(); + continue; + } + if (arg.startsWith('--manifest-dir=')) { + manifestDir = arg.substring('--manifest-dir='.length).trim(); + continue; + } + if (arg == '--channel') { + printChannel = true; + continue; + } + if (arg == '--help' || arg == '-h') { + stdout.writeln( + 'Usage: targets --manifest-dir= [--os=macos] [--channel]', + ); + return; + } + if (arg.trim().isEmpty) continue; + stderr.writeln('Unknown argument: $arg'); + exitCode = 2; + return; + } + + if (manifestDir == null || manifestDir.trim().isEmpty) { + stderr.writeln('Missing --manifest-dir'); + exitCode = 2; + return; + } + + try { + final toolchain = RustToolchain.load(manifestDir); + if (printChannel) { + stdout.write(toolchain.channel); + return; + } + final targets = toolchain.targets; + if (targets.isEmpty) { + stderr.writeln('No targets found in rust-toolchain.toml'); + exitCode = 1; + return; + } + final normalized = normalizeOs(os ?? Platform.operatingSystem); + final filtered = normalized == null + ? targets + : toolchain.targetsForOs(normalized); + if (filtered.isEmpty) { + stderr.writeln('No targets match os=${normalized ?? 'all'}'); + exitCode = 1; + return; + } + stdout.write(filtered.join(' ')); + } catch (e) { + stderr.writeln('Error: $e'); + exitCode = 1; + } +} From ac5f17413a20237b86b94edc33c8b4f06a90816f Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 15 Jan 2026 14:00:00 -0500 Subject: [PATCH 12/42] Add CLI commands: gen_key and sign --- lib/src/precompiled/cli/commands/gen_key.dart | 16 ++++++++ lib/src/precompiled/cli/commands/sign.dart | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lib/src/precompiled/cli/commands/gen_key.dart create mode 100644 lib/src/precompiled/cli/commands/sign.dart diff --git a/lib/src/precompiled/cli/commands/gen_key.dart b/lib/src/precompiled/cli/commands/gen_key.dart new file mode 100644 index 0000000..ce9912a --- /dev/null +++ b/lib/src/precompiled/cli/commands/gen_key.dart @@ -0,0 +1,16 @@ +import 'package:bdk_dart/src/precompiled/util.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; + +// Generate an Ed25519 keypair for signing. +Future run(List args) async { + if (args.contains('--help') || args.contains('-h')) { + return; + } + final kp = generateKey(); + final privateHex = hexEncode(kp.privateKey.bytes); + final publicHex = hexEncode(kp.publicKey.bytes); + // ignore: avoid_print + print('PRIVATE_KEY=$privateHex'); + // ignore: avoid_print + print('PUBLIC_KEY=$publicHex'); +} diff --git a/lib/src/precompiled/cli/commands/sign.dart b/lib/src/precompiled/cli/commands/sign.dart new file mode 100644 index 0000000..ebfffd7 --- /dev/null +++ b/lib/src/precompiled/cli/commands/sign.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:bdk_dart/src/precompiled/util.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; + +// Sign a binary file using the PRIVATE_KEY env var. +Future run(List args) async { + if (args.length != 2 || args.contains('--help') || args.contains('-h')) { + stderr.writeln('Usage: sign '); + if (args.contains('--help') || args.contains('-h')) return; + exitCode = 2; + return; + } + + final privateKeyHex = Platform.environment['PRIVATE_KEY']; + if (privateKeyHex == null) { + stderr.writeln('Missing PRIVATE_KEY environment variable'); + exitCode = 2; + return; + } + final privateKeyBytes = decodeHex(privateKeyHex); + if (privateKeyBytes.length != 64) { + stderr.writeln('PRIVATE_KEY must be 64 bytes (hex-encoded)'); + exitCode = 2; + return; + } + + final inputFile = File(args[0]); + if (!inputFile.existsSync()) { + stderr.writeln('Input file does not exist: ${inputFile.path}'); + exitCode = 1; + return; + } + + final outFile = File(args[1]); + outFile.parent.createSync(recursive: true); + + final data = inputFile.readAsBytesSync(); + final signature = sign(PrivateKey(privateKeyBytes), data); + outFile.writeAsBytesSync(signature); +} From 8434ef405f102a199ee7583844b9bdc59f07d208 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 15 Jan 2026 16:00:00 -0500 Subject: [PATCH 13/42] Add CLI command: precompile_binaries --- .../cli/commands/precompile_binaries.dart | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 lib/src/precompiled/cli/commands/precompile_binaries.dart diff --git a/lib/src/precompiled/cli/commands/precompile_binaries.dart b/lib/src/precompiled/cli/commands/precompile_binaries.dart new file mode 100644 index 0000000..fb5936f --- /dev/null +++ b/lib/src/precompiled/cli/commands/precompile_binaries.dart @@ -0,0 +1,441 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:bdk_dart/src/precompiled/crate_hash.dart'; +import 'package:bdk_dart/src/precompiled/rust_toolchain.dart'; +import 'package:bdk_dart/src/precompiled/util.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:path/path.dart' as p; + +import '../support/os.dart'; +import '../support/process.dart'; + +// Build, sign, and upload precompiled artifacts to a GitHub release. +Future run(List args) async { + String? manifestDir; + String? cratePackage; + String? repository; + String? os; + String? androidSdkLocation; + String? androidNdkVersion; + String? androidMinSdkVersion; + var verbose = false; + + // Basic argument parsing. + for (final arg in args) { + if (arg == '--help' || arg == '-h') { + stdout.writeln(''' +Usage: precompile-binaries [options] + +Options: + --manifest-dir= Rust crate directory (required) + --crate-package= Cargo package name (required) + --repository= GitHub repository for releases (required) + --os= macos | linux | windows | android | ios | all (default: current) + --android-sdk-location= Android SDK root (required when --os=android) + --android-ndk-version= NDK version folder name (required when --os=android) + --android-min-sdk-version= Android minSdkVersion (required when --os=android) + -v, --verbose Verbose output +'''); + return; + } + if (arg == '-v' || arg == '--verbose') { + verbose = true; + continue; + } + if (arg.startsWith('--manifest-dir=')) { + manifestDir = arg.substring('--manifest-dir='.length).trim(); + continue; + } + if (arg.startsWith('--crate-package=')) { + cratePackage = arg.substring('--crate-package='.length).trim(); + continue; + } + if (arg.startsWith('--repository=')) { + repository = arg.substring('--repository='.length).trim(); + continue; + } + if (arg.startsWith('--os=')) { + os = arg.substring('--os='.length).trim(); + continue; + } + if (arg.startsWith('--android-sdk-location=')) { + androidSdkLocation = arg + .substring('--android-sdk-location='.length) + .trim(); + continue; + } + if (arg.startsWith('--android-ndk-version=')) { + androidNdkVersion = arg.substring('--android-ndk-version='.length).trim(); + continue; + } + if (arg.startsWith('--android-min-sdk-version=')) { + androidMinSdkVersion = arg + .substring('--android-min-sdk-version='.length) + .trim(); + continue; + } + if (arg.trim().isEmpty) continue; + stderr.writeln('Unknown argument: $arg'); + exitCode = 2; + return; + } + + if (manifestDir == null || manifestDir.trim().isEmpty) { + stderr.writeln('Missing --manifest-dir'); + exitCode = 2; + return; + } + final manifestDirNormalized = manifestDir.trim(); + final manifestPath = Directory(manifestDirNormalized).absolute.path; + if (cratePackage == null || cratePackage.trim().isEmpty) { + stderr.writeln('Missing --crate-package'); + exitCode = 2; + return; + } + if (repository == null || repository.trim().isEmpty) { + stderr.writeln('Missing --repository'); + exitCode = 2; + return; + } + os ??= Platform.operatingSystem; + + final normalizedOs = normalizeOs(os); + if (normalizedOs == null && os.trim().toLowerCase() != 'all') { + stderr.writeln('Unsupported --os=$os'); + exitCode = 2; + return; + } + + if (verbose) { + stderr.writeln( + 'Precompiling binaries for manifest: $manifestDirNormalized', + ); + stderr.writeln('Crate package: $cratePackage'); + stderr.writeln('Repository: $repository'); + stderr.writeln('OS filter: ${normalizedOs ?? 'all'}'); + } + + // Load toolchain + derive crate hash for the release tag. + final toolchain = RustToolchain.load(manifestPath); + final crateHash = CrateHash.compute(manifestPath); + final tag = 'precompiled_$crateHash'; + + // Signing key used for artifact verification. + final privateKeyHex = Platform.environment['PRIVATE_KEY']; + if (privateKeyHex == null) { + stderr.writeln('Missing PRIVATE_KEY environment variable'); + exitCode = 2; + return; + } + final privateKeyBytes = decodeHex(privateKeyHex); + if (privateKeyBytes.length != 64) { + stderr.writeln('PRIVATE_KEY must be 64 bytes (hex-encoded)'); + exitCode = 2; + return; + } + final privateKey = PrivateKey(privateKeyBytes); + + // Required for GitHub release creation/upload. + final ghToken = + Platform.environment['GH_TOKEN'] ?? + Platform.environment['GITHUB_TOKEN'] ?? + Platform.environment['GITHUB_TOKEN'.toUpperCase()]; + if (ghToken == null || ghToken.trim().isEmpty) { + stderr.writeln('Missing GH_TOKEN/GITHUB_TOKEN for GitHub release upload'); + exitCode = 2; + return; + } + + final targets = normalizedOs == null + ? toolchain.targets + : toolchain.targetsForOs(normalizedOs); + if (targets.isEmpty) { + stderr.writeln( + 'No toolchain targets found for os=${normalizedOs ?? 'all'}', + ); + exitCode = 1; + return; + } + + String? abiForTarget(String t) => switch (t) { + 'armv7-linux-androideabi' => 'armeabi-v7a', + 'aarch64-linux-android' => 'arm64-v8a', + 'x86_64-linux-android' => 'x86_64', + _ => null, + }; + + final buildableTargets = normalizedOs == 'android' + ? targets.where((t) => abiForTarget(t) != null).toList(growable: false) + : List.from(targets); + + // If release already contains assets, avoid duplicate builds. + final releaseHasAssets = buildableTargets.isNotEmpty + ? await _releaseHasAllAssets(tag: tag, targets: buildableTargets) + : false; + + // Local build and upload staging folders. + final buildDir = Directory('precompiled_build'); + final uploadDir = Directory('precompiled_upload'); + final buildDirAbs = Directory( + p.join(Directory.current.absolute.path, buildDir.path), + ); + final uploadDirAbs = Directory( + p.join(Directory.current.absolute.path, uploadDir.path), + ); + + await _ensureReleaseExists(tag: tag, crateHash: crateHash); + if (releaseHasAssets) { + if (verbose) { + stderr.writeln( + 'Release $tag already contains assets for ${buildableTargets.join(', ')}; skipping build.', + ); + } + return; + } + + if (buildDirAbs.existsSync()) buildDirAbs.deleteSync(recursive: true); + if (uploadDirAbs.existsSync()) uploadDirAbs.deleteSync(recursive: true); + buildDirAbs.createSync(recursive: true); + uploadDirAbs.createSync(recursive: true); + + // Android builds use cargo-ndk with ABI mapping. + if (normalizedOs == 'android') { + if (androidSdkLocation == null || androidSdkLocation.trim().isEmpty) { + stderr.writeln( + 'Missing --android-sdk-location (required for --os=android)', + ); + exitCode = 2; + return; + } + if (androidNdkVersion == null || androidNdkVersion.trim().isEmpty) { + stderr.writeln( + 'Missing --android-ndk-version (required for --os=android)', + ); + exitCode = 2; + return; + } + if (androidMinSdkVersion == null || androidMinSdkVersion.trim().isEmpty) { + stderr.writeln( + 'Missing --android-min-sdk-version (required for --os=android)', + ); + exitCode = 2; + return; + } + + final ndkHome = '$androidSdkLocation/ndk/$androidNdkVersion'; + final env = { + ...Platform.environment, + 'ANDROID_SDK_ROOT': androidSdkLocation, + 'ANDROID_NDK_HOME': ndkHome, + 'ANDROID_NDK_ROOT': ndkHome, + }; + + final buildOut = Directory(p.join(buildDirAbs.path, 'android')); + buildOut.createSync(recursive: true); + + String? abiForTarget(String t) => switch (t) { + 'armv7-linux-androideabi' => 'armeabi-v7a', + 'aarch64-linux-android' => 'arm64-v8a', + 'x86_64-linux-android' => 'x86_64', + _ => null, + }; + + for (final target in buildableTargets) { + final abi = abiForTarget(target)!; + if (verbose) + stderr.writeln('Building Android target: $target (abi=$abi)'); + + await runOrThrow( + 'rustup', + ['target', 'add', target, '--toolchain', toolchain.channel], + verbose: verbose, + environment: env, + ); + + await runOrThrow( + 'rustup', + [ + 'run', + toolchain.channel, + 'cargo', + 'ndk', + '--platform', + androidMinSdkVersion, + '-t', + abi, + '-o', + buildOut.absolute.path, + 'build', + '--manifest-path', + '$manifestPath/Cargo.toml', + '--package', + cratePackage, + '--release', + '--locked', + ], + verbose: verbose, + environment: env, + workingDirectory: manifestPath, + ); + + final soPath = p.join(buildOut.path, abi, 'lib$cratePackage.so'); + final soFile = File(soPath); + if (!soFile.existsSync()) { + stderr.writeln('Expected Android artifact not found: $soPath'); + exitCode = 1; + return; + } + + final outPath = p.join( + uploadDirAbs.path, + '${target}_lib$cratePackage.so', + ); + final outFile = File(outPath)..writeAsBytesSync(soFile.readAsBytesSync()); + final sig = sign(privateKey, outFile.readAsBytesSync()); + File('$outPath.sig').writeAsBytesSync(sig); + if (verbose) stderr.writeln('Prepared: $outPath (+ .sig)'); + } + } else { + for (final target in buildableTargets) { + if (verbose) stderr.writeln('Building target: $target'); + await runOrThrow('rustup', [ + 'target', + 'add', + target, + '--toolchain', + toolchain.channel, + ], verbose: verbose); + await runOrThrow( + 'rustup', + [ + 'run', + toolchain.channel, + 'cargo', + 'build', + '--manifest-path', + '$manifestPath/Cargo.toml', + '--package', + cratePackage, + '--release', + '--locked', + '--target', + target, + '--target-dir', + buildDirAbs.path, + ], + verbose: verbose, + workingDirectory: manifestPath, + ); + + final artDir = Directory(p.join(buildDirAbs.path, target, 'release')); + if (!artDir.existsSync()) { + stderr.writeln('Missing artifact directory: ${artDir.path}'); + exitCode = 1; + return; + } + + final artifacts = []; + for (final ent in artDir.listSync(followLinks: false)) { + if (ent is! File) continue; + final name = ent.uri.pathSegments.last; + final ok = + name.startsWith('lib$cratePackage.') || + name.startsWith('$cratePackage.') || + name == 'lib$cratePackage.a'; + if (ok) artifacts.add(ent); + } + + if (artifacts.isEmpty) { + stderr.writeln( + 'No artifacts found in ${artDir.path} for $cratePackage', + ); + exitCode = 1; + return; + } + + for (final file in artifacts) { + final base = file.uri.pathSegments.last; + final outPath = p.join(uploadDirAbs.path, '${target}_$base'); + final outFile = File(outPath); + outFile.writeAsBytesSync(file.readAsBytesSync()); + final sig = sign(privateKey, outFile.readAsBytesSync()); + File('$outPath.sig').writeAsBytesSync(sig); + if (verbose) stderr.writeln('Prepared: $outPath (+ .sig)'); + } + } + } + + // Upload all artifacts for this hash. + await runOrThrow('gh', [ + 'release', + 'upload', + tag, + '${uploadDirAbs.path}/*', + '--clobber', + ], verbose: verbose); +} + +Future _ensureReleaseExists({ + required String tag, + required String crateHash, +}) async { + final view = await Process.run('gh', ['release', 'view', tag]); + if (view.exitCode == 0) return; + final create = await Process.run('gh', [ + 'release', + 'create', + tag, + '--title', + 'Precompiled binaries $crateHash', + '--notes', + 'Precompiled binaries for crate hash $crateHash.', + ]); + stdout.write(create.stdout); + stderr.write(create.stderr); + if (create.exitCode != 0) { + exitCode = create.exitCode; + throw StateError('Failed to create release $tag'); + } +} + +Future _releaseHasAllAssets({ + required String tag, + required List targets, +}) async { + final view = await Process.run('gh', [ + 'release', + 'view', + tag, + '--json', + 'assets', + '--jq', + '.assets[].name', + ]); + if (view.exitCode != 0) { + return false; + } + final output = view.stdout.toString(); + final assets = LineSplitter.split( + output, + ).map((name) => name.trim()).where((name) => name.isNotEmpty).toSet(); + if (assets.isEmpty) { + return false; + } + + for (final target in targets) { + final prefix = '${target}_'; + final binaryNames = assets + .where((name) => name.startsWith(prefix) && !name.endsWith('.sig')) + .toList(); + if (binaryNames.isEmpty) { + return false; + } + final hasPair = binaryNames.any((name) => assets.contains('$name.sig')); + if (!hasPair) { + return false; + } + } + + return true; +} From e2781fe38b6e69d8e05f9dbe2911dd083bd06221 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 16 Jan 2026 09:00:00 -0500 Subject: [PATCH 14/42] Add build tool executable --- bin/build_tool.dart | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 bin/build_tool.dart diff --git a/bin/build_tool.dart b/bin/build_tool.dart new file mode 100644 index 0000000..17a3b86 --- /dev/null +++ b/bin/build_tool.dart @@ -0,0 +1,6 @@ +import 'package:bdk_dart/src/precompiled/cli/cli.dart'; + +Future main(List args) async { + await runCli(args); +} + From 0d197a244ed327ae77cc3c4f427c1e79fa91b61b Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 16 Jan 2026 11:00:00 -0500 Subject: [PATCH 15/42] Add tests for precompiled binaries --- test/precompiled/cargo_test.dart | 22 ++++++ test/precompiled/crate_hash_test.dart | 89 ++++++++++++++++++++++ test/precompiled/options_test.dart | 103 ++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 test/precompiled/cargo_test.dart create mode 100644 test/precompiled/crate_hash_test.dart create mode 100644 test/precompiled/options_test.dart diff --git a/test/precompiled/cargo_test.dart b/test/precompiled/cargo_test.dart new file mode 100644 index 0000000..52549f3 --- /dev/null +++ b/test/precompiled/cargo_test.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +import 'package:bdk_dart/src/precompiled/cargo.dart'; + +void main() { + test('CrateInfo.load extracts the package name', () { + final dir = Directory.systemTemp.createTempSync('cargo-test-'); + addTearDown(() => dir.deleteSync(recursive: true)); + + final manifest = File(path.join(dir.path, 'Cargo.toml')); + manifest.writeAsStringSync(''' +[package] +name = "bdk_dart_ffi" +'''); + + final info = CrateInfo.load(dir.path); + expect(info.packageName, 'bdk_dart_ffi'); + }); +} diff --git a/test/precompiled/crate_hash_test.dart b/test/precompiled/crate_hash_test.dart new file mode 100644 index 0000000..58debf6 --- /dev/null +++ b/test/precompiled/crate_hash_test.dart @@ -0,0 +1,89 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +import 'package:bdk_dart/src/precompiled/crate_hash.dart'; + +void main() { + group('CrateHash', () { + test('produces stable hash when pubspec config order changes', () { + final root = Directory.systemTemp.createTempSync('crate-hash-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final crateDir = _prepareCrate(root); + final firstHash = CrateHash.compute(crateDir.path); + + final pubspec = File(path.join(root.path, 'pubspec.yaml')); + pubspec.writeAsStringSync(''' +name: sample +bdk_dart: + precompiled_binaries: + public_key: ${_hexKey(64)} + artifact_host: owner/repo +'''); + + final secondHash = CrateHash.compute(crateDir.path); + expect(secondHash, equals(firstHash)); + }); + + test('hash changes when the precompiled configuration changes', () { + final root = Directory.systemTemp.createTempSync('crate-hash-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final crateDir = _prepareCrate(root); + final original = CrateHash.compute(crateDir.path); + + final pubspec = File(path.join(root.path, 'pubspec.yaml')); + pubspec.writeAsStringSync(''' +name: sample +bdk_dart: + precompiled_binaries: + artifact_host: other/repo + public_key: ${_hexKey(64)} +'''); + + final updated = CrateHash.compute(crateDir.path); + expect(updated, isNot(equals(original))); + }); + + test('collectFiles sees cargo manifest and src code', () { + final root = Directory.systemTemp.createTempSync('crate-hash-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final crateDir = _prepareCrate(root); + final files = CrateHash.collectFiles(crateDir.path); + final basenames = files.map((file) => path.basename(file.path)).toSet(); + + expect(basenames, contains('Cargo.toml')); + expect(files.any((file) => file.path.contains(path.join('src', 'lib.rs'))), + isTrue); + }); + }); +} + +Directory _prepareCrate(Directory root) { + final crateDir = Directory(path.join(root.path, 'native')); + crateDir.createSync(recursive: true); + + File(path.join(crateDir.path, 'Cargo.toml')) + .writeAsStringSync('[package]\nname = "bdk_test"\nversion = "0.1.0"\n'); + File(path.join(crateDir.path, 'Cargo.lock')).writeAsStringSync('# empty\n'); + File(path.join(crateDir.path, 'build.rs')).writeAsStringSync(''); + + final srcFile = File(path.join(crateDir.path, 'src', 'lib.rs')); + srcFile.createSync(recursive: true); + srcFile.writeAsStringSync('pub fn hello() {}\n'); + + File(path.join(root.path, 'pubspec.yaml')).writeAsStringSync(''' +name: sample +bdk_dart: + precompiled_binaries: + artifact_host: owner/repo + public_key: ${_hexKey(64)} +'''); + + return crateDir; +} + +String _hexKey(int length) => ''.padLeft(length, 'a'); diff --git a/test/precompiled/options_test.dart b/test/precompiled/options_test.dart new file mode 100644 index 0000000..42eeee2 --- /dev/null +++ b/test/precompiled/options_test.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:bdk_dart/src/precompiled/options.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +void main() { + group('PrecompiledBinariesConfig', () { + test( + 'normalizes artifact_host into owner/repo and respects mode aliases', + () { + final node = + loadYamlNode(''' +artifact_host: https://github.com/nucode-tech/bdk-dart/ +public_key: ${_hexKey(64)} +mode: download +''') + as YamlMap; + + final config = PrecompiledBinariesConfig.parse(node); + expect(config.mode, PrecompiledBinaryMode.always); + expect(config.artifactHost, 'nucode-tech/bdk-dart'); + + final fileUrl = config.fileUrl(crateHash: 'abc123', fileName: 'asset'); + expect( + fileUrl.toString(), + 'https://github.com/nucode-tech/bdk-dart/releases/download/precompiled_abc123/asset', + ); + }, + ); + + test('uses url_prefix when configured and leaves artifact host empty', () { + final node = + loadYamlNode(''' +url_prefix: http://example.com/exports/ +public_key: ${_hexKey(64)} +''') + as YamlMap; + + final config = PrecompiledBinariesConfig.parse(node); + expect(config.artifactHost, isEmpty); + + final fileUrl = config.fileUrl(crateHash: 'hash', fileName: 'binary'); + expect(fileUrl.toString(), 'http://example.com/exports/hash/binary'); + }); + }); + + group('PubspecOptions', () { + test('returns null when pubspec is missing', () { + final root = Directory.systemTemp.createTempSync('precompiled-options-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final options = PubspecOptions.load( + packageRoot: root.path, + pluginConfigKey: 'bdk_dart', + ); + expect(options.precompiledBinaries, isNull); + }); + + test('loads precompiled configuration from pubspec', () { + final root = Directory.systemTemp.createTempSync('precompiled-options-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final pubspecFile = File(path.join(root.path, 'pubspec.yaml')); + pubspecFile.writeAsStringSync(''' +name: sample +bdk_dart: + precompiled_binaries: + artifact_host: nuCode-Tech/bdk-dart + public_key: ${_hexKey(64)} +'''); + + final options = PubspecOptions.load( + packageRoot: root.path, + pluginConfigKey: 'bdk_dart', + ); + expect(options.precompiledBinaries, isNotNull); + expect(options.precompiledBinaries!.artifactHost, 'nuCode-Tech/bdk-dart'); + }); + + test('loadModeOverride honors invoker pubspec mode overrides', () { + final root = Directory.systemTemp.createTempSync('precompiled-mode-'); + addTearDown(() => root.deleteSync(recursive: true)); + + final pubspecFile = File(path.join(root.path, 'pubspec.yaml')); + pubspecFile.writeAsStringSync(''' +name: invoker +bdk_dart: + precompiled_binaries: + mode: download +'''); + + final mode = PubspecOptions.loadModeOverride( + packageRoot: root.path, + packageName: 'bdk_dart', + ); + expect(mode, PrecompiledBinaryMode.always); + }); + }); +} + +String _hexKey(int length) => ''.padLeft(length, 'a'); From 675d6e9b86914f993251fff5b4d6a30e8b014bc5 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 16 Jan 2026 14:00:00 -0500 Subject: [PATCH 16/42] Add documentation for precompiled binaries --- docs/precompiled_binaries.md | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/precompiled_binaries.md diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md new file mode 100644 index 0000000..52eb931 --- /dev/null +++ b/docs/precompiled_binaries.md @@ -0,0 +1,83 @@ +# Precompiled binaries (maintainers) + +This document describes how precompiled binaries are built, signed, and published for the plugin. + +## Overview + +- CI builds and uploads precompiled binaries via `.github/workflows/precompile_binaries.yml`. +- Artifacts are tagged by the crate hash and uploaded to a GitHub release. +- Each binary is signed with an Ed25519 key; the public key is embedded in `pubspec.yaml`. +- The build hook tries to download a verified binary first and falls back to a local build if needed. + +## CI workflow + +The workflow runs on `push` to `main` and on manual dispatch. It invokes: + +``` +dart run bin/build_tool.dart precompile-binaries ... +``` + +It currently builds macOS/iOS and Android targets. + +## Release expectations + +- The workflow creates/releases a GitHub release named `precompiled_` where `` comes from the verified crate sources and config. +- If the release already exists, the workflow uploads missing assets without rebuilding anything already present. +- If `gh release view precompiled_` fails locally, rerun `dart run bin/build_tool.dart precompile-binaries ...` with the same crate hash to recreate or update the release. + +## How the download works + +- The crate hash is computed from the Rust crate sources plus the plugin's `precompiled_binaries` config. +- The release tag is `precompiled_`. +- Assets are named `_` with a matching `.sig` file. +- Each binary is paired with the `.sig` file that the hook uses to verify the download before applying it. +- The hook chooses the correct `lib$cratePackage` (or `lib$cratePackage.so`) artifact by matching the target triple and link mode from the Dart build config. +- On build, the hook downloads the signature and binary, verifies it, then places it in the build output. +- If any step fails (missing asset, bad signature), the hook builds locally via the standard build hook. + +## Manual release (local) + +Use this when debugging CI or producing artifacts manually. + +Required environment variables: + +- `PRIVATE_KEY` (Ed25519 private key, hex-encoded, 64 bytes) +- `GH_TOKEN` or `GITHUB_TOKEN` (GitHub token with release upload permissions) + +Example: + +``` +dart run bin/build_tool.dart precompile-binaries \ + --manifest-dir="native" \ + --crate-package="bdk_dart_ffi" \ + --repository="owner/repo" \ + --os=macos +``` + +## Troubleshooting & ops tips + +- If `gh release view precompiled_` shows a release without the expected `_` assets, rerun the build locally to regenerate them. +- A stale crate hash (because sources or `precompiled_binaries` config changed) will point to a release that either doesn’t exist yet or lacks current binaries; re-run `dart run bin/build_tool.dart hash --manifest-dir=native` to confirm the hash and rebuild with the same inputs. +- Use `gh release view precompiled_ --json assets --jq '.assets[].name'` to inspect what’s uploaded and verify `.sig` coverage. +- When debugging download failures, set `BDK_DART_PRECOMPILED_VERBOSE=1` to see why the hook skipped an asset. + +## Configuration knobs + +- `rust-toolchain.toml` controls the Rust channel and target list. +- `pubspec.yaml` under `bdk_dart.precompiled_binaries` must include: + - `artifact_host` (owner/repo) + - `public_key` (Ed25519 public key, hex-encoded, 32 bytes) + +## Environment, keys, and secrets + +- `PRIVATE_KEY`: 64-byte hex string (Ed25519 private key). This must be set locally or as a GitHub Actions secret before running `precompile-binaries`. Keep it out of source control. +- `PUBLIC_KEY`: Add the matching 32-byte hex public key to `pubspec.yaml` so consumers can verify downloads. +- `GH_TOKEN` / `GITHUB_TOKEN`: release upload permissions (already used in the CI workflow). +- `BDK_DART_PRECOMPILED_VERBOSE=1`: optional; shows download and verification details when debugging consumer builds. + +Generate a keypair with `dart run bin/build_tool.dart gen-key` and copy the printed `PRIVATE_KEY`/`PUBLIC_KEY` values. Rotate the pair if you ever suspect the signing key was exposed, and update every release’s config accordingly. + +## Security reminder + +- Treat the `PRIVATE_KEY` used for signing as highly sensitive; do not commit it to version control and rotate it immediately if you suspect compromise. +- Update the public key in `pubspec.yaml` if the private key is rotated so consumers can still verify downloads. From e9051e69a65602d6d157bb71be688f84f1f99737 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 09:00:00 -0500 Subject: [PATCH 17/42] Add CI workflow for precompiling binaries --- .github/workflows/precompile_binaries.yml | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/precompile_binaries.yml diff --git a/.github/workflows/precompile_binaries.yml b/.github/workflows/precompile_binaries.yml new file mode 100644 index 0000000..bfb0bc2 --- /dev/null +++ b/.github/workflows/precompile_binaries.yml @@ -0,0 +1,88 @@ +name: Precompile Binaries + +on: + workflow_call: + secrets: + PRECOMPILED_PRIVATE_KEY: + required: true + workflow_dispatch: + +permissions: + contents: write + +env: + CRATE_DIR: "native" + CRATE_PACKAGE: "bdk_dart_ffi" + +jobs: + precompile_macos_ios: + runs-on: macos-latest + env: + PRIVATE_KEY: ${{ secrets.PRECOMPILED_PRIVATE_KEY }} + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: dart-lang/setup-dart@v1 + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + - name: Pub get + run: dart pub get + - name: Precompile (macOS + iOS) + run: | + set -euo pipefail + dart run bin/build_tool.dart precompile-binaries \ + -v \ + --os=macos \ + --manifest-dir="${CRATE_DIR}" \ + --crate-package="${CRATE_PACKAGE}" \ + --repository="${GITHUB_REPOSITORY}" + + precompile_android: + runs-on: ubuntu-latest + env: + PRIVATE_KEY: ${{ secrets.PRECOMPILED_PRIVATE_KEY }} + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + ANDROID_NDK_VERSION: "26.3.11579264" + ANDROID_MIN_SDK: "23" + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: dart-lang/setup-dart@v1 + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + - name: Install NDK + run: | + set -euo pipefail + sdkmanager --install "ndk;${ANDROID_NDK_VERSION}" + - name: Install cargo-ndk + run: cargo install cargo-ndk --locked + - name: Pub get + run: dart pub get + - name: Precompile (Android) + env: + ANDROID_SDK_ROOT: ${{ env.ANDROID_SDK_ROOT }} + run: | + set -euo pipefail + ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" + dart run bin/build_tool.dart precompile-binaries \ + -v \ + --os=android \ + --manifest-dir="${CRATE_DIR}" \ + --crate-package="${CRATE_PACKAGE}" \ + --repository="${GITHUB_REPOSITORY}" \ + --android-sdk-location="${ANDROID_SDK_ROOT}" \ + --android-ndk-version="${ANDROID_NDK_VERSION}" \ + --android-min-sdk-version="${ANDROID_MIN_SDK}" + From 518cfa5ab7301b4c553224847105e9cfb4dd7a6e Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 11:00:00 -0500 Subject: [PATCH 18/42] Integrate precompile workflow into main CI --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 403e2bc..5623342 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main, "**"] + branches: [main] pull_request: branches: [main] @@ -62,3 +62,11 @@ jobs: - name: Run tests run: | dart test + + precompile-binaries: + needs: build-and-test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: write + uses: ./.github/workflows/precompile_binaries.yml + secrets: inherit From 7728dda2d8a8c1617df486382c99d538bd0f4b9b Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 14:00:00 -0500 Subject: [PATCH 19/42] Update build hook to use precompiled builder --- hook/build.dart | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/hook/build.dart b/hook/build.dart index 7e7790e..46d6192 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,10 +1,25 @@ +import 'package:bdk_dart/src/precompiled/precompiled_builder.dart'; import 'package:hooks/hooks.dart'; -import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:native_toolchain_rust/native_toolchain_rust.dart' as ntr; void main(List args) async { await build(args, (input, output) async { - await const RustBuilder( + final builder = PrecompiledBuilder( assetName: 'uniffi:bdk_dart_ffi', - ).run(input: input, output: output); + buildModeName: ntr.BuildMode.release.name, + fallback: (input, output, assetRouting, logger) async { + final rustBuilder = ntr.RustBuilder( + assetName: 'uniffi:bdk_dart_ffi', + buildMode: ntr.BuildMode.release, + ); + await rustBuilder.run( + input: input, + output: output, + assetRouting: assetRouting, + logger: logger, + ); + }, + ); + await builder.run(input: input, output: output); }); } From 8576a9cb6baeb2450831ac43d0af2b696d06e421 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 16:00:00 -0500 Subject: [PATCH 20/42] Add dependencies and configuration to pubspec.yaml --- pubspec.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index 1499649..a45797d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,10 +7,27 @@ environment: dependencies: ffi: ^2.1.4 + code_assets: ^1.0.0 + convert: ^3.1.2 + crypto: ^3.0.7 + ed25519_edwards: ^0.3.1 + http: ^1.2.2 hooks: ^1.0.0 + logging: ^1.0.0 native_toolchain_rust: ^1.0.0 + path: ^1.9.0 + toml: ^0.17.0 + yaml: ^3.1.2 dev_dependencies: test: ^1.26.2 hooks: build: hook/build.dart + +executables: + build_tool: build_tool + +bdk_dart: + precompiled_binaries: + artifact_host: nuCode-Tech/bdk-dart + public_key: 4a13d74312b96c0b8a98f9cc48540c8bf083a47a34bd3ff691a940bef44ee4dc From fbf3f6fad6d0d773b5f344e81b75174fb8a8841a Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 18:00:00 -0500 Subject: [PATCH 21/42] Update .gitignore and README documentation --- .gitignore | 1 - README.md | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3adc1b6..8b1f506 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /target native/target/ **/*.rs.bk -Cargo.lock # Flutter/Dart **/build/ diff --git a/README.md b/README.md index f0793af..d9777d3 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,28 @@ If you have the Rust toolchain installed, the native library will be automatical As a user of the package, you don't need to worry about building the native library or bindings yourself. Only if you want to contribute to the bindings or modify the native code yourself, you can follow the instructions in [development](#development) below. +## Precompiled binaries + +This plugin adds a precompiled-binary layer on top of the standard Native Assets approach. +When enabled, the build hook tries to download a signed precompiled binary for your target first. +If no matching binary is available or verification fails, it falls back to building from scratch via the Flutter/Dart build hook. +This gives consumers a choice between using published binaries or building locally. + +### pubspec.yaml configuration + +In your app's `pubspec.yaml`, add the `bdk_dart` section at the top level (next to `dependencies`), like: + +```yaml +bdk_dart: + precompiled_binaries: + mode: auto # auto | always | never +``` + +`mode` controls when the precompiled path is used: +- `auto` prefers precompiled binaries when available, otherwise builds locally +- `always` requires precompiled binaries and skips local builds +- `never` always builds from source via the build hook + ## Development ### Generating bindings @@ -80,6 +102,10 @@ Dart test suite, which covers wallet creation, persistence, offline behavior, an dart test ``` +### Precompiled binaries (maintainers) + +See `docs/precompiled_binaries.md` for CI details, manual release steps, and configuration. + ## License The Rust crate and generated bindings are dual-licensed under MIT or Apache 2.0 per the From e73cf33deaf906eac3d66d87f368229382330f09 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 15 Jan 2026 10:00:00 -0500 Subject: [PATCH 22/42] Update artifact host to bitcoindevkit/bdk-dart --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a45797d..028581c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,5 +29,5 @@ executables: bdk_dart: precompiled_binaries: - artifact_host: nuCode-Tech/bdk-dart + artifact_host: bitcoindevkit/bdk-dart public_key: 4a13d74312b96c0b8a98f9cc48540c8bf083a47a34bd3ff691a940bef44ee4dc From d7d4a3ec11158f73a17aeedaa12fdfecbbfc2f96 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 16 Jan 2026 11:30:00 -0500 Subject: [PATCH 23/42] Update artifact host references in tests --- test/precompiled/options_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/precompiled/options_test.dart b/test/precompiled/options_test.dart index 42eeee2..8727b05 100644 --- a/test/precompiled/options_test.dart +++ b/test/precompiled/options_test.dart @@ -12,7 +12,7 @@ void main() { () { final node = loadYamlNode(''' -artifact_host: https://github.com/nucode-tech/bdk-dart/ +artifact_host: https://github.com/bitcoindevkit/bdk-dart/ public_key: ${_hexKey(64)} mode: download ''') @@ -20,12 +20,12 @@ mode: download final config = PrecompiledBinariesConfig.parse(node); expect(config.mode, PrecompiledBinaryMode.always); - expect(config.artifactHost, 'nucode-tech/bdk-dart'); + expect(config.artifactHost, 'bitcoindevkit/bdk-dart'); final fileUrl = config.fileUrl(crateHash: 'abc123', fileName: 'asset'); expect( fileUrl.toString(), - 'https://github.com/nucode-tech/bdk-dart/releases/download/precompiled_abc123/asset', + 'https://github.com/bitcoindevkit/bdk-dart/releases/download/precompiled_abc123/asset', ); }, ); @@ -67,7 +67,7 @@ public_key: ${_hexKey(64)} name: sample bdk_dart: precompiled_binaries: - artifact_host: nuCode-Tech/bdk-dart + artifact_host: bitcoindevkit/bdk-dart public_key: ${_hexKey(64)} '''); @@ -76,7 +76,7 @@ bdk_dart: pluginConfigKey: 'bdk_dart', ); expect(options.precompiledBinaries, isNotNull); - expect(options.precompiledBinaries!.artifactHost, 'nuCode-Tech/bdk-dart'); + expect(options.precompiledBinaries!.artifactHost, 'bitcoindevkit/bdk-dart'); }); test('loadModeOverride honors invoker pubspec mode overrides', () { From c062e86b0593414988267dc0da6875afe492d9b2 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 17 Jan 2026 14:15:00 -0500 Subject: [PATCH 24/42] Format crate_hash.dart code --- lib/src/precompiled/crate_hash.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/src/precompiled/crate_hash.dart b/lib/src/precompiled/crate_hash.dart index 54547c8..fb30ec9 100644 --- a/lib/src/precompiled/crate_hash.dart +++ b/lib/src/precompiled/crate_hash.dart @@ -11,8 +11,10 @@ import 'package:yaml/yaml.dart'; class CrateHash { // Compute (and optionally cache) the crate hash. static String compute(String manifestDir, {String? tempStorage}) { - return CrateHash._(manifestDir: manifestDir, tempStorage: tempStorage) - ._compute(); + return CrateHash._( + manifestDir: manifestDir, + tempStorage: tempStorage, + )._compute(); } CrateHash._({required this.manifestDir, required this.tempStorage}); @@ -87,8 +89,9 @@ class CrateHash { final yamlContent = file.readAsStringSync(); final doc = loadYaml(yamlContent); final extensionSection = doc is YamlMap ? doc['bdk_dart'] : null; - final precompiled = - extensionSection is YamlMap ? extensionSection['precompiled_binaries'] : null; + final precompiled = extensionSection is YamlMap + ? extensionSection['precompiled_binaries'] + : null; final normalized = _normalizeYaml(precompiled ?? {}); input.add(utf8.encode('pubspec.yaml:bdk_dart.precompiled_binaries:')); input.add(utf8.encode(jsonEncode(normalized))); @@ -168,4 +171,3 @@ class CrateHash { return files; } } - From 8ed8ef691d8d834d0831744af6d2439e6aaf20a4 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sun, 18 Jan 2026 09:45:00 -0500 Subject: [PATCH 25/42] Format rust_toolchain.dart code --- lib/src/precompiled/rust_toolchain.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/src/precompiled/rust_toolchain.dart b/lib/src/precompiled/rust_toolchain.dart index 8209e3b..328b1ab 100644 --- a/lib/src/precompiled/rust_toolchain.dart +++ b/lib/src/precompiled/rust_toolchain.dart @@ -5,10 +5,7 @@ import 'package:toml/toml.dart'; // Parses rust-toolchain.toml and exposes channel/targets. class RustToolchain { - RustToolchain._({ - required this.channel, - required this.targets, - }); + RustToolchain._({required this.channel, required this.targets}); final String channel; final List targets; @@ -37,10 +34,7 @@ class RustToolchain { final targets = targetsRaw is List ? targetsRaw.whereType().toList(growable: false) : const []; - return RustToolchain._( - channel: channel.trim(), - targets: targets, - ); + return RustToolchain._(channel: channel.trim(), targets: targets); } // Filter targets for a given OS label. @@ -73,10 +67,10 @@ class RustToolchain { 'ios' => t.contains('apple-ios'), 'linux' => t.endsWith('unknown-linux-gnu'), 'windows' => t.endsWith('pc-windows-msvc'), - 'android' => t.endsWith('linux-android') || t.endsWith('linux-androideabi'), + 'android' => + t.endsWith('linux-android') || t.endsWith('linux-androideabi'), _ => false, }; return targets.where(include).toList(growable: false); } } - From 5edfa04ccd252802846056f2faffad54119564fd Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Mon, 19 Jan 2026 16:20:00 -0500 Subject: [PATCH 26/42] Format crate_hash_test.dart code --- test/precompiled/crate_hash_test.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/precompiled/crate_hash_test.dart b/test/precompiled/crate_hash_test.dart index 58debf6..05d28fa 100644 --- a/test/precompiled/crate_hash_test.dart +++ b/test/precompiled/crate_hash_test.dart @@ -56,8 +56,10 @@ bdk_dart: final basenames = files.map((file) => path.basename(file.path)).toSet(); expect(basenames, contains('Cargo.toml')); - expect(files.any((file) => file.path.contains(path.join('src', 'lib.rs'))), - isTrue); + expect( + files.any((file) => file.path.contains(path.join('src', 'lib.rs'))), + isTrue, + ); }); }); } @@ -66,8 +68,9 @@ Directory _prepareCrate(Directory root) { final crateDir = Directory(path.join(root.path, 'native')); crateDir.createSync(recursive: true); - File(path.join(crateDir.path, 'Cargo.toml')) - .writeAsStringSync('[package]\nname = "bdk_test"\nversion = "0.1.0"\n'); + File( + path.join(crateDir.path, 'Cargo.toml'), + ).writeAsStringSync('[package]\nname = "bdk_test"\nversion = "0.1.0"\n'); File(path.join(crateDir.path, 'Cargo.lock')).writeAsStringSync('# empty\n'); File(path.join(crateDir.path, 'build.rs')).writeAsStringSync(''); From c572e7715d9c42a4f3459e34afaaf5bbc3851f1f Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 20 Jan 2026 13:10:00 -0500 Subject: [PATCH 27/42] Remove trailing newlines from CLI files --- lib/src/precompiled/cli/cli.dart | 1 - lib/src/precompiled/cli/support/os.dart | 1 - lib/src/precompiled/cli/support/process.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/src/precompiled/cli/cli.dart b/lib/src/precompiled/cli/cli.dart index 116a915..630080e 100644 --- a/lib/src/precompiled/cli/cli.dart +++ b/lib/src/precompiled/cli/cli.dart @@ -54,4 +54,3 @@ Commands: Run 'dart run bdk_dart --help' for command-specific help. '''); } - diff --git a/lib/src/precompiled/cli/support/os.dart b/lib/src/precompiled/cli/support/os.dart index 58089fe..87e68be 100644 --- a/lib/src/precompiled/cli/support/os.dart +++ b/lib/src/precompiled/cli/support/os.dart @@ -11,4 +11,3 @@ String? normalizeOs(String raw) { _ => v.isEmpty ? null : v, }; } - diff --git a/lib/src/precompiled/cli/support/process.dart b/lib/src/precompiled/cli/support/process.dart index 9273c99..c590774 100644 --- a/lib/src/precompiled/cli/support/process.dart +++ b/lib/src/precompiled/cli/support/process.dart @@ -22,4 +22,3 @@ Future runOrThrow( throw StateError('Command failed: $exe ${args.join(' ')}'); } } - From 0087a44ea7a14b0bd0fc1a4114a4a605ae969f8f Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 21 Jan 2026 15:30:00 -0500 Subject: [PATCH 28/42] Remove trailing newline from target.dart --- lib/src/precompiled/target.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/precompiled/target.dart b/lib/src/precompiled/target.dart index 7d8f489..a38a968 100644 --- a/lib/src/precompiled/target.dart +++ b/lib/src/precompiled/target.dart @@ -39,4 +39,3 @@ extension CodeConfigTargetTriple on CodeConfig { }; } } - From 4e3094ec72f29b9c39fe4ce968b75be9869f7a8d Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 14 Jan 2026 21:04:40 -0500 Subject: [PATCH 29/42] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9777d3..8dd39f2 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Only if you want to contribute to the bindings or modify the native code yoursel ## Precompiled binaries This plugin adds a precompiled-binary layer on top of the standard Native Assets approach. -When enabled, the build hook tries to download a signed precompiled binary for your target first. +Depending on the mode configuration, the build hook may download signed precompiled binaries or build locally. If no matching binary is available or verification fails, it falls back to building from scratch via the Flutter/Dart build hook. This gives consumers a choice between using published binaries or building locally. From 00b0daf86d0dee501824fb21e3ec0e954e69ffc2 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 22 Jan 2026 09:15:00 -0500 Subject: [PATCH 30/42] Pin Rust toolchain to 1.85.1 in CI workflow --- .github/workflows/precompile_binaries.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/precompile_binaries.yml b/.github/workflows/precompile_binaries.yml index bfb0bc2..e093564 100644 --- a/.github/workflows/precompile_binaries.yml +++ b/.github/workflows/precompile_binaries.yml @@ -26,7 +26,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.85.1 override: true - uses: dart-lang/setup-dart@v1 - uses: subosito/flutter-action@v2 @@ -57,7 +57,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.85.1 override: true - uses: dart-lang/setup-dart@v1 - name: Set up Android SDK From 8412bc8c76dc2988ab6db1f872d1c1fdbbce1fb4 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Thu, 22 Jan 2026 14:30:00 -0500 Subject: [PATCH 31/42] Add exception class for required precompiled binaries --- lib/src/precompiled/artifacts_provider.dart | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/src/precompiled/artifacts_provider.dart b/lib/src/precompiled/artifacts_provider.dart index 54d59f1..220dbed 100644 --- a/lib/src/precompiled/artifacts_provider.dart +++ b/lib/src/precompiled/artifacts_provider.dart @@ -33,6 +33,16 @@ class DownloadedArtifact { final List dependencies; } +class PrecompiledBinaryRequiredException implements Exception { + PrecompiledBinaryRequiredException(this.message); + + final String message; + + @override + String toString() => + 'PrecompiledBinaryRequiredException: $message'; +} + // Resolves config, downloads binary + signature, and verifies them. class PrecompiledArtifactProvider { PrecompiledArtifactProvider({ @@ -134,24 +144,38 @@ class PrecompiledArtifactProvider { fileName: remoteSignatureName, ); + final bool requirePrecompiled = + precompiled.mode == PrecompiledBinaryMode.always; + + DownloadedArtifact? handleFailure(String message) { + if (requirePrecompiled) { + throw PrecompiledBinaryRequiredException(message); + } + return null; + } + _log.info('Downloading signature from $signatureUrl'); final signatureRes = await httpGetWithRetry(signatureUrl); if (signatureRes.statusCode == 404) { _log.info('No precompiled binaries for crate hash $crateHash'); - return null; + return handleFailure('No precompiled binaries for crate hash $crateHash'); } if (signatureRes.statusCode != 200) { _log.warning( 'Failed to download signature: status ${signatureRes.statusCode}', ); - return null; + return handleFailure( + 'Failed to download signature: status ${signatureRes.statusCode}', + ); } _log.info('Downloading binary from $binaryUrl'); final binaryRes = await httpGetWithRetry(binaryUrl); if (binaryRes.statusCode != 200) { _log.warning('Failed to download binary: status ${binaryRes.statusCode}'); - return null; + return handleFailure( + 'Failed to download binary: status ${binaryRes.statusCode}', + ); } // Verify binary integrity before writing it to disk. @@ -162,7 +186,7 @@ class PrecompiledArtifactProvider { ); if (!ok) { _log.warning('Signature verification failed; ignoring binary'); - return null; + return handleFailure('Signature verification failed; ignoring binary'); } await writeBytesAtomically(File(finalLibPath), binaryRes.bodyBytes); From 1f98a55de26620099301025c3c566681eab1fc05 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 23 Jan 2026 10:00:00 -0500 Subject: [PATCH 32/42] Update precompiled binaries documentation intro --- docs/precompiled_binaries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md index 52eb931..26b2fbb 100644 --- a/docs/precompiled_binaries.md +++ b/docs/precompiled_binaries.md @@ -7,7 +7,7 @@ This document describes how precompiled binaries are built, signed, and publishe - CI builds and uploads precompiled binaries via `.github/workflows/precompile_binaries.yml`. - Artifacts are tagged by the crate hash and uploaded to a GitHub release. - Each binary is signed with an Ed25519 key; the public key is embedded in `pubspec.yaml`. -- The build hook tries to download a verified binary first and falls back to a local build if needed. +- The build hook downloads verified binaries when appropriate (depending on mode configuration) and falls back to local builds if needed. ## CI workflow From f6ee437da2def9370cbafaa035f4848b38395391 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 23 Jan 2026 15:30:00 -0500 Subject: [PATCH 33/42] Add mode behavior documentation section --- docs/precompiled_binaries.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md index 26b2fbb..bade24c 100644 --- a/docs/precompiled_binaries.md +++ b/docs/precompiled_binaries.md @@ -9,6 +9,14 @@ This document describes how precompiled binaries are built, signed, and publishe - Each binary is signed with an Ed25519 key; the public key is embedded in `pubspec.yaml`. - The build hook downloads verified binaries when appropriate (depending on mode configuration) and falls back to local builds if needed. +## Mode behavior + +The `mode` configuration in `pubspec.yaml` controls fallback behavior: + +- `auto`: Uses a heuristic to prefer local builds for development. If the Rust toolchain (`rustup`) is detected, it disables precompiled binaries and builds locally. If no Rust toolchain is found, it uses precompiled binaries. This provides optimal developer experience while keeping end-user builds fast. +- `always`: Attempts to use precompiled binaries and falls back to local builds if download/verification fails. Future versions may disable fallback entirely for this mode. +- `never`: Always builds locally via the standard build hook, ignoring precompiled binaries. + ## CI workflow The workflow runs on `push` to `main` and on manual dispatch. It invokes: From c1446c1d71bed50448116806adeea531568d6d95 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 24 Jan 2026 09:00:00 -0500 Subject: [PATCH 34/42] Update README precompiled binaries intro --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dd39f2..55e0eaf 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Only if you want to contribute to the bindings or modify the native code yoursel This plugin adds a precompiled-binary layer on top of the standard Native Assets approach. Depending on the mode configuration, the build hook may download signed precompiled binaries or build locally. -If no matching binary is available or verification fails, it falls back to building from scratch via the Flutter/Dart build hook. +If precompiled binaries are attempted but unavailable or verification fails, it falls back to building from scratch via the Flutter/Dart build hook. This gives consumers a choice between using published binaries or building locally. ### pubspec.yaml configuration From cdc2fc2dcbfee9d98e3f291f05d95f13ecf14391 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 24 Jan 2026 14:00:00 -0500 Subject: [PATCH 35/42] Update README mode behavior descriptions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55e0eaf..a910c1c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ bdk_dart: ``` `mode` controls when the precompiled path is used: -- `auto` prefers precompiled binaries when available, otherwise builds locally +- `auto` prefers local builds if Rust toolchain is detected (for development), otherwise uses precompiled binaries - `always` requires precompiled binaries and skips local builds - `never` always builds from source via the build hook From 229cdeb81cd9517da3152f9775aab9e5a836db12 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 24 Jan 2026 18:45:00 -0500 Subject: [PATCH 36/42] Add repository parameter to GitHub CLI commands --- .../cli/commands/precompile_binaries.dart | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/src/precompiled/cli/commands/precompile_binaries.dart b/lib/src/precompiled/cli/commands/precompile_binaries.dart index fb5936f..05cde52 100644 --- a/lib/src/precompiled/cli/commands/precompile_binaries.dart +++ b/lib/src/precompiled/cli/commands/precompile_binaries.dart @@ -171,7 +171,11 @@ Options: // If release already contains assets, avoid duplicate builds. final releaseHasAssets = buildableTargets.isNotEmpty - ? await _releaseHasAllAssets(tag: tag, targets: buildableTargets) + ? await _releaseHasAllAssets( + tag: tag, + targets: buildableTargets, + repository: repository, + ) : false; // Local build and upload staging folders. @@ -184,7 +188,11 @@ Options: p.join(Directory.current.absolute.path, uploadDir.path), ); - await _ensureReleaseExists(tag: tag, crateHash: crateHash); + await _ensureReleaseExists( + tag: tag, + crateHash: crateHash, + repository: repository, + ); if (releaseHasAssets) { if (verbose) { stderr.writeln( @@ -370,6 +378,8 @@ Options: await runOrThrow('gh', [ 'release', 'upload', + '--repo', + repository, tag, '${uploadDirAbs.path}/*', '--clobber', @@ -379,6 +389,7 @@ Options: Future _ensureReleaseExists({ required String tag, required String crateHash, + required String repository, }) async { final view = await Process.run('gh', ['release', 'view', tag]); if (view.exitCode == 0) return; @@ -387,6 +398,8 @@ Future _ensureReleaseExists({ 'create', tag, '--title', + '--repo', + repository, 'Precompiled binaries $crateHash', '--notes', 'Precompiled binaries for crate hash $crateHash.', @@ -402,11 +415,14 @@ Future _ensureReleaseExists({ Future _releaseHasAllAssets({ required String tag, required List targets, + required String repository, }) async { final view = await Process.run('gh', [ 'release', 'view', tag, + '--repo', + repository, '--json', 'assets', '--jq', From 8a8e82d389ca7ba24178c797aa496ddcd9a103cc Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Fri, 13 Feb 2026 10:00:00 -0500 Subject: [PATCH 37/42] update readme --- docs/precompiled_binaries.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md index bade24c..2a22665 100644 --- a/docs/precompiled_binaries.md +++ b/docs/precompiled_binaries.md @@ -60,6 +60,7 @@ dart run bin/build_tool.dart precompile-binaries \ --crate-package="bdk_dart_ffi" \ --repository="owner/repo" \ --os=macos + ``` ## Troubleshooting & ops tips From 54394f2ae86122a615326a39f5762496f80238a8 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sat, 14 Feb 2026 10:00:00 -0500 Subject: [PATCH 38/42] docs: state that always mode throws on failure --- docs/precompiled_binaries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md index 2a22665..a360f03 100644 --- a/docs/precompiled_binaries.md +++ b/docs/precompiled_binaries.md @@ -14,7 +14,7 @@ This document describes how precompiled binaries are built, signed, and publishe The `mode` configuration in `pubspec.yaml` controls fallback behavior: - `auto`: Uses a heuristic to prefer local builds for development. If the Rust toolchain (`rustup`) is detected, it disables precompiled binaries and builds locally. If no Rust toolchain is found, it uses precompiled binaries. This provides optimal developer experience while keeping end-user builds fast. -- `always`: Attempts to use precompiled binaries and falls back to local builds if download/verification fails. Future versions may disable fallback entirely for this mode. +- `always`: Throws an exception if download/verification fails; does not fall back. - `never`: Always builds locally via the standard build hook, ignoring precompiled binaries. ## CI workflow From fcff4765cee288b572c67f135960479ce32e5834 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Sun, 15 Feb 2026 14:30:00 -0500 Subject: [PATCH 39/42] docs: trim trailing newline in example --- docs/precompiled_binaries.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/precompiled_binaries.md b/docs/precompiled_binaries.md index a360f03..3fff858 100644 --- a/docs/precompiled_binaries.md +++ b/docs/precompiled_binaries.md @@ -60,7 +60,6 @@ dart run bin/build_tool.dart precompile-binaries \ --crate-package="bdk_dart_ffi" \ --repository="owner/repo" \ --os=macos - ``` ## Troubleshooting & ops tips From d137ff908157e18dd43274158cb2ad36b5e7e2c3 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Mon, 16 Feb 2026 11:00:00 -0500 Subject: [PATCH 40/42] style: format PrecompiledBinaryRequiredException.toString --- lib/src/precompiled/artifacts_provider.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/precompiled/artifacts_provider.dart b/lib/src/precompiled/artifacts_provider.dart index 220dbed..b916bdc 100644 --- a/lib/src/precompiled/artifacts_provider.dart +++ b/lib/src/precompiled/artifacts_provider.dart @@ -39,8 +39,7 @@ class PrecompiledBinaryRequiredException implements Exception { final String message; @override - String toString() => - 'PrecompiledBinaryRequiredException: $message'; + String toString() => 'PrecompiledBinaryRequiredException: $message'; } // Resolves config, downloads binary + signature, and verifies them. From 16ac55360eeef2325bac3cd9fbfff5668645db3c Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Tue, 17 Feb 2026 16:00:00 -0500 Subject: [PATCH 41/42] fix: correct GitHub release create argument order --- lib/src/precompiled/cli/commands/precompile_binaries.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/precompiled/cli/commands/precompile_binaries.dart b/lib/src/precompiled/cli/commands/precompile_binaries.dart index 05cde52..af03a2f 100644 --- a/lib/src/precompiled/cli/commands/precompile_binaries.dart +++ b/lib/src/precompiled/cli/commands/precompile_binaries.dart @@ -398,9 +398,9 @@ Future _ensureReleaseExists({ 'create', tag, '--title', + 'Precompiled binaries $crateHash', '--repo', repository, - 'Precompiled binaries $crateHash', '--notes', 'Precompiled binaries for crate hash $crateHash.', ]); From b55cbcee9b4a9308a23eb1f9f93ff73a7427b4f0 Mon Sep 17 00:00:00 2001 From: BitcoinZavior Date: Wed, 18 Feb 2026 12:00:00 -0500 Subject: [PATCH 42/42] chore: add native Cargo.lock --- native/Cargo.lock | 1859 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1859 insertions(+) create mode 100644 native/Cargo.lock diff --git a/native/Cargo.lock b/native/Cargo.lock new file mode 100644 index 0000000..71053a5 --- /dev/null +++ b/native/Cargo.lock @@ -0,0 +1,1859 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "askama" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 2.0.114", +] + +[[package]] +name = "askama_parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + +[[package]] +name = "async-compat" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.1", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bdk-ffi" +version = "2.3.0-alpha.0" +source = "git+https://github.com/bitcoindevkit/bdk-ffi.git?branch=master#af2475e9eff9dd422c3346aa284a87e24a2bfba0" +dependencies = [ + "bdk_electrum", + "bdk_esplora", + "bdk_kyoto", + "bdk_wallet", + "thiserror", + "uniffi", +] + +[[package]] +name = "bdk_chain" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5d691fd092aacec7e05046b7d04897d58d6d65ed3152cb6cf65dababcfabed" +dependencies = [ + "bdk_core", + "bitcoin", + "miniscript", + "rusqlite", + "serde", +] + +[[package]] +name = "bdk_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dbbe4aad0c898bfeb5253c222be3ea3dccfb380a07e72c87e3e4ed6664a6753" +dependencies = [ + "bitcoin", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "bdk_dart_ffi" +version = "2.3.0-alpha.0" +dependencies = [ + "bdk-ffi", + "camino", + "uniffi", + "uniffi-dart", +] + +[[package]] +name = "bdk_electrum" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b59a3f7fbe678874fa34354097644a171276e02a49934c13b3d61c54610ddf39" +dependencies = [ + "bdk_core", + "electrum-client", +] + +[[package]] +name = "bdk_esplora" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9f5961444b5f51b9c3937e729a212363d0e4cde6390ded6e01e16292078df4" +dependencies = [ + "bdk_core", + "esplora-client", +] + +[[package]] +name = "bdk_kyoto" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da8010d2587aba368afd0be83ad87563f465744eb124cda431905cf06f8bd1c" +dependencies = [ + "bdk_wallet", + "bip157", +] + +[[package]] +name = "bdk_wallet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03f1e31ccc562f600981f747d2262b84428cbff52c9c9cdf14d15fb15bd2286" +dependencies = [ + "bdk_chain", + "bip39", + "bitcoin", + "miniscript", + "rand_core", + "serde", + "serde_json", +] + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bip157" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88df5c18baaea9be4219679afbd4fc26491606f89f6ecdaffcdcabd67635b07b" +dependencies = [ + "bip324", + "bitcoin", + "bitcoin-address-book", + "tokio", +] + +[[package]] +name = "bip324" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53157fcb2d6ec2851c7602d0690536d0b79209e393972cb2b36bd5d72dbd1879" +dependencies = [ + "bitcoin", + "bitcoin_hashes 0.15.0", + "chacha20-poly1305", + "rand", + "tokio", +] + +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes 0.14.1", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "base64 0.21.7", + "bech32", + "bitcoin-internals 0.3.0", + "bitcoin-io 0.1.4", + "bitcoin-units", + "bitcoin_hashes 0.14.1", + "hex-conservative 0.2.2", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-address-book" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060c05780195789a7b89bbfe7f57a1a8cd6ae0bb3daa9b96eeca4fbe0ba8014f" +dependencies = [ + "bitcoin", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90bbbfa552b49101a230fb2668f3f9ef968c81e6f83cf577e1d4b80f689e1aa" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26792cd2bf245069a1c5acb06aa7ad7abe1de69b507c90b490bca81e0665d0ee" +dependencies = [ + "bitcoin-internals 0.4.2", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals 0.3.0", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io 0.1.4", + "hex-conservative 0.2.2", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0982261c82a50d89d1a411602afee0498b3e0debe3d36693f0c661352809639" +dependencies = [ + "bitcoin-io 0.2.0", + "hex-conservative 0.3.1", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20-poly1305" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b0fc281743d80256607bd65e8beedc42cb0787ea119c85b81b4c0eab85e5f" + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "electrum-client" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5059f13888a90486e7268bbce59b175f5f76b1c55e5b9c568ceaa42d2b8507c" +dependencies = [ + "bitcoin", + "byteorder", + "libc", + "log", + "rustls 0.23.36", + "serde", + "serde_json", + "webpki-roots", + "winapi", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "esplora-client" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0af349d96a5d9ad77ba59f1437aa6f348b03c5865d4f7d6e7a662d60aedce39" +dependencies = [ + "bitcoin", + "hex-conservative 0.2.2", + "log", + "minreq", + "serde", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "genco" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35958104272e516c2a5f66a9d82fba4784d2b585fc1e2358b8f96e15d342995" +dependencies = [ + "genco-macros", + "relative-path", + "smallvec", +] + +[[package]] +name = "genco-macros" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b9348ee0d8d4e3a894946c1ab104d08a2e44ca13656613afada8905ea609b6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniscript" +version = "12.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c" +dependencies = [ + "bech32", + "bitcoin", + "serde", +] + +[[package]] +name = "minreq" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05015102dad0f7d61691ca347e9d9d9006685a64aefb3d79eecf62665de2153d" +dependencies = [ + "base64 0.22.1", + "rustls 0.21.12", + "rustls-webpki 0.101.7", + "serde", + "serde_json", + "webpki-roots", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.14.1", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringcase" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04028eeb851ed08af6aba5caa29f2d59a13ed168cee4d6bd753aeefcf1d636b0" + +[[package]] +name = "stringcase" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "uniffi" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap", + "uniffi_bindgen", + "uniffi_build", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi-dart" +version = "0.1.0" +source = "git+https://github.com/Uniffi-Dart/uniffi-dart#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9" +dependencies = [ + "anyhow", + "camino", + "genco", + "heck", + "lazy_static", + "paste", + "proc-macro2", + "serde", + "stringcase 0.4.0", + "toml", + "uniffi", + "uniffi_bindgen", + "uniffi_build", + "uniffi_dart_macro", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8ca600167641ebe7c8ba9254af40492dda3397c528cc3b2f511bd23e8541a5" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "indexmap", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] + +[[package]] +name = "uniffi_build" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e55c05228f4858bb258f651d21d743fcc1fe5a2ec20d3c0f9daefddb105ee4d" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_core" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7a5a038ebffe8f4cf91416b154ef3c2468b18e828b7009e01b1b99938089f9" +dependencies = [ + "anyhow", + "async-compat", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_dart_macro" +version = "0.1.0" +source = "git+https://github.com/Uniffi-Dart/uniffi-dart#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9" +dependencies = [ + "futures", + "proc-macro2", + "quote", + "stringcase 0.3.0", + "syn 1.0.109", + "uniffi", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" +dependencies = [ + "anyhow", + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "uniffi_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c6309fc36c7992afc03bc0c5b059c656bccbef3f2a4bc362980017f8936141" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.114", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a138823392dba19b0aa494872689f97d0ee157de5852e2bec157ce6de9cdc22" +dependencies = [ + "anyhow", + "siphasher", + "uniffi_internal_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0adacdd848aeed7af4f5af7d2f621d5e82531325d405e29463482becfdeafca" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"