diff --git a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart
index 026be5c55..8d2dbb959 100644
--- a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart
+++ b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:quantus_miner/src/services/binary_manager.dart';
import 'package:quantus_miner/src/services/miner_settings_service.dart';
import 'package:quantus_miner/src/services/miner_wallet_service.dart';
-import 'package:quantus_miner/src/services/wormhole_claim_service.dart';
import 'package:quantus_miner/src/utils/app_logger.dart';
import 'package:quantus_sdk/quantus_sdk.dart';
@@ -30,7 +29,7 @@ enum _Screen { input, confirm, progress }
class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
final _addressController = TextEditingController();
- final _claimService = WormholeClaimService();
+ WormholeClaimService? _claimService;
final _walletService = MinerWalletService();
final _settingsService = MinerSettingsService();
@@ -105,14 +104,13 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
final binsDir = '${await BinaryManager.getQuantusHomeDirectoryPath()}/generated-bins';
await Directory(binsDir).create(recursive: true);
- final rpcUrl = chainConfig.rpcUrl;
+ _claimService = WormholeClaimService(rpcUrl: chainConfig.rpcUrl);
_log.i('Starting claim for ${keyPair.address} to ${_addressController.text.trim()}');
- final result = await _claimService.claimRewards(
+ final result = await _claimService!.claimRewards(
wormholeAddress: keyPair.address,
secretHex: keyPair.secretHex,
destinationAddress: _addressController.text.trim(),
- rpcUrl: rpcUrl,
circuitBinsDir: binsDir,
onProgress: (progress) {
if (!mounted) return;
@@ -147,7 +145,7 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
}
void _cancelClaim() {
- _claimService.cancel();
+ _claimService?.cancel();
}
@override
diff --git a/mobile-app/analysis_options.yaml b/mobile-app/analysis_options.yaml
index 1c434ce4e..e2315f253 100644
--- a/mobile-app/analysis_options.yaml
+++ b/mobile-app/analysis_options.yaml
@@ -21,6 +21,8 @@ analyzer:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "lib/generated/**"
+ - "build/**"
+
errors:
avoid_print: ignore # print is the most reliable way to debug the app
missing_required_param: error
diff --git a/mobile-app/fix_ios_build.sh b/mobile-app/fix_ios_build.sh
old mode 100644
new mode 100755
diff --git a/mobile-app/ios/Runner/Runner.entitlements b/mobile-app/ios/Runner/Runner.entitlements
index fa27cd68b..2af29348d 100644
--- a/mobile-app/ios/Runner/Runner.entitlements
+++ b/mobile-app/ios/Runner/Runner.entitlements
@@ -8,5 +8,7 @@
applinks:www.quantus.com
+ com.apple.developer.kernel.extended-virtual-addressing
+
diff --git a/mobile-app/lib/generated/version.g.dart b/mobile-app/lib/generated/version.g.dart
index 98166afb0..42207b05d 100644
--- a/mobile-app/lib/generated/version.g.dart
+++ b/mobile-app/lib/generated/version.g.dart
@@ -1,2 +1,2 @@
const appVersion = '1.5.0';
-const appBuildNumber = '106';
+const appBuildNumber = '107';
diff --git a/mobile-app/lib/v2/components/address_input_field.dart b/mobile-app/lib/v2/components/address_input_field.dart
new file mode 100644
index 000000000..c42da5bd9
--- /dev/null
+++ b/mobile-app/lib/v2/components/address_input_field.dart
@@ -0,0 +1,111 @@
+import 'package:flutter/material.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
+import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';
+
+/// Dual-state recipient input. When the controller holds a valid SS58 address
+/// (`hasValid`), shows a pill with the truncated address and optional human
+/// checksum. Otherwise shows a regular search-style input.
+class AddressInputField extends StatelessWidget {
+ final TextEditingController controller;
+ final FocusNode focusNode;
+ final bool hasValid;
+ final String? recipientChecksum;
+ final String hintText;
+ final bool showSearchIcon;
+
+ const AddressInputField({
+ super.key,
+ required this.controller,
+ required this.focusNode,
+ required this.hasValid,
+ required this.recipientChecksum,
+ this.hintText = 'Search ${AppConstants.tokenSymbol} Address',
+ this.showSearchIcon = true,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final colors = context.colors;
+ final text = context.themeText;
+
+ return SizedBox(
+ height: 48,
+ child: Stack(
+ children: [
+ Positioned.fill(
+ child: IgnorePointer(
+ ignoring: hasValid,
+ child: Opacity(
+ opacity: hasValid ? 0 : 1,
+ child: Container(
+ padding: const EdgeInsets.only(left: 12, right: 8),
+ decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(8)),
+ child: Row(
+ children: [
+ if (showSearchIcon) ...[
+ Icon(Icons.search, size: 14, color: colors.textLabel),
+ const SizedBox(width: 12),
+ ],
+ Expanded(
+ child: TextField(
+ controller: controller,
+ focusNode: focusNode,
+ keyboardType: TextInputType.text,
+ textInputAction: TextInputAction.done,
+ autocorrect: false,
+ enableSuggestions: false,
+ textCapitalization: TextCapitalization.none,
+ scrollPadding: const EdgeInsets.only(bottom: 120),
+ style: text.smallParagraph?.copyWith(color: colors.textPrimary),
+ decoration: InputDecoration(hintText: hintText),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ if (hasValid)
+ Positioned.fill(
+ child: GestureDetector(
+ onTap: () {
+ controller.clear();
+ focusNode.requestFocus();
+ },
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ decoration: BoxDecoration(color: colors.toasterBackground, borderRadius: BorderRadius.circular(8)),
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ AddressFormattingService.formatAddress(controller.text.trim()),
+ style: text.smallParagraph?.copyWith(
+ color: colors.textPrimary,
+ fontWeight: FontWeight.w500,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ if (recipientChecksum != null)
+ Text(recipientChecksum!, style: text.detail?.copyWith(color: colors.checksum)),
+ ],
+ ),
+ ),
+ Icon(Icons.edit_outlined, size: 16, color: colors.textLabel),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/v2/components/recent_addresses_list.dart b/mobile-app/lib/v2/components/recent_addresses_list.dart
new file mode 100644
index 000000000..f427c98c5
--- /dev/null
+++ b/mobile-app/lib/v2/components/recent_addresses_list.dart
@@ -0,0 +1,113 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:resonance_network_wallet/features/components/skeleton.dart';
+import 'package:resonance_network_wallet/providers/wallet_providers.dart';
+import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart';
+import 'package:resonance_network_wallet/v2/components/loader.dart';
+import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
+import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';
+
+/// Recents list backed by [recentAddressesServiceProvider]. Filters out any
+/// addresses in [excludeAddresses] (typically the user's own active account).
+/// Renders as slivers so it can live inside the screen's main CustomScrollView.
+class RecentAddressesSlivers extends ConsumerStatefulWidget {
+ final Set excludeAddresses;
+ final ValueChanged onTap;
+ final String title;
+
+ const RecentAddressesSlivers({
+ super.key,
+ required this.excludeAddresses,
+ required this.onTap,
+ this.title = 'Recents',
+ });
+
+ @override
+ ConsumerState createState() => _RecentAddressesSliversState();
+}
+
+class _RecentAddressesSliversState extends ConsumerState {
+ final Map _checksums = {};
+ List _recents = [];
+ bool _loading = true;
+
+ @override
+ void initState() {
+ super.initState();
+ _load();
+ }
+
+ Future _load() async {
+ final recentAddressesService = ref.read(recentAddressesServiceProvider);
+ final checksumService = ref.read(humanReadableChecksumServiceProvider);
+ try {
+ final all = await recentAddressesService.getAddresses();
+ final addresses = all.where((a) => !widget.excludeAddresses.contains(a)).toList();
+ if (!mounted) return;
+ setState(() {
+ _recents = addresses;
+ _loading = false;
+ });
+ for (final addr in addresses) {
+ checksumService.getHumanReadableName(addr).then((name) {
+ if (mounted) setState(() => _checksums[addr] = name);
+ });
+ }
+ } catch (e) {
+ debugPrint('RecentAddressesSlivers load failed: $e');
+ if (mounted) setState(() => _loading = false);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_loading) {
+ return const SliverFillRemaining(hasScrollBody: false, child: Center(child: Loader()));
+ }
+ if (_recents.isEmpty) {
+ return const SliverFillRemaining(hasScrollBody: false, child: SizedBox.shrink());
+ }
+
+ final colors = context.colors;
+ final text = context.themeText;
+
+ return SliverMainAxisGroup(
+ slivers: [
+ SliverToBoxAdapter(
+ child: Text(widget.title, style: text.smallTitle?.copyWith(color: colors.textPrimary)),
+ ),
+ const SliverToBoxAdapter(child: SizedBox(height: 32)),
+ SliverList(
+ delegate: SliverChildBuilderDelegate((context, i) {
+ final isFirst = i == 0;
+ final isLast = i == _recents.length - 1;
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (!isFirst) const SizedBox(height: 14),
+ _recentRow(_recents[i], colors),
+ if (!isLast) ...[const SizedBox(height: 14), Divider(height: 1, color: colors.txItemSeparator)],
+ ],
+ );
+ }, childCount: _recents.length),
+ ),
+ ],
+ );
+ }
+
+ Widget _recentRow(String address, AppColorsV2 colors) {
+ final checksum = _checksums[address];
+
+ return Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: () => widget.onTap(address),
+ borderRadius: BorderRadius.circular(8),
+ child: checksum != null
+ ? AddressCheckphraseWithInitial(recipientChecksum: checksum, recipientAddress: address)
+ : const Skeleton(height: 36),
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart
index 9b2eaa11b..bee4337a3 100644
--- a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart
+++ b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart
@@ -11,6 +11,7 @@ import 'package:resonance_network_wallet/v2/components/scaffold_base.dart';
import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart';
import 'package:resonance_network_wallet/v2/components/split_card.dart';
import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart';
+import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart';
import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';
@@ -35,9 +36,21 @@ class MiningRewardsScreen extends ConsumerWidget {
),
],
bottomContent: miningAsync.when(
- data: (data) => data.totalBlocks > 0
- ? const ScaffoldBaseBottomContent(child: QuantusButton.simple(label: 'Redeem', onTap: null))
- : null,
+ data: (data) {
+ if (data.totalBlocks == 0) return null;
+ final canRedeem = data.redeemableRewards > BigInt.zero;
+ return ScaffoldBaseBottomContent(
+ child: QuantusButton.simple(
+ label: 'Redeem',
+ isDisabled: !canRedeem,
+ onTap: canRedeem
+ ? () => Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => RedeemAddressScreen(redeemableRewards: data.redeemableRewards)),
+ )
+ : null,
+ ),
+ );
+ },
loading: () => null,
error: (err, _) => null,
),
diff --git a/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart
new file mode 100644
index 000000000..4028c6ec6
--- /dev/null
+++ b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart
@@ -0,0 +1,217 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/providers/wallet_providers.dart';
+import 'package:resonance_network_wallet/v2/components/address_input_field.dart';
+import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart';
+import 'package:resonance_network_wallet/v2/components/quantus_button.dart';
+import 'package:resonance_network_wallet/v2/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart';
+import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart';
+import 'package:resonance_network_wallet/v2/screens/settings/redeem_progress_screen.dart';
+import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
+import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';
+
+/// Fullscreen flow to pick a destination address for redeeming mining
+/// rewards. Defaults the field to the user's primary account (Account 0).
+class RedeemAddressScreen extends ConsumerStatefulWidget {
+ final BigInt redeemableRewards;
+
+ const RedeemAddressScreen({super.key, required this.redeemableRewards});
+
+ @override
+ ConsumerState createState() => _RedeemAddressScreenState();
+}
+
+class _RedeemAddressScreenState extends ConsumerState {
+ final _recipientController = TextEditingController();
+ final _recipientFocus = FocusNode();
+
+ bool _hasAddressError = true;
+ String? _recipientChecksum;
+
+ @override
+ void initState() {
+ super.initState();
+ _recipientController.addListener(_onRecipientChanged);
+ _prefillPrimaryAccount();
+ }
+
+ @override
+ void dispose() {
+ _recipientController.removeListener(_onRecipientChanged);
+ _recipientController.dispose();
+ _recipientFocus.dispose();
+ super.dispose();
+ }
+
+ Future _prefillPrimaryAccount() async {
+ final settings = ref.read(settingsServiceProvider);
+ final primary = await settings.getAccount(walletIndex: 0, index: 0);
+ if (!mounted || primary == null) return;
+ _recipientController.text = primary.accountId;
+ }
+
+ void _onRecipientChanged() {
+ final text = _recipientController.text.trim();
+ if (text.isEmpty) {
+ setState(() {
+ _hasAddressError = true;
+ _recipientChecksum = null;
+ });
+ return;
+ }
+ final substrate = ref.read(substrateServiceProvider);
+ final isValid = substrate.isValidSS58Address(text);
+ setState(() {
+ _hasAddressError = !isValid;
+ _recipientChecksum = null;
+ });
+ if (isValid) {
+ ref.read(humanReadableChecksumServiceProvider).getHumanReadableName(text).then((checksum) {
+ if (mounted) setState(() => _recipientChecksum = checksum);
+ });
+ }
+ }
+
+ bool get _canRedeem =>
+ _recipientController.text.trim().isNotEmpty && !_hasAddressError && widget.redeemableRewards > BigInt.zero;
+
+ Future _redeem() async {
+ if (!_canRedeem) return;
+ final destination = _recipientController.text.trim();
+ final fmt = ref.read(numberFormattingServiceProvider);
+ final formatted = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true);
+
+ final confirmed = await BottomSheetContainer.show(
+ context,
+ builder: (_) => _RedeemConfirmSheet(formatted: formatted, destination: destination),
+ );
+ if (confirmed != true || !mounted) return;
+
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (_) =>
+ RedeemProgressScreen(redeemableRewards: widget.redeemableRewards, destinationAddress: destination),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final colors = context.colors;
+ final text = context.themeText;
+ final fmt = ref.watch(numberFormattingServiceProvider);
+
+ final hasValid = _recipientController.text.trim().isNotEmpty && !_hasAddressError;
+ final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true);
+
+ return ScaffoldBase(
+ appBar: const V2AppBar(title: 'Redeem'),
+ mainContent: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ _AmountSummary(amountLabel: amountLabel, colors: colors, text: text),
+ const SizedBox(height: 28),
+ Text('Redeem To', style: text.sendSectionLabel?.copyWith(color: colors.textPrimary)),
+ const SizedBox(height: 12),
+ AddressInputField(
+ controller: _recipientController,
+ focusNode: _recipientFocus,
+ hasValid: hasValid,
+ recipientChecksum: _recipientChecksum,
+ hintText: 'Paste a ${AppConstants.tokenSymbol} Address',
+ showSearchIcon: false,
+ ),
+ ],
+ ),
+ bottomContent: ScaffoldBaseBottomContent(
+ child: QuantusButton.simple(
+ label: _canRedeem ? 'Redeem $amountLabel' : 'Enter Address',
+ variant: ButtonVariant.primary,
+ isDisabled: !_canRedeem,
+ onTap: _redeem,
+ ),
+ ),
+ );
+ }
+}
+
+class _AmountSummary extends StatelessWidget {
+ final String amountLabel;
+ final AppColorsV2 colors;
+ final AppTextTheme text;
+
+ const _AmountSummary({required this.amountLabel, required this.colors, required this.text});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text('REDEEMABLE', style: text.receiveLabel?.copyWith(color: colors.textLabel)),
+ FittedBox(
+ fit: BoxFit.scaleDown,
+ alignment: Alignment.centerRight,
+ child: Text(
+ amountLabel,
+ maxLines: 1,
+ softWrap: false,
+ style: text.sendSectionLabel?.copyWith(color: colors.success),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class _RedeemConfirmSheet extends StatelessWidget {
+ final String formatted;
+ final String destination;
+
+ const _RedeemConfirmSheet({required this.formatted, required this.destination});
+
+ @override
+ Widget build(BuildContext context) {
+ final colors = context.colors;
+ final text = context.themeText;
+
+ return BottomSheetContainer(
+ title: 'Confirm Redeem',
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _row('Amount', formatted, colors, text),
+ Divider(color: colors.separator, height: 32),
+ _row('To', AddressFormattingService.formatAddress(destination), colors, text),
+ Divider(color: colors.separator, height: 32),
+ _row('Fee', '0.1% volume fee', colors, text),
+ const SizedBox(height: 32),
+ QuantusButton.simple(label: 'Redeem $formatted', onTap: () => Navigator.of(context).pop(true)),
+ ],
+ ),
+ );
+ }
+
+ Widget _row(String label, String value, AppColorsV2 colors, AppTextTheme text) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(label, style: text.smallParagraph?.copyWith(color: colors.textSecondary)),
+ Flexible(
+ child: Text(
+ value,
+ style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500),
+ textAlign: TextAlign.end,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart
new file mode 100644
index 000000000..2378e0007
--- /dev/null
+++ b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart
@@ -0,0 +1,390 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart';
+import 'package:resonance_network_wallet/providers/wallet_providers.dart';
+import 'package:resonance_network_wallet/v2/components/quantus_button.dart';
+import 'package:resonance_network_wallet/v2/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart';
+import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart';
+import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
+import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';
+
+class RedeemProgressScreen extends ConsumerStatefulWidget {
+ final BigInt redeemableRewards;
+ final String destinationAddress;
+
+ const RedeemProgressScreen({super.key, required this.redeemableRewards, required this.destinationAddress});
+
+ @override
+ ConsumerState createState() => _RedeemProgressScreenState();
+}
+
+class _RedeemProgressScreenState extends ConsumerState {
+ WormholeClaimService? _claimService;
+ bool _running = true;
+ bool _done = false;
+ bool _cancelled = false;
+ String? _errorMessage;
+ int _currentStep = 0;
+ final Map _stepProgress = {};
+ ClaimResult? _result;
+
+ @override
+ void initState() {
+ super.initState();
+ _startClaim();
+ }
+
+ Future _startClaim() async {
+ try {
+ final mnemonic = await ref.read(settingsServiceProvider).getMnemonic(0);
+ if (mnemonic == null) throw StateError('Mnemonic not found');
+
+ final keyPair = ref.read(hdWalletServiceProvider).deriveWormholeKeyPair(mnemonic: mnemonic);
+ if (keyPair.secretHex.isEmpty) throw StateError('Wormhole key pair not available');
+
+ final circuitDir = await CircuitManager.getCircuitDirectory();
+ _claimService = WormholeClaimService(
+ maxProofsPerBatch: 16,
+ proofConcurrency: 1,
+ freshBuild: true,
+ provingThreads: 8,
+ );
+
+ final result = await _claimService!.claimRewards(
+ wormholeAddress: keyPair.address,
+ secretHex: keyPair.secretHex,
+ destinationAddress: widget.destinationAddress,
+ circuitBinsDir: circuitDir,
+ onProgress: (progress) {
+ if (!mounted) return;
+ setState(() {
+ _currentStep = progress.step;
+ _stepProgress[progress.step] = progress;
+ });
+ },
+ );
+
+ if (!mounted) return;
+ setState(() {
+ _done = true;
+ _running = false;
+ _result = result;
+ });
+ ref.invalidate(miningRewardsProvider);
+ } on ClaimCancelled {
+ if (!mounted) return;
+ setState(() {
+ _running = false;
+ _cancelled = true;
+ });
+ } catch (e) {
+ print('[Redeem] Claim failed: $e');
+ if (!mounted) return;
+ setState(() {
+ _running = false;
+ _errorMessage = e.toString();
+ });
+ }
+ }
+
+ void _cancel() {
+ _claimService?.cancel();
+ }
+
+ void _retry() {
+ setState(() {
+ _running = true;
+ _done = false;
+ _cancelled = false;
+ _errorMessage = null;
+ _currentStep = 0;
+ _stepProgress.clear();
+ _result = null;
+ });
+ _startClaim();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final colors = context.colors;
+ final text = context.themeText;
+
+ return PopScope(
+ canPop: !_running,
+ child: ScaffoldBase(
+ appBar: V2AppBar(
+ title: _done
+ ? 'Redeem Complete'
+ : _errorMessage != null
+ ? 'Redeem Failed'
+ : 'Redeeming...',
+ showBackButton: !_running,
+ ),
+ mainContent: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SizedBox(height: 24),
+ _buildStatusHeader(colors, text),
+ const SizedBox(height: 32),
+ _buildSteps(colors, text),
+ if (_errorMessage != null) ...[const SizedBox(height: 24), _buildErrorBanner(colors, text)],
+ if (_done && _result != null) ...[const SizedBox(height: 24), _buildSuccessBanner(colors, text)],
+ ],
+ ),
+ bottomContent: _buildBottomContent(colors),
+ ),
+ );
+ }
+
+ Widget _buildStatusHeader(AppColorsV2 colors, AppTextTheme text) {
+ final fmt = ref.watch(numberFormattingServiceProvider);
+ final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true);
+
+ return Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text('REDEEMING', style: text.receiveLabel?.copyWith(color: colors.textLabel)),
+ Text(amountLabel, style: text.sendSectionLabel?.copyWith(color: colors.success)),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildSteps(AppColorsV2 colors, AppTextTheme text) {
+ const steps = [
+ (1, 'Preparing circuits'),
+ (2, 'Fetching transfers'),
+ (3, 'Computing nullifiers'),
+ (4, 'Checking nullifiers'),
+ (5, 'Generating ZK proofs'),
+ (6, 'Aggregating & submitting'),
+ ];
+
+ return Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)),
+ child: Column(
+ children: [
+ for (int i = 0; i < steps.length; i++) ...[
+ _buildStepRow(steps[i].$1, steps[i].$2, colors, text),
+ if (i < steps.length - 1) _buildConnector(steps[i].$1, colors),
+ ],
+ ],
+ ),
+ );
+ }
+
+ Widget _buildStepRow(int step, String title, AppColorsV2 colors, AppTextTheme text) {
+ final isCompleted = _done ? true : _currentStep > step;
+ final isActive = !_done && !_cancelled && _currentStep == step && _errorMessage == null;
+ final isError = !_done && _currentStep == step && _errorMessage != null;
+ final progress = _stepProgress[step];
+
+ final Widget icon;
+ if (isCompleted) {
+ icon = Container(
+ width: 28,
+ height: 28,
+ decoration: BoxDecoration(color: colors.success, shape: BoxShape.circle),
+ child: const Icon(Icons.check, color: Colors.white, size: 16),
+ );
+ } else if (isError) {
+ icon = Container(
+ width: 28,
+ height: 28,
+ decoration: BoxDecoration(
+ color: colors.textError.withValues(alpha: 0.15),
+ shape: BoxShape.circle,
+ border: Border.all(color: colors.textError, width: 2),
+ ),
+ child: Icon(Icons.close, color: colors.textError, size: 14),
+ );
+ } else if (isActive) {
+ icon = Container(
+ width: 28,
+ height: 28,
+ decoration: BoxDecoration(
+ color: colors.success.withValues(alpha: 0.12),
+ shape: BoxShape.circle,
+ border: Border.all(color: colors.success, width: 2),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(5),
+ child: CircularProgressIndicator(strokeWidth: 2, color: colors.success),
+ ),
+ );
+ } else {
+ icon = Container(
+ width: 28,
+ height: 28,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ border: Border.all(color: colors.borderButton.withValues(alpha: 0.3), width: 1.5),
+ ),
+ child: Center(
+ child: Text('$step', style: text.detail?.copyWith(color: colors.textTertiary)),
+ ),
+ );
+ }
+
+ final titleColor = isCompleted
+ ? colors.success
+ : isActive
+ ? colors.textPrimary
+ : isError
+ ? colors.textError
+ : colors.textTertiary;
+
+ String progressText = '';
+ if (progress != null && (isActive || isCompleted)) {
+ if (step == 2) {
+ progressText = '${progress.completed} fetched';
+ } else if (progress.total != null) {
+ progressText = '${progress.completed} / ${progress.total}';
+ }
+ }
+
+ double? progressFraction;
+ if (isActive && progress != null && progress.total != null && progress.total! > 0) {
+ progressFraction = (progress.completed / progress.total!).clamp(0.0, 1.0);
+ }
+
+ return Column(
+ children: [
+ Row(
+ children: [
+ icon,
+ const SizedBox(width: 14),
+ Expanded(
+ child: Text(title, style: text.smallParagraph?.copyWith(color: titleColor)),
+ ),
+ if (progressText.isNotEmpty)
+ Text(
+ progressText,
+ style: text.detail?.copyWith(
+ color: isCompleted ? colors.success : colors.textPrimary,
+ fontFamily: AppTextTheme.fontFamilySecondary,
+ ),
+ ),
+ ],
+ ),
+ if (progressFraction != null) ...[
+ const SizedBox(height: 8),
+ Padding(
+ padding: const EdgeInsets.only(left: 42),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(3),
+ child: LinearProgressIndicator(
+ value: progressFraction,
+ backgroundColor: colors.borderButton.withValues(alpha: 0.2),
+ valueColor: AlwaysStoppedAnimation(colors.success),
+ minHeight: 4,
+ ),
+ ),
+ ),
+ ],
+ ],
+ );
+ }
+
+ Widget _buildConnector(int afterStep, AppColorsV2 colors) {
+ final isCompleted = _done ? true : _currentStep > afterStep;
+ return Padding(
+ padding: const EdgeInsets.only(left: 13),
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: Container(
+ width: 2,
+ height: 20,
+ decoration: BoxDecoration(
+ color: isCompleted ? colors.success.withValues(alpha: 0.4) : colors.borderButton.withValues(alpha: 0.15),
+ borderRadius: BorderRadius.circular(1),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildErrorBanner(AppColorsV2 colors, AppTextTheme text) {
+ return Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: colors.textError.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: colors.textError.withValues(alpha: 0.2)),
+ ),
+ child: Row(
+ children: [
+ Icon(Icons.error_outline, color: colors.textError, size: 16),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(_errorMessage!, style: text.detail?.copyWith(color: colors.textError)),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildSuccessBanner(AppColorsV2 colors, AppTextTheme text) {
+ final fmt = ref.watch(numberFormattingServiceProvider);
+ final withdrawn = fmt.formatBalance(_result!.totalWithdrawn, maxDecimals: 4, addSymbol: true);
+
+ return Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: colors.success.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: colors.success.withValues(alpha: 0.2)),
+ ),
+ child: Row(
+ children: [
+ Icon(Icons.check_circle_outline, color: colors.success, size: 16),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ '$withdrawn redeemed in ${_result!.batchesSubmitted} batch(es)',
+ style: text.detail?.copyWith(color: colors.success),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget? _buildBottomContent(AppColorsV2 colors) {
+ if (_running) {
+ return ScaffoldBaseBottomContent(
+ child: QuantusButton.simple(label: 'Cancel', variant: ButtonVariant.secondary, onTap: _cancel),
+ );
+ }
+
+ if (_errorMessage != null) {
+ return ScaffoldBaseBottomContent(
+ child: Row(
+ children: [
+ Expanded(
+ child: QuantusButton.simple(label: 'Retry', onTap: _retry),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: QuantusButton.simple(
+ label: 'Close',
+ variant: ButtonVariant.secondary,
+ onTap: () => Navigator.of(context).pop(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ return ScaffoldBaseBottomContent(
+ child: QuantusButton.simple(label: 'Done', onTap: () => Navigator.of(context).popUntil((route) => route.isFirst)),
+ );
+ }
+}
diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml
index 0d743e03e..d43dedb63 100644
--- a/mobile-app/pubspec.yaml
+++ b/mobile-app/pubspec.yaml
@@ -2,7 +2,7 @@ name: resonance_network_wallet
description: A Flutter wallet for the Quantus blockchain.
publish_to: "none"
-version: 1.5.0+106
+version: 1.5.0+107
environment:
sdk: ">=3.8.0 <4.0.0"
diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart
index 212f0d196..39c25dbb5 100644
--- a/quantus_sdk/lib/quantus_sdk.dart
+++ b/quantus_sdk/lib/quantus_sdk.dart
@@ -66,6 +66,7 @@ export 'src/services/taskmaster_service.dart';
export 'src/services/senoti_service.dart';
export 'src/services/circuit_manager.dart';
export 'src/services/wormhole_address_manager.dart';
+export 'src/services/wormhole_claim_service.dart';
export 'src/services/wormhole_utxo_service.dart';
export 'src/extensions/account_extension.dart';
export 'src/quantus_signing_payload.dart';
diff --git a/quantus_sdk/lib/src/rust/api/wormhole.dart b/quantus_sdk/lib/src/rust/api/wormhole.dart
index 11714503a..c4648e9d4 100644
--- a/quantus_sdk/lib/src/rust/api/wormhole.dart
+++ b/quantus_sdk/lib/src/rust/api/wormhole.dart
@@ -6,7 +6,8 @@
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
-// These functions are ignored because they are not marked as `pub`: `all_required_files_exist`, `vec_to_32`, `vec_to_digest`
+// These functions are ignored because they are not marked as `pub`: `all_required_files_exist`, `leaf_files_exist`, `log_mem`, `process_memory`, `read_task_vm_info`, `vec_to_32`, `vec_to_digest`
+// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `TaskVmInfoRev1`
String computeAddressHashHex({required List rawAddress}) =>
RustLib.instance.api.crateApiWormholeComputeAddressHashHex(rawAddress: rawAddress);
@@ -42,6 +43,13 @@ MerkleProcessed computeMerklePositions({
Future ensureCircuitBinaries({required String binsDir}) =>
RustLib.instance.api.crateApiWormholeEnsureCircuitBinaries(binsDir: binsDir);
+/// Lightweight variant that only generates leaf circuit binaries (prover.bin,
+/// common.bin, verifier.bin, dummy_proof.bin). Skips the heavy aggregation
+/// circuit generation entirely. Use with `aggregate_proofs_fresh` which builds
+/// the aggregation circuit in memory at proving time.
+Future ensureLeafCircuitBinaries({required String binsDir}) =>
+ RustLib.instance.api.crateApiWormholeEnsureLeafCircuitBinaries(binsDir: binsDir);
+
Future generateProof({
required ProofInput input,
required String proverBinPath,
@@ -55,6 +63,37 @@ Future generateProof({
Future aggregateProofs({required List proofBytesList, required String binsDir}) =>
RustLib.instance.api.crateApiWormholeAggregateProofs(proofBytesList: proofBytesList, binsDir: binsDir);
+/// Dump every field of task_vm_info for deep debugging. Call sparingly.
+Future logMemDetailed({required String tag}) => RustLib.instance.api.crateApiWormholeLogMemDetailed(tag: tag);
+
+/// Returns (phys_footprint_bytes, virtual_bytes). Useful for Dart-side polling.
+(BigInt, BigInt) getProcessMemory() => RustLib.instance.api.crateApiWormholeGetProcessMemory();
+
+/// Returns (phys_footprint, resident_size, compressed). Dart-side memory probe.
+(BigInt, BigInt, BigInt) getProcessMemoryDetailed() => RustLib.instance.api.crateApiWormholeGetProcessMemoryDetailed();
+
+/// Dart-side hook to dump every memory field.
+void logMemorySnapshot({required String tag}) => RustLib.instance.api.crateApiWormholeLogMemorySnapshot(tag: tag);
+
+/// Force the system allocator (Apple libmalloc) to return freed pages to the OS.
+/// On other platforms this is a no-op.
+/// Apple's malloc keeps freed memory in per-zone caches by default; after a heavy
+/// allocation phase like ZK proving this can leave 100s of MB of dirty pages.
+/// We iterate every registered zone — passing `null` only reaches the default
+/// zone, which is NOT where Rust's allocator lives, so calling on null released
+/// 0 bytes in practice.
+Future releaseMemory() => RustLib.instance.api.crateApiWormholeReleaseMemory();
+
+/// Limit rayon parallelism to reduce peak memory during proving.
+/// Plonky2 multiplies its per-thread FFT buffers by the rayon pool size, so
+/// fewer threads = lower peak memory (at the cost of wall-clock time).
+/// Idempotent / safe to call repeatedly; only the first call wins.
+Future setProvingThreadCount({required int numThreads}) =>
+ RustLib.instance.api.crateApiWormholeSetProvingThreadCount(numThreads: numThreads);
+
+Future aggregateProofsFresh({required List proofBytesList, required String binsDir}) =>
+ RustLib.instance.api.crateApiWormholeAggregateProofsFresh(proofBytesList: proofBytesList, binsDir: binsDir);
+
class MerkleProcessed {
final Uint8List sortedSiblingsFlat;
final Uint8List positions;
diff --git a/quantus_sdk/lib/src/rust/frb_generated.dart b/quantus_sdk/lib/src/rust/frb_generated.dart
index 71530da69..ab2da38f5 100644
--- a/quantus_sdk/lib/src/rust/frb_generated.dart
+++ b/quantus_sdk/lib/src/rust/frb_generated.dart
@@ -64,7 +64,7 @@ class RustLib extends BaseEntrypoint {
String get codegenVersion => '2.12.0';
@override
- int get rustContentHash => 623793143;
+ int get rustContentHash => 1186293619;
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
stem: 'rust_lib_quantus_wallet',
@@ -77,6 +77,11 @@ class RustLib extends BaseEntrypoint {
abstract class RustLibApi extends BaseApi {
Future crateApiWormholeAggregateProofs({required List proofBytesList, required String binsDir});
+ Future crateApiWormholeAggregateProofsFresh({
+ required List proofBytesList,
+ required String binsDir,
+ });
+
String crateApiWormholeComputeAddressHashHex({required List rawAddress});
MerkleProcessed crateApiWormholeComputeMerklePositions({
@@ -111,6 +116,8 @@ abstract class RustLibApi extends BaseApi {
Future crateApiWormholeEnsureCircuitBinaries({required String binsDir});
+ Future crateApiWormholeEnsureLeafCircuitBinaries({required String binsDir});
+
String crateApiCryptoFirstHashToAddress({required String firstHashHex});
Keypair crateApiCryptoGenerateDerivedKeypair({required String mnemonicStr, required String path});
@@ -125,16 +132,28 @@ abstract class RustLibApi extends BaseApi {
required String commonBinPath,
});
+ (BigInt, BigInt) crateApiWormholeGetProcessMemory();
+
+ (BigInt, BigInt, BigInt) crateApiWormholeGetProcessMemoryDetailed();
+
Future crateApiCryptoInitApp();
bool crateApiUrIsCompleteUr({required List urParts});
+ Future crateApiWormholeLogMemDetailed({required String tag});
+
+ void crateApiWormholeLogMemorySnapshot({required String tag});
+
BigInt crateApiCryptoPublicKeyBytes();
+ Future crateApiWormholeReleaseMemory();
+
BigInt crateApiCryptoSecretKeyBytes();
void crateApiCryptoSetDefaultSs58Prefix({required int prefix});
+ Future crateApiWormholeSetProvingThreadCount({required int numThreads});
+
Uint8List crateApiCryptoSignMessage({required Keypair keypair, required List message, U8Array32? entropy});
Uint8List crateApiCryptoSignMessageWithPubkey({
@@ -196,6 +215,30 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiWormholeAggregateProofsConstMeta =>
const TaskConstMeta(debugName: 'aggregate_proofs', argNames: ['proofBytesList', 'binsDir']);
+ @override
+ Future crateApiWormholeAggregateProofsFresh({
+ required List proofBytesList,
+ required String binsDir,
+ }) {
+ return handler.executeNormal(
+ NormalTask(
+ callFfi: (port_) {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ sse_encode_list_list_prim_u_8_strict(proofBytesList, serializer);
+ sse_encode_String(binsDir, serializer);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2, port: port_);
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String),
+ constMeta: kCrateApiWormholeAggregateProofsFreshConstMeta,
+ argValues: [proofBytesList, binsDir],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeAggregateProofsFreshConstMeta =>
+ const TaskConstMeta(debugName: 'aggregate_proofs_fresh', argNames: ['proofBytesList', 'binsDir']);
+
@override
String crateApiWormholeComputeAddressHashHex({required List rawAddress}) {
return handler.executeSync(
@@ -203,7 +246,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(rawAddress, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeComputeAddressHashHexConstMeta,
@@ -229,7 +272,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_prim_u_8_loose(unsortedSiblingsFlat, serializer);
sse_encode_list_prim_u_8_loose(leafHash, serializer);
sse_encode_u_32(depth, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_merkle_processed, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeComputeMerklePositionsConstMeta,
@@ -252,7 +295,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(secret, serializer);
sse_encode_u_64(transferCount, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeComputeNullifierConstMeta,
@@ -272,7 +315,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(secret, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeComputeWormholeAddressConstMeta,
@@ -291,7 +334,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null),
constMeta: kCrateApiCryptoCrystalAliceConstMeta,
@@ -310,7 +353,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null),
constMeta: kCrateApiCryptoCrystalBobConstMeta,
@@ -328,7 +371,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null),
constMeta: kCrateApiCryptoCrystalCharlieConstMeta,
@@ -348,7 +391,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(leafData, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeDecodeLeafAmountConstMeta,
@@ -368,7 +411,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(leafData, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeDecodeLeafToAccountConstMeta,
@@ -388,7 +431,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(leafData, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_u_64, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeDecodeLeafTransferCountConstMeta,
@@ -408,7 +451,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_String(urParts, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String),
constMeta: kCrateApiUrDecodeUrConstMeta,
@@ -428,7 +471,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(seed, serializer);
sse_encode_String(path, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null),
constMeta: kCrateApiCryptoDeriveHdPathConstMeta,
@@ -449,7 +492,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(mnemonicStr, serializer);
sse_encode_String(path, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_wormhole_result,
@@ -473,7 +516,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(data, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_String, decodeErrorData: sse_decode_String),
constMeta: kCrateApiUrEncodeUrConstMeta,
@@ -492,7 +535,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(binsDir, serializer);
- pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16, port: port_);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17, port: port_);
},
codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeEnsureCircuitBinariesConstMeta,
@@ -505,6 +548,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiWormholeEnsureCircuitBinariesConstMeta =>
const TaskConstMeta(debugName: 'ensure_circuit_binaries', argNames: ['binsDir']);
+ @override
+ Future crateApiWormholeEnsureLeafCircuitBinaries({required String binsDir}) {
+ return handler.executeNormal(
+ NormalTask(
+ callFfi: (port_) {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ sse_encode_String(binsDir, serializer);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18, port: port_);
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String),
+ constMeta: kCrateApiWormholeEnsureLeafCircuitBinariesConstMeta,
+ argValues: [binsDir],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeEnsureLeafCircuitBinariesConstMeta =>
+ const TaskConstMeta(debugName: 'ensure_leaf_circuit_binaries', argNames: ['binsDir']);
+
@override
String crateApiCryptoFirstHashToAddress({required String firstHashHex}) {
return handler.executeSync(
@@ -512,7 +575,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(firstHashHex, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String),
constMeta: kCrateApiCryptoFirstHashToAddressConstMeta,
@@ -533,7 +596,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(mnemonicStr, serializer);
sse_encode_String(path, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_keypair,
@@ -557,7 +620,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(mnemonicStr, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null),
constMeta: kCrateApiCryptoGenerateKeypairConstMeta,
@@ -577,7 +640,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_prim_u_8_loose(seed, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null),
constMeta: kCrateApiCryptoGenerateKeypairFromSeedConstMeta,
@@ -603,7 +666,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_proof_input(input, serializer);
sse_encode_String(proverBinPath, serializer);
sse_encode_String(commonBinPath, serializer);
- pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21, port: port_);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23, port: port_);
},
codec: SseCodec(decodeSuccessData: sse_decode_proof_output, decodeErrorData: sse_decode_String),
constMeta: kCrateApiWormholeGenerateProofConstMeta,
@@ -616,13 +679,51 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiWormholeGenerateProofConstMeta =>
const TaskConstMeta(debugName: 'generate_proof', argNames: ['input', 'proverBinPath', 'commonBinPath']);
+ @override
+ (BigInt, BigInt) crateApiWormholeGetProcessMemory() {
+ return handler.executeSync(
+ SyncTask(
+ callFfi: () {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!;
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_record_u_64_u_64, decodeErrorData: null),
+ constMeta: kCrateApiWormholeGetProcessMemoryConstMeta,
+ argValues: [],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeGetProcessMemoryConstMeta =>
+ const TaskConstMeta(debugName: 'get_process_memory', argNames: []);
+
+ @override
+ (BigInt, BigInt, BigInt) crateApiWormholeGetProcessMemoryDetailed() {
+ return handler.executeSync(
+ SyncTask(
+ callFfi: () {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!;
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_record_u_64_u_64_u_64, decodeErrorData: null),
+ constMeta: kCrateApiWormholeGetProcessMemoryDetailedConstMeta,
+ argValues: [],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeGetProcessMemoryDetailedConstMeta =>
+ const TaskConstMeta(debugName: 'get_process_memory_detailed', argNames: []);
+
@override
Future crateApiCryptoInitApp() {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
- pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22, port: port_);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26, port: port_);
},
codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null),
constMeta: kCrateApiCryptoInitAppConstMeta,
@@ -641,7 +742,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_list_String(urParts, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null),
constMeta: kCrateApiUrIsCompleteUrConstMeta,
@@ -654,13 +755,53 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiUrIsCompleteUrConstMeta =>
const TaskConstMeta(debugName: 'is_complete_ur', argNames: ['urParts']);
+ @override
+ Future crateApiWormholeLogMemDetailed({required String tag}) {
+ return handler.executeNormal(
+ NormalTask(
+ callFfi: (port_) {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ sse_encode_String(tag, serializer);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28, port: port_);
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String),
+ constMeta: kCrateApiWormholeLogMemDetailedConstMeta,
+ argValues: [tag],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeLogMemDetailedConstMeta =>
+ const TaskConstMeta(debugName: 'log_mem_detailed', argNames: ['tag']);
+
+ @override
+ void crateApiWormholeLogMemorySnapshot({required String tag}) {
+ return handler.executeSync(
+ SyncTask(
+ callFfi: () {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ sse_encode_String(tag, serializer);
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!;
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null),
+ constMeta: kCrateApiWormholeLogMemorySnapshotConstMeta,
+ argValues: [tag],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeLogMemorySnapshotConstMeta =>
+ const TaskConstMeta(debugName: 'log_memory_snapshot', argNames: ['tag']);
+
@override
BigInt crateApiCryptoPublicKeyBytes() {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null),
constMeta: kCrateApiCryptoPublicKeyBytesConstMeta,
@@ -673,13 +814,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiCryptoPublicKeyBytesConstMeta =>
const TaskConstMeta(debugName: 'public_key_bytes', argNames: []);
+ @override
+ Future crateApiWormholeReleaseMemory() {
+ return handler.executeNormal(
+ NormalTask(
+ callFfi: (port_) {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31, port: port_);
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String),
+ constMeta: kCrateApiWormholeReleaseMemoryConstMeta,
+ argValues: [],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeReleaseMemoryConstMeta =>
+ const TaskConstMeta(debugName: 'release_memory', argNames: []);
+
@override
BigInt crateApiCryptoSecretKeyBytes() {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null),
constMeta: kCrateApiCryptoSecretKeyBytesConstMeta,
@@ -699,7 +859,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_16(prefix, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null),
constMeta: kCrateApiCryptoSetDefaultSs58PrefixConstMeta,
@@ -712,6 +872,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiCryptoSetDefaultSs58PrefixConstMeta =>
const TaskConstMeta(debugName: 'set_default_ss58_prefix', argNames: ['prefix']);
+ @override
+ Future crateApiWormholeSetProvingThreadCount({required int numThreads}) {
+ return handler.executeNormal(
+ NormalTask(
+ callFfi: (port_) {
+ final serializer = SseSerializer(generalizedFrbRustBinding);
+ sse_encode_u_32(numThreads, serializer);
+ pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 34, port: port_);
+ },
+ codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String),
+ constMeta: kCrateApiWormholeSetProvingThreadCountConstMeta,
+ argValues: [numThreads],
+ apiImpl: this,
+ ),
+ );
+ }
+
+ TaskConstMeta get kCrateApiWormholeSetProvingThreadCountConstMeta =>
+ const TaskConstMeta(debugName: 'set_proving_thread_count', argNames: ['numThreads']);
+
@override
Uint8List crateApiCryptoSignMessage({required Keypair keypair, required List message, U8Array32? entropy}) {
return handler.executeSync(
@@ -721,7 +901,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_keypair(keypair, serializer);
sse_encode_list_prim_u_8_loose(message, serializer);
sse_encode_opt_u_8_array_32(entropy, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 35)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null),
constMeta: kCrateApiCryptoSignMessageConstMeta,
@@ -747,7 +927,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_keypair(keypair, serializer);
sse_encode_list_prim_u_8_loose(message, serializer);
sse_encode_opt_u_8_array_32(entropy, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 36)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null),
constMeta: kCrateApiCryptoSignMessageWithPubkeyConstMeta,
@@ -766,7 +946,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 37)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null),
constMeta: kCrateApiCryptoSignatureBytesConstMeta,
@@ -786,7 +966,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_String(s, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 38)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null),
constMeta: kCrateApiCryptoSs58ToAccountIdConstMeta,
@@ -806,7 +986,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_box_autoadd_keypair(obj, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 39)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: null),
constMeta: kCrateApiCryptoToAccountIdConstMeta,
@@ -832,7 +1012,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_keypair(keypair, serializer);
sse_encode_list_prim_u_8_loose(message, serializer);
sse_encode_list_prim_u_8_loose(signature, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 40)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null),
constMeta: kCrateApiCryptoVerifyMessageConstMeta,
@@ -853,7 +1033,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(inputAmount, serializer);
sse_encode_u_32(feeBps, serializer);
- return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!;
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 41)!;
},
codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null),
constMeta: kCrateApiWormholeWormholeComputeOutputAmountConstMeta,
@@ -999,6 +1179,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
+ @protected
+ (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw) {
+ // Codec=Dco (DartCObject based), see doc to use other codecs
+ final arr = raw as List;
+ if (arr.length != 2) {
+ throw Exception('Expected 2 elements, got ${arr.length}');
+ }
+ return (dco_decode_u_64(arr[0]), dco_decode_u_64(arr[1]));
+ }
+
+ @protected
+ (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw) {
+ // Codec=Dco (DartCObject based), see doc to use other codecs
+ final arr = raw as List;
+ if (arr.length != 3) {
+ throw Exception('Expected 3 elements, got ${arr.length}');
+ }
+ return (dco_decode_u_64(arr[0]), dco_decode_u_64(arr[1]), dco_decode_u_64(arr[2]));
+ }
+
@protected
int dco_decode_u_16(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -1208,6 +1408,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return ProofOutput(proofBytes: var_proofBytes, nullifier: var_nullifier);
}
+ @protected
+ (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ var var_field0 = sse_decode_u_64(deserializer);
+ var var_field1 = sse_decode_u_64(deserializer);
+ return (var_field0, var_field1);
+ }
+
+ @protected
+ (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ var var_field0 = sse_decode_u_64(deserializer);
+ var var_field1 = sse_decode_u_64(deserializer);
+ var var_field2 = sse_decode_u_64(deserializer);
+ return (var_field0, var_field1, var_field2);
+ }
+
@protected
int sse_decode_u_16(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1392,6 +1609,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_prim_u_8_strict(self.nullifier, serializer);
}
+ @protected
+ void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ sse_encode_u_64(self.$1, serializer);
+ sse_encode_u_64(self.$2, serializer);
+ }
+
+ @protected
+ void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ sse_encode_u_64(self.$1, serializer);
+ sse_encode_u_64(self.$2, serializer);
+ sse_encode_u_64(self.$3, serializer);
+ }
+
@protected
void sse_encode_u_16(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
diff --git a/quantus_sdk/lib/src/rust/frb_generated.io.dart b/quantus_sdk/lib/src/rust/frb_generated.io.dart
index 3ec4249a9..2244f87c0 100644
--- a/quantus_sdk/lib/src/rust/frb_generated.io.dart
+++ b/quantus_sdk/lib/src/rust/frb_generated.io.dart
@@ -70,6 +70,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
ProofOutput dco_decode_proof_output(dynamic raw);
+ @protected
+ (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw);
+
+ @protected
+ (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw);
+
@protected
int dco_decode_u_16(dynamic raw);
@@ -143,6 +149,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
ProofOutput sse_decode_proof_output(SseDeserializer deserializer);
+ @protected
+ (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer);
+
+ @protected
+ (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer);
+
@protected
int sse_decode_u_16(SseDeserializer deserializer);
@@ -221,6 +233,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
void sse_encode_proof_output(ProofOutput self, SseSerializer serializer);
+ @protected
+ void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer);
+
+ @protected
+ void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer);
+
@protected
void sse_encode_u_16(int self, SseSerializer serializer);
diff --git a/quantus_sdk/lib/src/rust/frb_generated.web.dart b/quantus_sdk/lib/src/rust/frb_generated.web.dart
index 0da556119..e63829c9b 100644
--- a/quantus_sdk/lib/src/rust/frb_generated.web.dart
+++ b/quantus_sdk/lib/src/rust/frb_generated.web.dart
@@ -72,6 +72,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
ProofOutput dco_decode_proof_output(dynamic raw);
+ @protected
+ (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw);
+
+ @protected
+ (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw);
+
@protected
int dco_decode_u_16(dynamic raw);
@@ -145,6 +151,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
ProofOutput sse_decode_proof_output(SseDeserializer deserializer);
+ @protected
+ (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer);
+
+ @protected
+ (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer);
+
@protected
int sse_decode_u_16(SseDeserializer deserializer);
@@ -223,6 +235,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl {
@protected
void sse_encode_proof_output(ProofOutput self, SseSerializer serializer);
+ @protected
+ void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer);
+
+ @protected
+ void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer);
+
@protected
void sse_encode_u_16(int self, SseSerializer serializer);
diff --git a/quantus_sdk/lib/src/services/circuit_manager.dart b/quantus_sdk/lib/src/services/circuit_manager.dart
index 0ac7b838b..0241079b9 100644
--- a/quantus_sdk/lib/src/services/circuit_manager.dart
+++ b/quantus_sdk/lib/src/services/circuit_manager.dart
@@ -48,7 +48,7 @@ class CircuitManager {
static Future getCircuitDirectory() async {
final appDir = await getApplicationSupportDirectory();
- return path.join(appDir.path, 'circuits');
+ return path.join(appDir.path, 'circuits_v3');
}
Future checkStatus() async {
diff --git a/miner-app/lib/src/services/wormhole_claim_service.dart b/quantus_sdk/lib/src/services/wormhole_claim_service.dart
similarity index 54%
rename from miner-app/lib/src/services/wormhole_claim_service.dart
rename to quantus_sdk/lib/src/services/wormhole_claim_service.dart
index 19c02880a..3ee72d636 100644
--- a/miner-app/lib/src/services/wormhole_claim_service.dart
+++ b/quantus_sdk/lib/src/services/wormhole_claim_service.dart
@@ -1,13 +1,15 @@
import 'dart:async';
+import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
-import 'package:quantus_miner/src/services/chain_rpc_client.dart';
-import 'package:quantus_miner/src/utils/app_logger.dart';
+import 'package:http/http.dart' as http;
import 'package:quantus_sdk/generated/planck/pallets/wormhole.dart' as wormhole_pallet;
-import 'package:quantus_sdk/quantus_sdk.dart';
-
-final _log = log.withTag('WormholeClaim');
+import 'package:quantus_sdk/src/rust/api/wormhole.dart' as wormhole_ffi;
+import 'package:quantus_sdk/src/services/network/redundant_endpoint.dart';
+import 'package:quantus_sdk/src/services/substrate_service.dart' show getAccountId32;
+import 'package:quantus_sdk/src/services/wormhole_utxo_service.dart';
+import 'package:polkadart/scale_codec.dart' show ByteOutput, CompactCodec;
class ClaimProgressItem {
final int step;
@@ -41,13 +43,7 @@ class ClaimCancelled implements Exception {
}
class WormholeClaimService {
- static const int _maxProofsPerBatch = 16;
- static const int _proofConcurrency = 16;
static const int _volumeFeeBps = 10;
-
- /// Scaled-down → planck multiplier; matches `SCALE_DOWN_FACTOR` in the Rust
- /// wormhole API. The proof commits to amounts in scaled-down units, and the
- /// chain dispatches `outputAmount * scaleDownFactor` planck.
static final BigInt _scaleDownFactor = BigInt.from(10000000000);
static const _stepTitles = {
@@ -56,17 +52,27 @@ class WormholeClaimService {
3: 'Computing nullifiers',
4: 'Checking nullifiers',
5: 'Generating ZK proofs',
- 6: 'Submitting to chain',
+ 6: 'Aggregating & submitting',
};
final WormholeUtxoService _utxoService = WormholeUtxoService();
+ final RpcEndpointService _rpcEndpoint = RpcEndpointService();
+ final String? _rpcUrl;
+ final int maxProofsPerBatch;
+ final int proofConcurrency;
+ final bool freshBuild;
+ final int? provingThreads;
- /// Completes when the user cancels. Polled by [_checkCancelled] for cheap
- /// chain-level checks and raced against the whole flow in [claimRewards] so
- /// cancellation is instantaneous even mid-FFI (in-flight proofs are simply
- /// orphaned — they'll finish in the background and their results discarded).
Completer? _cancelCompleter;
+ WormholeClaimService({
+ String? rpcUrl,
+ this.maxProofsPerBatch = 16,
+ this.proofConcurrency = 16,
+ this.freshBuild = false,
+ this.provingThreads,
+ }) : _rpcUrl = rpcUrl;
+
bool get _cancelled => _cancelCompleter?.isCompleted ?? false;
void cancel() {
@@ -78,38 +84,29 @@ class WormholeClaimService {
required String wormholeAddress,
required String secretHex,
required String destinationAddress,
- required String rpcUrl,
required String circuitBinsDir,
required ClaimProgressCallback onProgress,
}) async {
final cancelCompleter = Completer();
_cancelCompleter = cancelCompleter;
- final rpc = ChainRpcClient(rpcUrl: rpcUrl, timeout: const Duration(seconds: 30));
try {
final flow = _runClaimFlow(
- rpc: rpc,
wormholeAddress: wormholeAddress,
secretHex: secretHex,
destinationAddress: destinationAddress,
circuitBinsDir: circuitBinsDir,
onProgress: onProgress,
);
- // Race the flow against cancellation. Future.any returns the first to
- // complete; the loser's later completion (success or error) is silently
- // ignored by Future.any, so abandoned in-flight FFI work won't surface
- // as an unhandled async error.
final cancelGuard = cancelCompleter.future.then((_) => throw const ClaimCancelled());
return await Future.any([flow, cancelGuard]);
} on WormholeOperationCancelled {
throw const ClaimCancelled();
- } finally {
- rpc.dispose();
}
}
void _reportProgress(ClaimProgressCallback onProgress, int step, int completed, {int? total}) {
- _log.i('Step $step: ${_stepTitles[step]} $completed${total != null ? '/$total' : ''}');
+ _log('Step $step: ${_stepTitles[step]} $completed${total != null ? '/$total' : ''}');
onProgress(ClaimProgressItem(step: step, title: _stepTitles[step]!, completed: completed, total: total));
}
@@ -118,7 +115,6 @@ class WormholeClaimService {
}
Future _runClaimFlow({
- required ChainRpcClient rpc,
required String wormholeAddress,
required String secretHex,
required String destinationAddress,
@@ -126,11 +122,22 @@ class WormholeClaimService {
required ClaimProgressCallback onProgress,
}) async {
_checkCancelled();
+ _logMem('claim_start');
+
+ if (provingThreads != null) {
+ _log('Setting proving thread count to $provingThreads');
+ await wormhole_ffi.setProvingThreadCount(numThreads: provingThreads!);
+ }
_reportProgress(onProgress, 1, 0);
- _log.i('Ensuring circuit binaries at: $circuitBinsDir');
- await ensureCircuitBinaries(binsDir: circuitBinsDir);
- _log.i('Circuit binaries ready');
+ _log('Ensuring circuit binaries at: $circuitBinsDir (freshBuild=$freshBuild)');
+ if (freshBuild) {
+ await wormhole_ffi.ensureLeafCircuitBinaries(binsDir: circuitBinsDir);
+ } else {
+ await wormhole_ffi.ensureCircuitBinaries(binsDir: circuitBinsDir);
+ }
+ _log('Circuit binaries ready');
+ _logMem('after_ensure_binaries');
_reportProgress(onProgress, 1, 1);
_checkCancelled();
@@ -143,121 +150,129 @@ class WormholeClaimService {
_reportProgress(onProgress, phase + 1, completed, total: total);
},
);
+ _logMem('after_get_unspent_transfers');
if (unspent.isEmpty) {
return ClaimResult(totalWithdrawn: BigInt.zero, transfersProcessed: 0, batchesSubmitted: 0, txHashes: const []);
}
unspent.sort((a, b) => b.amount.compareTo(a.amount));
- _log.i('Found ${unspent.length} unspent transfers');
+ _log('Found ${unspent.length} unspent transfers');
+ _logMem('after_sort_unspent');
_checkCancelled();
- _reportProgress(onProgress, 5, 0, total: unspent.length);
+ final numTransfers = unspent.length;
+ final totalBatches = (numTransfers / maxProofsPerBatch).ceil();
+ _reportProgress(onProgress, 5, 0, total: numTransfers);
- // Use the current head (not finalized) as the proof block: the user is
- // claiming up to the chain tip, and the merkle tree at the finalized head
- // would not contain transfers in the last `reorgDepth` blocks. A reorg
- // before the claim batch lands will cause on-chain verification to fail
- // and the user can simply retry.
- final blockHash = await rpc.getBestBlockHash();
- final header = await rpc.getBlockHeader(blockHash: blockHash);
+ final String blockHash = await _rpcCall('chain_getBlockHash') as String;
+ final header = await _rpcCall('chain_getHeader', [blockHash]);
final blockNumber = _hexToInt(header['number'] as String);
final parentHash = _hexBytes(header['parentHash'] as String);
final stateRoot = _hexBytes(header['stateRoot'] as String);
final extrinsicsRoot = _hexBytes(header['extrinsicsRoot'] as String);
final digest = _encodeDigest(header['digest'] as Map);
- _log.i('Proof block: #$blockNumber ($blockHash)');
+ _log('Proof block: #$blockNumber ($blockHash)');
_checkCancelled();
- final numTransfers = unspent.length;
- final proofBytesList = List.filled(numTransfers, null);
final secretBytes = Uint8List.fromList(hex.decode(secretHex.replaceFirst('0x', '')));
final destinationBytes = Uint8List.fromList(getAccountId32(destinationAddress));
final blockHashBytes = Uint8List.fromList(_hexBytes(blockHash));
BigInt netTotal = BigInt.zero;
- int completed = 0;
+ int proofsCompleted = 0;
+ int batchesCompleted = 0;
+ final txHashes = [];
final genSw = Stopwatch()..start();
- for (int chunk = 0; chunk < numTransfers; chunk += _proofConcurrency) {
- _checkCancelled();
- final end = (chunk + _proofConcurrency).clamp(0, numTransfers);
- final futures = >[];
-
- for (int i = chunk; i < end; i++) {
- final transfer = unspent[i];
- futures.add(
- _generateLeafProof(
- rpc: rpc,
- transfer: transfer,
- blockHash: blockHash,
- blockNumber: blockNumber,
- parentHash: parentHash,
- stateRoot: stateRoot,
- extrinsicsRoot: extrinsicsRoot,
- digest: digest,
- blockHashBytes: blockHashBytes,
- secretBytes: secretBytes,
- destinationBytes: destinationBytes,
- circuitBinsDir: circuitBinsDir,
- outputBuffer: proofBytesList,
- outputIndex: i,
- onComplete: () {
- completed++;
- // Plain stdout print (not debugPrint) so it survives in release
- // builds and is visible from the launching terminal.
- // ignore: avoid_print
- print(
- '[WormholeClaim] Proof $completed/$numTransfers '
- 'leaf=${transfer.leafIndex} (${genSw.elapsedMilliseconds}ms elapsed)',
- );
- _reportProgress(onProgress, 5, completed, total: numTransfers);
- },
- ),
- );
- }
+ for (int batchStart = 0; batchStart < numTransfers; batchStart += maxProofsPerBatch) {
+ final batchEnd = (batchStart + maxProofsPerBatch).clamp(0, numTransfers);
+ final batchIndex = batchStart ~/ maxProofsPerBatch;
+ _logMem('batch_${batchIndex}_start');
+ final batchTransfers = unspent.sublist(batchStart, batchEnd);
+ final batchProofs = List.filled(batchTransfers.length, null);
+
+ for (int chunk = 0; chunk < batchTransfers.length; chunk += proofConcurrency) {
+ _checkCancelled();
+ final end = (chunk + proofConcurrency).clamp(0, batchTransfers.length);
+ final futures = >[];
+
+ for (int i = chunk; i < end; i++) {
+ final transfer = batchTransfers[i];
+ futures.add(
+ _generateLeafProof(
+ transfer: transfer,
+ blockHash: blockHash,
+ blockNumber: blockNumber,
+ parentHash: parentHash,
+ stateRoot: stateRoot,
+ extrinsicsRoot: extrinsicsRoot,
+ digest: digest,
+ blockHashBytes: blockHashBytes,
+ secretBytes: secretBytes,
+ destinationBytes: destinationBytes,
+ circuitBinsDir: circuitBinsDir,
+ outputBuffer: batchProofs,
+ outputIndex: i,
+ onComplete: () {
+ proofsCompleted++;
+ if (proofsCompleted % 16 == 0 || proofsCompleted == numTransfers) {
+ _logMem('after_proof_$proofsCompleted');
+ }
+ print(
+ '[WormholeClaim] Proof $proofsCompleted/$numTransfers '
+ 'leaf=${transfer.leafIndex} (${genSw.elapsedMilliseconds}ms elapsed)',
+ );
+ _reportProgress(onProgress, 5, proofsCompleted, total: numTransfers);
+ },
+ ),
+ );
+ }
- final outputs = await Future.wait(futures, eagerError: true);
- for (final out in outputs) {
- netTotal += out;
+ final outputs = await Future.wait(futures, eagerError: true);
+ for (final out in outputs) {
+ netTotal += out;
+ }
}
- _checkCancelled();
- }
-
- final finalProofs = proofBytesList.cast();
- final batches = >[];
- for (int i = 0; i < finalProofs.length; i += _maxProofsPerBatch) {
- final end = (i + _maxProofsPerBatch).clamp(0, finalProofs.length);
- batches.add(finalProofs.sublist(i, end));
- }
+ _logMem('batch_${batchIndex}_leaves_done');
- final txHashes = [];
- _reportProgress(onProgress, 6, 0, total: batches.length);
- for (int b = 0; b < batches.length; b++) {
_checkCancelled();
- _log.i('Aggregating batch ${b + 1}/${batches.length}');
- final aggregated = await aggregateProofs(proofBytesList: batches[b], binsDir: circuitBinsDir);
- _log.i('Batch ${b + 1} aggregated (${aggregated.length} bytes)');
+ _reportProgress(onProgress, 6, batchesCompleted, total: totalBatches);
+ _log('Releasing memory before aggregation...');
+ await wormhole_ffi.releaseMemory();
+ _logMem('batch_${batchIndex}_before_aggregate');
+ if (batchIndex == 0) {
+ wormhole_ffi.logMemorySnapshot(tag: 'before_first_aggregate_detailed');
+ }
+ _log('Aggregating batch ${batchesCompleted + 1}/$totalBatches (freshBuild=$freshBuild)');
+ final aggregated = freshBuild
+ ? await wormhole_ffi.aggregateProofsFresh(
+ proofBytesList: batchProofs.cast(),
+ binsDir: circuitBinsDir,
+ )
+ : await wormhole_ffi.aggregateProofs(proofBytesList: batchProofs.cast(), binsDir: circuitBinsDir);
+ _logMem('batch_${batchIndex}_after_aggregate');
+ _log('Releasing memory after aggregation...');
+ await wormhole_ffi.releaseMemory();
+ _log('Batch ${batchesCompleted + 1} aggregated (${aggregated.length} bytes)');
_checkCancelled();
- final txHash = await _submitExtrinsic(rpc, aggregated);
+ final txHash = await _submitExtrinsic(aggregated);
txHashes.add(txHash);
- _log.i('Batch ${b + 1} accepted by pool: $txHash');
- _reportProgress(onProgress, 6, b + 1, total: batches.length);
+ batchesCompleted++;
+ _logMem('batch_${batchIndex}_submitted');
+ _log('Batch $batchesCompleted accepted by pool: $txHash');
+ _reportProgress(onProgress, 6, batchesCompleted, total: totalBatches);
}
return ClaimResult(
totalWithdrawn: netTotal,
transfersProcessed: numTransfers,
- batchesSubmitted: batches.length,
+ batchesSubmitted: batchesCompleted,
txHashes: txHashes,
);
}
- /// Generates a single leaf proof and writes it to [outputBuffer]. Returns the
- /// net (post-fee) output amount this leaf contributes. [onComplete] fires
- /// once the proof is written so callers can update progress per-leaf.
Future _generateLeafProof({
- required ChainRpcClient rpc,
required WormholeTransfer transfer,
required String blockHash,
required int blockNumber,
@@ -273,7 +288,7 @@ class WormholeClaimService {
required int outputIndex,
void Function()? onComplete,
}) async {
- final zkProof = await rpc.getZkMerkleProof(transfer.leafIndex, blockHash);
+ final zkProof = await _rpcCall('zkTree_getMerkleProof', [transfer.leafIndex.toInt(), blockHash]);
final leafData = _toBytes(zkProof['leaf_data']);
final leafHash = _toBytes(zkProof['leaf_hash']);
@@ -282,14 +297,18 @@ class WormholeClaimService {
final rawSiblings = zkProof['siblings'] as List;
final siblingsFlat = _flattenSiblings(rawSiblings);
- final merkle = computeMerklePositions(unsortedSiblingsFlat: siblingsFlat, leafHash: leafHash, depth: depth);
+ final merkle = wormhole_ffi.computeMerklePositions(
+ unsortedSiblingsFlat: siblingsFlat,
+ leafHash: leafHash,
+ depth: depth,
+ );
- final inputAmount = decodeLeafAmount(leafData: leafData);
- final outputAmount = wormholeComputeOutputAmount(inputAmount: inputAmount, feeBps: _volumeFeeBps);
- final wormholeAddressBytes = decodeLeafToAccount(leafData: leafData);
+ final inputAmount = wormhole_ffi.decodeLeafAmount(leafData: leafData);
+ final outputAmount = wormhole_ffi.wormholeComputeOutputAmount(inputAmount: inputAmount, feeBps: _volumeFeeBps);
+ final wormholeAddressBytes = wormhole_ffi.decodeLeafToAccount(leafData: leafData);
- final proof = await generateProof(
- input: ProofInput(
+ final proof = await wormhole_ffi.generateProof(
+ input: wormhole_ffi.ProofInput(
secret: secretBytes,
transferCount: transfer.transferCount,
wormholeAddress: wormholeAddressBytes,
@@ -312,23 +331,17 @@ class WormholeClaimService {
commonBinPath: '$circuitBinsDir/common.bin',
);
outputBuffer[outputIndex] = proof.proofBytes;
+ await wormhole_ffi.releaseMemory();
onComplete?.call();
- // On-chain dispatch transfers `outputAmount * scaleDownFactor` planck to
- // the destination, so this is the exact net contribution per leaf.
return BigInt.from(outputAmount) * _scaleDownFactor;
}
- /// Submits an unsigned extrinsic via `author_submitExtrinsic` and returns the
- /// pool-accepted tx hash. We don't wait for inclusion: pool acceptance of a
- /// well-formed unsigned extrinsic is a strong signal it will land, and any
- /// rejection (validation, insufficient priority, etc.) surfaces here as a
- /// JSON-RPC error from [ChainRpcClient.rpcCall].
- Future _submitExtrinsic(ChainRpcClient rpc, Uint8List aggregatedProofBytes) async {
+ Future _submitExtrinsic(Uint8List aggregatedProofBytes) async {
final fullExtrinsic = _wrapUnsignedExtrinsic(aggregatedProofBytes);
final hexExtrinsic = '0x${hex.encode(fullExtrinsic)}';
- _log.i('Submitting unsigned extrinsic (${fullExtrinsic.length} bytes)');
+ _log('Submitting unsigned extrinsic (${fullExtrinsic.length} bytes)');
- final result = await rpc.rpcCall('author_submitExtrinsic', [hexExtrinsic]);
+ final result = await _rpcCall('author_submitExtrinsic', [hexExtrinsic]);
if (result is! String) {
throw StateError('author_submitExtrinsic returned ${result.runtimeType}: $result');
}
@@ -339,7 +352,6 @@ class WormholeClaimService {
final runtimeCall = const wormhole_pallet.Txs().verifyAggregatedProof(proofBytes: callBytes);
final callEncoded = runtimeCall.encode();
- // Unsigned extrinsic body: [version_byte=0x04][call_data]
const versionByte = 0x04;
final body = Uint8List(1 + callEncoded.length);
body[0] = versionByte;
@@ -352,6 +364,39 @@ class WormholeClaimService {
return full;
}
+ // --- RPC helpers ---
+
+ Future _rpcCall(String method, [List? params]) async {
+ final body = jsonEncode({'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params ?? []});
+
+ final http.Response response;
+ if (_rpcUrl != null) {
+ response = await http.post(Uri.parse(_rpcUrl), headers: {'Content-Type': 'application/json'}, body: body);
+ } else {
+ response = await _rpcEndpoint.post(body: body);
+ }
+
+ if (response.statusCode != 200) {
+ throw Exception('$method HTTP ${response.statusCode}: ${response.body}');
+ }
+ final parsed = jsonDecode(response.body) as Map;
+ if (parsed['error'] != null) {
+ throw Exception('$method RPC error: ${parsed['error']}');
+ }
+ return parsed['result'];
+ }
+
+ // --- Utilities ---
+
+ static void _log(String msg) => print('[WormholeClaim] $msg');
+
+ static void _logMem(String tag) {
+ final (phys, virt) = wormhole_ffi.getProcessMemory();
+ final physMb = phys ~/ BigInt.from(1024 * 1024);
+ final virtMb = virt ~/ BigInt.from(1024 * 1024);
+ print('[ClaimMem] $tag phys=${physMb}MB virt=${virtMb}MB');
+ }
+
static int _hexToInt(String hexStr) => int.parse(hexStr.replaceFirst('0x', ''), radix: 16);
static List _hexBytes(String hexStr) => hex.decode(hexStr.replaceFirst('0x', ''));
diff --git a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart
index 376f24bd1..44c2edfdf 100644
--- a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart
+++ b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart
@@ -82,6 +82,13 @@ class WormholeUtxoService {
static void _log(String msg) => print('[WormholeUtxo] $msg');
+ static void _logMem(String tag) {
+ final (phys, virt) = wormhole_ffi.getProcessMemory();
+ final physMb = phys ~/ BigInt.from(1024 * 1024);
+ final virtMb = virt ~/ BigInt.from(1024 * 1024);
+ print('[UtxoMem] $tag phys=${physMb}MB virt=${virtMb}MB');
+ }
+
static String _addressHash(Uint8List raw32) => wormhole_ffi.computeAddressHashHex(rawAddress: raw32);
static void _throwIfCancelled(IsCancelledCallback? isCancelled) {
@@ -400,6 +407,7 @@ query SpentNullifiers($hashes: [String!]!) {
}) async {
final sw = Stopwatch()..start();
_log('getTransfersTo START ($wormholeAddress)');
+ _logMem('getTransfersTo_start');
final raw = Uint8List.fromList(getAccountId32(wormholeAddress));
final fullHash = _addressHash(raw);
@@ -410,6 +418,7 @@ query SpentNullifiers($hashes: [String!]!) {
final cache = await _loadTransferCache(fullHash);
_log('Cache: ${cache.transfers.length} transfers up to block ${cache.cachedUpToBlock}');
+ _logMem('after_load_transfer_cache');
if (cache.transfers.isNotEmpty) onProgress?.call(1, cache.transfers.length);
_throwIfCancelled(isCancelled);
@@ -438,6 +447,7 @@ query SpentNullifiers($hashes: [String!]!) {
final allTransfers = [...cache.transfers, ...newTransfers];
onProgress?.call(1, allTransfers.length);
_log('Total transfers: ${allTransfers.length} (${cache.transfers.length} cached + ${newTransfers.length} new)');
+ _logMem('after_merge_transfers');
// Cache only the reorg-safe slice; the caller still sees recent (above-cutoff)
// transfers so balances / claims include them.
@@ -445,6 +455,7 @@ query SpentNullifiers($hashes: [String!]!) {
await _saveTransferCache(fullHash, _TransferCache(cachedUpToBlock: safeCutoff, transfers: safeTransfers));
_log('getTransfersTo DONE: ${allTransfers.length} transfers (${sw.elapsedMilliseconds}ms)');
+ _logMem('getTransfersTo_done');
return (transfers: allTransfers, safeCutoff: safeCutoff);
}
@@ -455,6 +466,7 @@ query SpentNullifiers($hashes: [String!]!) {
IsCancelledCallback? isCancelled,
}) async {
_log('getUnspentTransfers($wormholeAddress)');
+ _logMem('getUnspentTransfers_start');
final fetched = await getTransfersTo(wormholeAddress, onProgress: onProgress, isCancelled: isCancelled);
final transfers = fetched.transfers;
final safeCutoff = fetched.safeCutoff;
@@ -467,6 +479,7 @@ query SpentNullifiers($hashes: [String!]!) {
final fullHash = _addressHash(raw);
final cachedSpent = await _loadSpentNullifiers(fullHash);
_log('Nullifier cache: ${cachedSpent.length} known spent');
+ _logMem('after_load_nullifier_cache');
final hdWalletService = HdWalletService();
final uncheckedPairs = <(String, String)>[];
@@ -490,11 +503,14 @@ query SpentNullifiers($hashes: [String!]!) {
uncheckedPairs.add((nullifierHex, nullifierHash));
}
onProgress?.call(2, i + 1, total: transfers.length);
+ if ((i + 1) % 500 == 0) _logMem('nullifiers_computed_${i + 1}');
}
_log('Computed nullifiers: $skipped cached-spent, ${uncheckedPairs.length} to check');
+ _logMem('after_compute_all_nullifiers');
if (uncheckedPairs.isNotEmpty) {
final newSpent = await _checkNullifiersSpent(uncheckedPairs, onProgress: onProgress, isCancelled: isCancelled);
+ _logMem('after_check_nullifiers_spent');
// In-memory: every spent nullifier we've seen, including ones in
// unfinalized blocks — must not be re-claimed in this call.
allSpent.addAll(newSpent.keys);
@@ -512,6 +528,7 @@ query SpentNullifiers($hashes: [String!]!) {
final unspent = nullifierToTransfer.entries.where((e) => !allSpent.contains(e.key)).map((e) => e.value).toList();
_log('getUnspentTransfers: ${unspent.length} unspent out of ${transfers.length} total');
+ _logMem('getUnspentTransfers_done');
return unspent;
}
diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock
index 3552cb57f..d73a3d077 100644
--- a/quantus_sdk/rust/Cargo.lock
+++ b/quantus_sdk/rust/Cargo.lock
@@ -2666,8 +2666,6 @@ dependencies = [
[[package]]
name = "qp-wormhole-aggregator"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba5a23eea8e17c97632f5056d1fecdb83d2e997f79d823fb97c514823fce8580"
dependencies = [
"anyhow",
"hex",
@@ -2684,8 +2682,6 @@ dependencies = [
[[package]]
name = "qp-wormhole-circuit"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b72abd3357e1c486621431109d83a259076ed941fcc7f035353a629940c443d9"
dependencies = [
"anyhow",
"hex",
@@ -2697,8 +2693,6 @@ dependencies = [
[[package]]
name = "qp-wormhole-circuit-builder"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d27c981d34a35cb10ee96e16c1fb44fbb4c52f3e58a787ce089c21bf5469514"
dependencies = [
"anyhow",
"clap",
@@ -2711,8 +2705,6 @@ dependencies = [
[[package]]
name = "qp-wormhole-inputs"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f69e54a0f450d349cb1169bbb743a7a846475a1210760308e38f55841e5aa5c0"
dependencies = [
"anyhow",
]
@@ -2720,8 +2712,6 @@ dependencies = [
[[package]]
name = "qp-wormhole-prover"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cee4d6a0317f89d1ec7a6c40a9592e5525c75b9cb21bd7d5d3b408301068bd"
dependencies = [
"anyhow",
"qp-plonky2",
@@ -2733,8 +2723,6 @@ dependencies = [
[[package]]
name = "qp-zk-circuits-common"
version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6d5e1d764560fd797f71defbf75a1618596945c53e33033ac42cc7c2e4689f6"
dependencies = [
"anyhow",
"hex",
@@ -2957,6 +2945,7 @@ dependencies = [
"qp-wormhole-prover",
"qp-zk-circuits-common",
"quantus_ur",
+ "rayon",
"sp-core",
]
diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml
index 4a98973a1..6461f651e 100644
--- a/quantus_sdk/rust/Cargo.toml
+++ b/quantus_sdk/rust/Cargo.toml
@@ -28,11 +28,22 @@ qp-wormhole-aggregator = { version = "2.0.1", default-features = false, features
qp-wormhole-inputs = { version = "2.0.1", default-features = false, features = ["std"] }
qp-zk-circuits-common = { version = "2.0.1", default-features = false, features = ["std"] }
qp-wormhole-circuit-builder = { version = "2.0.1" }
+rayon = "1.10"
## This dependency has been yanked but sp-core depends on version 0.1.1... so we add it with a patch
[patch.crates-io]
ark-vrf = { git = "https://github.com/davxy/ark-vrf", tag = "v0.1.1" }
+# Local qp-zk-circuits checkout — picks up the tighter aggregator config
+# from PR #139 (wormhole_aggregator_circuit_config: 135/60/RowBlinding).
+# Revert these lines to fall back to crates.io.
+qp-zk-circuits-common = { path = "../../../qp-zk-circuits/common" }
+qp-wormhole-circuit = { path = "../../../qp-zk-circuits/wormhole/circuit" }
+qp-wormhole-prover = { path = "../../../qp-zk-circuits/wormhole/prover" }
+qp-wormhole-aggregator = { path = "../../../qp-zk-circuits/wormhole/aggregator" }
+qp-wormhole-inputs = { path = "../../../qp-zk-circuits/wormhole/inputs" }
+qp-wormhole-circuit-builder = { path = "../../../qp-zk-circuits/wormhole/circuit-builder" }
+
# Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s.
[profile.dev.build-override]
opt-level = 3
diff --git a/quantus_sdk/rust/src/api/wormhole.rs b/quantus_sdk/rust/src/api/wormhole.rs
index 18158ed45..476809212 100644
--- a/quantus_sdk/rust/src/api/wormhole.rs
+++ b/quantus_sdk/rust/src/api/wormhole.rs
@@ -225,6 +225,29 @@ pub fn ensure_circuit_binaries(bins_dir: String) -> Result {
Ok(config_str)
}
+/// Lightweight variant that only generates leaf circuit binaries (prover.bin,
+/// common.bin, verifier.bin, dummy_proof.bin). Skips the heavy aggregation
+/// circuit generation entirely. Use with `aggregate_proofs_fresh` which builds
+/// the aggregation circuit in memory at proving time.
+pub fn ensure_leaf_circuit_binaries(bins_dir: String) -> Result<(), String> {
+ let dir = Path::new(&bins_dir);
+ std::fs::create_dir_all(dir)
+ .map_err(|e| format!("Failed to create bins directory {}: {}", bins_dir, e))?;
+
+ if leaf_files_exist(dir) {
+ eprintln!("[ensure_leaf] all leaf files already exist at {}", bins_dir);
+ return Ok(());
+ }
+
+ eprintln!("[ensure_leaf] generating leaf circuit binaries at {}...", bins_dir);
+ let t0 = std::time::Instant::now();
+ qp_wormhole_circuit_builder::generate_circuit_binaries(dir, true)
+ .map_err(|e| format!("Leaf circuit binary generation failed: {}", e))?;
+ eprintln!("[ensure_leaf] DONE (+{}ms)", t0.elapsed().as_millis());
+
+ Ok(())
+}
+
fn all_required_files_exist(dir: &Path) -> bool {
const REQUIRED: &[&str] = &[
"prover.bin",
@@ -239,11 +262,21 @@ fn all_required_files_exist(dir: &Path) -> bool {
REQUIRED.iter().all(|f| dir.join(f).exists())
}
+fn leaf_files_exist(dir: &Path) -> bool {
+ const REQUIRED: &[&str] = &["prover.bin", "verifier.bin", "common.bin", "dummy_proof.bin"];
+ REQUIRED.iter().all(|f| dir.join(f).exists())
+}
+
pub fn generate_proof(
input: ProofInput,
prover_bin_path: String,
common_bin_path: String,
) -> Result {
+ let t0 = std::time::Instant::now();
+ let leaf_input_size = input.sorted_siblings_flat.len() + input.positions.len() + input.digest.len();
+ log_mem("leaf_entry");
+ eprintln!("[leaf] transfer_count={} input_size={}B", input.transfer_count, leaf_input_size);
+
let secret_digest = vec_to_digest(&input.secret, "secret")?;
let wormhole_address = vec_to_32(&input.wormhole_address, "wormhole_address")?;
@@ -288,6 +321,8 @@ pub fn generate_proof(
zk_merkle_siblings.push(sibs);
}
+ log_mem("before PrivateCircuitInputs");
+
let private = PrivateCircuitInputs {
secret: secret_digest,
transfer_count: input.transfer_count,
@@ -301,6 +336,7 @@ pub fn generate_proof(
zk_merkle_siblings,
zk_merkle_positions: input.positions.clone(),
};
+ log_mem("after PrivateCircuitInputs");
let public = PublicCircuitInputs {
asset_id: input.asset_id,
@@ -316,20 +352,38 @@ pub fn generate_proof(
let circuit_inputs = CircuitInputs { public, private };
+ let t_load = std::time::Instant::now();
let prover =
WormholeProver::new_from_files(Path::new(&prover_bin_path), Path::new(&common_bin_path))
.map_err(|e| format!("Failed to load prover: {}", e))?;
+ eprintln!("[leaf] prover loaded in {}ms", t_load.elapsed().as_millis());
+ log_mem("leaf_post_load");
+ let t_commit = std::time::Instant::now();
let prover_with_inputs = prover
.commit(&circuit_inputs)
.map_err(|e| format!("Failed to commit inputs: {}", e))?;
+ eprintln!("[leaf] commit in {}ms", t_commit.elapsed().as_millis());
+ log_mem("leaf_post_commit");
+ let t_prove = std::time::Instant::now();
let proof = prover_with_inputs
.prove()
.map_err(|e| format!("Proof generation failed: {}", e))?;
+ eprintln!("[leaf] prove in {}ms", t_prove.elapsed().as_millis());
+ log_mem("leaf_post_prove");
+
+ let proof_bytes = proof.to_bytes();
+ drop(proof);
+ log_mem("leaf_post_to_bytes");
+ eprintln!(
+ "[leaf] total {}ms, output {} bytes",
+ t0.elapsed().as_millis(),
+ proof_bytes.len()
+ );
Ok(ProofOutput {
- proof_bytes: proof.to_bytes(),
+ proof_bytes,
nullifier: nullifier_bytes.to_vec(),
})
}
@@ -342,13 +396,20 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res
use qp_zk_circuits_common::circuit::{C, D, F};
let bins_path = Path::new(&bins_dir);
+ let t0 = std::time::Instant::now();
+ eprintln!("[agg] start, num_proofs={}, bins_dir={}", proof_bytes_list.len(), bins_dir);
+ log_mem("agg_start");
let mut aggregator = Layer0Aggregator::new(bins_path)
.map_err(|e| format!("Failed to load aggregator: {}", e))?;
+ eprintln!("[agg] Layer0Aggregator::new done (+{}ms)", t0.elapsed().as_millis());
+ log_mem("agg_after_new");
let common_data = aggregator
.load_common_data(CircuitType::Leaf)
.map_err(|e| format!("Failed to load leaf circuit data: {}", e))?;
+ eprintln!("[agg] common_data loaded (+{}ms)", t0.elapsed().as_millis());
+ log_mem("agg_after_load_common");
for (i, proof_bytes) in proof_bytes_list.iter().enumerate() {
let proof = ProofWithPublicInputs::::from_bytes(proof_bytes.clone(), &common_data)
@@ -356,11 +417,318 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res
aggregator
.push_proof(proof)
.map_err(|e| format!("Failed to push proof {}: {}", i, e))?;
+ if i == 0 || i + 1 == proof_bytes_list.len() {
+ log_mem(&format!("agg_after_push_{}", i + 1));
+ }
}
+ eprintln!("[agg] all {} proofs pushed (+{}ms)", proof_bytes_list.len(), t0.elapsed().as_millis());
+ log_mem("agg_before_aggregate_call");
let aggregated = aggregator
.aggregate()
.map_err(|e| format!("Aggregation failed: {}", e))?;
+ eprintln!("[agg] aggregate() returned (+{}ms)", t0.elapsed().as_millis());
+ log_mem("agg_after_aggregate_call");
+
+ let bytes = aggregated.to_bytes();
+ drop(aggregated);
+ log_mem("agg_after_to_bytes");
+ eprintln!("[agg] DONE, output {} bytes (+{}ms)", bytes.len(), t0.elapsed().as_millis());
+ Ok(bytes)
+}
+
+// Layout must match Apple's task_vm_info exactly.
+// Critical types: mach_vm_size_t = u64, integer_t = i32, mach_vm_address_t = u64.
+// Previous version had region_count: u64 + page_size: u32 which shifted every
+// subsequent field by 4-8 bytes and made phys_footprint actually read the
+// `min_address` field, returning a phantom ~4 GB value on ARM64 iOS.
+// We stop at phys_footprint (TASK_VM_INFO_REV1 = 38 u32s = 152 bytes).
+#[cfg(target_vendor = "apple")]
+#[repr(C)]
+struct TaskVmInfoRev1 {
+ virtual_size: u64,
+ region_count: i32,
+ page_size: i32,
+ resident_size: u64,
+ resident_size_peak: u64,
+ device: u64,
+ device_peak: u64,
+ internal: u64,
+ internal_peak: u64,
+ external: u64,
+ external_peak: u64,
+ reusable: u64,
+ reusable_peak: u64,
+ purgeable_volatile_pmap: u64,
+ purgeable_volatile_resident: u64,
+ purgeable_volatile_virtual: u64,
+ compressed: u64,
+ compressed_peak: u64,
+ compressed_lifetime: u64,
+ phys_footprint: u64,
+}
+
+#[cfg(target_vendor = "apple")]
+const _: () = assert!(std::mem::size_of::() == 152);
+
+#[cfg(target_vendor = "apple")]
+const TASK_VM_INFO: u32 = 22;
+
+#[cfg(target_vendor = "apple")]
+unsafe fn read_task_vm_info() -> Option {
+ use std::mem::MaybeUninit;
+ extern "C" {
+ fn mach_task_self() -> u32;
+ fn task_info(task: u32, flavor: u32, info: *mut i32, count: *mut u32) -> i32;
+ }
+ let mut info: MaybeUninit = MaybeUninit::zeroed();
+ let mut count: u32 =
+ (std::mem::size_of::() / std::mem::size_of::()) as u32;
+ let kr = task_info(
+ mach_task_self(),
+ TASK_VM_INFO,
+ info.as_mut_ptr() as *mut i32,
+ &mut count,
+ );
+ if kr == 0 {
+ Some(info.assume_init())
+ } else {
+ None
+ }
+}
+
+/// Returns (phys_footprint_bytes, virtual_bytes) for the current process.
+fn process_memory() -> (u64, u64) {
+ #[cfg(target_vendor = "apple")]
+ unsafe {
+ if let Some(info) = read_task_vm_info() {
+ return (info.phys_footprint, info.virtual_size);
+ }
+ (0, 0)
+ }
+ #[cfg(not(target_vendor = "apple"))]
+ {
+ (0, 0)
+ }
+}
+
+fn log_mem(tag: &str) {
+ #[cfg(target_vendor = "apple")]
+ unsafe {
+ if let Some(info) = read_task_vm_info() {
+ eprintln!(
+ "[mem] {tag} phys={}MB resident={}MB compressed={}MB internal={}MB external={}MB virt={}MB",
+ info.phys_footprint / (1024 * 1024),
+ info.resident_size / (1024 * 1024),
+ info.compressed / (1024 * 1024),
+ info.internal / (1024 * 1024),
+ info.external / (1024 * 1024),
+ info.virtual_size / (1024 * 1024),
+ );
+ return;
+ }
+ }
+ eprintln!("[mem] {tag} (unavailable on this platform)");
+}
+
+/// Dump every field of task_vm_info for deep debugging. Call sparingly.
+pub fn log_mem_detailed(tag: String) -> Result<(), String> {
+ #[cfg(target_vendor = "apple")]
+ unsafe {
+ let Some(info) = read_task_vm_info() else {
+ eprintln!("[mem_detailed] {tag} task_info failed");
+ return Ok(());
+ };
+ eprintln!("[mem_detailed] {tag}");
+ eprintln!(" virtual_size = {} MB", info.virtual_size / (1024 * 1024));
+ eprintln!(" region_count = {}", info.region_count);
+ eprintln!(" page_size = {}", info.page_size);
+ eprintln!(" resident_size = {} MB", info.resident_size / (1024 * 1024));
+ eprintln!(" resident_size_peak = {} MB", info.resident_size_peak / (1024 * 1024));
+ eprintln!(" internal = {} MB", info.internal / (1024 * 1024));
+ eprintln!(" internal_peak = {} MB", info.internal_peak / (1024 * 1024));
+ eprintln!(" external = {} MB", info.external / (1024 * 1024));
+ eprintln!(" external_peak = {} MB", info.external_peak / (1024 * 1024));
+ eprintln!(" reusable = {} MB", info.reusable / (1024 * 1024));
+ eprintln!(" reusable_peak = {} MB", info.reusable_peak / (1024 * 1024));
+ eprintln!(" purgeable_volatile_pmap = {} MB", info.purgeable_volatile_pmap / (1024 * 1024));
+ eprintln!(" purgeable_volatile_resident= {} MB", info.purgeable_volatile_resident / (1024 * 1024));
+ eprintln!(" compressed = {} MB", info.compressed / (1024 * 1024));
+ eprintln!(" compressed_peak = {} MB", info.compressed_peak / (1024 * 1024));
+ eprintln!(" compressed_lifetime = {} MB", info.compressed_lifetime / (1024 * 1024));
+ eprintln!(" phys_footprint = {} MB", info.phys_footprint / (1024 * 1024));
+ }
+ #[cfg(not(target_vendor = "apple"))]
+ {
+ eprintln!("[mem_detailed] {tag} (only available on Apple platforms)");
+ }
+ Ok(())
+}
+
+/// Returns (phys_footprint_bytes, virtual_bytes). Useful for Dart-side polling.
+#[flutter_rust_bridge::frb(sync)]
+pub fn get_process_memory() -> (u64, u64) {
+ process_memory()
+}
+
+/// Returns (phys_footprint, resident_size, compressed). Dart-side memory probe.
+#[flutter_rust_bridge::frb(sync)]
+pub fn get_process_memory_detailed() -> (u64, u64, u64) {
+ #[cfg(target_vendor = "apple")]
+ unsafe {
+ if let Some(info) = read_task_vm_info() {
+ return (info.phys_footprint, info.resident_size, info.compressed);
+ }
+ }
+ (0, 0, 0)
+}
+
+/// Dart-side hook to dump every memory field.
+#[flutter_rust_bridge::frb(sync)]
+pub fn log_memory_snapshot(tag: String) {
+ let _ = log_mem_detailed(tag);
+}
+
+/// Force the system allocator (Apple libmalloc) to return freed pages to the OS.
+/// On other platforms this is a no-op.
+/// Apple's malloc keeps freed memory in per-zone caches by default; after a heavy
+/// allocation phase like ZK proving this can leave 100s of MB of dirty pages.
+/// We iterate every registered zone — passing `null` only reaches the default
+/// zone, which is NOT where Rust's allocator lives, so calling on null released
+/// 0 bytes in practice.
+pub fn release_memory() -> Result<(), String> {
+ log_mem("release_memory_before");
+ #[cfg(target_vendor = "apple")]
+ unsafe {
+ extern "C" {
+ fn malloc_get_all_zones(
+ task: u32,
+ reader: *mut std::ffi::c_void,
+ addresses: *mut *mut *mut std::ffi::c_void,
+ count: *mut u32,
+ ) -> i32;
+ fn malloc_zone_pressure_relief(
+ zone: *mut std::ffi::c_void,
+ goal: usize,
+ ) -> usize;
+ }
+ let mut zones: *mut *mut std::ffi::c_void = std::ptr::null_mut();
+ let mut count: u32 = 0;
+ let kr = malloc_get_all_zones(0, std::ptr::null_mut(), &mut zones, &mut count);
+ let mut total: usize = 0;
+ if kr == 0 && !zones.is_null() {
+ for i in 0..count as isize {
+ let zone = *zones.offset(i);
+ if zone.is_null() {
+ continue;
+ }
+ total = total.saturating_add(malloc_zone_pressure_relief(zone, 0));
+ }
+ }
+ let default_released = malloc_zone_pressure_relief(std::ptr::null_mut(), 0);
+ eprintln!(
+ "[release_memory] released {} bytes ({} MB) across {} zones (+{} from default)",
+ total,
+ total / (1024 * 1024),
+ count,
+ default_released
+ );
+ }
+ log_mem("release_memory_after");
+ Ok(())
+}
- Ok(aggregated.to_bytes())
+/// Limit rayon parallelism to reduce peak memory during proving.
+/// Plonky2 multiplies its per-thread FFT buffers by the rayon pool size, so
+/// fewer threads = lower peak memory (at the cost of wall-clock time).
+/// Idempotent / safe to call repeatedly; only the first call wins.
+pub fn set_proving_thread_count(num_threads: u32) -> Result<(), String> {
+ let n = num_threads.max(1) as usize;
+ eprintln!("[rayon] requesting global pool size = {}", n);
+ match rayon::ThreadPoolBuilder::new().num_threads(n).build_global() {
+ Ok(()) => {
+ eprintln!("[rayon] global pool initialized with {} threads", n);
+ Ok(())
+ }
+ Err(e) => {
+ eprintln!("[rayon] global pool already initialized: {}", e);
+ Ok(())
+ }
+ }
+}
+
+pub fn aggregate_proofs_fresh(proof_bytes_list: Vec>, bins_dir: String) -> Result, String> {
+ use plonky2::plonk::circuit_data::CommonCircuitData;
+ use plonky2::plonk::proof::ProofWithPublicInputs;
+ use plonky2::util::serialization::DefaultGateSerializer;
+ use qp_wormhole_aggregator::layer0::prover::Layer0AggregationProver;
+ use qp_wormhole_aggregator::dummy_proof::load_dummy_proof;
+ use qp_zk_circuits_common::circuit::{wormhole_aggregator_circuit_config, C, D, F};
+
+ let t0 = std::time::Instant::now();
+ eprintln!("[agg_fresh] start, num_proofs={}, bins_dir={}", proof_bytes_list.len(), bins_dir);
+ log_mem("start");
+
+ let bins_path = Path::new(&bins_dir);
+ let gate_serializer = DefaultGateSerializer;
+
+ eprintln!("[agg_fresh] reading common.bin (+{}ms)", t0.elapsed().as_millis());
+ let leaf_common_bytes = std::fs::read(bins_path.join("common.bin"))
+ .map_err(|e| format!("Failed to read common.bin: {}", e))?;
+ eprintln!("[agg_fresh] common.bin read, {} bytes", leaf_common_bytes.len());
+ let leaf_common = CommonCircuitData::::from_bytes(leaf_common_bytes, &gate_serializer)
+ .map_err(|e| format!("Failed to deserialize common.bin: {}", e))?;
+ eprintln!("[agg_fresh] common.bin deserialized (+{}ms)", t0.elapsed().as_millis());
+
+ let leaf_verifier_bytes = std::fs::read(bins_path.join("verifier.bin"))
+ .map_err(|e| format!("Failed to read verifier.bin: {}", e))?;
+ eprintln!("[agg_fresh] verifier.bin read, {} bytes", leaf_verifier_bytes.len());
+ let leaf_verifier_only = plonky2::plonk::circuit_data::VerifierOnlyCircuitData::::from_bytes(leaf_verifier_bytes)
+ .map_err(|e| format!("Failed to deserialize verifier.bin: {}", e))?;
+
+ let dummy_proof_bytes = std::fs::read(bins_path.join("dummy_proof.bin"))
+ .map_err(|e| format!("Failed to read dummy_proof.bin: {}", e))?;
+ eprintln!("[agg_fresh] dummy_proof.bin read, {} bytes", dummy_proof_bytes.len());
+ let dummy_proof = load_dummy_proof(dummy_proof_bytes, &leaf_common)
+ .map_err(|e| format!("Failed to deserialize dummy proof: {}", e))?;
+ eprintln!("[agg_fresh] leaf artifacts loaded (+{}ms)", t0.elapsed().as_millis());
+
+ eprintln!("[agg_fresh] deserializing {} input proofs", proof_bytes_list.len());
+ let mut proofs: Vec> = Vec::with_capacity(proof_bytes_list.len());
+ for (i, bytes) in proof_bytes_list.iter().enumerate() {
+ let p = ProofWithPublicInputs::::from_bytes(bytes.clone(), &leaf_common)
+ .map_err(|e| format!("Failed to deserialize proof {}: {:?}", i, e))?;
+ proofs.push(p);
+ eprintln!("[agg_fresh] deserialized proof {}/{} (input {} bytes)", i + 1, proof_bytes_list.len(), bytes.len());
+ }
+ eprintln!("[agg_fresh] all input proofs deserialized (+{}ms)", t0.elapsed().as_millis());
+ log_mem("after_deserialize");
+
+ eprintln!("[agg_fresh] BUILDING aggregation circuit in memory (this is the heavy step)...");
+ let prover = Layer0AggregationProver::new(
+ wormhole_aggregator_circuit_config(),
+ leaf_common,
+ &leaf_verifier_only,
+ DEFAULT_NUM_LEAF_PROOFS,
+ dummy_proof,
+ );
+ eprintln!("[agg_fresh] aggregation circuit BUILT (+{}ms)", t0.elapsed().as_millis());
+ log_mem("after_circuit_build");
+
+ eprintln!("[agg_fresh] committing proofs to witness...");
+ let prover = prover.commit(proofs)
+ .map_err(|e| format!("Failed to commit proofs: {}", e))?;
+ eprintln!("[agg_fresh] proofs committed (+{}ms)", t0.elapsed().as_millis());
+ log_mem("after_commit");
+
+ eprintln!("[agg_fresh] PROVING aggregation...");
+ log_mem("before_prove");
+ let aggregated = prover.prove()
+ .map_err(|e| format!("Aggregation proving failed: {}", e))?;
+ eprintln!("[agg_fresh] PROVED (+{}ms)", t0.elapsed().as_millis());
+ log_mem("after_prove");
+
+ let bytes = aggregated.to_bytes();
+ eprintln!("[agg_fresh] DONE, output {} bytes (+{}ms)", bytes.len(), t0.elapsed().as_millis());
+ Ok(bytes)
}
diff --git a/quantus_sdk/rust/src/frb_generated.rs b/quantus_sdk/rust/src/frb_generated.rs
index 27c154ab8..412a0a3c1 100644
--- a/quantus_sdk/rust/src/frb_generated.rs
+++ b/quantus_sdk/rust/src/frb_generated.rs
@@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
-pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 623793143;
+pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1186293619;
// Section: executor
@@ -82,6 +82,43 @@ fn wire__crate__api__wormhole__aggregate_proofs_impl(
},
)
}
+fn wire__crate__api__wormhole__aggregate_proofs_fresh_impl(
+ port_: flutter_rust_bridge::for_generated::MessagePort,
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "aggregate_proofs_fresh",
+ port: Some(port_),
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ let api_proof_bytes_list = >>::sse_decode(&mut deserializer);
+ let api_bins_dir = ::sse_decode(&mut deserializer);
+ deserializer.end();
+ move |context| {
+ transform_result_sse::<_, String>((move || {
+ let output_ok = crate::api::wormhole::aggregate_proofs_fresh(
+ api_proof_bytes_list,
+ api_bins_dir,
+ )?;
+ Ok(output_ok)
+ })())
+ }
+ },
+ )
+}
fn wire__crate__api__wormhole__compute_address_hash_hex_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
@@ -543,6 +580,40 @@ fn wire__crate__api__wormhole__ensure_circuit_binaries_impl(
},
)
}
+fn wire__crate__api__wormhole__ensure_leaf_circuit_binaries_impl(
+ port_: flutter_rust_bridge::for_generated::MessagePort,
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "ensure_leaf_circuit_binaries",
+ port: Some(port_),
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ let api_bins_dir = ::sse_decode(&mut deserializer);
+ deserializer.end();
+ move |context| {
+ transform_result_sse::<_, String>((move || {
+ let output_ok =
+ crate::api::wormhole::ensure_leaf_circuit_binaries(api_bins_dir)?;
+ Ok(output_ok)
+ })())
+ }
+ },
+ )
+}
fn wire__crate__api__crypto__first_hash_to_address_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
@@ -706,6 +777,65 @@ fn wire__crate__api__wormhole__generate_proof_impl(
},
)
}
+fn wire__crate__api__wormhole__get_process_memory_impl(
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "get_process_memory",
+ port: None,
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ deserializer.end();
+ transform_result_sse::<_, ()>((move || {
+ let output_ok = Result::<_, ()>::Ok(crate::api::wormhole::get_process_memory())?;
+ Ok(output_ok)
+ })())
+ },
+ )
+}
+fn wire__crate__api__wormhole__get_process_memory_detailed_impl(
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "get_process_memory_detailed",
+ port: None,
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ deserializer.end();
+ transform_result_sse::<_, ()>((move || {
+ let output_ok =
+ Result::<_, ()>::Ok(crate::api::wormhole::get_process_memory_detailed())?;
+ Ok(output_ok)
+ })())
+ },
+ )
+}
fn wire__crate__api__crypto__init_app_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -770,6 +900,71 @@ fn wire__crate__api__ur__is_complete_ur_impl(
},
)
}
+fn wire__crate__api__wormhole__log_mem_detailed_impl(
+ port_: flutter_rust_bridge::for_generated::MessagePort,
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "log_mem_detailed",
+ port: Some(port_),
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ let api_tag = ::sse_decode(&mut deserializer);
+ deserializer.end();
+ move |context| {
+ transform_result_sse::<_, String>((move || {
+ let output_ok = crate::api::wormhole::log_mem_detailed(api_tag)?;
+ Ok(output_ok)
+ })())
+ }
+ },
+ )
+}
+fn wire__crate__api__wormhole__log_memory_snapshot_impl(
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "log_memory_snapshot",
+ port: None,
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ let api_tag = ::sse_decode(&mut deserializer);
+ deserializer.end();
+ transform_result_sse::<_, ()>((move || {
+ let output_ok = Result::<_, ()>::Ok({
+ crate::api::wormhole::log_memory_snapshot(api_tag);
+ })?;
+ Ok(output_ok)
+ })())
+ },
+ )
+}
fn wire__crate__api__crypto__public_key_bytes_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
@@ -799,6 +994,38 @@ fn wire__crate__api__crypto__public_key_bytes_impl(
},
)
}
+fn wire__crate__api__wormhole__release_memory_impl(
+ port_: flutter_rust_bridge::for_generated::MessagePort,
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "release_memory",
+ port: Some(port_),
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ deserializer.end();
+ move |context| {
+ transform_result_sse::<_, String>((move || {
+ let output_ok = crate::api::wormhole::release_memory()?;
+ Ok(output_ok)
+ })())
+ }
+ },
+ )
+}
fn wire__crate__api__crypto__secret_key_bytes_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
@@ -860,6 +1087,40 @@ fn wire__crate__api__crypto__set_default_ss58_prefix_impl(
},
)
}
+fn wire__crate__api__wormhole__set_proving_thread_count_impl(
+ port_: flutter_rust_bridge::for_generated::MessagePort,
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
+ rust_vec_len_: i32,
+ data_len_: i32,
+) {
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(
+ flutter_rust_bridge::for_generated::TaskInfo {
+ debug_name: "set_proving_thread_count",
+ port: Some(port_),
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
+ },
+ move || {
+ let message = unsafe {
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
+ ptr_,
+ rust_vec_len_,
+ data_len_,
+ )
+ };
+ let mut deserializer =
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
+ let api_num_threads = ::sse_decode(&mut deserializer);
+ deserializer.end();
+ move |context| {
+ transform_result_sse::<_, String>((move || {
+ let output_ok =
+ crate::api::wormhole::set_proving_thread_count(api_num_threads)?;
+ Ok(output_ok)
+ })())
+ }
+ },
+ )
+}
fn wire__crate__api__crypto__sign_message_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
@@ -1262,6 +1523,25 @@ impl SseDecode for crate::api::wormhole::ProofOutput {
}
}
+impl SseDecode for (u64, u64) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
+ let mut var_field0 = ::sse_decode(deserializer);
+ let mut var_field1 = ::sse_decode(deserializer);
+ return (var_field0, var_field1);
+ }
+}
+
+impl SseDecode for (u64, u64, u64) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
+ let mut var_field0 = ::sse_decode(deserializer);
+ let mut var_field1 = ::sse_decode(deserializer);
+ let mut var_field2 = ::sse_decode(deserializer);
+ return (var_field0, var_field1, var_field2);
+ }
+}
+
impl SseDecode for u16 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -1341,14 +1621,34 @@ fn pde_ffi_dispatcher_primary_impl(
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
match func_id {
1 => wire__crate__api__wormhole__aggregate_proofs_impl(port, ptr, rust_vec_len, data_len),
- 16 => wire__crate__api__wormhole__ensure_circuit_binaries_impl(
+ 2 => wire__crate__api__wormhole__aggregate_proofs_fresh_impl(
+ port,
+ ptr,
+ rust_vec_len,
+ data_len,
+ ),
+ 17 => wire__crate__api__wormhole__ensure_circuit_binaries_impl(
+ port,
+ ptr,
+ rust_vec_len,
+ data_len,
+ ),
+ 18 => wire__crate__api__wormhole__ensure_leaf_circuit_binaries_impl(
+ port,
+ ptr,
+ rust_vec_len,
+ data_len,
+ ),
+ 23 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len),
+ 26 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len),
+ 28 => wire__crate__api__wormhole__log_mem_detailed_impl(port, ptr, rust_vec_len, data_len),
+ 31 => wire__crate__api__wormhole__release_memory_impl(port, ptr, rust_vec_len, data_len),
+ 34 => wire__crate__api__wormhole__set_proving_thread_count_impl(
port,
ptr,
rust_vec_len,
data_len,
),
- 21 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len),
- 22 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@@ -1361,39 +1661,46 @@ fn pde_ffi_dispatcher_sync_impl(
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
match func_id {
- 2 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len),
- 3 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len),
- 4 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len),
- 5 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len),
- 6 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len),
- 7 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len),
- 8 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len),
- 9 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len),
- 10 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len),
- 11 => {
+ 3 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len),
+ 4 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len),
+ 5 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len),
+ 6 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len),
+ 7 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len),
+ 8 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len),
+ 9 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len),
+ 10 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len),
+ 11 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len),
+ 12 => {
wire__crate__api__wormhole__decode_leaf_transfer_count_impl(ptr, rust_vec_len, data_len)
}
- 12 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len),
- 13 => wire__crate__api__crypto__derive_hd_path_impl(ptr, rust_vec_len, data_len),
- 14 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len),
- 15 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len),
- 17 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len),
- 18 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len),
- 19 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len),
- 20 => {
+ 13 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len),
+ 14 => wire__crate__api__crypto__derive_hd_path_impl(ptr, rust_vec_len, data_len),
+ 15 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len),
+ 16 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len),
+ 19 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len),
+ 20 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len),
+ 21 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len),
+ 22 => {
wire__crate__api__crypto__generate_keypair_from_seed_impl(ptr, rust_vec_len, data_len)
}
- 23 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len),
- 24 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len),
- 25 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len),
- 26 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len),
- 27 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len),
- 28 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len),
- 29 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len),
- 30 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len),
- 31 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len),
- 32 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len),
- 33 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl(
+ 24 => wire__crate__api__wormhole__get_process_memory_impl(ptr, rust_vec_len, data_len),
+ 25 => wire__crate__api__wormhole__get_process_memory_detailed_impl(
+ ptr,
+ rust_vec_len,
+ data_len,
+ ),
+ 27 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len),
+ 29 => wire__crate__api__wormhole__log_memory_snapshot_impl(ptr, rust_vec_len, data_len),
+ 30 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len),
+ 32 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len),
+ 33 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len),
+ 35 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len),
+ 36 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len),
+ 37 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len),
+ 38 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len),
+ 39 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len),
+ 40 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len),
+ 41 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl(
ptr,
rust_vec_len,
data_len,
@@ -1657,6 +1964,23 @@ impl SseEncode for crate::api::wormhole::ProofOutput {
}
}
+impl SseEncode for (u64, u64) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
+ ::sse_encode(self.0, serializer);
+ ::sse_encode(self.1, serializer);
+ }
+}
+
+impl SseEncode for (u64, u64, u64) {
+ // Codec=Sse (Serialization based), see doc to use other codecs
+ fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
+ ::sse_encode(self.0, serializer);
+ ::sse_encode(self.1, serializer);
+ ::sse_encode(self.2, serializer);
+ }
+}
+
impl SseEncode for u16 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
diff --git a/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart b/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart
index f43b4c336..ae592e2ad 100644
--- a/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart
+++ b/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart
@@ -148,7 +148,7 @@ class RustBuilder {
manifestPath,
'-p',
environment.crateInfo.packageName,
- if (!environment.configuration.isDebug) '--release',
+ '--release', // always build in release mode - debug is very slow
'--target',
target.rust,
'--target-dir',
@@ -159,7 +159,7 @@ class RustBuilder {
return path.join(
environment.targetTempDir,
target.rust,
- environment.configuration.rustName,
+ 'release',
);
}