RFC: Contract Instances #165
-
SummaryThis is a mini proposal for the concept of Contract Instances in viem. Basic ExampleThe ability to define contract instances up front so that folks don't have to switch between clients & pass an address/abi around: const contract = getContract({
abi,
address,
publicClient,
walletClient,
})
// readContract
contract.read.totalSupply({ blockNumber: 1n })
// writeContract
contract.write.mint([420], { value })
// simulateContract
contract.simulate.mint([420], { value })
// estimateContractGas
contract.estimateGas.mint([420], { value })
// createContractEventFilter
contract.createEventFilter({ fromBlock: 'latest' })
contract.createEventFilter.Transfer({ from, to }, { fromBlock: 'latest' })
// watchContractEvent
contract.watchEvent({ batch: true })
contract.watchEvent.Transfer({ from, to }, { batch: true }) The ProblemThe current APIs for contract interactions are currently primitives, which means they are a bit more verbose than folks would like. 1. They need to pass an
|
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 15 replies
-
Awesome! You were fast 😂 nice work! |
Beta Was this translation helpful? Give feedback.
-
Another alternative is to always expect the array, so the second example would be const totalSupply = await contract.read.totalSupply([], { blockNumber: 69420n }) This is trading a bit of verbosity for api consistency and clarity. It's always clear in that case that the object are call args. I don't have a strong opinion for either.
Maybe keeping those methods, without including them in the types, and just throwing an error in them could be useful for people using js and not ts. I think this can be done with something like without having to cast to Object.defineProperty(contract.write, 'mint', {
value: () => { throw new Error("You need a walletClient to be able to call mint"); },
writable: false,
enumerable: false,
configurable: false
}); |
Beta Was this translation helpful? Give feedback.
-
I personally am a fan of minimal apis. I would prefer to simply have objects I can pass around const myErc20 = { abi, address } const res = client.readContract({...myErc20, 'balanceOf'}) I don't think this rfc adds any value except for a small ergonomic boost for those of us who love typing |
Beta Was this translation helpful? Give feedback.
-
i was initially skeptical since i would rather have a single choice per action. i don't want to have to keep track of many things, and I don't want contributors to be able to surprise me. i think it would be a net gain, however. it's still pretty simple, it can cut down a lot of verbosity. if devs are expected to be going to inventing their own wheel in practice, then it should be supported. also, there's a very simple editorial choice to make when having to decide between client methods or using a contract instance:
|
Beta Was this translation helpful? Give feedback.
-
Thanks for exporting those additional types (super fast turnaround 😄 ). I've fudged together a simple Contract implementation. It's only rudimentary. https://gist.github.com/jackmellis/dd7e7360e3d3783dc8a1f662927e9b6c However I'm now also thinking that combining several interactions into a single "write" action might be too opinionated for what should be considered a low level library... |
Beta Was this translation helpful? Give feedback.
-
For simple, linear scripts contract instances can be great (it's still a matter of taste of course). But specifically for writes in the context of user interfaces where you have to react to wallet and parameter changes dynamically, handing around preinitalized objects like these can easily become a footgun. In our app state machines, we always just pass around the raw parameters until the very last moment. Initializing the contract instance there isn't necessary and doesn't add much value imho. But yeah, primarily for read operations (mostly in the context of linear scripts), and for more fluently readable documentation / examples I can see the benefit of this abstraction. Even with the excellent types & utilities provided by standalone viem, I still see protocols shipping their own abstractions and sdks around their contracts in the future. I wouldn't want to expose every third party users of our protocol to the full width of our low level contract apis. What's I'd like to be able to pass around typed output objects from Is there already an idiomatic approach to that in viem that I'm missing? Providing flavours of |
Beta Was this translation helpful? Give feedback.
-
Contract instances are now out as part of |
Beta Was this translation helpful? Give feedback.
Contract instances are now out as part of
viem@0.2
!