web3dart

repository·main·Indexed 41 days ago

https://github.com/xclud/web3dart

A Dart library for interacting with Ethereum and Celo-compatible blockchain nodes via JSON-RPC. It provides high-level abstractions for managing accounts, sending transactions, calling smart contracts, and listening to blockchain events. Includes support for V3 wallet files, ABI parsing, and a code generation package called web3dart_builders.

Tokens
10.2K
Snippets
40
Records
50
Agent score
51%

What's inside web3dart

  1. Interact with Smart Contracts

    main
    The library allows you to parse a smart contract's ABI to send data to it and listen for emitted events. For a full implementation example, refer to the contracts.dart example file in the repository. Code generation (see 'Generate Dart code from Smart Contract ABIs') is the recommended way to handle these interactions easily.
  2. Manage Credentials and Wallets

    main

    To send transactions, you need Credentials. The library supports creating them from hex private keys, generating them randomly, or using V3 wallet files.

    From Hex Private Key

    Use EthPrivateKey.fromHex() to create credentials from a hex string.

    Random Generation

    Use EthPrivateKey.createRandom(rng) with a secure random number generator to create a new identity.

    Using V3 Wallet Files

    Load a JSON wallet file using Wallet.fromJson(content, "password"). This returns a Wallet object from which you can extract the privateKey as Credentials.

    Creating Wallet Files

    Encrypt a private key into a V3 wallet using Wallet.createNew(credentials, "password", random). The resulting JSON can be exported via wallet.toJson() and is compatible with tools like MyEtherWallet or geth.

    import 'dart:math';
    import 'package:web3dart/web3dart.dart';
    
    // From hex
    Credentials fromHex = EthPrivateKey.fromHex("c87509a[...]dc0d3");
    
    // Randomly
    var rng = Random.secure();
    Credentials random = EthPrivateKey.createRandom(rng);
    
    // From V3 Wallet file
    String content = File("wallet.json").readAsStringSync();
    Wallet wallet = Wallet.fromJson(content, "testpassword");
    Credentials unlocked = wallet.privateKey;
    
    // Create new wallet
    Wallet newWallet = Wallet.createNew(credentials, "password", rng);
    print(newWallet.toJson());
  3. Connect to an RPC server using Web3Client

    main

    The library interacts with Ethereum or Celo nodes via an RPC endpoint. You must provide an RPC URL (e.g., from Infura, Geth, or Ganache) and an http.Client to initialize a Web3Client.

    Once connected, you can call common RPC methods like getBalance to query the network.

    import 'package:http/http.dart';
    import 'package:web3dart/web3dart.dart';
    
    var apiUrl = "http://localhost:7545"; // Your RPC endpoint
    var httpClient = Client();
    var ethClient = Web3Client(apiUrl, httpClient);
    
    var credentials = EthPrivateKey.fromHex("0x...");
    var address = credentials.address;
    
    // Query balance
    EtherAmount balance = await ethClient.getBalance(address);
    print(balance.getValueInUnit(EtherUnit.ether));
  4. Generate Dart code from Smart Contract ABIs

    main

    You can automate the creation of Dart classes for interacting with smart contracts using the web3dart_builders package and Dart's build system.

    Setup

    1. Add the builder to your dev dependencies:
      dart pub add web3dart_builders --dev
    2. Place your contract ABI JSON files in your lib/ directory. The filename must end with .abi.json (e.g., my_contract.abi.json).
    3. Run the build runner:
      dart run build_runner build

    This generates a .g.dart file containing the code needed to call contract functions and listen to events.

    Suppressing Analyzer Warnings

    If your ABI function names violate Dart naming conventions, the analyzer will show warnings in the generated files. To ignore these, create an analysis_options.yaml file in your project root:

    analyzer:
      exclude:
        - '**/*.g.dart'
    # Install builder
    dart pub add web3dart_builders --dev
    
    # Run generation
    dart run build_runner build
  5. Handle EIP-1559 and Celo transactions

    main

    The Transaction class provides helper properties to identify the transaction type:

    • isEIP1559: Returns true if maxFeePerGas or maxPriorityFeePerGas is set.
    • isCeloTx: Returns true if the transaction is an EIP-1559 type and a feeCurrency is provided (specific to the Celo network).
  6. Implement custom transaction sending

    main

    If your application does not hold the private key locally (for example, if signing happens in a hardware wallet or a browser extension), you can implement the CustomTransactionSender interface. This allows you to integrate external signing logic into the web3dart workflow by providing a custom implementation of sendTransaction.

    class MyHardwareWalletSender extends CustomTransactionSender {
      @override
      Future<String> sendTransaction(Transaction transaction) async {
        // Implement logic to send the transaction to a hardware wallet
        // and return the transaction hash.
      }
    }
  7. Understand Contract Function Types

    main

    The ContractFunctionType enum categorizes the role of a function within a contract:

    • function: A standard contract function.
    • constructor: A function called during contract deployment.
    • fallback: The default function called when no other function matches the request.

    Helper Properties on ContractFunction:

    • isConstructor: Returns true if the type is constructor.
    • isDefault: Returns true if the type is fallback.
  8. Understand Contract Function Mutability

    main

    The StateMutability enum defines how a contract function interacts with the blockchain. This determines if a function can be called locally (for free) or requires a transaction (with gas costs).

    MutabilityDescription
    pureOutput depends solely on input; does not read from the blockchain.
    viewReads from the blockchain but does not write to it.
    nonPayableMay write to the blockchain but does not accept Ether.
    payableMay write to the blockchain and accepts Ether.

    Helper Properties on ContractFunction:

    • isConstant: Returns true if the function is pure or view (can be called locally without gas).
    • isPayable: Returns true if the function is payable (can receive Ether).
  9. Initialize a Web3Client

    main

    The Web3Client is the primary entry point for interacting with Ethereum/Celo nodes via JSON-RPC. You can initialize it using an HTTP URL and an HTTP client. For real-time features like event listening, you can provide a socketConnector to enable WebSocket support.

    import "package:web3dart/web3dart.dart";
    import "package:http/http.dart";
    import "package:web_socket_channel/io.dart";
    
    // Standard HTTP initialization
    final client = Web3Client(rpcUrl, Client());
    
    // Initialization with WebSocket support for events
    final wsClient = Web3Client(rpcUrl, Client(), socketConnector: () {
       return IOWebSocketChannel.connect(wsUrl).cast<String>();
    });
  10. Send Ethereum transactions

    main

    To send a transaction, use Web3Client.sendTransaction. You must provide the Credentials and a Transaction object.

    Required/Common fields in Transaction:

    • to: The destination EthereumAddress.
    • gasPrice: The price per unit of gas (EtherAmount).
    • maxGas: The maximum gas limit.
    • value: The amount of Ether to send (EtherAmount).

    If fields like gasPrice, the sender, or the nonce are omitted, the library will attempt to fetch them from the connected node. If you only need to sign a transaction without broadcasting it, use client.signTransaction instead.

    import 'package:web3dart/web3dart.dart';
    
    // ethClient initialized via Web3Client(apiUrl, httpClient)
    var credentials = EthPrivateKey.fromHex("0x...");
    
    await ethClient.sendTransaction(
      credentials,
      Transaction(
        to: EthereumAddress.fromHex('0xC91...3706'),
        gasPrice: EtherAmount.inWei(BigInt.one),
        maxGas: 100000,
        value: EtherAmount.fromUnitAndValue(EtherUnit.ether, 1),
      ),
    );
  11. Configure event filters with FilterOptions

    main

    When using Web3Client.events, use FilterOptions to define the scope of the logs you want to monitor. You can filter by block range, contract address, and event topics.

    Key Properties

    • fromBlock: The earliest block to consider. Use BlockNum.current for the last mined block or BlockNum.pending for unmined transactions.
    • toBlock: The last block to consider. Defaults to BlockNum.current.
    • address: Limits the filter to a specific EthereumAddress. If null, all logs are reported.
    • topics: A list of topic filters. Topics must be hex-encoded strings prefixed with 0x and have an even number of digits.

    Topic Matching Logic

    Topics are order-dependent:

    • []: Matches everything.
    • [A]: Matches A in the first position.
    • [null, B]: Matches anything in the first position and B in the second.
    • [A, B]: Matches A in the first and B in the second.
    • [[A, B], [A, B]]: Matches (A or B) in the first position AND (A or B) in the second position.
    // Example: Creating options for a specific contract event
    final options = FilterOptions.events(
      contract: myDeployedContract,
      event: myContractEvent,
      fromBlock: BlockNum.current,
    );
  12. Serialize a transaction for signing with getUnsignedSerialized

    main

    To sign a transaction, you must first obtain its unsigned serialized pre-image using getUnsignedSerialized({int? chainId}). This method handles different transaction types:

    • Legacy Transactions: Uses RLP encoding.
    • EIP-1559 Transactions: Encodes with the appropriate type prefix.
    • Celo Transactions: If isCeloTx is true, it uses the Celo-specific type 123 (prefix 0x7b) and specialized encoding.

    Note: You should provide the chainId to ensure the pre-image is correctly bound to the network.

    // Example of getting the pre-image for signing
    Uint8List unsignedTx = transaction.getUnsignedSerialized(chainId: 1);