Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide covers sending ETH transactions including transaction creation, signing, and sending with the Dynamic Flutter SDK using the web3dart package.Prerequisites
- Dynamic SDK initialized (see Installation Guide)
- User authenticated (see Authentication Guide)
- Wallets available (see Wallet Creation)
- Network configured (see Networks)
dynamic_sdk_web3dartpackage installed
Send ETH Transaction
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_web3dart/dynamic_sdk_web3dart.dart';
import 'package:web3dart/web3dart.dart';
final sdk = DynamicSDK.instance;
Future<String> sendTransaction({
required BaseWallet wallet,
required String recipientAddress,
required double amountInEth,
}) async {
// Convert ETH to Wei (1 ETH = 10^18 Wei)
final amountInWei = (amountInEth * BigInt.from(10).pow(18).toDouble()).toInt();
// Create transaction
final transaction = Transaction(
from: EthereumAddress.fromHex(wallet.address),
to: EthereumAddress.fromHex(recipientAddress),
value: EtherAmount.inWei(BigInt.from(amountInWei)),
);
// Send transaction
final txHash = await sdk.web3dart.sendTransaction(
transaction: transaction,
wallet: wallet,
);
print('Transaction sent!');
print('Hash: $txHash');
return txHash;
}
Complete Send Transaction Widget
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_web3dart/dynamic_sdk_web3dart.dart';
import 'package:web3dart/web3dart.dart';
class SendTransactionWidget extends StatefulWidget {
final BaseWallet wallet;
const SendTransactionWidget({Key? key, required this.wallet}) : super(key: key);
@override
State<SendTransactionWidget> createState() => _SendTransactionWidgetState();
}
class _SendTransactionWidgetState extends State<SendTransactionWidget> {
final sdk = DynamicSDK.instance;
final _recipientController = TextEditingController();
final _amountController = TextEditingController();
String? txHash;
bool isLoading = false;
String? errorMessage;
@override
void dispose() {
_recipientController.dispose();
_amountController.dispose();
super.dispose();
}
Future<void> _sendTransaction() async {
final recipient = _recipientController.text.trim();
final amount = _amountController.text.trim();
if (recipient.isEmpty || amount.isEmpty) {
setState(() => errorMessage = 'Please fill all fields');
return;
}
final amountDouble = double.tryParse(amount);
if (amountDouble == null) {
setState(() => errorMessage = 'Invalid amount');
return;
}
setState(() {
isLoading = true;
errorMessage = null;
txHash = null;
});
try {
// Convert ETH to Wei
final amountInWei = (amountDouble * BigInt.from(10).pow(18).toDouble()).toInt();
// Create transaction
final transaction = Transaction(
from: EthereumAddress.fromHex(widget.wallet.address),
to: EthereumAddress.fromHex(recipient),
value: EtherAmount.inWei(BigInt.from(amountInWei)),
);
// Send transaction
final hash = await sdk.web3dart.sendTransaction(
transaction: transaction,
wallet: widget.wallet,
);
setState(() => txHash = hash);
} catch (e) {
setState(() => errorMessage = 'Failed: $e');
} finally {
setState(() => isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
controller: _recipientController,
decoration: const InputDecoration(
labelText: 'Recipient address',
border: OutlineInputBorder(),
),
autocorrect: false,
enableSuggestions: false,
),
const SizedBox(height: 16),
TextField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount (ETH)',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: isLoading ? null : _sendTransaction,
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Send Transaction'),
),
if (txHash != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Transaction Sent!',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
const SizedBox(height: 8),
Text(
'Hash: $txHash',
style: const TextStyle(
fontSize: 12,
fontFamily: 'monospace',
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: txHash!));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Hash copied!')),
);
},
child: const Text('Copy Hash'),
),
],
),
),
],
if (errorMessage != null) ...[
const SizedBox(height: 16),
Text(
errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
);
}
}
Sign Transaction (Without Sending)
To sign a transaction without broadcasting it, usesignEthereumTransaction:
import 'package:dynamic_sdk/dynamic_sdk.dart';
final sdk = DynamicSDK.instance;
Future<void> signTransactionOnly(BaseWallet wallet) async {
try {
final signedTx = await sdk.wallets.signEthereumTransaction(
wallet: wallet,
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bDd7',
value: '0.001',
gasLimit: '21000',
maxPriorityFeePerGas: '2',
maxFeePerGas: '50',
);
print('Signed transaction: $signedTx');
} catch (e) {
print('Failed to sign: $e');
}
}
Best Practices
1. Validate Input
String? validateAddress(String address) {
if (address.isEmpty) {
return 'Address is required';
}
if (!address.startsWith('0x') || address.length != 42) {
return 'Invalid Ethereum address';
}
return null;
}
String? validateAmount(String amount) {
if (amount.isEmpty) {
return 'Amount is required';
}
final value = double.tryParse(amount);
if (value == null || value <= 0) {
return 'Invalid amount';
}
return null;
}
2. Handle Transaction Errors
Future<String?> sendTransactionWithErrorHandling({
required BaseWallet wallet,
required String recipient,
required double amount,
}) async {
try {
final amountInWei = (amount * BigInt.from(10).pow(18).toDouble()).toInt();
final transaction = Transaction(
from: EthereumAddress.fromHex(wallet.address),
to: EthereumAddress.fromHex(recipient),
value: EtherAmount.inWei(BigInt.from(amountInWei)),
);
final txHash = await DynamicSDK.instance.web3dart.sendTransaction(
transaction: transaction,
wallet: wallet,
);
return txHash;
} catch (e) {
final errorStr = e.toString().toLowerCase();
if (errorStr.contains('insufficient')) {
throw Exception('Insufficient funds for transaction');
} else if (errorStr.contains('gas')) {
throw Exception('Gas estimation failed. Try increasing gas limit.');
} else if (errorStr.contains('rejected') || errorStr.contains('denied')) {
throw Exception('Transaction was rejected');
} else if (errorStr.contains('network')) {
throw Exception('Network error. Please check your connection.');
} else {
throw Exception('Transaction failed. Please try again.');
}
}
}
3. Show Transaction Status
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class TransactionStatusWidget extends StatelessWidget {
final String txHash;
final int chainId;
const TransactionStatusWidget({
Key? key,
required this.txHash,
required this.chainId,
}) : super(key: key);
String get explorerUrl {
// Map chain ID to block explorer
switch (chainId) {
case 1:
return 'https://etherscan.io/tx/$txHash';
case 84532:
return 'https://sepolia.basescan.org/tx/$txHash';
case 137:
return 'https://polygonscan.com/tx/$txHash';
default:
return '';
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text(
'Transaction Submitted',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
txHash,
style: const TextStyle(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (explorerUrl.isNotEmpty) ...[
const SizedBox(height: 8),
TextButton(
onPressed: () async {
final uri = Uri.parse(explorerUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
},
child: const Text('View on Explorer'),
),
],
],
);
}
}
4. Confirm Before Sending
import 'package:flutter/material.dart';
class ConfirmTransactionDialog extends StatelessWidget {
final String recipient;
final String amount;
final VoidCallback onConfirm;
const ConfirmTransactionDialog({
Key? key,
required this.recipient,
required this.amount,
required this.onConfirm,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Confirm Transaction'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('To: $recipient'),
const SizedBox(height: 8),
Text('Amount: $amount ETH'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
onConfirm();
},
child: const Text('Confirm'),
),
],
);
}
}
Error Handling
Common Transaction Errors
String getErrorMessage(dynamic error) {
final errorStr = error.toString().toLowerCase();
if (errorStr.contains('insufficient')) {
return 'Insufficient balance for this transaction';
} else if (errorStr.contains('gas')) {
return 'Gas estimation failed. Try increasing gas limit.';
} else if (errorStr.contains('rejected') || errorStr.contains('denied')) {
return 'Transaction was rejected';
} else if (errorStr.contains('network')) {
return 'Network error. Please check your connection.';
} else {
return 'Transaction failed. Please try again.';
}
}
// Usage
try {
await sendTransaction(
wallet: wallet,
recipientAddress: recipient,
amountInEth: amount,
);
} catch (e) {
final message = getErrorMessage(e);
print(message);
}
Next Steps
- ERC-20 Token Transfers - Send ERC-20 tokens
- Smart Contract Interactions - Interact with smart contracts
- Gas Management - Gas estimation and optimization
- Message Signing - Sign messages and typed data