Giter Site home page Giter Site logo

dgca / use-wagmi-contracts Goto Github PK

View Code? Open in Web Editor NEW
17.0 3.0 4.0 329 KB

Convert Solidity ABIs into a ready-to-consume React utility functions

License: MIT License

Solidity 0.97% TypeScript 98.22% JavaScript 0.59% HTML 0.22%
react solidity viem wagmi ethereum

use-wagmi-contracts's Introduction

use-wagmi-contracts

Description

This project includes a set of tools to transform Solidity contract ABIs into structured objects containing all contract methods. All methods are strongly typed, and both read and write methods are included.

It also includes a React hook to automatically provide the required wagmi arguments to the contract methods, and a way to provide a default contract address for each of your contracts.

Finally, it includes a utility for those using TypeChain to make processing your contract ABIs even easier.

Live Demo ๐ŸŽฅ

If you like videos, I put together this demo of creating a project using Rainbowkit + Hardhat + use-wagmi-contracts from scratch.

Requirements

  • React 18+
  • Viem 0.x
  • Wagmi 1.x
    • Note this library does not work with previous verisons of wagmi that used ethers.
    • If you are using older versions of wagmi + ethers, check out use-typechain-contracts.

Installation

yarn add @type_of/use-wagmi-contracts
# or
npm install @type_of/use-wagmi-contracts

Setup

The recommended way to use this library is to use the WagmiContractsProvider component to wrap your app. This will give you a convenient useContracts() hook that you can use to access your contracts, and all contract methods will have the appropriate wagmi arguments bound to them already.

To keep things organized, create a new file to initialize the library. In this example, we'll refer to this as /path/to/WagmiContractsProvider.

Initialize the library using initUseWagmiContracts. It expects an AbiMap, which is an object with the following shape below.

const abiMap = {
  ContractName: {
    abi: ContractAbi,
    defaultAddress: "0x1234...", // Optional
  },
  OtherContract: {
    abi: OtherAbi,
  },
  ...
}

Note to TypeChain users:

If you are using the TypeChain library to generate your contract ABIs, you can use the processTypechainAbis function to generate the AbiMap for you. See the "Using TypeChain" section.


The keys of the AbiMap are the names that you'll use later to refer to your contracts. defaultAddress is optional, and can be used to provide a default address for your contract. If you don't provide a default address, you'll need to provide one when you call the contract methods.

Once you have your AbiMap, you can initialize the library by calling initUseWagmiContracts with your AbiMap.

This returns an object with two properties: WagmiContractsProvider and useContracts. Export these for later consumption.

// /path/to/WagmiContractsProvider.tsx
import { initUseWagmiContracts } from "@type_of/use-wagmi-contracts";

const abiMap = {...};

const { WagmiContractsProvider, useContracts } = initUseWagmiContracts(abiMap);

export { WagmiContractsProvider, useContracts };

Using TypeChain

If you use TypeChain to generate your contract ABIs, you can use the processTypechainAbis function to generate the AbiMap for you. Assuming you have a typechain-types directory in your project, you can do the following:

// /path/to/WagmiContractsProvider.tsx
import { initUseWagmiContracts, processTypechainAbis } from "@type_of/use-wagmi-contracts";
import * as typechain from '/path/to/typechain-types';

const abiMap = processTypechainAbis(typechain, {
  ContractName: {
    defaultAddress: "0x1234...",
  },
});

const { WagmiContractsProvider, useContracts } = initUseWagmiContracts(abiMap);

export { WagmiContractsProvider, useContracts };

The second argument to processTypechainAbis is an object that allows you to provide default addresses for your contracts. If you don't provide a default address, you'll need to provide one when you call the contract methods.

Wrapping your app with WagmiContractsProvider

Next, wrap your app with the WagmiContractsProvider component you created. Note that this must go inside the <WagmiConfig> component since calls wagmi hooks.

import { WagmiContractsProvider } from "/path/to/WagmiContractsProvider";

export function App({ children }) {
  return (
    <WagmiConfig config={wagmiConfig}>
      <WagmiContractsProvider>
        {children}
      </WagmiContractsProvider>
    </WagmiConfig>
  )
}

That's it! You can now use the useContracts hook to access your contracts and call their methods.

Communicating with contracts

To communicate with your your contracts, use the useContracts hook. This hook returns a map of your contracts, with each contract containing a map of its methods.

Using the viem readContract and writeContract approach

Say we have a very simple contract that looks like this:

contract ValueStore {
  bool public value = false;

  function getValue() external view returns(bool) {
    return value;
  }

  function setValue(bool _nextValue) external returns(bool) {
    value = _nextValue;
    return value;
  }
}

Assume we named this contract "ValueStore" in the AbiMap, and we provided a default address. If we want to get the value, we can do the following:

(Note that this is a very bare bones approach to data fetching, and I would recommend using something like @tanstack/react-query to handle caching and data invalidation.)

import { useContracts } from "/path/to/WagmiContractsProvider";

function GetValueDemo() {
  const contracts = useContracts();
  const [value, setValue] = useState<boolean | null>(null);

  useEffect(() => {
    const getValue = async () => {
      /**
       * Note that we don't need to provide an address to `ValueStore()` since we
       * provided a default address in * the AbiMap.
       *
       * Also note that getValue takes no arguments because the contract method also
       * takes no arguments. We'll see an example that takes arguments below.
       */
      const value = await contracts.ValueStore().getValue();
      setValue(value);
    }

    getValue();
  }, [contracts]);

  if (value === null) return null;

  return (
    <p>The value is: {value ? 'TRUE' : 'FALSE'}</p>
  );
}

That's a very simple example of a read method. Now, let's look at a write method.

import { useContracts } from "/path/to/WagmiContractsProvider";

function SetValueDemo() {
  const contracts = useContracts();
  const [lastWrittenValue, setLastWrittenValue] = useState<boolean | null>(null);

  const handleWrite = useCallback(async () => {
    try {
      /**
       * The function signature for `setValue` will be typed based on the contract ABI.
       *
       * Unlike read methods, write methods return a tuple of `[hash, result]``. The hash
       * is the transaction hash, and the result is the return value of the method. If the
       * method doesn't return anything, the result will be `undefined`.
       */
      const [hash, result] = await contracts.ValueStore().setValue(Math.random() > 0.5);
      setLastWrittenValue(result);
      console.log('Success!');
    } catch (err) {
      console.error(err);
    }
  }, [contracts]);

  return (
    <div>
      <p>
        The last written value was: {lastWrittenValue === null
          ? 'null'
          : lastWrittenValue ? 'TRUE' : 'FALSE'}
      </p>
      <button onClick={handleWrite}>
        Set Random Value
      </button>
    </div>
  );
}

Using wagmi's useContractRead and useContractWrite hooks

This library also provides wrappers around wagmi's useContractRead and useContractWrite hooks for every contract method. Note that for simplicity and to differentiate, ours are named useRead and useWrite.

To learn how to use them, let's look at an example. We'll use the same contract example as above.

contract ValueStore {
  bool public value = false;

  function getValue() external view returns(bool) {
    return value;
  }

  function setValue(bool _nextValue) external returns(bool) {
    value = _nextValue;
    return value;
  }
}

Wagmi's useContractRead and useContractWrite hooks are wrappers over react-query, and they return an object with properties about your request, such as loading, data, error, etc. The return value of these hooks will be a React Query useQuery or useMutation hook, depending on whether the method is a read or write method. Read the react-query docs for more information.

To read contract data, you can do the following:

import { useContracts } from "/path/to/WagmiContractsProvider";

function GetValueDemo() {
  const contracts = useContracts();

  const valueResult = contracts.ValueStore().getValue.useRead();

  if (valueResult.loading) return null;

  return (
    <p>The value is: {valueResult.data ? 'TRUE' : 'FALSE'}</p>
  );
}

To write data using useWrite, you can do the following:

import { useContracts } from "/path/to/WagmiContractsProvider";

function SetValueDemo() {
  const contracts = useContracts();
  const handleSetValue = contracts.ValueStore().setValue.useWrite({
    args: [Math.random() > 0.5]
  });

  return (
    <div>
      <button onClick={handleSetValue.write}>
        Set Random Value
      </button>
    </div>
  );
}

Both useRead and useWrite take an object as their argument, and this object is merged with wagmi's useContractRead and useContractWrite hooks. This means you can pass any of the options that useContractRead and useContractWrite accept.

use-wagmi-contracts's People

Contributors

dgca avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

use-wagmi-contracts's Issues

Multichain support

Hey, love the package its definitely streamlined my workflow.

I was wondering what you thought of multichain support, would it be possible to parse the wagmi config to fetch the chains that are being used, and allow an address to be set for each chain.

I imagine the api would look something like this:

const abiMap = processTypechainAbis(typechain, {
  ContractName: {
    // defaultAddress: "0x1234...", //  Keep default address If only using one chain, prevent breaking changes
    chains: {
        [mainnet]: "0x01234...",
        [sepolia]: "0x11234...",
    },
  },
});

Then the useContract hook would fetch the correct contract based on the chain that is currently connected, instead of passing in the chain specific address each time it is called.

Let me know what you think

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.