Webux Lab

By Studio Webux

MVP to build a wallet mobile app with flutter

TG
Tommy Gingras Studio Webux 2025-01-18

Flutter and Cardano

Reviewed the code and improved everything. Built a class to simplified the integration

My goal is simple, I have a deno backend that creates the Transaction, the mobile app has its own wallet and is allowed to sign the transactions. Then it has to send back the signature and the backend handle the rest.

Dependencies

  # Cardano
  bip32_ed25519: ^0.6.2
  bip39: ^1.0.6
  hex: ^0.2.0
  cbor: ^6.3.3
  google_fonts: ^6.2.1

Flutter theme

Probably broken tho…


final lightThemeBW = ThemeData(
  fontFamily: GoogleFonts.robotoMono().fontFamily,
  elevatedButtonTheme: ElevatedButtonThemeData(
    style: ElevatedButton.styleFrom(
      foregroundColor: Color(0xFFEBEBEB),
      backgroundColor: Color(0xFF111111),
    ),
  ),
  colorScheme: ColorScheme(
    brightness: Brightness.light,
    primary: Color(0xFF212121),
    onPrimary: Color(0xFFEBEBEB),
    primaryContainer: Color(0xFF333333),
    onPrimaryContainer: Color(0xFFEBEBEB),
    secondary: Color(0xFFFFBF00),
    onSecondary: Color(0xFF333333),
    secondaryContainer: Color(0xFFEBEBEB),
    onSecondaryContainer: Color(0xFF212121),
    error: Color(0xFF93000A),
    onError: Color(0xFF690005),
    errorContainer: Color(0xFF93000A),
    onErrorContainer: Color(0xFFFFDAD6),
    surface: Color(0xFF333333),
    onSurface: Color(0xFF000000),
    surfaceContainerHighest: Color(0xFF212121),
    onSurfaceVariant: Color(0xFFFFFFFF),
    outline: Color(0xFF111111),
    shadow: Color(0xFF000000),
    inverseSurface: Color(0xFF333333),
    onInverseSurface: Color(0xFFFFFFFF),
    inversePrimary: Color(0xFFEBEBEB),
  ),
  useMaterial3: false,
);

Cardano Dart Class

You can use this class in your project, it allows to setup a wallet and sign transactions. You are gonna need a backend to create the transaction, fetch the tx body serialized in cbor format. Then once the transaction is signed, you are gonna need an endpoint to rebuild the transaction and submit it on-chain (see part 1 for Deno examples)

Part 1: https://webuxlab.com/en/tutorials/flutter-cardano

import 'package:bip32_ed25519/cardano.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:cbor/cbor.dart';
import 'package:hex/hex.dart';

class Cardano {
  String mnemonic = "";
  String entropy = "";
  late CardanoIcarusKey wallet;

  // Paths
  String index = "0"; // wallet index
  String wallet0 = "m/1852'/1815'/0'/0/"; // + index
  String stakePath = "m/1852'/1815'/0'/2/"; // + index
  String privateKeyPath = "m/1852'/1815'/0'";

  // Wallet info
  late Bip32SigningKey sk;
  late Bip32Key pk;
  late Uint8List paymentPart;
  late Bip32Key spk;
  late Uint8List stakePart;

  String get addressTestnet => ByteList([0x00] + paymentPart + stakePart)
      .encode(Bech32Encoder(hrp: 'addr_test'));
  String get addressMainnet => ByteList([0x01] + paymentPart + stakePart)
      .encode(Bech32Encoder(hrp: 'addr'));

  String get stakeAddressTestnet =>
      ByteList([0xE0] + stakePart).encode(Bech32Encoder(hrp: 'stake_test'));
  String get stakeAddressMainnet =>
      ByteList([0xE1] + stakePart).encode(Bech32Encoder(hrp: 'stake'));

  String get pkHex => HEX.encode(pk.rawKey);
  String get pkEd => pk.rawKey.encode(Bech32Encoder(hrp: 'ed25519_pk'));
  String get skHex => HEX.encode(sk.rawKey);
  String get keyhash => HEX.encode(paymentPart);

  void createOrRestoreWallet(String? index, String? mnemonic) {
    if (mnemonic == null || mnemonic.isEmpty) {
      print("Creating new wallet.");
      this.mnemonic = bip39.generateMnemonic(strength: 256);
    } else {
      print("Restoring wallet from seedphrases.");
      this.mnemonic = mnemonic;
    }

    if (index != null && index.isNotEmpty) {
      print("Wallet Index: $index");
      this.index = index;
    }

    entropy = bip39.mnemonicToEntropy(mnemonic);
    wallet = CardanoIcarusKey.seed(entropy);

    sk =
        wallet.pathToKey(privateKeyPath).derive(0).derive(0) as Bip32SigningKey;
    pk = wallet.pathToKey(wallet0 + this.index).publicKey as Bip32Key;
    paymentPart = Hash.blake2b(pk.rawKey.asTypedList, digestSize: 28);

    spk = wallet.pathToKey(stakePath + this.index).publicKey as Bip32Key;
    stakePart = Hash.blake2b(spk.rawKey.asTypedList, digestSize: 28);
  }

  String signTransaction(String txBodyHex) {
    final hash = Hash.blake2b(HEX.decode(txBodyHex), digestSize: 32);
    final txHash = HEX.encode(hash);
    print("TX Hash: $txHash");

    final signature = sk.sign(hash);

    // Limited Implementation
    // Only vkeys are implementeds
    final witnessSet = CborMap(
      {
        CborSmallInt(0): CborList(
          CborList([
            CborList(
              [CborBytes(pk.rawKey), CborBytes(signature.prefix)],
            ),
          ]),
          tags: [258],
        ),
      },
    );
    final cborHex = HEX.encode(cbor.encode(witnessSet));

    print("Witness set: $cborHex");
    return cborHex;
  }
}

Cardano Flutter Page

import 'package:flutter/material.dart';

import 'package:my_app/data/cardano.dart';

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

  @override
  State<StatefulWidget> createState() => _CardanoPageState();
}

class _CardanoPageState extends State<CardanoPage> {
  Cardano wallet = Cardano();
  TextEditingController txInputController = TextEditingController();

  @override
  initState() {
    setState(() {
      wallet.createOrRestoreWallet("0",
          "ADD YOUR 24 WORDS MNEMONIC");
    });
    super.initState();
  }

  void signTx() {
    final cborHex = wallet.signTransaction(txInputController.text);
    print(cborHex);

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          content: SizedBox(
            height: 200,
            child: Column(
              children: [
                SelectableText(cborHex),
              ],
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            // wallet info
            Padding(
              padding: const EdgeInsets.all(25.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(wallet.addressMainnet),
                  SizedBox(height: 8),
                  Text(wallet.stakeAddressMainnet),
                  SizedBox(height: 8),
                  Text(wallet.addressTestnet),
                  SizedBox(height: 8),
                  Text(wallet.stakeAddressTestnet),
                  SizedBox(height: 8),
                  Text(wallet.keyhash),
                ],
              ),
            ),

            // sign tx
            Padding(
              padding: const EdgeInsets.all(25.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextField(
                    controller: txInputController,
                    maxLines: 4, //or null
                    decoration: InputDecoration.collapsed(
                      hintText: "Enter Transaction cbor hex",
                      border: OutlineInputBorder(),
                    ),
                  ),
                  ElevatedButton(
                    onPressed: signTx,
                    child: Text("Sign Transaction"),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Screenshots

Conclusion

Flutter and Dart are pretty awesome tech !


Search