tresori_sdk 1.0.0 copy "tresori_sdk: ^1.0.0" to clipboard
tresori_sdk: ^1.0.0 copied to clipboard

A comprehensive interface for blockchain wallet management, supporting Self-Custodial, Custodial, and MPC wallet operations.

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tresori_sdk/tresori.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TreSori Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
          brightness: Brightness.light,
        ),
      ),
      home: const WalletDemoPage(),
    );
  }
}

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

  @override
  State<WalletDemoPage> createState() => _WalletDemoPageState();
}

enum LogType { info, success, error, warning }

class LogEntry {
  final DateTime timestamp;
  final String message;
  final LogType type;

  LogEntry(this.message, {this.type = LogType.info})
    : timestamp = DateTime.now();
}

class _WalletDemoPageState extends State<WalletDemoPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  final _apiKeyController = TextEditingController();
  final _loggerController = ScrollController();
  final List<LogEntry> _logs = [];
  bool _autoScroll = true;
  bool _showLogs = false;
  bool _isInitialized = false;
  bool _isLoading = false;
  String _loadingMessage = '';

  // Common User ID
  final _userIdController = TextEditingController();

  // ============ SELF CUSTODIAL STATE ============
  Chain? _selfCustodialChain;
  String? _currentMnemonic;
  String? _currentPrivateKey;
  String? _currentAddress;
  final _selfCustodialRpcController = TextEditingController();
  final _selfCustodialToController = TextEditingController();
  final _selfCustodialAmountController = TextEditingController();
  final _selfCustodialContractAddressController = TextEditingController();
  final _selfCustodialFunctionController = TextEditingController();
  final _selfCustodialAbiController = TextEditingController();
  final _selfCustodialParamsController = TextEditingController();

  // ============ CUSTODIAL STATE ============
  Chain? _custodialChain;
  String? _custodialAddress;
  final _custodialToController = TextEditingController();
  final _custodialAmountController = TextEditingController();
  final _custodialTokenAddressController = TextEditingController();
  final _custodialContractAddressController = TextEditingController();
  final _custodialFunctionController = TextEditingController();
  final _custodialAbiController = TextEditingController();
  final _custodialParamsController = TextEditingController();

  // ============ MPC STATE ============
  Chain? _mpcChain;
  String? _mpcAddress;
  String? _mpcUserShard;
  String? _mpcBalance;
  String? _mpcPublicKey;
  bool _mpcIsNewUser = false;
  final _emailController = TextEditingController();
  final _otpController = TextEditingController();
  final _phoneController = TextEditingController();
  final _countryCodeController = TextEditingController(text: '+1');
  final _mpcToController = TextEditingController();
  final _mpcAmountController = TextEditingController();
  final _mpcTokenAddressController = TextEditingController();
  final _mpcUserShardsController = TextEditingController();
  final _mpcContractAddressController = TextEditingController();
  final _mpcFunctionController = TextEditingController();
  final _mpcAbiController = TextEditingController();
  final _mpcParamsController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    _apiKeyController.dispose();
    _userIdController.dispose();
    _loggerController.dispose();
    // Self Custodial
    _selfCustodialRpcController.dispose();
    _selfCustodialToController.dispose();
    _selfCustodialAmountController.dispose();
    _selfCustodialContractAddressController.dispose();
    _selfCustodialFunctionController.dispose();
    _selfCustodialAbiController.dispose();
    _selfCustodialParamsController.dispose();
    // Custodial
    _custodialToController.dispose();
    _custodialAmountController.dispose();
    _custodialTokenAddressController.dispose();
    _custodialContractAddressController.dispose();
    _custodialFunctionController.dispose();
    _custodialAbiController.dispose();
    _custodialParamsController.dispose();
    // MPC
    _emailController.dispose();
    _otpController.dispose();
    _phoneController.dispose();
    _countryCodeController.dispose();
    _mpcToController.dispose();
    _mpcAmountController.dispose();
    _mpcTokenAddressController.dispose();
    _mpcUserShardsController.dispose();
    _mpcContractAddressController.dispose();
    _mpcFunctionController.dispose();
    _mpcAbiController.dispose();
    _mpcParamsController.dispose();
    super.dispose();
  }

  void _log(String message, {LogType type = LogType.info}) {
    setState(() {
      _logs.add(LogEntry(message, type: type));
    });
    if (_autoScroll) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (_loggerController.hasClients) {
          _loggerController.animateTo(
            _loggerController.position.maxScrollExtent,
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeOut,
          );
        }
      });
    }
  }

  // ============ LOADING HELPER ============
  Future<T?> _runWithLoading<T>(
    String message,
    Future<T> Function() operation,
  ) async {
    setState(() {
      _isLoading = true;
      _loadingMessage = message;
    });
    try {
      return await operation();
    } finally {
      setState(() {
        _isLoading = false;
        _loadingMessage = '';
      });
    }
  }

  // ============ INITIALIZATION ============
  Future<void> _initialize() async {
    await _runWithLoading('Initializing SDK...', () async {
      try {
        _log('Initializing SDK...');
        await TreSori().initialize(_apiKeyController.text);
        setState(() {
          _isInitialized = true;
          if (chain.all.isNotEmpty) {
            _selfCustodialChain = chain.all.first;
            _custodialChain = chain.all.first;
            _mpcChain = chain.all.first;
          }
        });
        _log(
          'Initialized with API Key: ${_apiKeyController.text}',
          type: LogType.success,
        );
        _log(
          'Loaded ${chain.all.length} chains: ${chain.all.map((c) => '${c.blockchain}-${c.network}').join(', ')}',
          type: LogType.info,
        );
      } catch (e) {
        _log('Error initializing: $e', type: LogType.error);
      }
    });
  }

  // ============ SELF CUSTODIAL METHODS ============
  Future<void> _createSelfCustodialWallet() async {
    if (_selfCustodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Creating wallet...', () async {
      try {
        final mnemonic = TreSori().generateMnemonic();
        final result = await TreSori().createWallet(
          chain: _selfCustodialChain!,
          mnemonic: mnemonic,
          userId: _userIdController.text,
        );
        setState(() {
          _currentMnemonic = result['mnemonic'];
          _currentPrivateKey = result['privateKey'];
          _currentAddress = result['address'];
        });
        _log(
          'Wallet Created:\nAddress: $_currentAddress\nPrivate Key: $_currentPrivateKey\nMnemonic: $_currentMnemonic',
          type: LogType.success,
        );
      } catch (e) {
        _log('Error creating wallet: $e', type: LogType.error);
      }
    });
  }

  void _importFromMnemonic() {
    showDialog(
      context: context,
      builder: (context) {
        final controller = TextEditingController();
        return AlertDialog(
          title: const Text('Import from Mnemonic'),
          content: TextField(
            controller: controller,
            decoration: const InputDecoration(
              hintText: 'Enter 12-word mnemonic',
            ),
            maxLines: 3,
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
                _doImportMnemonic(controller.text);
              },
              child: const Text('Import'),
            ),
          ],
        );
      },
    );
  }

  void _doImportMnemonic(String mnemonic) {
    try {
      final result = TreSori().importWalletFromMnemonic(mnemonic);
      setState(() {
        _currentMnemonic = mnemonic;
        _currentPrivateKey = result['privateKey'];
        _currentAddress = result['address'];
      });
      _log(
        'Wallet Imported (Mnemonic):\nAddress: $_currentAddress',
        type: LogType.success,
      );
    } catch (e) {
      _log('Error importing wallet: $e', type: LogType.error);
    }
  }

  void _importFromPrivateKey() {
    showDialog(
      context: context,
      builder: (context) {
        final controller = TextEditingController();
        return AlertDialog(
          title: const Text('Import from Private Key'),
          content: TextField(
            controller: controller,
            decoration: const InputDecoration(
              hintText: 'Enter Private Key (hex)',
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
                _doImportPrivateKey(controller.text);
              },
              child: const Text('Import'),
            ),
          ],
        );
      },
    );
  }

  void _doImportPrivateKey(String privateKey) {
    try {
      final result = TreSori().importWalletFromPrivateKey(privateKey);
      setState(() {
        _currentMnemonic = null;
        _currentPrivateKey = result['privateKey'];
        _currentAddress = result['address'];
      });
      _log(
        'Wallet Imported (Private Key):\nAddress: $_currentAddress',
        type: LogType.success,
      );
    } catch (e) {
      _log('Error importing wallet: $e', type: LogType.error);
    }
  }

  Future<void> _transferSelfCustodialTokens() async {
    if (_currentPrivateKey == null) {
      _log('Error: No wallet loaded.', type: LogType.error);
      return;
    }
    if (_selfCustodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Transferring tokens...', () async {
      try {
        final amount = EtherAmount.fromBigInt(
          EtherUnit.wei,
          BigInt.tryParse(_selfCustodialAmountController.text) ?? BigInt.zero,
        );
        _log('Transferring tokens...');
        final result = await TreSori().transferTokens(
          chain: _selfCustodialChain!,
          privateKey: _currentPrivateKey!,
          toAddress: _selfCustodialToController.text,
          amount: amount,
          rpcUrl: _selfCustodialRpcController.text,
        );
        _log('Transfer Successful. TX Hash: $result', type: LogType.success);
      } catch (e) {
        _log('Error transferring tokens: $e', type: LogType.error);
      }
    });
  }

  Future<void> _signSelfCustodialContract() async {
    if (_currentPrivateKey == null) {
      _log('Error: No wallet loaded.', type: LogType.error);
      return;
    }
    if (_selfCustodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Signing contract...', () async {
      try {
        final abiString = _selfCustodialAbiController.text;
        final abi = ContractAbi.fromJson(abiString, 'Contract');
        _log('Signing Contract Transaction...');
        final result = await TreSori().signContractTransaction(
          chain: _selfCustodialChain!,
          privateKey: _currentPrivateKey!,
          rpcUrl: _selfCustodialRpcController.text,
          transaction: Transaction.callContract(
            contract: DeployedContract(
              abi,
              EthereumAddress.fromHex(
                _selfCustodialContractAddressController.text,
              ),
            ),
            function: abi.functions.firstWhere(
              (f) => f.name == _selfCustodialFunctionController.text,
            ),
            parameters: [
              EthereumAddress.fromHex(
                '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9',
              ),
              BigInt.from(1000000000000000000),
            ],
          ),
        );
        _log('TX Hash: $result', type: LogType.success);
      } catch (e) {
        _log('Error signing contract: $e', type: LogType.error);
      }
    });
  }

  Future<void> _getSelfCustodialBalance() async {
    if (_currentAddress == null) {
      _log('Error: No wallet address.', type: LogType.error);
      return;
    }
    if (_selfCustodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Fetching balance...', () async {
      try {
        _log('Fetching balance...');
        final result = await TreSori().getWalletBalance(
          address: _currentAddress!,
          chain: _selfCustodialChain!,
        );
        _log('Balance: ${result.toString()}', type: LogType.success);
        _showBalanceDialog(
          'Self Custodial Wallet Balance',
          _currentAddress!,
          _selfCustodialChain!,
          result,
        );
      } catch (e) {
        _log('Error fetching balance: $e', type: LogType.error);
      }
    });
  }

  // ============ CUSTODIAL METHODS ============
  Future<void> _createCustodialWallet() async {
    if (_custodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Creating custodial wallet...', () async {
      try {
        _log('Creating Custodial Wallet...');
        final result = await TreSori().createCustodialWallet(
          chain: _custodialChain!,
          userId: _userIdController.text,
        );
        // Try multiple possible response structures
        final address =
            result['result']?['address'] ??
            result['address'] ??
            result['walletAddress'] ??
            result['data']?['address'] ??
            result['data']?['walletAddress'];
        setState(() {
          _custodialAddress = address?.toString();
        });
        _log('Custodial Wallet Created: $result', type: LogType.success);
        if (_custodialAddress != null) {
          _log('Address: $_custodialAddress', type: LogType.info);
        }
      } catch (e) {
        _log('Error creating custodial wallet: $e', type: LogType.error);
      }
    });
  }

  Future<void> _transferCustodialTokens() async {
    if (_custodialAddress == null) {
      _log('Error: No custodial wallet loaded.', type: LogType.error);
      return;
    }
    if (_custodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Transferring tokens...', () async {
      try {
        final amount = _custodialAmountController.text;
        final tokenAddress = _custodialTokenAddressController.text.trim();
        _log('Transferring Custodial Tokens...');
        final result = await TreSori().transferCustodialTokens(
          fromAddress: _custodialAddress!,
          toAddress: _custodialToController.text,
          amount: amount,
          chain: _custodialChain!,
          tokenAddress: tokenAddress,
        );
        _log('Transfer Successful: $result', type: LogType.success);
      } catch (e) {
        _log('Error transferring custodial tokens: $e', type: LogType.error);
      }
    });
  }

  Future<void> _signCustodialContract() async {
    if (_custodialAddress == null) {
      _log('Error: No custodial wallet loaded.', type: LogType.error);
      return;
    }
    if (_custodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Signing contract...', () async {
      try {
        final abiString = _custodialAbiController.text;
        final abiList = jsonDecode(abiString) as List;
        final paramsString = _custodialParamsController.text;
        // To ensure that string quotes in JSON are double quotes (")
        // (single quotes are invalid in JSON), we replace all single quotes with double quotes before decoding
        final params =
            paramsString.isNotEmpty
                ? jsonDecode(paramsString.replaceAll("'", '"')) as List
                : [];

        _log('Signing Custodial Contract Transaction...');
        final result = await TreSori().writeCustodialSmartContractTransaction(
          contractAddress: _custodialContractAddressController.text,
          functionName: _custodialFunctionController.text,
          parameters: params,
          contractAbi: abiList,
          fromAddress: _custodialAddress!,
          chain: _custodialChain!,
        );
        _log('TX: $result', type: LogType.success);
      } catch (e) {
        _log('Error signing custodial contract: $e', type: LogType.error);
      }
    });
  }

  Future<void> _getCustodialBalance() async {
    if (_custodialAddress == null) {
      _log('Error: No custodial wallet address.', type: LogType.error);
      return;
    }
    if (_custodialChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Fetching balance...', () async {
      try {
        _log('Fetching custodial balance...');
        final result = await TreSori().getWalletBalance(
          address: _custodialAddress!,
          chain: _custodialChain!,
        );
        _log('Balance: ${result.toString()}', type: LogType.success);
        _showBalanceDialog(
          'Custodial Wallet Balance',
          _custodialAddress!,
          _custodialChain!,
          result,
        );
      } catch (e) {
        _log('Error fetching custodial balance: $e', type: LogType.error);
      }
    });
  }

  // ============ MPC METHODS ============
  Future<void> _sendEmailVerification() async {
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Sending verification...', () async {
      try {
        _log('Sending Email Verification...');
        final result = await TreSori().sendEmailVerificationRequest(
          chain: _mpcChain!,
          email: _emailController.text,
          userId: _userIdController.text,
          isNewUser: _mpcIsNewUser,
        );
        _log('Email Request Sent: $result', type: LogType.success);
      } catch (e) {
        _log('Error sending email verification: $e', type: LogType.error);
      }
    });
  }

  Future<void> _verifyEmail() async {
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Verifying email...', () async {
      try {
        _log('Verifying Email OTP...');
        final result = await TreSori().verifyEmail(
          chain: _mpcChain!,
          email: _emailController.text,
          userId: _userIdController.text,
          otp: _otpController.text,
          isNewUser: _mpcIsNewUser,
        );
        // Extract MPC wallet details from response
        // Handle both new user response (from create-wallet) and existing user response (from verify)
        final resultData = result['result'] ?? result['data'] ?? result;
        // For existing users, wallet info is nested in status
        final statusData = resultData['status'];
        final dataSource = statusData is Map ? statusData : resultData;

        final address =
            dataSource['walletAddress'] ??
            dataSource['wallet_address'] ??
            dataSource['address'] ??
            resultData['address'] ??
            result['walletAddress'];
        final userShard =
            dataSource['userShard'] ??
            resultData['userShard'] ??
            result['userShard'];
        final balance =
            dataSource['balance'] ?? resultData['balance'] ?? result['balance'];
        final publicKey =
            dataSource['publicKey'] ??
            resultData['publicKey'] ??
            result['publicKey'];

        setState(() {
          if (address != null) _mpcAddress = address.toString();
          if (userShard != null) _mpcUserShard = userShard.toString();
          if (balance != null) _mpcBalance = balance.toString();
          if (publicKey != null) _mpcPublicKey = publicKey.toString();
        });

        if (_mpcAddress != null) {
          _log('MPC Wallet Address: $_mpcAddress', type: LogType.info);
        }
        if (_mpcUserShard != null) {
          _log('User Shard: $_mpcUserShard', type: LogType.info);
        }
        if (_mpcBalance != null) {
          _log('Balance: $_mpcBalance', type: LogType.info);
        }
        if (_mpcPublicKey != null) {
          _log('Public Key: $_mpcPublicKey', type: LogType.info);
        }
        _log('Email Verified: $result', type: LogType.success);
      } catch (e) {
        _log('Error verifying email: $e', type: LogType.error);
      }
    });
  }

  Future<void> _sendPhoneVerification() async {
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Sending verification...', () async {
      try {
        _log('Sending Phone Verification...');
        final result = await TreSori().sendPhoneVerificationCode(
          chain: _mpcChain!,
          countryCode: _countryCodeController.text,
          phone: _phoneController.text,
          userId: _userIdController.text,
          isNewUser: _mpcIsNewUser,
        );
        _log('Phone Request Sent: $result', type: LogType.success);
      } catch (e) {
        _log('Error sending phone verification: $e', type: LogType.error);
      }
    });
  }

  Future<void> _verifyPhone() async {
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Verifying phone...', () async {
      try {
        _log('Verifying Phone OTP...');
        final result = await TreSori().verifyPhone(
          chain: _mpcChain!,
          countryCode: _countryCodeController.text,
          phone: _phoneController.text,
          userId: _userIdController.text,
          otp: _otpController.text,
          isNewUser: _mpcIsNewUser,
        );
        // Extract MPC wallet details from response
        // Handle both new user response (from create-wallet) and existing user response (from verify)
        final resultData = result['result'] ?? result['data'] ?? result;
        // For existing users, wallet info is nested in status
        final statusData = resultData['status'];
        final dataSource = statusData is Map ? statusData : resultData;

        final address =
            dataSource['walletAddress'] ??
            dataSource['wallet_address'] ??
            dataSource['address'] ??
            resultData['address'] ??
            result['walletAddress'];
        final userShard =
            dataSource['userShard'] ??
            resultData['userShard'] ??
            result['userShard'];
        final balance =
            dataSource['balance'] ?? resultData['balance'] ?? result['balance'];
        final publicKey =
            dataSource['publicKey'] ??
            resultData['publicKey'] ??
            result['publicKey'];

        setState(() {
          if (address != null) _mpcAddress = address.toString();
          if (userShard != null) _mpcUserShard = userShard.toString();
          if (balance != null) _mpcBalance = balance.toString();
          if (publicKey != null) _mpcPublicKey = publicKey.toString();
        });

        if (_mpcAddress != null) {
          _log('MPC Wallet Address: $_mpcAddress', type: LogType.info);
        }
        if (_mpcUserShard != null) {
          _log('User Shard: $_mpcUserShard', type: LogType.info);
        }
        if (_mpcBalance != null) {
          _log('Balance: $_mpcBalance', type: LogType.info);
        }
        if (_mpcPublicKey != null) {
          _log('Public Key: $_mpcPublicKey', type: LogType.info);
        }
        _log('Phone Verified: $result', type: LogType.success);
      } catch (e) {
        _log('Error verifying phone: $e', type: LogType.error);
      }
    });
  }

  Future<void> _getMpcBalance() async {
    if (_mpcAddress == null) {
      _log('Error: No MPC wallet address.', type: LogType.error);
      return;
    }
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    await _runWithLoading('Fetching balance...', () async {
      try {
        _log('Fetching MPC wallet balance...');
        final result = await TreSori().getWalletBalance(
          address: _mpcAddress!,
          chain: _mpcChain!,
        );
        _log('Balance: ${result.toString()}', type: LogType.success);
        _showBalanceDialog(
          'MPC Wallet Balance',
          _mpcAddress!,
          _mpcChain!,
          result,
        );
      } catch (e) {
        _log('Error fetching MPC balance: $e', type: LogType.error);
      }
    });
  }

  Future<void> _transferMpcTokens() async {
    if (_mpcAddress == null) {
      _log('Error: No MPC wallet address.', type: LogType.error);
      return;
    }
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    if (_mpcUserShard == null && _mpcUserShardsController.text.isEmpty) {
      _log('Error: No user shard available.', type: LogType.error);
      return;
    }
    await _runWithLoading('Transferring MPC tokens...', () async {
      try {
        final userShard =
            _mpcUserShardsController.text.isNotEmpty
                ? _mpcUserShardsController.text
                : _mpcUserShard!;
        final tokenAddress = _mpcTokenAddressController.text.trim();
        _log('Transferring MPC Tokens...');
        final result = await TreSori().transferMpcTokens(
          fromAddress: _mpcAddress!,
          toAddress: _mpcToController.text,
          amount: _mpcAmountController.text,
          chain: _mpcChain!,
          userShards: userShard,
          userIdentity: 'email',
          tokenAddress: tokenAddress,
        );
        _log('Transfer Successful: $result', type: LogType.success);
      } catch (e) {
        _log('Error transferring MPC tokens: $e', type: LogType.error);
      }
    });
  }

  Future<void> _signMpcContract() async {
    if (_mpcAddress == null) {
      _log('Error: No MPC wallet address.', type: LogType.error);
      return;
    }
    if (_mpcChain == null) {
      _log('Error: No chain selected.', type: LogType.error);
      return;
    }
    if (_mpcUserShard == null && _mpcUserShardsController.text.isEmpty) {
      _log('Error: No user shard available.', type: LogType.error);
      return;
    }
    await _runWithLoading('Signing MPC contract...', () async {
      try {
        final abiString = _mpcAbiController.text;
        final abiList = jsonDecode(abiString) as List;
        final paramsString = _mpcParamsController.text;
        final params =
            paramsString.isNotEmpty
                ? jsonDecode(paramsString.replaceAll("'", '"')) as List
                : [];
        final userShard =
            _mpcUserShardsController.text.isNotEmpty
                ? _mpcUserShardsController.text
                : _mpcUserShard!;

        _log('Signing MPC Contract Transaction...');
        final result = await TreSori().writeMpcSmartContractTransaction(
          contractAddress: _mpcContractAddressController.text,
          functionName: _mpcFunctionController.text,
          parameters: params,
          contractAbi: abiList,
          fromAddress: _mpcAddress!,
          chain: _mpcChain!,
          userShards: userShard,
          userIdentity: 'email',
        );
        _log('TX: $result', type: LogType.success);
      } catch (e) {
        _log('Error signing MPC contract: $e', type: LogType.error);
      }
    });
  }

  // ============ UI HELPERS ============
  void _showBalanceDialog(
    String title,
    String address,
    Chain selectedChain,
    Map<String, dynamic> result,
  ) {
    if (!mounted) return;
    // Parse balance from various response structures
    final balance =
        result['result'] ?? result['balance'] ?? result['data']?['balance'];
    final balanceStr = balance?.toString() ?? 'N/A';

    showDialog(
      context: context,
      builder:
          (context) => AlertDialog(
            title: Text(title),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _buildDialogLabel('Address:'),
                SelectableText(
                  address,
                  style: const TextStyle(fontSize: 12, fontFamily: 'Courier'),
                ),
                const SizedBox(height: 12),
                _buildDialogLabel('Chain:'),
                Text(
                  _getChainDisplayName(selectedChain),
                  style: const TextStyle(fontSize: 14),
                ),
                const SizedBox(height: 12),
                _buildDialogLabel('Balance:'),
                Text(
                  balanceStr,
                  style: const TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                    color: Colors.green,
                  ),
                ),
              ],
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: const Text('Close'),
              ),
            ],
          ),
    );
  }

  Widget _buildDialogLabel(String text) {
    return Text(
      text,
      style: TextStyle(
        fontSize: 12,
        color: Colors.grey.shade600,
        fontWeight: FontWeight.bold,
      ),
    );
  }

  Widget _buildLoadingOverlay({required Widget child}) {
    return Stack(
      children: [
        child,
        if (_isLoading)
          Container(
            color: Colors.black.withOpacity(0.5),
            child: Center(
              child: Card(
                margin: const EdgeInsets.all(32),
                child: Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 32,
                    vertical: 24,
                  ),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const CircularProgressIndicator(),
                      const SizedBox(height: 16),
                      Text(
                        _loadingMessage,
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w500,
                        ),
                        textAlign: TextAlign.center,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
      ],
    );
  }

  Widget _buildInfoTile(
    String label,
    String? value, {
    bool isSensitive = false,
  }) {
    if (value == null) return const SizedBox.shrink();
    return Container(
      margin: const EdgeInsets.only(bottom: 8),
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.grey.shade100,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.grey.shade300),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                label,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 12,
                  color: Colors.grey.shade700,
                ),
              ),
              InkWell(
                borderRadius: BorderRadius.circular(12),
                onTap: () {
                  Clipboard.setData(ClipboardData(text: value));
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('$label copied to clipboard'),
                      duration: const Duration(seconds: 1),
                    ),
                  );
                },
                child: const Padding(
                  padding: EdgeInsets.all(4.0),
                  child: Icon(Icons.copy, size: 16, color: Colors.blue),
                ),
              ),
            ],
          ),
          const SizedBox(height: 4),
          SelectableText(
            value,
            style: const TextStyle(fontSize: 13, fontFamily: 'Courier'),
          ),
        ],
      ),
    );
  }

  Widget _buildChainDropdown({
    required Chain? value,
    required ValueChanged<Chain?> onChanged,
    String label = 'Select Chain',
  }) {
    if (!_isInitialized) {
      return Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.grey.shade200,
          borderRadius: BorderRadius.circular(8),
        ),
        child: const Text(
          'Initialize SDK first to load chains',
          style: TextStyle(color: Colors.grey),
        ),
      );
    }
    return DropdownButtonFormField<Chain>(
      decoration: InputDecoration(
        labelText: label,
        border: const OutlineInputBorder(),
      ),
      value: value,
      isExpanded: true,
      onChanged: onChanged,
      items:
          (chain.all as List<Chain>)
              .map(
                (c) => DropdownMenuItem(
                  value: c,
                  child: Text(_getChainDisplayName(c)),
                ),
              )
              .toList(),
    );
  }

  String _getChainDisplayName(Chain c) => '${c.blockchain} - ${c.network}';

  String _formatTime(DateTime dt) {
    return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}';
  }

  // ============ TAB CONTENT BUILDERS ============
  Widget _buildSelfCustodialTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Chain Selection
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Chain Configuration',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  _buildChainDropdown(
                    value: _selfCustodialChain,
                    onChanged:
                        (val) => setState(() => _selfCustodialChain = val),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _selfCustodialRpcController,
                    decoration: const InputDecoration(
                      labelText: 'RPC URL',
                      border: OutlineInputBorder(),
                      hintText: 'https://rpc-amoy.polygon.technology',
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Wallet Info
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Wallet Management',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  if (_currentAddress != null) ...[
                    _buildInfoTile('Address', _currentAddress),
                    _buildInfoTile(
                      'Private Key',
                      _currentPrivateKey,
                      isSensitive: true,
                    ),
                    if (_currentMnemonic != null)
                      _buildInfoTile(
                        'Mnemonic',
                        _currentMnemonic,
                        isSensitive: true,
                      ),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton.icon(
                        onPressed:
                            _isInitialized ? _getSelfCustodialBalance : null,
                        icon: const Icon(Icons.account_balance_wallet),
                        label: const Text('Get Balance'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.teal,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                    const SizedBox(height: 12),
                  ],
                  Wrap(
                    spacing: 10,
                    runSpacing: 10,
                    children: [
                      ElevatedButton.icon(
                        onPressed:
                            _isInitialized ? _createSelfCustodialWallet : null,
                        icon: const Icon(Icons.add),
                        label: const Text('Create New'),
                      ),
                      OutlinedButton.icon(
                        onPressed: _isInitialized ? _importFromMnemonic : null,
                        icon: const Icon(Icons.key),
                        label: const Text('Import Mnemonic'),
                      ),
                      OutlinedButton.icon(
                        onPressed:
                            _isInitialized ? _importFromPrivateKey : null,
                        icon: const Icon(Icons.vpn_key),
                        label: const Text('Import PK'),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Transfer Tokens
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Transfer Tokens',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _selfCustodialToController,
                    decoration: const InputDecoration(
                      labelText: 'To Address',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _selfCustodialAmountController,
                    decoration: const InputDecoration(
                      labelText: 'Amount (Wei)',
                      border: OutlineInputBorder(),
                    ),
                    keyboardType: TextInputType.number,
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed:
                          _isInitialized && _currentPrivateKey != null
                              ? _transferSelfCustodialTokens
                              : null,
                      icon: const Icon(Icons.send),
                      label: const Text('Transfer'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Contract Interaction
          Card(
            child: ExpansionTile(
              title: const Text(
                'Contract Interaction',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              children: [
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    children: [
                      TextField(
                        controller: _selfCustodialContractAddressController,
                        decoration: const InputDecoration(
                          labelText: 'Contract Address',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _selfCustodialFunctionController,
                        decoration: const InputDecoration(
                          labelText: 'Function Name',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _selfCustodialAbiController,
                        decoration: const InputDecoration(
                          labelText: 'ABI JSON',
                          border: OutlineInputBorder(),
                        ),
                        maxLines: 3,
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _selfCustodialParamsController,
                        decoration: const InputDecoration(
                          labelText: 'Params JSON Array ([])',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      SizedBox(
                        width: double.infinity,
                        child: ElevatedButton.icon(
                          onPressed:
                              _isInitialized && _currentPrivateKey != null
                                  ? _signSelfCustodialContract
                                  : null,
                          icon: const Icon(Icons.edit_document),
                          label: const Text('Sign Contract Transaction'),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildCustodialTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Chain Selection
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Chain Configuration',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  _buildChainDropdown(
                    value: _custodialChain,
                    onChanged: (val) => setState(() => _custodialChain = val),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Wallet Management
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.wallet, color: Colors.blue),
                      const SizedBox(width: 8),
                      const Text(
                        'Wallet Management',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const Spacer(),
                      if (_custodialAddress != null)
                        Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 4,
                          ),
                          decoration: BoxDecoration(
                            color: Colors.green.shade100,
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Icon(
                                Icons.check_circle,
                                size: 14,
                                color: Colors.green.shade700,
                              ),
                              const SizedBox(width: 4),
                              Text(
                                'Wallet Created',
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.green.shade700,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  if (_custodialAddress != null) ...[
                    _buildInfoTile('Custodial Address', _custodialAddress),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton.icon(
                        onPressed: _isInitialized ? _getCustodialBalance : null,
                        icon: const Icon(Icons.account_balance_wallet),
                        label: const Text('Get Balance'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.teal,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                    const SizedBox(height: 12),
                  ] else ...[
                    Container(
                      padding: const EdgeInsets.all(16),
                      decoration: BoxDecoration(
                        color: Colors.grey.shade100,
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(color: Colors.grey.shade300),
                      ),
                      child: Row(
                        children: [
                          Icon(Icons.info_outline, color: Colors.grey.shade600),
                          const SizedBox(width: 12),
                          const Expanded(
                            child: Text(
                              'No custodial wallet created yet. Create one to see the address and balance.',
                              style: TextStyle(color: Colors.grey),
                            ),
                          ),
                        ],
                      ),
                    ),
                    const SizedBox(height: 12),
                  ],
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isInitialized ? _createCustodialWallet : null,
                      icon: const Icon(Icons.add),
                      label: Text(
                        _custodialAddress != null
                            ? 'Create Another Wallet'
                            : 'Create Custodial Wallet',
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Transfer Tokens
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Transfer Tokens',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _custodialToController,
                    decoration: const InputDecoration(
                      labelText: 'To Address',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _custodialAmountController,
                    decoration: const InputDecoration(
                      labelText: 'Amount',
                      border: OutlineInputBorder(),
                    ),
                    keyboardType: TextInputType.number,
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _custodialTokenAddressController,
                    decoration: const InputDecoration(
                      labelText: 'Token Address (Optional)',
                      border: OutlineInputBorder(),
                      hintText: 'Leave empty for native token',
                    ),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed:
                          _isInitialized && _custodialAddress != null
                              ? _transferCustodialTokens
                              : null,
                      icon: const Icon(Icons.send),
                      label: const Text('Transfer'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Contract Interaction
          Card(
            child: ExpansionTile(
              title: const Text(
                'Contract Interaction',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              children: [
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    children: [
                      TextField(
                        controller: _custodialContractAddressController,
                        decoration: const InputDecoration(
                          labelText: 'Contract Address',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _custodialFunctionController,
                        decoration: const InputDecoration(
                          labelText: 'Function Name',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _custodialAbiController,
                        decoration: const InputDecoration(
                          labelText: 'ABI JSON',
                          border: OutlineInputBorder(),
                        ),
                        maxLines: 3,
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _custodialParamsController,
                        decoration: const InputDecoration(
                          labelText: 'Params JSON Array ([])',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      SizedBox(
                        width: double.infinity,
                        child: ElevatedButton.icon(
                          onPressed:
                              _isInitialized && _custodialAddress != null
                                  ? _signCustodialContract
                                  : null,
                          icon: const Icon(Icons.edit_document),
                          label: const Text('Write Contract Transaction'),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildMpcTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Chain Selection
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Chain Configuration',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  _buildChainDropdown(
                    value: _mpcChain,
                    onChanged: (val) => setState(() => _mpcChain = val),
                  ),
                  const SizedBox(height: 12),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      const Text('Is New User', style: TextStyle(fontSize: 14)),
                      Switch(
                        value: _mpcIsNewUser,
                        onChanged: (val) => setState(() => _mpcIsNewUser = val),
                        activeColor: Colors.purple,
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Wallet Info
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.security, color: Colors.purple),
                      const SizedBox(width: 8),
                      const Text(
                        'MPC Wallet',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const Spacer(),
                      if (_mpcAddress != null)
                        Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 4,
                          ),
                          decoration: BoxDecoration(
                            color: Colors.green.shade100,
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Icon(
                                Icons.check_circle,
                                size: 14,
                                color: Colors.green.shade700,
                              ),
                              const SizedBox(width: 4),
                              Text(
                                'Wallet Created',
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.green.shade700,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  if (_mpcAddress != null) ...[
                    _buildInfoTile('MPC Address', _mpcAddress),
                    if (_mpcUserShard != null)
                      _buildInfoTile('User Shard', _mpcUserShard),
                    if (_mpcBalance != null)
                      _buildInfoTile('Balance', _mpcBalance),
                    if (_mpcPublicKey != null)
                      _buildInfoTile('Public Key', _mpcPublicKey),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton.icon(
                        onPressed: _isInitialized ? _getMpcBalance : null,
                        icon: const Icon(Icons.account_balance_wallet),
                        label: const Text('Get Balance'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.teal,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                  ] else
                    Container(
                      padding: const EdgeInsets.all(16),
                      decoration: BoxDecoration(
                        color: Colors.grey.shade100,
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(color: Colors.grey.shade300),
                      ),
                      child: Row(
                        children: [
                          Icon(Icons.info_outline, color: Colors.grey.shade600),
                          const SizedBox(width: 12),
                          const Expanded(
                            child: Text(
                              'No MPC wallet created yet. Verify your email or phone below to create one.',
                              style: TextStyle(color: Colors.grey),
                            ),
                          ),
                        ],
                      ),
                    ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Email Verification
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Email Verification',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      Expanded(
                        child: TextField(
                          controller: _emailController,
                          decoration: const InputDecoration(
                            labelText: 'Email',
                            border: OutlineInputBorder(),
                          ),
                        ),
                      ),
                      const SizedBox(width: 8),
                      ElevatedButton(
                        onPressed:
                            _isInitialized ? _sendEmailVerification : null,
                        child: const Text('Send OTP'),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      Expanded(
                        child: TextField(
                          controller: _otpController,
                          decoration: const InputDecoration(
                            labelText: 'OTP',
                            border: OutlineInputBorder(),
                          ),
                          keyboardType: TextInputType.number,
                        ),
                      ),
                      const SizedBox(width: 8),
                      ElevatedButton(
                        onPressed: _isInitialized ? _verifyEmail : null,
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.green,
                          foregroundColor: Colors.white,
                        ),
                        child: const Text('Verify'),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Phone Verification
          // Card(
          //   child: Padding(
          //     padding: const EdgeInsets.all(16),
          //     child: Column(
          //       crossAxisAlignment: CrossAxisAlignment.start,
          //       children: [
          //         const Text(
          //           'Phone Verification',
          //           style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          //         ),
          //         const SizedBox(height: 12),
          //         Row(
          //           children: [
          //             SizedBox(
          //               width: 80,
          //               child: TextField(
          //                 controller: _countryCodeController,
          //                 decoration: const InputDecoration(
          //                   labelText: 'Code',
          //                   border: OutlineInputBorder(),
          //                 ),
          //               ),
          //             ),
          //             const SizedBox(width: 8),
          //             Expanded(
          //               child: TextField(
          //                 controller: _phoneController,
          //                 decoration: const InputDecoration(
          //                   labelText: 'Phone',
          //                   border: OutlineInputBorder(),
          //                 ),
          //                 keyboardType: TextInputType.phone,
          //               ),
          //             ),
          //             const SizedBox(width: 8),
          //             ElevatedButton(
          //               onPressed:
          //                   _isInitialized ? _sendPhoneVerification : null,
          //               child: const Text('Send OTP'),
          //             ),
          //           ],
          //         ),
          //         const SizedBox(height: 12),
          //         Row(
          //           children: [
          //             Expanded(
          //               child: TextField(
          //                 controller: _otpController,
          //                 decoration: const InputDecoration(
          //                   labelText: 'OTP',
          //                   border: OutlineInputBorder(),
          //                 ),
          //                 keyboardType: TextInputType.number,
          //               ),
          //             ),
          //             const SizedBox(width: 8),
          //             ElevatedButton(
          //               onPressed: _isInitialized ? _verifyPhone : null,
          //               style: ElevatedButton.styleFrom(
          //                 backgroundColor: Colors.green,
          //                 foregroundColor: Colors.white,
          //               ),
          //               child: const Text('Verify'),
          //             ),
          //           ],
          //         ),
          //       ],
          //     ),
          //   ),
          // ),
          // const SizedBox(height: 16),

          // Transfer Tokens (MPC)
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Transfer Tokens',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcToController,
                    decoration: const InputDecoration(
                      labelText: 'To Address',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcAmountController,
                    decoration: const InputDecoration(
                      labelText: 'Amount',
                      border: OutlineInputBorder(),
                    ),
                    keyboardType: TextInputType.number,
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcTokenAddressController,
                    decoration: const InputDecoration(
                      labelText: 'Token Address (Optional)',
                      border: OutlineInputBorder(),
                      hintText: 'Leave empty for native token',
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcUserShardsController,
                    decoration: InputDecoration(
                      labelText: 'User Shards',
                      border: const OutlineInputBorder(),
                      hintText:
                          _mpcUserShard != null
                              ? 'Auto-filled from wallet'
                              : null,
                    ),
                  ),
                  if (_mpcUserShard != null &&
                      _mpcUserShardsController.text.isEmpty)
                    Padding(
                      padding: const EdgeInsets.only(top: 4),
                      child: Text(
                        'Will use saved user shard if left empty',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed:
                          _isInitialized && _mpcAddress != null
                              ? _transferMpcTokens
                              : null,
                      icon: const Icon(Icons.send),
                      label: const Text('Transfer'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Smart Contract (MPC)
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Smart Contract Interaction',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcContractAddressController,
                    decoration: const InputDecoration(
                      labelText: 'Contract Address',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcFunctionController,
                    decoration: const InputDecoration(
                      labelText: 'Function Name',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcAbiController,
                    decoration: const InputDecoration(
                      labelText: 'Contract ABI (JSON)',
                      border: OutlineInputBorder(),
                      hintText: '[{"name":"...","type":"function",...}]',
                    ),
                    maxLines: 3,
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: _mpcParamsController,
                    decoration: const InputDecoration(
                      labelText: 'Parameters (JSON Array)',
                      border: OutlineInputBorder(),
                      hintText: '["param1", 123, true]',
                    ),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed:
                          _isInitialized && _mpcAddress != null
                              ? _signMpcContract
                              : null,
                      icon: const Icon(Icons.edit_document),
                      label: const Text('Write Contract'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.deepPurple,
                        foregroundColor: Colors.white,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildLogger() {
    return Container(
      height: 250,
      decoration: BoxDecoration(
        color: const Color(0xFF1E1E1E),
        border: Border(top: BorderSide(color: Colors.grey.shade800)),
      ),
      child: Column(
        children: [
          // Toolbar
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            color: const Color(0xFF2D2D2D),
            child: Row(
              children: [
                const Icon(Icons.terminal, color: Colors.white70, size: 16),
                const SizedBox(width: 8),
                const Text(
                  'Logs',
                  style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const Spacer(),
                Row(
                  children: [
                    Transform.scale(
                      scale: 0.8,
                      child: Checkbox(
                        value: _autoScroll,
                        onChanged: (v) => setState(() => _autoScroll = v!),
                        fillColor: WidgetStateProperty.all(Colors.blue),
                        visualDensity: VisualDensity.compact,
                      ),
                    ),
                    const Text(
                      'Auto-scroll',
                      style: TextStyle(color: Colors.white70, fontSize: 12),
                    ),
                  ],
                ),
                const SizedBox(width: 8),
                IconButton(
                  padding: EdgeInsets.zero,
                  constraints: const BoxConstraints(),
                  icon: const Icon(
                    Icons.copy_all,
                    color: Colors.white70,
                    size: 18,
                  ),
                  tooltip: 'Copy All',
                  onPressed: () {
                    final text = _logs
                        .map((l) => '[${l.timestamp}] ${l.message}')
                        .join('\n');
                    Clipboard.setData(ClipboardData(text: text));
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(
                        content: Text('All logs copied'),
                        duration: Duration(seconds: 1),
                      ),
                    );
                  },
                ),
                const SizedBox(width: 16),
                IconButton(
                  padding: EdgeInsets.zero,
                  constraints: const BoxConstraints(),
                  icon: const Icon(
                    Icons.delete_outline,
                    color: Colors.white70,
                    size: 18,
                  ),
                  tooltip: 'Clear Logs',
                  onPressed: () => setState(() => _logs.clear()),
                ),
                const SizedBox(width: 8),
              ],
            ),
          ),
          // Log List
          Expanded(
            child: ListView.builder(
              controller: _loggerController,
              padding: const EdgeInsets.all(8),
              itemCount: _logs.length,
              itemBuilder: (context, index) {
                final log = _logs[index];
                Color color;
                IconData icon;
                switch (log.type) {
                  case LogType.error:
                    color = Colors.redAccent;
                    icon = Icons.error_outline;
                    break;
                  case LogType.success:
                    color = Colors.greenAccent;
                    icon = Icons.check_circle_outline;
                    break;
                  case LogType.warning:
                    color = Colors.orangeAccent;
                    icon = Icons.warning_amber_rounded;
                    break;
                  case LogType.info:
                    color = Colors.lightBlueAccent;
                    icon = Icons.info_outline;
                    break;
                }

                return Padding(
                  padding: const EdgeInsets.only(bottom: 4.0),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Padding(
                        padding: const EdgeInsets.only(top: 2.0),
                        child: Icon(icon, color: color, size: 14),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        _formatTime(log.timestamp),
                        style: TextStyle(
                          color: Colors.grey.shade500,
                          fontFamily: 'Courier',
                          fontSize: 12,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: SelectableText(
                          log.message,
                          style: TextStyle(
                            color: color,
                            fontFamily: 'Courier',
                            fontSize: 13,
                          ),
                        ),
                      ),
                      const SizedBox(width: 4),
                      InkWell(
                        onTap: () {
                          Clipboard.setData(ClipboardData(text: log.message));
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                              content: Text('Log copied'),
                              duration: Duration(milliseconds: 500),
                            ),
                          );
                        },
                        child: Padding(
                          padding: const EdgeInsets.all(2.0),
                          child: Icon(
                            Icons.copy,
                            size: 12,
                            color: Colors.grey.shade700,
                          ),
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return _buildLoadingOverlay(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TreSori Demo'),
          bottom: PreferredSize(
            preferredSize: const Size.fromHeight(48),
            child: TabBar(
              controller: _tabController,
              tabs: const [
                Tab(text: 'Self Custodial'),
                Tab(text: 'Custodial'),
                Tab(text: 'MPC'),
              ],
            ),
          ),
          actions: [
            IconButton(
              icon: Icon(_showLogs ? Icons.visibility_off : Icons.visibility),
              tooltip: _showLogs ? 'Hide Logs' : 'Show Logs',
              onPressed: () => setState(() => _showLogs = !_showLogs),
            ),
          ],
        ),
        body: Column(
          children: [
            // Common Initialization Section
            Container(
              color: Colors.grey.shade50,
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.settings, size: 20),
                      const SizedBox(width: 8),
                      const Text(
                        'Initialization',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(width: 16),
                      if (_isInitialized)
                        Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 4,
                          ),
                          decoration: BoxDecoration(
                            color: Colors.green.shade100,
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Icon(
                                Icons.check_circle,
                                size: 14,
                                color: Colors.green.shade700,
                              ),
                              const SizedBox(width: 4),
                              Text(
                                'SDK Initialized',
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.green.shade700,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      Expanded(
                        flex: 2,
                        child: TextField(
                          controller: _apiKeyController,
                          decoration: const InputDecoration(
                            labelText: 'API Key',
                            border: OutlineInputBorder(),
                            isDense: true,
                          ),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: TextField(
                          controller: _userIdController,
                          decoration: const InputDecoration(
                            labelText: 'User ID',
                            border: OutlineInputBorder(),
                            isDense: true,
                          ),
                        ),
                      ),
                      const SizedBox(width: 12),
                      ElevatedButton(
                        onPressed: _initialize,
                        style: ElevatedButton.styleFrom(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 24,
                            vertical: 16,
                          ),
                        ),
                        child: const Text('Initialize'),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            const Divider(height: 1),

            // Tab Content
            Expanded(
              child: TabBarView(
                controller: _tabController,
                children: [
                  _buildSelfCustodialTab(),
                  _buildCustodialTab(),
                  _buildMpcTab(),
                ],
              ),
            ),

            // Logger
            if (_showLogs) _buildLogger(),
          ],
        ),
      ),
    );
  }
}
1
likes
130
points
12
downloads

Documentation

API reference

Publisher

verified publisherkalp.studio

Weekly Downloads

A comprehensive interface for blockchain wallet management, supporting Self-Custodial, Custodial, and MPC wallet operations.

License

MIT (license)

Dependencies

bip32_plus, bip39_mnemonic, dio, freezed_annotation, hex, http, json_annotation, wallet, web3dart

More

Packages that depend on tresori_sdk