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 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: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: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
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:// 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
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
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
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
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
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