zetrix_vc_flutter 0.0.9 copy "zetrix_vc_flutter: ^0.0.9" to clipboard
zetrix_vc_flutter: ^0.0.9 copied to clipboard

Zetrix flutter SDK for Verifiable Credential (Verifiable Presentation)

example/lib/main.dart

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:example/json_screen.dart';
import 'package:example/src/services/vc_service.dart';
import 'package:example/src/services/vp_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show Clipboard, ClipboardData;
import 'package:bs58/bs58.dart';
import 'package:logger/logger.dart';
import 'package:zetrix_vc_flutter/zetrix_vc_flutter.dart';
import 'package:zetrix_vc_flutter/frb_generated.dart'; // Import for RustLib
import 'qr_code.dart';

var logger = Logger(printer: PrettyPrinter());

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  try {
    await ZetrixVcFlutter().init(); // Initialize the Zetrix VC SDK
  } catch (e, st) {
    // Log but don't crash — app still opens and shows UI
    logger.e('⚠️ SDK init failed: $e', stackTrace: st);
  }
  runApp(const MyApp());
}

// Simple reusable ActionButton widget
class ActionButton extends StatelessWidget {
  final IconData icon;
  final String label;
  final VoidCallback onPressed;

  const ActionButton({
    super.key,
    required this.icon,
    required this.label,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6.0),
      child: SizedBox(
        width: double.infinity,
        child: ElevatedButton.icon(
          icon: Icon(icon, size: 20),
          label: Text(label),
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(vertical: 14),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
          ),
          onPressed: onPressed,
        ),
      ),
    );
  }
}

// Simple SectionHeader widget for section titles
class SectionHeader extends StatelessWidget {
  final String title;
  const SectionHeader(this.title, {super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 16.0),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
          color: Colors.indigo,
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Zetrix VC Plugin Demo',
      home: Scaffold(
        appBar: AppBar(title: const Text('Zetrix VC SDK')),
        body: const Center(child: ZetrixSdkTestWidget()),
      ),
    );
  }
}

class ZetrixSdkTestWidget extends StatefulWidget {
  const ZetrixSdkTestWidget({super.key});

  @override
  State<ZetrixSdkTestWidget> createState() => _ZetrixSdkTestWidgetState();
}

class _ZetrixSdkTestWidgetState extends State<ZetrixSdkTestWidget> with WidgetsBindingObserver {

  // ── DCQL VP Demo ────────────────────────────────────────────────────────────

  /// Pops a dialog where the user pastes a presentation request JSON
  /// (from GET /v1/presentation/{id}), calls [ZetrixVpService.createVPFromDCQL],
  /// and pushes the resulting [VpSubmissionBody] to [JsonViewScreen].
  Future<void> _runDcqlVpDemo() async {
    const defaultRequest = '''{
  "object": {
    "credential_query": {
      "credentials": [
        {
          "id": "did:zid:ba4f1fcf68831a5c",
          "format": "ldp_vc",
          "meta": {
            "vct_values": ["VerifiableCredential", "IDENTITY CARD MALAYSIA"]
          },
          "claims": [
            {
              "path": ["credentialSubject", "nationality"],
              "constraints": { "const": "Malaysian" }
            },
            {
              "path": ["credentialSubject", "gender"],
              "constraints": { "enum": ["Male", "Female"] }
            },
            {
              "path": ["credentialSubject", "age"],
              "constraints": { "minimum": 18 }
            }
          ]
        }
      ]
    },
    "nonce": "15e49bc18f0eaec5abe3fa6381cff76b",
    "state": "user_session_demo",
    "response_uri": "http://localhost:8080/v1/presentation/submit",
    "response_mode": "direct_post"
  }
}''';

    final requestController = TextEditingController(text: defaultRequest);

    final confirmed = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('DCQL Presentation Request'),
        content: SizedBox(
          width: double.maxFinite,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Paste the JSON from GET /v1/presentation/{id}:',
                style: TextStyle(fontSize: 13, color: Colors.grey),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: requestController,
                maxLines: 14,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Paste presentation request JSON here…',
                ),
                style: const TextStyle(fontFamily: 'monospace', fontSize: 11),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx, false),
            child: const Text('Cancel'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pop(ctx, true),
            child: const Text('Generate VP'),
          ),
        ],
      ),
    );

    if (confirmed != true || !mounted) return;

    setState(() => _result = '⏳ Generating DCQL VP…');

    // Demo VC provided by user (Identity Card Malaysia)
    const vcJson = '''{
        "id": "did:zid:798d3458a858771808b7c5957fef7c1d2aed6689b8364b9c1659d26478d05d3d",
        "type": [
          "VerifiableCredential",
          "IDENTITY CARD MALAYSIA"
        ],
        "issuer": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
        "validFrom": "2026-04-03T00:00:00Z",
        "validUntil": "2029-12-02T00:00:00Z",
        "credentialSubject": {
          "id": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
          "name": "muhammad harith",
          "DOB": "01-10-1990",
          "idNo": "901001014937",
          "nationality": "Malaysian",
          "gender": "Male",
          "age": 36
        },
        "proof": [
          {
            "type": "BbsBlsSignature2020",
            "created": "2026-03-04T06:14:13.255991952Z",
            "proofPurpose": "assertionMethod",
            "proofValue": "utxbeMnOkBuEp1F_ow-CL2hKXHHOD9I6XXa5steBfwG0gf6wnHPL5ekRwYMi5R-skXuK8cgBkqp7EIGQHysK97xcgZCNJWta1U5CPf5k0p2gLy6WuX_KmxIpFi0N05KypsbSid41LZdjiS8YF9Z8YMA",
            "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#delegateKey-1"
          },
          {
            "type": "Ed25519Signature2020",
            "created": "2026-03-04T06:14:13.256985568Z",
            "proofPurpose": "assertionMethod",
            "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#controllerKey",
            "jws": "ewogICJhbGciOiAiRWREU0EiCn0.ewogICJpZCIgOiAiZGlkOnppZDo3OThkMzQ1OGE4NTg3NzE4MDhiN2M1OTU3ZmVmN2MxZDJhZWQ2Njg5YjgzNjRiOWMxNjU5ZDI2NDc4ZDA1ZDNkIiwKICAidHlwZSIgOiBbICJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsICJJREVOVElUWSBDQVJEIE1BTEFZU0lBIiBdLAogICJpc3N1ZXIiIDogImRpZDp6aWQ6OWJlZTc2NTY3NGRlMDAwZGU3MzZjZDczZTIzNTM4OTBkZjFmZGI2YWMwZGY5ZWNjM2E1YTc4YTRhZmVlMDNhOSIsCiAgInZhbGlkRnJvbSIgOiAiMjAyNi0wNC0wM1QwMDowMDowMFoiLAogICJ2YWxpZFVudGlsIiA6ICIyMDI5LTEyLTAyVDAwOjAwOjAwWiIsCiAgImNyZWRlbnRpYWxTdWJqZWN0IiA6IHsKICAgICJpZCIgOiAiZGlkOnppZDo5YmVlNzY1Njc0ZGUwMDBkZTczNmNkNzNlMjM1Mzg5MGRmMWZkYjZhYzBkZjllY2MzYTVhNzhhNGFmZWUwM2E5IiwKICAgICJpZGVudGl0eUNhcmRNYWxheXNpYSIgOiB7CiAgICAgICJuYW1lIiA6ICJtdWhhbW1hZCBoYXJpdGgiLAogICAgICAiRE9CIiA6ICIwMS0xMC0xOTkwIiwKICAgICAgImlkTm8iIDogOTAxMDAxMDE0OTM3LAogICAgICAiZ2VuZGVyIiA6ICJNYWxlIiwKICAgICAgImFnZSIgOiAzNgogICAgfQogIH0sCiAgIkBjb250ZXh0IiA6IFsgImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwgImh0dHBzOi8vdzNpZC5vcmcvc2VjdXJpdHkvYmJzL3YxIiwgImh0dHBzOi8vdGVzdC1ub2RlLnpldHJpeC5jb20vZ2V0QWNjb3VudE1ldGFEYXRhP2FkZHJlc3M9WlRYM0pzenFQZ1JVeDc0M1NBcDdxN3pVUmZqdmtXdUgyRk1FeiZrZXk9dGVtcGxhdGVfX2RpZDp6aWQ6NmFkODM4NGZmZmJjNmY3NzJjNWVmN2ZjMDNjOWRkMDkzY2JkMDRiNTc2ZmU2ZmIyMzYyZDZmNDgwZTdmN2JhOSIgXQp9.NkFFQjE2NjdGRjdGNjc1NzY4M0FFNjVFMDgwMjdFNkIwOEIwQUIyMDFCMDQ0NzRBNENENEE0MjU3NDkxQ0YwNzIxM0IxREJEOEY4QTE1MEVCNDdCQjg0OTlCMTREQkNBODhCNzE5Q0QxRDYxRTk1QTg5QzU3NkExMzVERjg5MDk"
          }
        ],
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://w3id.org/security/bbs/v1",
          "https://test-node.zetrix.com/getAccountMetaData?address=ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz&key=template__did:zid:6ad8384fffbc6f772c5ef7fc03c9dd093cbd04b576fe6fb2362d6f480e7f7ba9"
        ]
      }''';

    // Decode holder Ed25519 private key seed.
    // Zetrix format: "privB..." → strip "priv", base58-decode → first 32 bytes.
    // Use user-supplied keys
    Uint8List ed25519Seed;
    try {
      const rawPrivKey = 'privBueqLZ7z5eMUpSpgxsdaZqehtnSCkzzrjBQHFpmYXR28kor3ucm5';
      final decoded = base58.decode(rawPrivKey.substring(4));
      ed25519Seed = Uint8List.fromList(decoded.take(32).toList());
    } catch (_) {
      ed25519Seed = Uint8List(32);
    }

    // Decode provided BBS private key (strip leading 'z' if present)
    Uint8List bbsSeed;
    try {
      final rawBbs = 'z3Wyh9R6dvUR1YyCiJMb1LQEt9EsEYFihB2Yo1cBjHh46';
      final strippedBbs = rawBbs.startsWith('z') ? rawBbs.substring(1) : rawBbs;
      bbsSeed = Uint8List.fromList(base58.decode(strippedBbs).take(32).toList());
    } catch (_) {
      bbsSeed = Uint8List(32);
    }

    final keys = WalletKeyMaterial(
      holderDid:
          'did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9',
      ed25519PrivateKey: ed25519Seed,
      ed25519PublicKey:
          'b0019bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9c8b47173',
      bbsPrivateKey: bbsSeed,
      bbsPublicKey:
          'z23VEBWyZUQGqU5e43dJDwxBGYi4mosy3gm1PQgqQod7qZbeWKfUboofmeUjtHNZusGZXv7ZUSG3ehjyzzo5d6XghCezk4rYyXKosEctzgDeHfQppGuXstCQGWrE37S1K8KLB',
    );

    try {
      final requestMap =
          jsonDecode(requestController.text) as Map<String, dynamic>;
      final vcMap = jsonDecode(vcJson) as Map<String, dynamic>;

      final result = await zetrixVpService.createVPFromDCQL(
        presentationResponse: requestMap,
        vc: vcMap,
        keys: keys,
      );

      logger.i('✅ DCQL VP generated');
      setState(() => _result = '✅ DCQL VP generated');

      if (!mounted) return;
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) =>
              JsonViewScreen(jsonString: jsonEncode(result.toJson())),
        ),
      );
    } on DcqlMatchException catch (e) {
      logger.e('DcqlMatchException: $e');
      setState(() => _result = '❌ No matching credential');
      if (!mounted) return;
      _showDcqlErrorDialog('No Matching Credential', e.message);
    } on ClaimNotFoundException catch (e) {
      logger.e('ClaimNotFoundException: $e');
      setState(() => _result = '❌ Claim not found: ${e.path}');
      if (!mounted) return;
      _showDcqlErrorDialog(
          'Claim Not Found', 'Required field missing in VC:\n${e.path}');
    } on RangeProofFailException catch (e) {
      logger.e('RangeProofFailException: $e');
      setState(() => _result = '❌ Does not meet requirements');
      if (!mounted) return;
      _showDcqlErrorDialog('Requirement Not Met',
          'Field "${e.fieldName}" = ${e.value} does not satisfy '
          '[min=${e.minimum ?? "—"}, max=${e.maximum ?? "—"}]');
    } on ProofCreationException catch (e) {
      logger.e('ProofCreationException: $e');
      setState(() => _result = '❌ Proof generation failed');
      if (!mounted) return;
      _showDcqlErrorDialog('Proof Generation Failed', e.message);
    } catch (e, st) {
      logger.e('DCQL VP error', error: e, stackTrace: st);
      setState(() => _result = '❌ Error: $e');
      if (!mounted) return;
      _showDcqlErrorDialog('Error', e.toString());
    }
  }

  // ── DCQL VP Real Demo (user-supplied keys + VC) ──────────────────────────

  Future<void> _runDcqlVpDemoReal() async {
    final requestCtrl = TextEditingController(text: _cachedDcqlRequest);
    final holderDidCtrl = TextEditingController(text: _cachedHolderDid);
    final ed25519PrivCtrl = TextEditingController(text: _cachedEd25519Priv);
    final ed25519PubCtrl = TextEditingController(text: _cachedEd25519Pub);
    final bbsPrivCtrl = TextEditingController(text: _cachedBbsPriv);
    final bbsPubCtrl = TextEditingController(text: _cachedBbsPub);
    final vcCtrl = TextEditingController(text: _cachedVc);

    // Local helper to build a labelled text field
    Widget buildField({
      required String label,
      required TextEditingController ctrl,
      int maxLines = 1,
      String hint = '',
      bool optional = false,
    }) {
      return Padding(
        padding: const EdgeInsets.only(bottom: 10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              optional ? '$label (optional)' : label,
              style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 4),
            TextField(
              controller: ctrl,
              maxLines: maxLines,
              decoration: InputDecoration(
                border: const OutlineInputBorder(),
                hintText: hint,
                hintStyle: const TextStyle(fontSize: 11),
                isDense: true,
                contentPadding:
                    const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
              ),
              style: const TextStyle(fontFamily: 'monospace', fontSize: 11),
            ),
          ],
        ),
      );
    }

    final confirmed = await showDialog<bool>(
      context: context,
      builder: (ctx) => StatefulBuilder(
        builder: (ctx, setDialogState) => AlertDialog(
        title: const Text('DCQL VP — Real Demo'),
        content: SizedBox(
          width: double.maxFinite,
          height: MediaQuery.of(ctx).size.height * 0.75,
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                buildField(
                  label: 'Presentation Request JSON',
                  ctrl: requestCtrl,
                  maxLines: 6,
                  hint: '{ "object": { "credential_query": … } }',
                ),
                const Divider(height: 20),
                const Text(
                  'Wallet Key Material',
                  style: TextStyle(
                      fontSize: 13,
                      fontWeight: FontWeight.bold,
                      color: Colors.indigo),
                ),
                const SizedBox(height: 8),
                buildField(
                  label: 'holderDid',
                  ctrl: holderDidCtrl,
                  hint: 'did:zid:…',
                ),
                buildField(
                  label: 'ed25519PrivateKey',
                  ctrl: ed25519PrivCtrl,
                  hint: 'privB… (Zetrix format)',
                ),
                buildField(
                  label: 'ed25519PublicKey',
                  ctrl: ed25519PubCtrl,
                  hint: 'hex string, e.g. b001…',
                ),
                buildField(
                  label: 'bbsPrivateKey',
                  ctrl: bbsPrivCtrl,
                  hint: 'privB… or leave blank',
                  optional: true,
                ),
                buildField(
                  label: 'bbsPublicKey (issuer BLS key)',
                  ctrl: bbsPubCtrl,
                  hint: 'z… (multibase BLS12-381 G2)',
                ),
                const Divider(height: 20),
                buildField(
                  label: 'Verifiable Credential JSON',
                  ctrl: vcCtrl,
                  maxLines: 8,
                  hint: '{ "@context": […], "type": […], "proof": […], … }',
                ),
              ],
            ),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () {
              requestCtrl.clear();
              holderDidCtrl.clear();
              ed25519PrivCtrl.clear();
              ed25519PubCtrl.clear();
              bbsPrivCtrl.clear();
              bbsPubCtrl.clear();
              vcCtrl.clear();
            },
            child: const Text('Clear All'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(ctx, false),
            child: const Text('Cancel'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pop(ctx, true),
            child: const Text('Generate VP'),
          ),
        ],
        ),
      ),
    );

    if (confirmed != true || !mounted) return;

    setState(() {
      _result = '⏳ Generating DCQL VP…';
      _cachedDcqlRequest = requestCtrl.text.trim();
      _cachedHolderDid = holderDidCtrl.text.trim();
      _cachedEd25519Priv = ed25519PrivCtrl.text.trim();
      _cachedEd25519Pub = ed25519PubCtrl.text.trim();
      _cachedBbsPriv = bbsPrivCtrl.text.trim();
      _cachedBbsPub = bbsPubCtrl.text.trim();
      _cachedVc = vcCtrl.text.trim();
    });

    // ── Decode Ed25519 private key (Zetrix privB… format) ───────────────────
    Uint8List ed25519Seed;
    try {
      final raw = ed25519PrivCtrl.text.trim();
      final stripped = raw.startsWith('priv') ? raw.substring(4) : raw;
      ed25519Seed =
          Uint8List.fromList(base58.decode(stripped).take(32).toList());
    } catch (e) {
      setState(() => _result = '❌ Invalid ed25519PrivateKey');
      _showDcqlErrorDialog('Invalid Key',
          'Could not decode ed25519PrivateKey.\n'
          'Expected Zetrix "privB…" format.\n\nError: $e');
      return;
    }

    // ── Decode BBS private key (optional) ────────────────────────────────
    Uint8List? bbsSeed;
    final bbsPrivRaw = bbsPrivCtrl.text.trim();
    if (bbsPrivRaw.isNotEmpty) {
      try {
        final stripped = bbsPrivRaw.startsWith('priv')
            ? bbsPrivRaw.substring(4)
            : bbsPrivRaw;
        bbsSeed =
            Uint8List.fromList(base58.decode(stripped).take(32).toList());
      } catch (e) {
        setState(() => _result = '❌ Invalid bbsPrivateKey');
        _showDcqlErrorDialog('Invalid Key',
            'Could not decode bbsPrivateKey.\nLeave blank to skip.\n\nError: $e');
        return;
      }
    }
    // bbsSeed remains null when blank — field is optional in WalletKeyMaterial.

    final keys = WalletKeyMaterial(
      holderDid: holderDidCtrl.text.trim(),
      ed25519PrivateKey: ed25519Seed,
      ed25519PublicKey: ed25519PubCtrl.text.trim(),
      bbsPrivateKey: bbsSeed,
      bbsPublicKey: bbsPubCtrl.text.trim(),
    );

    try {
      final requestMap =
          jsonDecode(requestCtrl.text.trim()) as Map<String, dynamic>;
      final vcMap = jsonDecode(vcCtrl.text.trim()) as Map<String, dynamic>;

      final result = await zetrixVpService.createVPFromDCQL(
        presentationResponse: requestMap,
        vc: vcMap,
        keys: keys,
      );

      logger.i('✅ DCQL VP (real) generated');
      setState(() => _result = '✅ DCQL VP generated');

      if (!mounted) return;
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) =>
              JsonViewScreen(jsonString: jsonEncode(result.toJson())),
        ),
      );
    } on DcqlMatchException catch (e) {
      logger.e('DcqlMatchException: $e');
      setState(() => _result = '❌ No matching credential');
      if (!mounted) return;
      _showDcqlErrorDialog('No Matching Credential', e.message);
    } on ClaimNotFoundException catch (e) {
      logger.e('ClaimNotFoundException: $e');
      setState(() => _result = '❌ Claim not found: ${e.path}');
      if (!mounted) return;
      _showDcqlErrorDialog(
          'Claim Not Found', 'Required field missing in VC:\n${e.path}');
    } on RangeProofFailException catch (e) {
      logger.e('RangeProofFailException: $e');
      setState(() => _result = '❌ Does not meet requirements');
      if (!mounted) return;
      _showDcqlErrorDialog('Requirement Not Met',
          'Field "${e.fieldName}" = ${e.value} does not satisfy '
          '[min=${e.minimum ?? "—"}, max=${e.maximum ?? "—"}]');
    } on ProofCreationException catch (e) {
      logger.e('ProofCreationException: $e');
      setState(() => _result = '❌ Proof generation failed');
      if (!mounted) return;
      _showDcqlErrorDialog('Proof Generation Failed', e.message);
    } catch (e, st) {
      logger.e('DCQL VP (real) error', error: e, stackTrace: st);
      setState(() => _result = '❌ Error: $e');
      if (!mounted) return;
      _showDcqlErrorDialog('Error', e.toString());
    }
  }

  Future<void> _runDcqlVpDemoPresentationRequestandVC() async {
    final prCtrl = TextEditingController();
    final vcCtrl = TextEditingController();

    final generate = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Paste Presentation Request and VC'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Align(alignment: Alignment.centerLeft, child: Text('Presentation Request (JSON)')),
              const SizedBox(height: 8),
              TextField(controller: prCtrl, maxLines: 8, decoration: const InputDecoration(border: OutlineInputBorder())),
              const SizedBox(height: 12),
              const Align(alignment: Alignment.centerLeft, child: Text('Verifiable Credential (JSON)')),
              const SizedBox(height: 8),
              TextField(controller: vcCtrl, maxLines: 8, decoration: const InputDecoration(border: OutlineInputBorder())),
            ],
          ),
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
          ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Generate')),
        ],
      ),
    );

    if (generate != true) return;

    setState(() => _result = '⏳ Generating DCQL VP (presentation+vc)...');

    try {
      final presentationResponse = jsonDecode(prCtrl.text.trim()) as Map<String, dynamic>;
      final vc = jsonDecode(vcCtrl.text.trim()) as Map<String, dynamic>;

      // Build WalletKeyMaterial from provided constants (fall back to zeros on error)
      Uint8List ed25519Seed;
      try {
        const rawPrivKey = 'privBueqLZ7z5eMUpSpgxsdaZqehtnSCkzzrjBQHFpmYXR28kor3ucm5';
        final decoded = base58.decode(rawPrivKey.substring(4));
        ed25519Seed = Uint8List.fromList(decoded.take(32).toList());
      } catch (_) {
        ed25519Seed = Uint8List(32);
      }

      Uint8List bbsSeed;
      try {
        final rawBbs = 'z6sdSJeEaj1GYt4urS4GmaNJ59tc6gQMDqjU6PVubTpSq';
        final stripped = rawBbs.startsWith('z') ? rawBbs.substring(1) : rawBbs;
        bbsSeed = Uint8List.fromList(base58.decode(stripped).take(32).toList());
      } catch (_) {
        bbsSeed = Uint8List(32);
      }

      final keys = WalletKeyMaterial(
        holderDid: 'did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9',
        ed25519PrivateKey: ed25519Seed,
        ed25519PublicKey: 'b0019bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9c8b47173',
        bbsPrivateKey: bbsSeed,
        bbsPublicKey:
            'z23VEBWyZUQGqU5e43dJDwxBGYi4mosy3gm1PQgqQod7qZbeWKfUboofmeUjtHNZusGZXv7ZUSG3ehjyzzo5d6XghCezk4rYyXKosEctzgDeHfQppGuXstCQGWrE37S1K8KLB',
      );

      final result = await zetrixVpService.createVPFromDCQL(
        presentationResponse: presentationResponse,
        vc: vc,
        keys: keys,
      );

      logger.i('✅ DCQL VP (presentation+vc) generated');
      setState(() => _result = '✅ DCQL VP generated');
      if (!mounted) return;
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => JsonViewScreen(jsonString: jsonEncode(result.toJson()))),
      );
    } on FormatException catch (e) {
      _showDcqlErrorDialog('Invalid JSON', e.toString());
    } on DcqlMatchException catch (e) {
      _showDcqlErrorDialog('No Matching Credential', e.message);
    } on ClaimNotFoundException catch (e) {
      _showDcqlErrorDialog('Claim Not Found', 'Required field missing: ${e.path}');
    } on RangeProofFailException catch (e) {
      _showDcqlErrorDialog('Requirement Not Met', 'Field ${e.fieldName} value=${e.value} not in [${e.minimum},${e.maximum}]');
    } on ProofCreationException catch (e) {
      _showDcqlErrorDialog('Proof Generation Failed', e.message);
    } catch (e, st) {
      logger.e('DCQL VP (presentation+vc) error', error: e, stackTrace: st);
      _showDcqlErrorDialog('Error', e.toString());
    }
  }

  // ─────────────────────────────────────────────────────────────────────────

  /// Demo: createVpLite with range proof for age >= 18
  Future<void> _runVpLiteRangeProofDemo() async {
    final vcCtrl = TextEditingController();

    final generate = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('VP Lite with Range Proof (Age ≥ 18)'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  'Paste your Identity Card VC JSON:\n'
                  '(will reveal identityCardMalaysia.age and prove age ≥ 18)',
                  style: TextStyle(fontSize: 12),
                ),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: vcCtrl,
                maxLines: 10,
                decoration: const InputDecoration(border: OutlineInputBorder()),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
          ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Generate VP')),
        ],
      ),
    );

    if (generate != true) return;

    setState(() => _result = '⏳ Generating VP Lite with range proof...');

    try {
      final vcMap = jsonDecode(vcCtrl.text.trim()) as Map<String, dynamic>;
      final vc = VerifiableCredential.fromJson(vcMap);

      // Reveal attribute for selective disclosure
      final revealAttribute = ["identityCardMalaysia.age"];

      // Range proof: prove age >= 18 (without revealing exact value)
      final rangeProofRequest = RangeProofRequest(
        attributes: ["identityCardMalaysia.age"],
        minValues: [18],
        maxValues: [BulletproofUtil.noMaxValue], // No upper limit
        bits: 32,
        domain: 'age-verification',
      );

      // BLS public key from the VC issuer
      const String issuerBlsPublicKey =
          'z23VEBWyZUQGqU5e43dJDwxBGYi4mosy3gm1PQgqQod7qZbeWKfUboofmeUjtHNZusGZXv7ZUSG3ehjyzzo5d6XghCezk4rYyXKosEctzgDeHfQppGuXstCQGWrE37S1K8KLB';
      const String holderPublicKey = 'b0019bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9c8b47173';

      final ZetrixSDKResult<String> result = await zetrixVpService.createVpLite(
        vc,
        revealAttribute,
        issuerBlsPublicKey,
        holderPublicKey,
        rangeProofRequest,
      );

      if (result is Success<String> && result.data != null) {
        logger.i('✅ VP Lite with range proof generated!');
        setState(() => _result = '✅ VP generated successfully');
        if (!mounted) return;

        // Parse and display result
        try {
          var parsedJson = jsonDecode(result.data!);
          if (parsedJson is! Map) {
            parsedJson = {'vpCompressed': parsedJson};
          }
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(jsonString: jsonEncode(parsedJson)),
            ),
          );
        } catch (e) {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(
                jsonString: jsonEncode({'vpCompressed': result.data!}),
              ),
            ),
          );
        }
      } else if (result is Failure) {
        final failure = result as Failure;
        logger.e('❌ VP generation failed: ${failure.error}');
        setState(() => _result = '❌ VP generation failed: ${failure.error}');
        if (!mounted) return;
        _showDcqlErrorDialog('VP Generation Failed', failure.error.toString());
      } else {
        setState(() => _result = '❌ Unknown error');
      }
    } on FormatException catch (e) {
      logger.e('Invalid JSON: $e');
      _showDcqlErrorDialog('Invalid JSON', e.toString());
    } catch (e, stackTrace) {
      logger.e('❌ Exception during VP Lite generation: $e', stackTrace: stackTrace);
      setState(() => _result = '❌ Exception: ${e.toString()}');
      _showDcqlErrorDialog('Error', e.toString());
    }
  }

  // ─────────────────────────────────────────────────────────────────────────

  void _showDcqlErrorDialog(String title, String body) {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text(title),
        content: SingleChildScrollView(child: SelectableText(body)),
        actions: [
          TextButton(
            onPressed: () {
              Clipboard.setData(ClipboardData(text: body));
              Navigator.pop(ctx);
            },
            child: const Text('Copy'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────

  Future<void> _showExampleBulletproofVC() async {
    // Use Driving License VC data for bulletproof VP generation
      const vcJson = '''{
            "id": "did:zid:eb25b601831d5a11f7412f6beef0eb8abe9c554b78ff9a2684b1fa218f4a1eb5",
            "type": [
                "VerifiableCredential",
                "IDENTITY CARD MALAYSIA"
            ],
            "issuer": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
            "validFrom": "2026-02-15T00:00:00Z",
            "validUntil": "2036-12-31T00:00:00Z",
            "credentialSubject": {
                "id": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
                "identityCardMalaysia": {
                    "name": "muhammad harith",
                    "DOB": "01-10-1990",
                    "idNo": 901001014937,
                    "gender": "Male",
                    "age": 36
                }
            },
            "proof": [
                {
                    "type": "BbsBlsSignature2020",
                    "created": "2026-03-06T00:07:08.102236052Z",
                    "proofPurpose": "assertionMethod",
                    "proofValue": "uqYfjajSZfrUjkJ5cn3G74iOKQYV-he-IiHE3aMzt2LTTnaQsEqN8ENX-ba6w11WFK27CizcCmwmC8D7IkTRrozjTW3isUNE0k_Y1mFYwTZFksEEU5_AKzoqhqAGdaUFYLO9kZaG78FZh8Y2NygMeYw",
                    "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#delegateKey-3"
                },
                {
                    "type": "Ed25519Signature2020",
                    "created": "2026-03-06T00:07:08.103848738Z",
                    "proofPurpose": "assertionMethod",
                    "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#controllerKey",
                    "jws": "ewogICJhbGciOiAiRWREU0EiCn0.ewogICJpZCIgOiAiZGlkOnppZDplYjI1YjYwMTgzMWQ1YTExZjc0MTJmNmJlZWYwZWI4YWJlOWM1NTRiNzhmZjlhMjY4NGIxZmEyMThmNGExZWI1IiwKICAidHlwZSIgOiBbICJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsICJJREVOVElUWSBDQVJEIE1BTEFZU0lBIiBdLAogICJpc3N1ZXIiIDogImRpZDp6aWQ6OWJlZTc2NTY3NGRlMDAwZGU3MzZjZDczZTIzNTM4OTBkZjFmZGI2YWMwZGY5ZWNjM2E1YTc4YTRhZmVlMDNhOSIsCiAgInZhbGlkRnJvbSIgOiAiMjAyNi0wMi0xNVQwMDowMDowMFoiLAogICJ2YWxpZFVudGlsIiA6ICIyMDM2LTEyLTMxVDAwOjAwOjAwWiIsCiAgImNyZWRlbnRpYWxTdWJqZWN0IiA6IHsKICAgICJpZCIgOiAiZGlkOnppZDo5YmVlNzY1Njc0ZGUwMDBkZTczNmNkNzNlMjM1Mzg5MGRmMWZkYjZhYzBkZjllY2MzYTVhNzhhNGFmZWUwM2E5IiwKICAgICJpZGVudGl0eUNhcmRNYWxheXNpYSIgOiB7CiAgICAgICJuYW1lIiA6ICJtdWhhbW1hZCBoYXJpdGgiLAogICAgICAiRE9CIiA6ICIwMS0xMC0xOTkwIiwKICAgICAgImlkTm8iIDogOTAxMDAxMDE0OTM3LAogICAgICAiZ2VuZGVyIiA6ICJNYWxlIiwKICAgICAgImFnZSIgOiAzNgogICAgfQogIH0sCiAgIkBjb250ZXh0IiA6IFsgImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwgImh0dHBzOi8vdzNpZC5vcmcvc2VjdXJpdHkvYmJzL3YxIiwgImh0dHBzOi8vdGVzdC1ub2RlLnpldHJpeC5jb20vZ2V0QWNjb3VudE1ldGFEYXRhP2FkZHJlc3M9WlRYM0pzenFQZ1JVeDc0M1NBcDdxN3pVUmZqdmtXdUgyRk1FeiZrZXk9dGVtcGxhdGVfX2RpZDp6aWQ6NmFkODM4NGZmZmJjNmY3NzJjNWVmN2ZjMDNjOWRkMDkzY2JkMDRiNTc2ZmU2ZmIyMzYyZDZmNDgwZTdmN2JhOSIgXQp9.N0RGQzQyMjMxRUEwQUVBNTVBQzk1QzIzRTc4OUQ4NkYyQUU2NDIwQkIzOUQ3N0U0Mjk4MzM4NDAyMDhGNDRCMzY2RDYwQzlGMDExRDZDNTU1MTRDRTE4QzAyQ0JFMUM1QTJEQUI4MTYyQTVFMTgyMTM4RkI4QTYyNDk4RDMwMDk"
                }
            ],
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://w3id.org/security/bbs/v1",
                "https://test-node.zetrix.com/getAccountMetaData?address=ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz&key=template__did:zid:6ad8384fffbc6f772c5ef7fc03c9dd093cbd04b576fe6fb2362d6f480e7f7ba9"
            ]
        }''';

    final revealAttribute = ["identityCardMalaysia.age"];
    final proofAttributes = ["identityCardMalaysia.age"];
    final minValues = [18];
    final maxValues = [50];
    const String domain = 'age-range-proof';
    const int bits = 32;
    final vcMap = jsonDecode(vcJson) as Map<String, dynamic>;
    final vc = VerifiableCredential.fromJson(vcMap);

    final rangeProofRequest = RangeProofRequest(
      attributes: proofAttributes,
      minValues: minValues,
      maxValues: maxValues,
      bits: bits,
      domain: domain,
    );
    final ZetrixSDKResult<String> vp = await zetrixVpService.createVp(
      vc,
      revealAttribute,
      'z23VEBWyZUQGqU5e43dJDwxBGYi4mosy3gm1PQgqQod7qZbeWKfUboofmeUjtHNZusGZXv7ZUSG3ehjyzzo5d6XghCezk4rYyXKosEctzgDeHfQppGuXstCQGWrE37S1K8KLB',
      'b0019bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9c8b47173',
      'privBueqLZ7z5eMUpSpgxsdaZqehtnSCkzzrjBQHFpmYXR28kor3ucm5',
      rangeProofRequest,
    );
    if (!mounted) return;
    if (vp is Success<String> && vp.data != null) {
      logger.i('✅ Lite VP with Bulletproof generated!');
      if (!mounted) return;
      try {
        // Try parsing as JSON directly
        final parsedJson = jsonDecode(vp.data!);
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => JsonViewScreen(jsonString: jsonEncode(parsedJson)),
          ),
        );
      } catch (e1) {
        try {
          // Fallback: decode base64 and decompress gzip
          final compressed = base64.decode(vp.data!);
          final decompressed = gzip.decode(compressed);
          final parsedJson = jsonDecode(utf8.decode(decompressed));
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(jsonString: jsonEncode(parsedJson)),
            ),
          );
        } catch (e2) {
          final errorJson = {
            'error': 'Invalid JSON returned from createVp',
            'rawResult': vp.data,
            'exception': 'Direct JSON error: ${e1.toString()}\nDecode error: ${e2.toString()}',
          };
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(jsonString: jsonEncode(errorJson)),
            ),
          );
          setState(() => _result = '❌ VP generation failed: Invalid JSON returned');
        }
      }
    } else if (vp is Failure) {
      final failure = vp as Failure;
      final errorJson = {
        'error': 'VP generation failed',
        'details': failure.error.toString(),
      };
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => JsonViewScreen(jsonString: jsonEncode(errorJson)),
        ),
      );
      setState(() => _result = '❌ VP generation failed: \n${failure.error}');
    } else {
      setState(() => _result = '❌ VP generation failed: Unknown error');
    }
  }

  Future<void> _generateLiteVpWithBulletproof() async {
    // Example VC with drivingLicense credential
    const vcJson = '''{
            "id": "did:zid:8a8447142bce8a7afd2ac8466d5dc513c529a2a9b5739d44872e581b5cb1249a",
            "type": [
                "VerifiableCredential",
                "IDENTITY CARD MALAYSIA"
            ],
            "issuer": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
            "validFrom": "2026-02-15T00:00:00Z",
            "validUntil": "2036-12-31T00:00:00Z",
            "credentialSubject": {
                "id": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9",
                "identityCardMalaysia": {
                    "name": "muhammad harith",
                    "DOB": "01-10-1990",
                    "idNo": 901001014937,
                    "gender": "Male",
                    "age": 36
                }
            },
            "proof": [
                {
                    "type": "BbsBlsSignature2020",
                    "created": "2026-03-05T11:01:44.095235758Z",
                    "proofPurpose": "assertionMethod",
                    "proofValue": "urlUPLwE-zHH6GRtW5sI09jcVaAYbLa6f2r6o7f9uTQPXiYqi2DEYMpNiM8T9BOqGHMYu-QsMUgoP_jG9YU1eAayZ0GztdBe86LTPkFm_T7gGqWhPH0xjrrVtTyshONb1TQguIkH47qqHwKHheyAgYQ",
                    "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#delegateKey-3"
                },
                {
                    "type": "Ed25519Signature2020",
                    "created": "2026-03-05T11:01:44.106170963Z",
                    "proofPurpose": "assertionMethod",
                    "verificationMethod": "did:zid:9bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9#controllerKey",
                    "jws": "ewogICJhbGciOiAiRWREU0EiCn0.ewogICJpZCIgOiAiZGlkOnppZDo4YTg0NDcxNDJiY2U4YTdhZmQyYWM4NDY2ZDVkYzUxM2M1MjlhMmE5YjU3MzlkNDQ4NzJlNTgxYjVjYjEyNDlhIiwKICAidHlwZSIgOiBbICJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsICJJREVOVElUWSBDQVJEIE1BTEFZU0lBIiBdLAogICJpc3N1ZXIiIDogImRpZDp6aWQ6OWJlZTc2NTY3NGRlMDAwZGU3MzZjZDczZTIzNTM4OTBkZjFmZGI2YWMwZGY5ZWNjM2E1YTc4YTRhZmVlMDNhOSIsCiAgInZhbGlkRnJvbSIgOiAiMjAyNi0wMi0xNVQwMDowMDowMFoiLAogICJ2YWxpZFVudGlsIiA6ICIyMDM2LTEyLTMxVDAwOjAwOjAwWiIsCiAgImNyZWRlbnRpYWxTdWJqZWN0IiA6IHsKICAgICJpZCIgOiAiZGlkOnppZDo5YmVlNzY1Njc0ZGUwMDBkZTczNmNkNzNlMjM1Mzg5MGRmMWZkYjZhYzBkZjllY2MzYTVhNzhhNGFmZWUwM2E5IiwKICAgICJpZGVudGl0eUNhcmRNYWxheXNpYSIgOiB7CiAgICAgICJuYW1lIiA6ICJtdWhhbW1hZCBoYXJpdGgiLAogICAgICAiRE9CIiA6ICIwMS0xMC0xOTkwIiwKICAgICAgImlkTm8iIDogOTAxMDAxMDE0OTM3LAogICAgICAiZ2VuZGVyIiA6ICJNYWxlIiwKICAgICAgImFnZSIgOiAzNgogICAgfQogIH0sCiAgIkBjb250ZXh0IiA6IFsgImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwgImh0dHBzOi8vdzNpZC5vcmcvc2VjdXJpdHkvYmJzL3YxIiwgImh0dHBzOi8vdGVzdC1ub2RlLnpldHJpeC5jb20vZ2V0QWNjb3VudE1ldGFEYXRhP2FkZHJlc3M9WlRYM0pzenFQZ1JVeDc0M1NBcDdxN3pVUmZqdmtXdUgyRk1FeiZrZXk9dGVtcGxhdGVfX2RpZDp6aWQ6NmFkODM4NGZmZmJjNmY3NzJjNWVmN2ZjMDNjOWRkMDkzY2JkMDRiNTc2ZmU2ZmIyMzYyZDZmNDgwZTdmN2JhOSIgXQp9.OEVFMjBEMTJEQTU3RkZFQzRFMEQxRUJDN0JGMUQxOEY0N0QzODlFMzQxNThENTZGNDBDRTA2Q0FGRUREOTQzRDlBMTI2NTAxODBCMzFBNDFDRkQ0MEYyN0UzRDhFNTcyNENCNTgwQTJFNTA0OUEzNkRCMDYyRTM2Q0FGMjVDMDY"
                }
            ],
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://w3id.org/security/bbs/v1",
                "https://test-node.zetrix.com/getAccountMetaData?address=ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz&key=template__did:zid:6ad8384fffbc6f772c5ef7fc03c9dd093cbd04b576fe6fb2362d6f480e7f7ba9"
            ]
        }''';

    final revealAttribute = ["identityCardMalaysia.age"];
    final proofAttributes = ["identityCardMalaysia.age"];
    final minValues = [18];
    final maxValues = [50];
    const String domain = 'age-range-proof';
    const int bits = 32;

    try {
      setState(() => _result = '⏳ Generating Lite VP with Bulletproof...');
      final vcMap = jsonDecode(vcJson) as Map<String, dynamic>;
      final vc = VerifiableCredential.fromJson(vcMap);

      // The BLS public key must match the issuer's BLS key that signed the VC
      const String issuerBlsPublicKey = 'z23VEBWyZUQGqU5e43dJDwxBGYi4mosy3gm1PQgqQod7qZbeWKfUboofmeUjtHNZusGZXv7ZUSG3ehjyzzo5d6XghCezk4rYyXKosEctzgDeHfQppGuXstCQGWrE37S1K8KLB';
      const String holderPublicKey = 'b0019bee765674de000de736cd73e2353890df1fdb6ac0df9ecc3a5a78a4afee03a9c8b47173';

      final rangeProofRequest = RangeProofRequest(
        attributes: proofAttributes,
        minValues: minValues,
        maxValues: maxValues,
        bits: bits,
        domain: domain,
      );

      final ZetrixSDKResult<String> result = await zetrixVpService.createVpLite(
        vc,
        revealAttribute,
        issuerBlsPublicKey,
        holderPublicKey,
        rangeProofRequest,
      );

      if (result is Success<String> && result.data != null) {
        logger.i('✅ Lite VP with Bulletproof generated!');
        if (!mounted) return;
        try {
          var parsedJson = jsonDecode(result.data!);
          if (parsedJson is! Map) {
            parsedJson = {'result': parsedJson};
          }
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(jsonString: jsonEncode(parsedJson)),
            ),
          );
        } catch (e) {
          // If not valid JSON, show as string in an object
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => JsonViewScreen(jsonString: jsonEncode({'result': result.data!})),
            ),
          );
        }
      } else if (result is Failure) {
        final failure = result as Failure;
        final errorJson = {
          'error': 'VP generation failed',
          'details': failure.error.toString(),
        };
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => JsonViewScreen(jsonString: jsonEncode(errorJson)),
          ),
        );
        setState(() => _result = '❌ VP generation failed: \n${failure.error}');
      } else {
        setState(() => _result = '❌ VP generation failed: Unknown error');
      }
    } catch (e, stackTrace) {
      logger.e('❌ Exception during Lite VP generation: $e');
      logger.e('Stack trace: $stackTrace');
      setState(() => _result = '❌ Exception: ${e.toString()}');
    }
  }
  String _result = 'Tap the button to start.';

  // ── DCQL VP input cache (restores previous values when dialog re-opens) ──
  String _cachedDcqlRequest = '';
  String _cachedHolderDid = '';
  String _cachedEd25519Priv = '';
  String _cachedEd25519Pub = '';
  String _cachedBbsPriv = '';
  String _cachedBbsPub = '';
  String _cachedVc = '';

  ZetrixVpService zetrixVpService = ZetrixVpService();
  late final Dio _dio;
  late final bool network;
  late final VcService vcService;
  late final VpService vpService;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    try {
      _dio = ZetrixVcFlutter().dio;
      network = ZetrixVcFlutter().isMainnet;
    } catch (_) {
      _dio = Dio();
      network = false;
    }
    vcService = VcService(_dio, network);
    vpService = VpService(_dio, network);
    zetrixVpService = ZetrixVpService(dio: _dio, isMainnet: network);
  }

  /// Shows a dialog informing that BBS+ is not available on Windows
  void _showWindowsNotSupported() {
    if (!mounted) return;
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('BBS+ Not Available on Windows'),
        content: const Text(
          'BBS+ selective disclosure proofs are not available on Windows desktop.\n\n'
          'BBS+ works on iOS and Android.\n\n'
          'For Windows, use Bulletproof range proofs instead (fully functional).',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  Future<void> _generateVP() async {
    // BBS+ not available on Windows
    if (Platform.isWindows) {
      _showWindowsNotSupported();
      return;
    }

    const vcJson = '''{
            "id": "did:zid:6601378b18e96707d25b2070fd7125549ece3f2e3ad4b5e1dda67d262975ba4f",
            "type": [
                "VerifiableCredential",
                "TestPassport"
            ],
            "issuer": "did:zid:a0ef91714f1b84317d395118706796e38f012c48893f5063ebd7db2d9406c9ff",
            "issuanceDate": "2025-06-17T00:00:00Z",
            "expirationDate": "2035-06-17T00:00:00Z",
            "credentialSubject": {
                "id": "did:zid:eff30af3427a38c5cd021f5ac28578d27c3bd1ab53fc4d2789c1f8cb1827e83c",
                "testPassport": {
                    "name": "John Doeee",
                    "dob": "1990-01-01",
                    "gender": "Male",
                    "nationality": "Myanmarese",
                    "identityNo": "A123456789",
                    "passportNo": "P987654321",
                    "citizenType": "Permanent Resident",
                    "dateOfExpiry": "2030-12-31",
                    "countryIssue": "Malaysia",
                    "photo": "google.com"
                }
            },
            "proof": [
                {
                    "type": "BbsBlsSignature2020",
                    "created": "2025-06-18T03:20:49.542446Z",
                    "proofPurpose": "assertionMethod",
                    "proofValue": "uprfR-i_9Apk88cc-UxqYie61cfHoi9TQLj3nvARxJjcL_dDSto2GpP2PI-LARq5jHfjfGw5IZg_yqOGewi9Fd3Iu7BRsh4zq56My7qo28XUOpc5dzaXzoyTUc8yGPUaHzP6V-UgvEhuIpRiBZIjEmQ",
                    "verificationMethod": "did:zid:a0ef91714f1b84317d395118706796e38f012c48893f5063ebd7db2d9406c9ff#delegateKey-1"
                },
                {
                    "type": "Ed25519Signature2020",
                    "created": "2025-06-18T03:20:49.544218Z",
                    "proofPurpose": "assertionMethod",
                    "verificationMethod": "did:zid:a0ef91714f1b84317d395118706796e38f012c48893f5063ebd7db2d9406c9ff#controllerKey",
                    "jws": "eyJhbGciOiJFZERTQSJ9.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vdzNpZC5vcmcvc2VjdXJpdHkvYmJzL3YxIiwiaHR0cHM6Ly90ZXN0LW5vZGUuemV0cml4LmNvbS9nZXRBY2NvdW50TWV0YURhdGE_YWRkcmVzcz1aVFgzSnN6cVBnUlV4NzQzU0FwN3E3elVSZmp2a1d1SDJGTUV6JmtleT10ZW1wbGF0ZV9fZGlkOnppZDo5YWE3ZWNlZmMwNzY1ZmU5MzkyMzZiNDQ3N2NiNTI2MjIwNWJkNDNhNDI4MWQ1MWZjYTJjZDdlNDNjMjljODM4Il0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOnppZDplZmYzMGFmMzQyN2EzOGM1Y2QwMjFmNWFjMjg1NzhkMjdjM2JkMWFiNTNmYzRkMjc4OWMxZjhjYjE4MjdlODNjIiwidGVzdFBhc3Nwb3J0Ijp7ImNpdGl6ZW5UeXBlIjoiUGVybWFuZW50IFJlc2lkZW50IiwiY291bnRyeUlzc3VlIjoiTWFsYXlzaWEiLCJkYXRlT2ZFeHBpcnkiOiIyMDMwLTEyLTMxIiwiZG9iIjoiMTk5MC0wMS0wMSIsImdlbmRlciI6Ik1hbGUiLCJpZGVudGl0eU5vIjoiQTEyMzQ1Njc4OSIsIm5hbWUiOiJKb2huIERvZWVlIiwibmF0aW9uYWxpdHkiOiJNeWFubWFyZXNlIiwicGFzc3BvcnRObyI6IlA5ODc2NTQzMjEiLCJwaG90byI6Imdvb2dsZS5jb20ifX0sImV4cGlyYXRpb25EYXRlIjoiMjAzNS0wNi0xN1QwMDowMDowMFoiLCJpZCI6ImRpZDp6aWQ6NjYwMTM3OGIxOGU5NjcwN2QyNWIyMDcwZmQ3MTI1NTQ5ZWNlM2YyZTNhZDRiNWUxZGRhNjdkMjYyOTc1YmE0ZiIsImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDYtMTdUMDA6MDA6MDBaIiwiaXNzdWVyIjoiZGlkOnppZDphMGVmOTE3MTRmMWI4NDMxN2QzOTUxMTg3MDY3OTZlMzhmMDEyYzQ4ODkzZjUwNjNlYmQ3ZGIyZDk0MDZjOWZmIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlRlc3RQYXNzcG9ydCJdfQ.QjdDQTJGNUU2MTA4MEZCNjEwNzJENzhBQzM4M0E0MUQyQ0U1MEZDQkM1NkMzRDQ5ODAwMEQ2ODBDRTUxNEQyN0IyNTg5M0VDODlBMUI5QjYyMDJDRUMwMUEyOEI3MTk3NjNERDUyMDAzMDgwRkJENzE3QTkxOEI4ODA1NThEMDA"
                }
            ],
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://w3id.org/security/bbs/v1",
                "https://test-node.zetrix.com/getAccountMetaData?address=ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz&key=template__did:zid:9aa7ecefc0765fe939236b4477cb5262205bd43a4281d51fca2cd7e43c29c838"
            ]
        }''';

    final reveal = [
      "testPassport.name",
      // "testPassport.gender",
      // "testPassport.nationality",
    ];

    final vcMap = jsonDecode(vcJson) as Map<String, dynamic>;
    final vc = VerifiableCredential.fromJson(vcMap);

    final ZetrixSDKResult<String> vp = await zetrixVpService.createVpMC(
      vc,
      reveal,
      'z23ENzoxDd3PJFWMLQYCoPwJBC7epEKoG3wdEFYLU6JTtznnL4zukArZMEFX4n1DSwi5GmssJnx7gsjsQRi7fcf7seZ3rqQBv48Mef2hHTdtkeDrqV7SHdv4YkAx5o9MxhjLw',
      'b001a0ef91714f1b84317d395118706796e38f012c48893f5063ebd7db2d9406c9ffe3b775cb',
    );

    if (vp is Success<String> && vp.data != null) {
      logger.d(vp.data);
      if (!mounted) return;

      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => QrCodeScreen(data: vp.data!)),
      );
    } else {
      setState(() => _result = 'VP generation failed');
    }
  }

  Future<void> _generateAndSubmitVPBlob() async {

    final VerifiablePresentation? vp = await vpService.generateAndSubmitVPBlob();
    if (vp  != null) {
      if (!mounted) return;

      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => JsonViewScreen(jsonString: jsonEncode(vp)),
        ),
      );
    } else {
      setState(() => _result =  'VP generation failed');
    }
  }

  Future<void> _generateFullVP() async {
    // BBS+ not available on Windows
    if (Platform.isWindows) {
      _showWindowsNotSupported();
      return;
    }

    const vcJson = '''{
            "id": "did:zid:69bd3c95f51fc2ca134fe67e6303e3e83633bbbdb14cf00400e87cd3388a8eeb",
            "type": [
                "VerifiableCredential",
                "Passport"
            ],
            "issuer": "did:zid:d545dc623b0562e9b02a0b4f280b32bd060c9ff1b3582290e6d760e3cc3bfd15",
            "issuanceDate": "2025-07-02T00:00:00Z",
            "expirationDate": "2035-07-02T00:00:00Z",
            "credentialSubject": {
                "id": "did:zid:eff30af3427a38c5cd021f5ac28578d27c3bd1ab53fc4d2789c1f8cb1827e83c",
                "passport": {
                    "name": "John Doe",
                    "dob": "1990-01-01",
                    "gender": "Male",
                    "nationality": "Myanmarese",
                    "identityNo": "A123456789",
                    "passportNo": "P987654321",
                    "citizenType": "Permanent Resident",
                    "dateOfExpiry": "2030-12-31",
                    "countryIssue": "Malaysia",
                    "photo": "https://img/a.jpg"
                }
            },
            "proof": [
                {
                    "type": "BbsBlsSignature2020",
                    "created": "2025-07-16T01:41:07.227776Z",
                    "proofPurpose": "assertionMethod",
                    "proofValue": "uozMvrMFy2PbetH0pndRm7BIxITxo0Z1_ffVmvGloknN0I-wUqgIpRHO05op2g3UCKs6I72n_kjt1u6B-rHGV3zWyyfOxE0DT5plOK8dlDMsdX4NNBKAy2dUaTkleztnmC3aStYKhXUgJmnnLDlWzIg",
                    "verificationMethod": "did:zid:d545dc623b0562e9b02a0b4f280b32bd060c9ff1b3582290e6d760e3cc3bfd15#delegateKey-2"
                },
                {
                    "type": "Ed25519Signature2020",
                    "created": "2025-07-16T01:41:07.228880Z",
                    "proofPurpose": "assertionMethod",
                    "verificationMethod": "did:zid:d545dc623b0562e9b02a0b4f280b32bd060c9ff1b3582290e6d760e3cc3bfd15#controllerKey",
                    "jws": "eyJhbGciOiJFZERTQSJ9.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vdzNpZC5vcmcvc2VjdXJpdHkvYmJzL3YxIiwiaHR0cHM6Ly90ZXN0LW5vZGUuemV0cml4LmNvbS9nZXRBY2NvdW50TWV0YURhdGE_YWRkcmVzcz1aVFgzSnN6cVBnUlV4NzQzU0FwN3E3elVSZmp2a1d1SDJGTUV6JmtleT10ZW1wbGF0ZV9fZGlkOnppZDo3MzliMDAwZTZhODk2MTc4ZTMzODZhMmVkODQ4ZDAxYTg3M2I4MzNkM2MwMjQ4ZDI4ZTVjN2QyYmJkZTQ2MDZlIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOnppZDplZmYzMGFmMzQyN2EzOGM1Y2QwMjFmNWFjMjg1NzhkMjdjM2JkMWFiNTNmYzRkMjc4OWMxZjhjYjE4MjdlODNjIiwicGFzc3BvcnQiOnsiY2l0aXplblR5cGUiOiJQZXJtYW5lbnQgUmVzaWRlbnQiLCJjb3VudHJ5SXNzdWUiOiJNYWxheXNpYSIsImRhdGVPZkV4cGlyeSI6IjIwMzAtMTItMzEiLCJkb2IiOiIxOTkwLTAxLTAxIiwiZ2VuZGVyIjoiTWFsZSIsImlkZW50aXR5Tm8iOiJBMTIzNDU2Nzg5IiwibmFtZSI6IkpvaG4gRG9lIiwibmF0aW9uYWxpdHkiOiJNeWFubWFyZXNlIiwicGFzc3BvcnRObyI6IlA5ODc2NTQzMjEiLCJwaG90byI6Imh0dHBzOi8vaW1nL2EuanBnIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMzUtMDctMDJUMDA6MDA6MDBaIiwiaWQiOiJkaWQ6emlkOjY5YmQzYzk1ZjUxZmMyY2ExMzRmZTY3ZTYzMDNlM2U4MzYzM2JiYmRiMTRjZjAwNDAwZTg3Y2QzMzg4YThlZWIiLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA3LTAyVDAwOjAwOjAwWiIsImlzc3VlciI6ImRpZDp6aWQ6ZDU0NWRjNjIzYjA1NjJlOWIwMmEwYjRmMjgwYjMyYmQwNjBjOWZmMWIzNTgyMjkwZTZkNzYwZTNjYzNiZmQxNSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJQYXNzcG9ydCJdfQ.MkU4ODI2ODRBRjdCQjUwMUQ4QzRCQjVDMTQ0MTIzRTMxRDZGMTBBRUQ3REE0N0Y3NDA1NjcxOENFQ0U0NzQ4MkIxMkM0N0ZBNzAwREE4NzcyNDNBQjcxRkMyNEZGNENEQkVDMTIwNTdBRjNDMzY0Q0Y2QUExQ0YyM0JBMkY5MDA"
                }
            ],
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://w3id.org/security/bbs/v1",
                "https://test-node.zetrix.com/getAccountMetaData?address=ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz&key=template__did:zid:739b000e6a896178e3386a2ed848d01a873b833d3c0248d28e5c7d2bbde4606e"
            ]
        }''';

    final reveal = ["passport.name", "passport.nationality"];

    final vcMap = jsonDecode(vcJson) as Map<String, dynamic>;
    final vc = VerifiableCredential.fromJson(vcMap);

    final ZetrixSDKResult<String> vp = await zetrixVpService.createVp(
      vc,
      reveal,
      'zyztLoBw5uQwwitr5rpXUWMAya91CHLqvnubTVWLFR8RJC1TDhvjteYxGojMGDBPxwyXRVUzB35Zu1tRDXhEmLW3fYe5uKcpQDKyx4dQCrEdMVhNrH3En3NRZr14bfBR3uWK',
      'b001eff30af3427a38c5cd021f5ac28578d27c3bd1ab53fc4d2789c1f8cb1827e83c58de414a',
      'privBxpL2meqP4CHanp4KRzRrabwCEnTgJx8DAddWkveUoZWiYmuHFZx',
      null,
    );

    if (vp is Success<String> && vp.data != null) {
      logger.d(vp.data);
      if (!mounted) return;

      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => JsonViewScreen(jsonString: vp.data!),
        ),
      );
    } else if (vp is Failure) {
      final failure = vp as Failure;
      final message = ZetrixSDKExceptions.getErrorMessage(failure.error as ZetrixSDKExceptions);
      setState(() => _result = message);
    } else {
      setState(() => _result = 'VP generation failed');
    }
  }

  Future<void> _applyVc() async {
    String vcId = await vcService.applyVc();
    setState(() {
      _result = '✅ VC Applied\nID: $vcId';
    });
  }

  Future<void> _applyVcEnc() async {
    String vcId = await vcService.applyVcEnc();
    setState(() {
      _result = '✅ VC Applied\nID: $vcId';
    });
  }

  Future<void> _downloadVc() async {
    await vcService.downloadVc();

    setState(() {
      _result = '✅ VC Downloaded';
    });
  }

  Future<void> _downloadVcEnc() async {
    await vcService.downloadVcEnc();

    setState(() {
      _result = '✅ VC Downloaded';
    });
  }

  Future<void> _createVpEnc() async {

    final VerifiablePresentation? vp = await vpService.createVpEncrypted();
    if (vp  != null) {
      if (!mounted) return;

      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => JsonViewScreen(jsonString: jsonEncode(vp)),
        ),
      );
    } else {
      setState(() => _result =  'VP generation failed');
    }
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Center(
        child: Card(
          margin: const EdgeInsets.all(24),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
          elevation: 4,
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  "Zetrix VC SDK",
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.indigo),
                ),
                const SizedBox(height: 20),
                if (_result.isNotEmpty)
                  Container(
                    width: double.infinity,
                    decoration: BoxDecoration(
                      color: _result.startsWith('✅')
                          ? Colors.green[50]
                          : Colors.red[50],
                      borderRadius: BorderRadius.circular(12),
                    ),
                    padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
                    margin: const EdgeInsets.only(bottom: 20),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(
                          _result.startsWith('✅') ? Icons.check_circle : Icons.info,
                          color: _result.startsWith('✅') ? Colors.green : Colors.red,
                          size: 22,
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: Text(
                            _result,
                            style: TextStyle(
                              color: _result.startsWith('✅') ? Colors.green[900] : Colors.red[900],
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                const SectionHeader("Key Management"),
                ActionButton(
                  icon: Icons.vpn_key,
                  label: "Generate BLS12381G1 Key",
                  onPressed: () async {
                    // BBS+ not available on Windows
                    if (Platform.isWindows) {
                      _showWindowsNotSupported();
                      return;
                    }
                    
                    final seed = Uint8List(32);
                    final keyPair = await BbsFlutter.generateBls12381G1Key(seed);
                    logger.d('Public Key: ${base64.encode(keyPair['publicKey']!)}');
                    logger.d('Secret Key: ${base64.encode(keyPair['secretKey']!)}');
                  },
                ),
                const SectionHeader("VC Operations"),
                ActionButton(
                  icon: Icons.upload_file,
                  label: "Call Apply VC",
                  onPressed: _applyVc,
                ),
                ActionButton(
                  icon: Icons.lock,
                  label: "Call Apply VC Encrypted",
                  onPressed: _applyVcEnc,
                ),
                ActionButton(
                  icon: Icons.download,
                  label: "Call Download VC",
                  onPressed: _downloadVc,
                ),
                ActionButton(
                  icon: Icons.vpn_lock,
                  label: "Call Download VC Encrypted",
                  onPressed: _downloadVcEnc,
                ),
                const SectionHeader('DCQL Presentation'),
                ActionButton(
                  icon: Icons.verified_user,
                  label: 'DCQL VP — createVPFromDCQL',
                  onPressed: _runDcqlVpDemo,
                ),
                ActionButton(
                  icon: Icons.manage_accounts,
                  label: 'DCQL VP — Real Demo',
                  onPressed: _runDcqlVpDemoReal,
                ),
                ActionButton(
                  icon: Icons.manage_accounts,
                  label: 'DCQL VP — Presentation Request & VC',
                  onPressed: _runDcqlVpDemoPresentationRequestandVC,
                ),
                ActionButton(
                  icon: Icons.playlist_add_check,
                  label: 'VP Lite — Age Range Proof (≥18)',
                  onPressed: _runVpLiteRangeProofDemo,
                ),
                const SectionHeader("Presentation"),
                ActionButton(
                  icon: Icons.description,
                  label: "Generate Lite Verifiable Presentation",
                  onPressed: _generateVP,
                ),
                ActionButton(
                  icon: Icons.description,
                  label: "Generate Lite Verifiable Presentation with range proof",
                  onPressed: _generateLiteVpWithBulletproof,
                ),
                ActionButton(
                  icon: Icons.verified,
                  label: "Generate Verifiable Presentation",
                  onPressed: _generateFullVP,
                ),
                ActionButton(
                  icon: Icons.verified,
                  label: "Generate Verifiable Presentation with range proof",
                  onPressed: _showExampleBulletproofVC,
                ),
                const SectionHeader('VP Operations'),
                ActionButton(
                  icon: Icons.edit_document,
                  label: "Generate VP blob,sign and submit",
                  onPressed: _generateAndSubmitVPBlob,
                ),
                ActionButton(
                  icon: Icons.edit_document,
                  label: "Generate VP blob,sign and submit Encrypted",
                  onPressed: _createVpEnc,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}