huseyindeniz / vite-react-dapp-template

Vite React Template for dApp Frontend Development
https://huseyindeniz.github.io/react-dapp-template-documentation/
MIT License
9 stars 5 forks source link

Integrate Viem #5

Open huseyindeniz opened 1 year ago

huseyindeniz commented 1 year ago

If it's possible, integrate Viem as an alternate to Ethers

huseyindeniz commented 1 year ago

It seems TypeChain package will no longer receive updates. TypeChain was very important for frontend while using ethers. As I understand, this burden is not exist in Viem. Viem integration is most essential now.

huseyindeniz commented 7 months ago

Apparently, Viem finally supports contract instances: https://viem.sh/docs/contract/getContract.html

The following horrible/stupid usage

  client.readContract({
    ...wagmiContract,
    functionName: 'totalSupply',
  }),

can be replaced with:

const result = await contract.read.totalSupply()

So, this integration can be done now. If the same abi could be used by both ethers and viem, TypeChain could be removed completely.

jxom commented 7 months ago

We have supported contract instances for about a year now.

jxom commented 7 months ago

The following horrible/stupid usage

I'd like to convince you otherwise :)

Advantages:

Disadvantages:

Pros significantly outweigh the cons IMO.

huseyindeniz commented 7 months ago

Hi @jxom , thank you very much for the insights on Viem.

First of all, I've checked the documentation a couple of times and couldn't find that getContract page before. I stumbled upon it accidentally yesterday while browsing through the "Issues" in the Viem repository. I can't seem to remember or find the issue now, but it was about getContract being a proposal. I noticed it was closed recently, and I assumed that getContract was added in that PR. That's why I mentioned "finally supports" in my comment.

For the second part, it's not that easy to explain for me, to be honest. I feel there is an unsolvable paradigm difference here. I believe the underlying issue probably comes from the Functional Programming/OOP dilemma. It's not about "Interface people." It could be an interesting discussion, but I guess this isn't the right place for it. In summary, I'm not against functional programming; I'm not against pragmatism. But in most cases I don't prefer them. For me, as a user of smart contracts, the underlying communication between my frontend and the smart contract isn't my concern. I desire as much abstraction as possible. I don't think performance suffers significantly with abstraction. The size and performance of ethers.js (v6) are totally fine by me. I'm willing to accept a few extra bytes for more maintainable code. After all, every decision in software development is a trade-off.

PS: As a solid example, even this level of abstraction doesn't quite cut it for me. What does "read" imply in this context. (Of course I know it implies "methods under read are not transactions, they are views"). Why do I need to specify "read". It's unnecessary.

const totalSupply = await contract.read.totalSupply()

I prefer

const totalSupply = await contract.totalSupply();
const mintTx = await contract.mint();

PS2: When I integrate Viem, I'll try to prepare a benchmark to observe the performance difference. Perhaps the difference will be more significant than I anticipate.

jxom commented 7 months ago

As for your examples provided, they internally do significantly different things, and require different JSON-RPC interfaces & parameters. Not distinguishing this will throw users off, and isn't very aligned with one of Viem's principles being Rule of Least Surprise

.read -> eth_call .write -> eth_sendTransaction .estimateGas -> eth_estimateGas etc...

What if you accidently called a payable/nonpayable write method that you thought might have been a pure/view method? Would lead to loss of funds. 😅 Some methods also apply to multiple types (e.g. contract.write.mint(), contract.simulate.mint(), contract.estimateGas.mint()).

huseyindeniz commented 7 months ago

this is a good example to explain my thought process I think.

I have following data flow:

Library => Service => Domain Model => View(React component)

In each layer, my concern is different:

As an example I'd like to write something like this in Service Layer

class MyNFTContract {

const simulateMint = async () => {
  const simulateresult = await contractSimulator.simulate(myContract.mint);
  // process/convert data and return it to consumers in desired format
}

const estimateMintGas = async () => {
  const gas = await gasEstimater.estimate(myContract.mint);
    // process/convert data and return it to consumers in desired format
}

const mint = async() => {
  const isMintingOpen = await myContract.isMintingOpen();
  if(!isMintingOpen) {
    throw new Error(MyContractErrors.MintingIsNotOpen);
  }
  const mintTx = await myContract.mint();
  return mintTx.hash;
}

// I can listen transaction results like this
  public listenTransaction = (txHash: string): EventChannel<string> => {
    const txListener = eventChannel<string>(emit => {
      this._provider?.once(txHash, transaction => {
        emit(transaction.status.toString());
      });
      return (): void => {
      };
    });
    return txListener;
  };
}
huseyindeniz commented 7 months ago

note to myself: do not start working on this until #48 completed.