Facing --- flutter: RPCError: got code -32700 with msg "Invalid signature v value"

432 views Asked by At

I am a beginner to Flutter and Web3. I am trying to call a setter method on a button press and sending this transaction over a local blockchain network (Ganache). I am using Truffle for managing and deploying smart contracts.

My truffle-config.js file:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 7545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
    }
  },

  // Set default mocha options here, use special reporters, etc.
  mocha: {
    // timeout: 100000
  },
  contracts_build_directory: "./contracts",
  //Configure your compilers
  compilers: {
    solc: {
      version: "0.8.19",
      settings: {
        optimizer: {
          enabled: false,
          runs: 200
        },
      }
    }
  },
  db: {
    enabled: false
  }
};

My Smart Contract - MyContract.sol:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract MyContract {
    string public sentence = "Default Sentence Value";

    function set(string memory x) public {
        sentence = x;
    }

    function get() public view returns (string memory) {
        return sentence;
    }
}

My main.dart file (Flutter Bootstrap Starting Point):

import 'package:flutter/material.dart';
import 'package:flutter_app/contract_provider.dart';
import 'package:flutter_app/my_app.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ContractProvider(context),
      child: MaterialApp(
        title: 'Flutter Practice App',
        theme: ThemeData(
          useMaterial3: true,
        ),
        home: const HomeScreen(title: 'HomeScreen Title'),
      ),
    );
  }
}

My my_app.dart file (The flutter UI concerning the button and the text fields):

import 'package:flutter/material.dart';
import 'package:flutter_app/contract_provider.dart';
import 'package:provider/provider.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key, required this.title});

  final String title;

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    TextEditingController inputController = TextEditingController();
    final contractProvider = Provider.of<ContractProvider>(context);
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Consumer<ContractProvider>(builder: (context, model, child) {
          return Center(
              child: model.isLoading
                  ? const CircularProgressIndicator()
                  : Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                            'Current value of the sentence: ${model.sentence.toString()}'),
                        const SizedBox(
                          height: 30,
                        ),
                        SizedBox(
                          width: 200,
                          child: TextFormField(
                            controller: inputController,
                            keyboardType: TextInputType.text,
                            decoration: const InputDecoration(
                                labelText: 'Please set a sentence!'),
                          ),
                        ),
                        const SizedBox(
                          height: 30,
                        ),
                        ElevatedButton(
                            onPressed: () async {
                              try {
                                // Here we call our setSentence() function of the smart contract
                                await contractProvider.setSentence(
                                    inputController.text.toString());
                              } catch (e) {
                                print(e);
                              } finally {
                                // Clear the text field eventually.
                                inputController.clear();
                              }
                            },
                            child: const Text('Set'))
                      ],
                    ));
        }));
  }
}

My contract_provider.dart file (the file that binds the ABI and Flutter App) :

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
import 'package:web_socket_channel/io.dart';

class ContractProvider extends ChangeNotifier {
  static const String contract_name = "MyContract";
  static const String ganache_host = "127.0.0.1";
  static const String ganache_port = "7545";

  final String _rpcURL = "http://$ganache_host:$ganache_port";
  final String _wsURL = "wss://$ganache_host:$ganache_port";
  final String _privateKey =
      "0x67e633f41624a1d742d753fe52bcdfe96b54fe3d91a87ce5260101605fced7fb";

  late Web3Client _client;
  late Credentials _credentials;
  late DeployedContract _deployedContract;
  late ContractFunction _getSentence, _setSentence;
  String? sentence;
  bool isLoading = true;

  ContractProvider(context) {
    initialize(context);
  }

  initialize(context) async {
    // 1. Initialize connection to Ganache Service.
    _client = Web3Client(_rpcURL, Client(), socketConnector: () {
      return IOWebSocketChannel.connect(_wsURL).cast<String>();
    });

    // 2. Get ABI section of the json (compiled contract json).
    final abiStringFile = await DefaultAssetBundle.of(context)
        .loadString('build/contracts/MyContract.json');
    final abiJson = jsonDecode(abiStringFile);
    final abi = jsonEncode(abiJson["abi"]);

    // 3. Get Contract address from the json (compiled contract json)
    final contractAddress =
        EthereumAddress.fromHex(abiJson["networks"]["5777"]["address"]);

    // 4. Credentials to access the Ganache account in order to carry out transactions.
    _credentials = EthPrivateKey.fromHex(_privateKey);

    // 5. Initialize the final deployed contract and activate the connection.
    _deployedContract = DeployedContract(
        ContractAbi.fromJson(abi, contract_name), contractAddress);

    // 6. Also, give life to the functions exposed by the ABI json (compiled contract json)
    _getSentence = _deployedContract.function("get");
    _setSentence = _deployedContract.function("set");

    getSentence();
  }

  Future<void> getSentence() async {
    final result = await _client
        .call(contract: _deployedContract, function: _getSentence, params: []);
    sentence = result[0];
    isLoading = false;
    notifyListeners();
  }

  Future<void> setSentence(String value) async {
    isLoading = true;
    notifyListeners();
    await _client.sendTransaction(
        _credentials,
        Transaction.callContract(
            contract: _deployedContract,
            function: _setSentence,
            parameters: [value]));

    // Calling getSentence() just to test if the `value` is set properly.
    getSentence();
  }
}

Error:
enter image description here

2

There are 2 answers

0
mr-possible On BEST ANSWER

UPDATE:

I managed to find a fix for this.

  1. I added chainId and fetchChainIdFromNetworkId while using the sendTransaction method.

Updated contract_provider.dart file:

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';

class ContractProvider extends ChangeNotifier {
  static const String contractName = "MyContract";

  final String _rpcURL =
      Platform.isAndroid ? 'http://10.0.2.2:7545' : 'http://127.0.0.1:7545';
  final String _wsURL = 'ws://127.0.0.1:7545';

  final String _privateKey =
      "0x7f66624f37e1ca2946fe20982f0912163b7a71e8588bd2f4bdcc39c732ab1e10";

  late Web3Client _client;
  late Credentials _credentials;
  late DeployedContract _deployedContract;
  late ContractFunction _getSentence, _setSentence;
  String? sentence;
  bool isLoading = true;

  ContractProvider(context) {
    initialize(context);
  }

  initialize(context) async {
    // 1. Initialize connection to Ganache Service.
    _client = Web3Client(_rpcURL, Client());

    // 2. Get ABI section of the json (compiled contract json).
    final abiStringFile = await DefaultAssetBundle.of(context)
        .loadString('truffle-artificats/MyContract.json');
    final abiJson = jsonDecode(abiStringFile);
    final abi = jsonEncode(abiJson["abi"]);

    // 3. Get Contract address from the json (compiled contract json)
    final contractAddress =
        EthereumAddress.fromHex(abiJson["networks"]["5777"]["address"]);

    // 4. Credentials to access the Ganache account in order to carry out transactions.
    _credentials = EthPrivateKey.fromHex(_privateKey);

    // 5. Initialize the final deployed contract and activate the connection.
    _deployedContract = DeployedContract(
        ContractAbi.fromJson(abi, contractName), contractAddress);

    // 6. Also, give life to the functions exposed by the ABI json (compiled contract json)
    _getSentence = _deployedContract.function("get");
    _setSentence = _deployedContract.function("set");

    getSentence();
  }

  Future<void> getSentence() async {
    final result = await _client
        .call(contract: _deployedContract, function: _getSentence, params: []);
    sentence = result[0];
    isLoading = false;
    notifyListeners();
  }

  Future<void> setSentence(String value) async {
    isLoading = true;
    notifyListeners();
    await _client.sendTransaction(
        _credentials,
        Transaction.callContract(
            contract: _deployedContract,
            function: _setSentence,
            parameters: [value]),
        chainId: 1337,
        fetchChainIdFromNetworkId: false);

    // Calling getSentence() just to test if the `value` is set properly.
    getSentence();
  }
}
  1. I also changed the dependency version of web3dart to the latest one (2.7.0):
name: flutter_app
description: A new Flutter project.
publish_to: 'none'
version: 0.1.0

environment:
  sdk: '>=3.0.5 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  http: 1.1.0
  provider: 6.0.5
  web3dart: 2.7.0
  web_socket_channel: 2.4.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
  assets:
    - truffle-artificats/MyContract.json
0
Emmanuelson Zico On

All you need to do is add the chainId chainId:1337 to sendTransaction because the fetchChainIdFromNetworkId is already set to false by default.