Flutter (Dart) with Cardano Blockchain
This is only a MVP and the goal is to learn Dart.
Goals
- Create using an mnemonic a cardano wallet.
- Sign Transaction using the created wallet.
- Get the CBOR format of the signature.
- Using the work previously done with Deno and CSL.
Todos
- Setup Flutter with a wallet UI (currently built using Dart and ran on the terminal and simple example on the mobile device)
Requirements
- 1 Cardano Preprod Wallet
- https://github.com/Cardano-Forge/cardano-wallet-cli
- Or any browser based wallet
- 1 Cardano Preprod Wallet will be used in flutter
- For both Wallet I recommend using the cardano-wallet-cli, this way you will have full flexibility and also the mnemonic, so you will be able to import the wallet in the browser, the CLI, the Deno stuff and the mobile app.
- Funds from the faucet
- https://docs.cardano.org/cardano-testnets/tools/faucet (From Preprod network)
- You only need fund on one of the 2 wallets.
- Flutter / Dart Installed
- Deno 2+ Installed
The “Backend”
Built with deno
and cardano serialization lib
.
Here are the snippets I used to test:
Create a transaction
You need the list of UTXOs, in this case I had only one. You can get it from multiple places, I used eternl and copied the utxo details manually.
Easiest way to retrieve the string[]
of utxos, ìn your browser console, do the following:
const w = await cardano.eternl.enable()
await w.getUtxos()
import { TxBuilder } from "jsr:@studiowebux/cardano";
const tx_builder = new TxBuilder()
.with_receiver_address(
"addr_test1qrdt5dsgs4mcy8ynpnu9a8xr5pfknx0ad3c7y36afa6t0hwr24y3xm20jlxu4t8yhue2tpqr587np3f94hdhvlw9ecmqjvt49r",
)
.with_sender_address(
"addr_test1qra369fzgacfz9edsnel4kcpx8r9d7dqc8rfhvqmpajzn6pu8sm3t2e5gfnfsn7328k0rtde0yytk7gnjaau45z2cl6q0pxm0c",
)
// support an array of utxos as well.
.with_utxos([
{
output_index: 0,
address:
"addr_test1qra369fzgacfz9edsnel4kcpx8r9d7dqc8rfhvqmpajzn6pu8sm3t2e5gfnfsn7328k0rtde0yytk7gnjaau45z2cl6q0pxm0c",
amount: [{ quantity: "10000000", unit: "lovelace" }],
tx_hash:
"c00cc77e0d51c301b9f77a7a57cdc43551c39c1b3351b54b11666bcdefd40196",
},
])
.with_ada_to_send(5_000_000)
.build();
const tip_slot = 1_000_000_000_000;
tx_builder
.add_tx_metadata(["Sent using webux cardano tx builder"])
.parse_utxos()
.set_ttl(tip_slot)
.add_output() // Send 5 ADA to receiver address
.add_inputs()
.build_body_and_hash()
.add_signers()
.build_tx()
.assemble_tx();
console.log(tx_builder.get_hash()?.to_hex());
// you are gonna need this tx hex for the next step.
console.log(tx_builder.get_unsigned_tx()?.to_hex());
Prepare the TX to be sent to the Flutter app
There is no API or anything, this is only a learning exercice.
So you are gonna need to do the following:
import { TransactionWitnessSet, FixedTransaction } from "@studiowebux/cardano";
const tx = FixedTransaction.from_hex(
"84a500d9010281825820c00cc77e0d51c301b9f77a7a57cdc43551c39c1b3351b54b11666bcdefd4019600018282583900daba36088577821c930cf85e9cc3a0536999fd6c71e2475d4f74b7ddc35549136d4f97cdcaace4bf32a58403a1fd30c525addb767dc5ce361a004c4b4082583900fb1d1522477091172d84f3fadb0131c656f9a0c1c69bb01b0f6429e83c3c3715ab344266984fd151ecf1adb97908bb7913977bcad04ac7f41a0049aa1f021a0002a121031b000000e8d4a5100007582097b9aca0f75f27cae8f41c8806f8f7ec5ad13e5f68735725a57d5ad2b206b1e6a0f5a11902a2a1636d736781782353656e74207573696e672077656275782063617264616e6f207478206275696c646572",
);
// This is the body you need in the dart program.
console.log("body", tx.body().to_hex());
The Fronted “Dart”
import 'package:bip32_ed25519/cardano.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:cbor/cbor.dart';
import 'package:hex/hex.dart';
Future<void> main() async {
// // 24 words mnemonic (use 128 to get 12 words and 256 for 24 words.)
// // https://pub.dev/documentation/bip39/latest/
// Doing so will override the last one everytime...
// var mnemonic = bip39.generateMnemonic(strength: 256);
const mnemonic =
'PASTE ONE OF THE MNEMONIC GENERATED WITH THE CLI TOOL';
final entropy = bip39.mnemonicToEntropy(mnemonic);
final icarusKeyTree = CardanoIcarusKey.seed(entropy);
// https://cips.cardano.org/cip/CIP-1852
String wallet0 = "m/1852'/1815'/0'/0/0";
String stakePath = "m/1852'/1815'/0'/2/0";
String privateKeyPath = "m/1852'/1815'/0'";
final sk = icarusKeyTree.pathToKey(privateKeyPath).derive(0).derive(0)
as Bip32SigningKey;
final pk = icarusKeyTree.pathToKey(wallet0).publicKey as Bip32Key;
final paymentPart = Hash.blake2b(pk.rawKey.asTypedList, digestSize: 28);
final spk = icarusKeyTree.pathToKey(stakePath).publicKey as Bip32Key;
final stakePart = Hash.blake2b(spk.rawKey.asTypedList, digestSize: 28);
// https://cardano.stackexchange.com/a/7059
// https://github.com/cardano-foundation/CIPs/blob/master/CIP-0019/README.md
final addrTestnet = ByteList([0x00] + paymentPart + stakePart)
.encode(Bech32Encoder(hrp: 'addr_test'));
final addrMainnet = ByteList([0x01] + paymentPart + stakePart)
.encode(Bech32Encoder(hrp: 'addr'));
// https://cardano.stackexchange.com/a/4108
// https://github.com/IntersectMBO/cardano-ledger/blob/f2a783cf00911b7492e81dd6c7fb8a963f9ce8fe/eras/shelley/test-suite/cddl-files/shelley.cddl
final stakeTest =
ByteList([0xE0] + stakePart).encode(Bech32Encoder(hrp: 'stake_test'));
final stakeMainnet =
ByteList([0xE1] + stakePart).encode(Bech32Encoder(hrp: 'stake'));
print('Address Testnet: $addrTestnet');
print('Address Mainnet: $addrMainnet');
print('Stake test: $stakeTest');
print('Stake mainnet: $stakeMainnet');
print("pk Hex: ${HEX.encode(pk.rawKey)}");
print("sk Hex: ${HEX.encode(sk.rawKey)}");
print("keyhash: ${HEX.encode(paymentPart)}");
// To get the hash we need only the body (do not keep the aux and witnesses)
final hash = Hash.blake2b(
HEX.decode(
"PASTE_THE_BODY_HEX_FROM_DENO_HERE_ULTIMATELY_THIS_WILL_BE_FETCHED"),
digestSize: 32);
print("tx hash: ${HEX.encode(hash)}");
final signed = sk.sign(hash);
print("Signature: ${HEX.encode(signed.prefix)}");
final pkEd = pk.rawKey.encode(Bech32Encoder(hrp: 'ed25519_pk'));
print(pkEd);
final witnessSet = CborMap(
{
CborSmallInt(0): CborList(
CborList([
CborList(
[CborBytes(pk.rawKey), CborBytes(signed.prefix)],
),
]),
tags: [258],
),
},
);
print(HEX.encode(cbor.encode(witnessSet)));
// Paste this output in the next step
// The goal will be to send this tx back to the backend to then submit it onto the cardano network.
}
Finalize Transaction and Submit
// replace with the a100.. printed from the flutter app.
tx.add_vkey_witness(
TransactionWitnessSet.from_hex(
"a10081825820e482b66b52779cd4641129cff01c718f97aab61521f167307a2327b703f939d25840c96a86e7f0ae6494c7a8c47a179b548a7f5b4932e2b13088f6da1e199517ea1c672ccb57763ad53cbe75814f1b87ff33aa7985514ef7f3c889a01665f1e2c109",
)
.vkeys()
?.get(0)!,
);
console.log("TX Hash", tx.transaction_hash().to_hex());
// Submit this tx to the network.
console.log("TX hex", tx.to_hex());
You can use the browser console like that:
await w.submitTx("your-signed-tx-hex-from-above")
Or with Eternl Wallet, click “Send”, then “Options”, then “import transaction” and paste the CBOR hex.
Conclusion
I have learnt a lot of concepts for cardano !
But you should use an existing and supported library to do all of that.
References
- https://cbor.me
- https://pub.dev/documentation/cbor/latest/cbor/
- https://github.com/mrtnetwork/blockchain_utils
- https://github.com/shamblett/cbor
- https://pub.dev/packages/on_chain
- https://github.com/ilap/bip32-ed25519-dart
- https://pub.dev/documentation/bip39/latest/
- https://pub.dev/packages/hex
My open source projects made with Deno