Overview
This guide covers how to interact with smart contracts on EVM chains, including reading contract state and executing contract functions using the web3dart package.Prerequisites
- Dynamic SDK initialized (see Installation Guide)
- User authenticated (see Authentication Guide)
- EVM wallet available (see Wallet Creation)
- Contract ABI and address
dynamic_sdk_web3dartpackage installed
Write to Contract
Execute state-changing functions on a smart contract:Copy
Ask AI
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> writeContract({
required BaseWallet wallet,
required String contractAddress,
required String abiJson,
required String functionName,
required List<dynamic> parameters,
}) async {
// Get network information
final network = await sdk.wallets.getNetwork(wallet: wallet);
final chainId = network.intValue()!;
// Create public client
final client = sdk.web3dart.createPublicClient(chainId: chainId);
// Get gas price
final gasPrice = await client.getGasPrice();
// Create contract
final contract = DeployedContract(
ContractAbi.fromJson(abiJson, 'Contract'),
EthereumAddress.fromHex(contractAddress),
);
final function = contract.function(functionName);
// Create transaction
final transaction = Transaction.callContract(
contract: contract,
function: function,
parameters: parameters,
maxFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2),
),
maxPriorityFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei),
),
);
// Send transaction
final txHash = await sdk.web3dart.sendTransaction(
transaction: transaction,
wallet: wallet,
);
print('Contract interaction successful!');
print('Hash: $txHash');
return txHash;
}
Read Contract Data
Query contract state without sending a transaction:Copy
Ask AI
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<List<dynamic>> readContract({
required int chainId,
required String contractAddress,
required String abiJson,
required String functionName,
List<dynamic> parameters = const [],
}) async {
final client = sdk.web3dart.createPublicClient(chainId: chainId);
final contract = DeployedContract(
ContractAbi.fromJson(abiJson, 'Contract'),
EthereumAddress.fromHex(contractAddress),
);
final function = contract.function(functionName);
final result = await client.call(
contract: contract,
function: function,
params: parameters,
);
print('Contract value: $result');
return result;
}
Complete Contract Interaction Widget
Copy
Ask AI
import 'package:flutter/material.dart';
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_web3dart/dynamic_sdk_web3dart.dart';
import 'package:web3dart/web3dart.dart';
class ContractInteractionWidget extends StatefulWidget {
final BaseWallet wallet;
final String contractAddress;
final int chainId;
const ContractInteractionWidget({
Key? key,
required this.wallet,
required this.contractAddress,
required this.chainId,
}) : super(key: key);
@override
State<ContractInteractionWidget> createState() => _ContractInteractionWidgetState();
}
class _ContractInteractionWidgetState extends State<ContractInteractionWidget> {
final sdk = DynamicSDK.instance;
final _inputValueController = TextEditingController();
String? currentValue;
String? txHash;
bool isLoading = false;
String? error;
// Example ABI for a simple storage contract
final contractAbi = '''[
{
"constant": false,
"inputs": [{"name": "value", "type": "uint256"}],
"name": "setValue",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getValue",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]''';
@override
void initState() {
super.initState();
_readValue();
}
@override
void dispose() {
_inputValueController.dispose();
super.dispose();
}
Future<void> _readValue() async {
try {
final client = sdk.web3dart.createPublicClient(chainId: widget.chainId);
final contract = DeployedContract(
ContractAbi.fromJson(contractAbi, 'Storage'),
EthereumAddress.fromHex(widget.contractAddress),
);
final function = contract.function('getValue');
final result = await client.call(
contract: contract,
function: function,
params: [],
);
setState(() => currentValue = result.first.toString());
} catch (e) {
setState(() => error = 'Failed to read: $e');
}
}
Future<void> _setValue() async {
setState(() {
isLoading = true;
error = null;
txHash = null;
});
try {
final value = _inputValueController.text.trim();
if (value.isEmpty) {
throw Exception('Value is required');
}
// Get gas price
final client = sdk.web3dart.createPublicClient(chainId: widget.chainId);
final gasPrice = await client.getGasPrice();
// Create contract
final contract = DeployedContract(
ContractAbi.fromJson(contractAbi, 'Storage'),
EthereumAddress.fromHex(widget.contractAddress),
);
final function = contract.function('setValue');
// Create transaction
final transaction = Transaction.callContract(
contract: contract,
function: function,
parameters: [BigInt.parse(value)],
maxFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2),
),
maxPriorityFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei),
),
);
// Send transaction
final hash = await sdk.web3dart.sendTransaction(
transaction: transaction,
wallet: widget.wallet,
);
setState(() => txHash = hash);
// Refresh the value after transaction
await Future.delayed(const Duration(seconds: 2));
await _readValue();
} catch (e) {
setState(() => error = 'Failed to set value: $e');
} finally {
setState(() => isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Read section
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Read Contract',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
if (currentValue != null)
Row(
children: [
const Text('Current Value: '),
Text(
currentValue!,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _readValue,
child: const Text('Refresh Value'),
),
],
),
),
const SizedBox(height: 24),
const Divider(),
const SizedBox(height: 24),
// Write section
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Write to Contract',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextField(
controller: _inputValueController,
decoration: const InputDecoration(
labelText: 'New value',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: isLoading ? null : _setValue,
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Set Value'),
),
if (txHash != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Transaction sent!',
style: TextStyle(color: Colors.green),
),
Text(
'Hash: $txHash',
style: const TextStyle(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
if (error != null) ...[
const SizedBox(height: 8),
Text(
error!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
),
],
),
);
}
}
Working with Contract ABIs
Defining ABIs
ABIs (Application Binary Interfaces) define how to interact with smart contracts:Copy
Ask AI
// Simple storage contract ABI
const storageAbi = '''[
{
"constant": false,
"inputs": [{"name": "value", "type": "uint256"}],
"name": "store",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "retrieve",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]''';
// ERC-721 NFT contract functions
const nftAbi = '''[
{
"constant": false,
"inputs": [
{"name": "to", "type": "address"},
{"name": "tokenId", "type": "uint256"}
],
"name": "mint",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [{"name": "owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]''';
Advanced Contract Interactions
NFT Minting
Copy
Ask AI
Future<String> mintNFT({
required BaseWallet wallet,
required String nftContractAddress,
required String recipient,
required String tokenId,
required int chainId,
}) async {
const nftAbi = '''[
{
"constant": false,
"inputs": [
{"name": "to", "type": "address"},
{"name": "tokenId", "type": "uint256"}
],
"name": "mint",
"outputs": [],
"type": "function"
}
]''';
final client = DynamicSDK.instance.web3dart.createPublicClient(chainId: chainId);
final gasPrice = await client.getGasPrice();
final contract = DeployedContract(
ContractAbi.fromJson(nftAbi, 'NFT'),
EthereumAddress.fromHex(nftContractAddress),
);
final mintFunction = contract.function('mint');
final transaction = Transaction.callContract(
contract: contract,
function: mintFunction,
parameters: [
EthereumAddress.fromHex(recipient),
BigInt.parse(tokenId),
],
maxFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2),
),
maxPriorityFeePerGas: EtherAmount.inWei(
gasPrice.getValueInUnitBI(EtherUnit.wei),
),
);
return await DynamicSDK.instance.web3dart.sendTransaction(
transaction: transaction,
wallet: wallet,
);
}
Check Token Allowance
Copy
Ask AI
Future<BigInt> checkAllowance({
required String tokenAddress,
required String ownerAddress,
required String spenderAddress,
required int chainId,
}) async {
const erc20Abi = '''[
{
"constant": true,
"inputs": [
{"name": "_owner", "type": "address"},
{"name": "_spender", "type": "address"}
],
"name": "allowance",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]''';
final client = DynamicSDK.instance.web3dart.createPublicClient(chainId: chainId);
final contract = DeployedContract(
ContractAbi.fromJson(erc20Abi, 'ERC20'),
EthereumAddress.fromHex(tokenAddress),
);
final allowanceFunction = contract.function('allowance');
final result = await client.call(
contract: contract,
function: allowanceFunction,
params: [
EthereumAddress.fromHex(ownerAddress),
EthereumAddress.fromHex(spenderAddress),
],
);
return result.first as BigInt;
}
Best Practices
1. Validate Contract Addresses
Copy
Ask AI
bool isValidEthereumAddress(String address) {
final pattern = RegExp(r'^0x[a-fA-F0-9]{40}$');
return pattern.hasMatch(address);
}
// Usage
if (!isValidEthereumAddress(contractAddress)) {
throw Exception('Invalid contract address');
}
2. Handle Contract Errors
Copy
Ask AI
Future<String?> callContractSafely({
required BaseWallet wallet,
required String contractAddress,
required String abiJson,
required String functionName,
required List<dynamic> parameters,
}) async {
try {
return await writeContract(
wallet: wallet,
contractAddress: contractAddress,
abiJson: abiJson,
functionName: functionName,
parameters: parameters,
);
} catch (e) {
final errorDesc = e.toString().toLowerCase();
if (errorDesc.contains('revert')) {
print('Contract reverted. Check contract requirements.');
} else if (errorDesc.contains('gas')) {
print('Out of gas. Increase gas limit.');
} else {
print('Contract call failed: $e');
}
return null;
}
}
3. Use Try-Catch for Read Operations
Copy
Ask AI
Future<String?> safeReadContract({
required int chainId,
required String contractAddress,
required String abiJson,
required String functionName,
}) async {
try {
final result = await readContract(
chainId: chainId,
contractAddress: contractAddress,
abiJson: abiJson,
functionName: functionName,
);
return result.first.toString();
} catch (e) {
print('Failed to read contract: $e');
return null;
}
}
Next Steps
- Send ETH Transactions - Basic ETH transfers
- ERC-20 Token Transfers - Send tokens
- Gas Management - Optimize gas costs
- Message Signing - Sign messages for verification