Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Viem #5

Open
huseyindeniz opened this issue Sep 23, 2023 · 8 comments
Open

Integrate Viem #5

huseyindeniz opened this issue Sep 23, 2023 · 8 comments
Labels
blocked enhancement New feature or request

Comments

@huseyindeniz
Copy link
Owner

huseyindeniz commented Sep 23, 2023

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

@huseyindeniz huseyindeniz added the enhancement New feature or request label Sep 23, 2023
@huseyindeniz
Copy link
Owner Author

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 huseyindeniz self-assigned this Nov 1, 2023
@huseyindeniz huseyindeniz removed their assignment Mar 6, 2024
@huseyindeniz
Copy link
Owner Author

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
Copy link

jxom commented Mar 27, 2024

We have supported contract instances for about a year now.

@jxom
Copy link

jxom commented Mar 27, 2024

The following horrible/stupid usage

I'd like to convince you otherwise :)

Advantages:

  • Atomic
  • Low bundle size
  • Faster
  • Verbose API makes it easier to compose
  • Literally just a light abstraction over eth_call, meaning consumers can learn how Ethereum APIs actually work

Disadvantages:

  • Not an interface people are familiar with
  • Not one-line.

Pros significantly outweigh the cons IMO.

@huseyindeniz
Copy link
Owner Author

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
Copy link

jxom commented Mar 28, 2024

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
Copy link
Owner Author

huseyindeniz commented Mar 28, 2024

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:

  • If I'm writing code in the Library, I do care read, write, estimateGas internals, I'd separate them, but thanks to you, it's ready to use
  • If I'm writing code in the Service, I do not care read/write etc. classification, how internal structure/communication works, I just want to access SmartContract methods. I can easily recognize method usages thanks to Typescript, I see parameters and return type
  • If I'm writing code in the Domain Model, I do not care transaction handling, I do not care Solidity types etc. But I do care error handling, listening transaction result etc.
  • If I'm writing code in the View, I do not care the operation sequence, domain business logic, I just need some values

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
Copy link
Owner Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants