Building a Multi-Chain Wallet: Bitcoin, Ethereum, and Tron with Flutter

·

This guide dives into creating a multi-chain wallet application capable of managing Bitcoin, Ethereum, and Tron assets. We'll use the Flutter framework and the Dart programming language to build a single application that can generate wallets and send transactions across these different blockchains.

The core concept enabling this is the use of a Hierarchical Deterministic (HD) Wallet. This allows a user to manage all their assets across multiple chains using a single recovery phrase, or seed.

Generating a Multi-Chain Wallet with HD Wallets

An HD Wallet is the most efficient method for managing a multi-chain wallet. From a single seed, you can deterministically generate unique key pairs for numerous blockchains, adhering to the BIP-44 standard. This standard defines a hierarchical structure for deriving keys, ensuring the same seed will always produce the same sequence of keys.

Each blockchain network is assigned a unique "coin type" identifier within this structure. For our purposes:

By using these identifiers in the derivation path, we can generate separate wallets for each chain from one seed.

Code Implementation for Wallet Creation

In your Flutter project, you will need to add the necessary dependencies. The code below demonstrates how to generate a mnemonic, create a seed, and derive individual wallet key pairs for each blockchain.

import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter_bitcoin/flutter_bitcoin.dart';

// Generate a new 12-word mnemonic phrase
final mnemonic = bip39.generateMnemonic(strength: 128);
// Convert the mnemonic into a seed for HD wallet generation
final seed = bip39.mnemonicToSeed(mnemonic);
final hdWallet = HDWallet.fromSeed(seed);

// Derive wallet key pairs for each blockchain using BIP-44 paths
btcWallet = hdWallet.derivePath("m/44'/0'/0'/0/0");
ethWallet = hdWallet.derivePath("m/44'/60'/0'/0/0");
tronWallet = hdWallet.derivePath("m/44'/195'/0'/0/0");

The variables btcWallet, ethWallet, and tronWallet now hold HDWallet objects containing the public and private keys for each respective chain.

Generating Public Addresses

A public key is not the same as a public address used for receiving funds. Each blockchain has a unique method for converting a public key into a human-readable address.

Bitcoin Address:
The flutter_bitcoin package provides a convenient address property.

final btcAddress = btcWallet.address;

Ethereum Address:
The web3dart package includes utilities for Ethereum-specific operations.

import 'package:web3dart/web3dart.dart';
final ethPriKey = EthPrivateKey.fromHex(ethWallet.privKey!);
final ethAddress = ethPriKey.address.hex;

Tron Address:
The wallet package can be used for Tron address generation.

import 'package:wallet/wallet.dart' as wallet;
final tronPrivateKey = wallet.PrivateKey(BigInt.parse(tronWallet.privKey!, radix: 16));
final tronPubKey = wallet.tron.createPublicKey(tronPrivateKey);
tronAddress = wallet.tron.createAddress(tronPubKey);

Address Format Differences:
The different cryptographic processes result in distinct address formats, which helps users identify the network:

Signing and Sending Transactions

The process of creating and sending transactions varies significantly between blockchains due to their different architectural models.

Ethereum Transactions (Account Model)

Ethereum uses an account-based model. Each account has a state (like its balance) and a nonce, which is a transaction counter to prevent replay attacks. To send ETH, you construct a transaction object, sign it, and broadcast it to the network.

// Initialize a connection to an Ethereum node (e.g., via Alchemy or Infura)
const alchemyApiKey = 'your_api_key_here';
final web3Client = Web3Client('https://eth-sepolia.g.alchemy.com/v2/${alchemyApiKey}', Client());

Future<String> signTransaction({
  required EthPrivateKey privateKey,
  required Transaction transaction,
}) async {
  try {
    final result = await web3Client.signTransaction(
      privateKey,
      transaction,
      chainId: 11155111, // Sepolia testnet ID
    );
    return HEX.encode(result);
  } catch (e) {
    rethrow;
  }
}

// Usage
final ethPriKey = EthPrivateKey.fromHex(ethWallet.privKey!);
final tx = await signTransaction(
  privateKey: ethPriKey,
  transaction: Transaction(
    from: ethPriKey.address,
    to: EthereumAddress.fromHex("0xE2Dc3214f7096a94077E71A3E218243E289F1067"),
    value: EtherAmount.fromBase10String(EtherUnit.gwei, "10000"),
  ),
);
// Send the signed transaction
final txHash = await web3Client.sendRawTransaction(Uint8List.fromList(HEX.decode(tx)));
print("Transaction Hash: $txHash");

Bitcoin Transactions (UTXO Model)

Bitcoin uses a Unspent Transaction Output (UTXO) model. Coins are represented as outputs from previous transactions. A new transaction spends these existing UTXOs as inputs and creates new UTXOs as outputs for the recipient and for change.

Signing a Bitcoin transaction requires knowing which UTXOs you can spend.

String sampleBitcoinTx(HDWallet btcWallet) {
  final txb = TransactionBuilder();
  txb.setVersion(1);
  // Add a specific UTXO to use as an input (txid and output index)
  txb.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0);
  // Add an output: recipient's address and amount in satoshis
  txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1jr5ZsP', 12000);
  // Sign the input with the private key
  txb.sign(vin: 0, keyPair: ECPair.fromWIF(btcWallet.wif!));
  // Build and return the raw transaction hex
  return txb.build().toHex();
}

👉 Explore more strategies for UTXO management

In a real application, you must first use a service like Blockchair or a Bitcoin node to query for unspent UTXOs belonging to your address before you can construct a valid transaction. The miner fee is the difference between the total value of the inputs and the total value of the outputs.

Tron Transactions

Tron's transaction model has its own unique characteristics and requires specific libraries for interaction. While Dart libraries are less common, the TronWeb JavaScript library is the standard for web integration. The process involves similar steps: constructing, signing, and broadcasting a transaction object with parameters specific to the Tron network.

Building the Complete Flutter Application

Combining these concepts allows you to build a functional application flow:

  1. Input/Generate Seed: The user enters an existing mnemonic or generates a new one.
  2. Derive Wallets: The app uses the seed to derive HD wallets for BTC, ETH, and TRX.
  3. Display Addresses: The public addresses for each wallet are calculated and displayed.
  4. Create Transactions: The user can input recipient addresses and amounts to create transactions.
  5. Sign and Broadcast: The app uses the private key to sign the transaction and broadcasts it to the appropriate network via an RPC provider.

This foundation allows you to create a basic multi-chain wallet. By simply changing the index in the derivation path (e.g., changing the last 0 to 1), you can generate a virtually unlimited number of addresses for each chain from the same seed.

Frequently Asked Questions

What is an HD Wallet?
An HD (Hierarchical Deterministic) Wallet is a system that allows a single seed to generate a tree-like structure of key pairs. This means you only need to back up one seed phrase to recover all the private keys and addresses ever generated by the wallet across multiple blockchains, making it incredibly secure and user-friendly.

Why do different blockchains have different address formats?
Different address formats arise from the unique cryptographic algorithms and encoding standards used by each blockchain. For example, Bitcoin and Tron use different hashing functions before applying Base58 encoding. These format differences are a safety feature that helps prevent users from accidentally sending funds to an address on the wrong network.

Can I use the same private key for Bitcoin and Ethereum?
Technically, you could import the same raw private key into both a Bitcoin and Ethereum wallet. However, the resulting public addresses would be completely different and incompatible due to the distinct address generation algorithms. It is a dangerous practice and not recommended. The proper way is to use an HD Wallet with standard BIP-44 paths to correctly derive separate keys for each network from a common seed.

What is the main difference between UTXO and Account models?
The UTXO model (Bitcoin) treats cryptocurrency as pieces of cash: you spend entire "pieces" (UTXOs) and get change back. The blockchain tracks these pieces. The Account model (Ethereum, Tron) treats addresses like bank accounts: the blockchain directly tracks the balance of each account, and transactions simply deduct from one balance and add to another.

How do I get testnet coins to experiment with?
Most blockchain testnets have "faucets" – websites that distribute free testnet coins for development purposes. You can search for "Bitcoin testnet faucet", "Ethereum Sepolia faucet", or "Tron Nile faucet", provide your testnet address, and receive a small amount of coins to use for testing transactions.

Is it safe to build a wallet with these code examples?
The code provided illustrates core concepts for learning and development. For a production-grade wallet, you must implement additional critical security features. These include secure encrypted storage for the seed and private keys, robust error handling, and thorough testing. Always prioritize user security and follow best practices for key management. 👉 Get advanced methods for securing private keys