WalletConnect / Web3ModalFlutter

The Web3Modal for WalletConnect built using Flutter.
https://pub.dev/packages/web3modal_flutter
Apache License 2.0
27 stars 30 forks source link

Error: Null check operator used on a null value in requestWriteContract() #107

Closed Kolovrat19 closed 1 month ago

Kolovrat19 commented 1 month ago

When we call

   await _w3mService.requestWriteContract(
        topic: _w3mService.session?.topic ?? '',
        chainId: 'eip155:11155111',
        rpcUrl: _sepolia.rpcUrl,
        deployedContract: deployedContract,
        functionName: 'setGreet',
        transaction: Transaction(),
        parameters: [str]);
    get error _Null check operator used on a null value_

Web3ModalFlutter version 3.1.1-beta02

quetool commented 1 month ago

Hello @Kolovrat19, please move to version 3.1.2 or 3.2.0-beta01. Can you share a complete reproducible code? Hard to say with this information, I don't know what setGreet requires, which contract are we talking about, what parameters or transaction are you sending , etc... Or where exactly the code is crashing.

Kolovrat19 commented 1 month ago

Smart contract

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract HelloWorld {
    string public greet = "Hello,  World!";

    function setGreet(string memory _greet) public {
        greet = _greet;
    }

    function getGreet() public view returns (string memory) {
        return greet;
    }
}

Flutter

import 'dart:convert';
import 'dart:developer';

import 'package:celebrity_coin/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
import 'package:web3modal_flutter/widgets/buttons/address_button.dart';

const abiDirectory = 'lib/services/celo.abi.json';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Super coin',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late W3MService _w3mService;
  String contractAddress = '0xA36be7934646fbF045247fCE6DA3833Bdec3D1a1';

  final _sepolia = W3MChainInfo(
    chainName: 'Sepolia Testnet',
    chainId: '11155111',
    namespace: 'eip155:11155111',
    tokenName: 'SepoliaETH',
    rpcUrl: 'https://ethereum-sepolia.publicnode.com',
    blockExplorer: W3MBlockExplorer(
      name: 'Sepolia Etherscan',
      url: 'https://sepolia.etherscan.io/',
    ),
  );

  @override
  void initState() {
    _initWalletService();
    super.initState();
  }

  void _initWalletService() async {
    W3MChainPresets.chains.putIfAbsent(_sepolia.chainId, () => _sepolia);
    _w3mService = W3MService(
      projectId: '4be72d26aa49c5aec529c6109cd28938',
      metadata: const PairingMetadata(
        name: 'Web3Modal Flutter Example',
        description: 'Web3Modal Flutter Example',
        url: 'https://www.walletconnect.com/',
        icons: ['https://walletconnect.com/walletconnect-logo.png'],
        redirect: Redirect(
          native: 'w3m://', // your own custom scheme
          universal: 'https://www.walletconnect.com',
        ),
      ),
    );
    await _w3mService.init();
  }

  Future<dynamic> _getGreet() async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final deployedContract = DeployedContract(
      ContractAbi.fromJson(
        contractABI,
        'MusicShop',
      ),
      EthereumAddress.fromHex(contractAddress),
    );

    final res = await _w3mService.requestReadContract(
      deployedContract: deployedContract,
      functionName: 'getGreet',
      rpcUrl: _sepolia.rpcUrl,
    );

    log(res.toString());
    return res;
  }

  Future<void> _setGreet(String str) async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final deployedContract = DeployedContract(
      ContractAbi.fromJson(
        contractABI,
        'MusicShop',
      ),
      EthereumAddress.fromHex(contractAddress),
    );

    await _w3mService.requestWriteContract(
        topic: _w3mService.session?.topic ?? '',
        chainId: 'eip155:11155111',
        rpcUrl: _sepolia.rpcUrl,
        deployedContract: deployedContract,
        functionName: 'setGreet',
        transaction: Transaction(),
        parameters: [str]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      // body: const HomeScreen()
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(onPressed: _getGreet, child: const Text('Get Hello')),
            TextButton(
                onPressed: () async {
                  await _setGreet("New Hello");
                },
                child: const Text('Set Hello')),
          ],
        ),
      ),
    );
  }
}
Kolovrat19 commented 1 month ago

getGreet work good.

quetool commented 1 month ago

Hello @Kolovrat19, I would need the ABI object as well

Kolovrat19 commented 1 month ago

celo.abi.json

ABI have getHello and setHello plus new methods

quetool commented 1 month ago

Hello @Kolovrat19, I modified a little your code, I was able to call the write function properly

import 'dart:developer';

// import 'package:celebrity_coin/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';

const abiDirectory = 'lib/services/celo.abi.json';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Super coin',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late W3MService _w3mService;
  String contractAddress = '0xA36be7934646fbF045247fCE6DA3833Bdec3D1a1';

  final _sepolia = W3MChainInfo(
    chainName: 'Sepolia Testnet',
    chainId: '11155111',
    namespace: 'eip155:11155111',
    tokenName: 'SepoliaETH',
    rpcUrl: 'https://ethereum-sepolia.publicnode.com',
    blockExplorer: W3MBlockExplorer(
      name: 'Sepolia Etherscan',
      url: 'https://sepolia.etherscan.io/',
    ),
  );

  @override
  void initState() {
    _initWalletService();
    super.initState();
  }

  void _initWalletService() async {
    W3MChainPresets.chains.putIfAbsent(_sepolia.chainId, () => _sepolia);
    _w3mService = W3MService(
      projectId: '4be72d26aa49c5aec529c6109cd28938',
      metadata: const PairingMetadata(
        name: 'Web3Modal Flutter Example',
        description: 'Web3Modal Flutter Example',
        url: 'https://www.walletconnect.com/',
        icons: ['https://walletconnect.com/walletconnect-logo.png'],
        redirect: Redirect(
          native: 'w3m://', // your own custom scheme
          universal: 'https://www.walletconnect.com',
        ),
      ),
    );
    await _w3mService.init();
  }

  Future<dynamic> _getGreet() async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final deployedContract = DeployedContract(
      ContractAbi.fromJson(
        contractABI,
        'MusicShop',
      ),
      EthereumAddress.fromHex(contractAddress),
    );

    final res = await _w3mService.requestReadContract(
      deployedContract: deployedContract,
      functionName: 'getGreet',
      rpcUrl: _sepolia.rpcUrl,
    );

    log(res.toString());
    return res;
  }

  Future<void> _setGreet(String str) async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final deployedContract = DeployedContract(
      ContractAbi.fromJson(
        contractABI,
        'MusicShop',
      ),
      EthereumAddress.fromHex(contractAddress),
    );

    _w3mService.launchConnectedWallet();
    final result = await _w3mService.requestWriteContract(
      topic: _w3mService.session?.topic ?? '',
      chainId: 'eip155:11155111',
      rpcUrl: _sepolia.rpcUrl,
      deployedContract: deployedContract,
      functionName: 'setGreet',
      transaction: Transaction(
        from: EthereumAddress.fromHex(_w3mService.session!.address!),
      ),
      parameters: [str],
    );
    debugPrint('result $result');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      // body: const HomeScreen()
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(onPressed: _getGreet, child: const Text('Get Hello')),
            TextButton(
                onPressed: () async {
                  await _setGreet("New Hello");
                },
                child: const Text('Set Hello')),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          _w3mService.openModal(context);
        },
        child: const Icon(
          Icons.change_circle_rounded,
          size: 30.0,
        ),
      ),
    );
  }
}

The change is mainly here in the _setGreet() function:

final result = await _w3mService.requestWriteContract(
  topic: _w3mService.session?.topic ?? '',
  chainId: 'eip155:11155111',
  rpcUrl: _sepolia.rpcUrl,
  deployedContract: deployedContract,
  functionName: 'setGreet',
  transaction: Transaction(
    from: EthereumAddress.fromHex(_w3mService.session!.address!),
  ),
  parameters: [str],
);

This is the hash of my tx https://sepolia.etherscan.io/tx/0x60923a9a8a09f03db20ed1cfe5ce224da591245b3ab0e60de377da698312a6bd

Kolovrat19 commented 1 month ago

Thank you. Could you please add this example "Write to contract with parameters" to your docs? In your docs example you missed coma to: EthereumAddress.fromHex('0xaddressTo....')

quetool commented 1 month ago

Thanks for confirming that it's working! I added it to the docs along with some other minor changes https://github.com/WalletConnect/walletconnect-docs/pull/1523