-
Notifications
You must be signed in to change notification settings - Fork 869
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
Feat/multi chain #615
Feat/multi chain #615
Conversation
…stomConnectButton.tsx
…foldConfigContext for configuredNetwork variable
… useScaffoldConfig hook instead of getTargetNetwork().id
…g hook instead of getTargetNetwork()
…ldConfig hook instead of getTargetNetwork()
…om useScaffoldConfig hook instead of getTargetNetwork()
… from useScaffoldConfig hook instead of getTargetNetwork()
…om useScaffoldConfig hook instead of getTargetNetwork()
…rom useScaffoldConfig hook instead of getTargetNetwork()
…ScaffoldConfig hook instead of getTargetNetwork(), Refactored RainbowKitCustomConnectButton.tsx to add check for when burner wallet is used
19d9f1c
to
0be5315
Compare
packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton.tsx
Outdated
Show resolved
Hide resolved
packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton.tsx
Outdated
Show resolved
Hide resolved
This is looking amazing! Great job @sverps. Left a few review comments/open questions.
I think this is ok. Live apps shouldn't have hardhat/foundry configured, so you'll only want that on your dev env (where you can tweak the file for dev purposes) Also, thank you @blahkheart!! Sorry for the miscommunication with the PRs. As @sverps said, we'll give you attribution on merging, since this PR took some inspiration from yours. |
Great job! Apart from todos and unresolved fixes current version is lgtm |
…reak contracts types
I gave it a go, and it got complex quite quickly. 🙈 If we just make it a generic type, then for it to have a default value, it can't be the first generic. So that implies users would need to specify the When they do so, it will only do autocompletion based on the given chainId, but it's really only typescript, so internally the hook will continue to use whichever chain the user is actually on. So in order to make those two things work, the hook should really accept an optional Maybe there's a way, but it started feeling like it wasn't really worth it... 😅 I think, for now, it is an acceptable trade-off if users have autocompletion based on the default chain. Multi-chain projects that use different contract names and functions on each chain are probably advanced enough that devs working on that will understand how to use wagmi directly, I suppose. (In trying out this idea, I did find a small issue with how the scaffold config file type was defined, not sure why it didn't pop up earlier.) |
Thanks for raising the autocompletion issue @technophile-04 and @sverps for looking into it. If Samuel says it gets too complex in TS, I 100% trust him haha
Unless there was a simple solution (which it seems there is not)... I agree with this!
What is the issue? 🤓 |
In other words, setting target networks as Fix was to turn the targetNetworks into a tuple (which necessitated a custom Hope that makes sense 😅 |
Ohh this was strange ideally it shouldn't have happened right? Since we were using I tried doing some research and found another approach without using a custom Tuple utility and keeping Also with custom Tuple if we specify < 10 chains we get Tysm @sverps for exploring and great explainantion!! Completely agree that it is not in the scope of this PR and may require a lot of changes and brainstorming on changes to the custom hooks API design. The reason I mentioned that point was there is a slight difference b/w cross chain vs multi-chain and people tend to use cross chain smart contracts more and probability of having different Abi's / name in cross-chain stuff is more, where one chain smart-contract holds more functionalities than others. Will create a new issue with a more detailed explanation and there we can discuss what's the best approach 🙌 Also, I think we are already handling mult-chain stuff in this PR nicely 🙌 !!! Tysm again Samuel !! Let's get the merge conflict fixed, I think we are almost there merging this 🙌 |
Nice find, I'll try it out. Sounds like a better solution than the custom |
@technophile-04 @carletex @rin-st |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome stuff nicely tested out and works like a charm, Tysm Samuel !!!
Just found two minor bugs : one with useScaffoldContract
and other with useScaffoldEventHistory
Screen.Recording.2023-11-30.at.12.52.19.AM.mov
Regarding useScaffoldContract
: If you look at the above video the read
from the contract instance of useScaffoldContract
gives undefined
ideally it should have given value from 0th
chain if the wallet is not connected (similar to what it's doing when we use useScaffoldContractRead
)......I think the contract instance we are getting from useScaffoldContract
is always undefined
for some reason.
Regarding useScaffoldEvenHistory
: It also kind of broken I think when you switch chain, if you look at the video when I switched from sepolia (which had 6
events ) to goerli (which had only 1
event) it showed me 7
I think it appended the 1
goerli event on top of sepolia.
Here are steps to reproduce this locally :
1 . Update targetNetwork : [chains.sepolia, chains.goerli]
2. Copy paste this in `externalContracts.ts` :
import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
const externalContracts = {
5: {
MyContract: {
address: "0x5bB28a827e250F6BF45D75809Ff21733e7c6704f",
abi: [
{
inputs: [
{
internalType: "address",
name: "_owner",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "previousOwner",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "OwnershipTransferred",
type: "event",
},
{
inputs: [],
name: "increaseCounter",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "owner",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "renounceOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "totalCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "transferOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
name: "userGreetingCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "withdraw",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
stateMutability: "payable",
type: "receive",
},
],
inheritedFunctions: {
owner: "@openzeppelin/contracts/access/Ownable.sol",
renounceOwnership: "@openzeppelin/contracts/access/Ownable.sol",
transferOwnership: "@openzeppelin/contracts/access/Ownable.sol",
},
},
YourContract: {
address: "0x1a2de031A50Fe9918f0BCe71Cb060C8CA3911f55",
abi: [
{
inputs: [
{
internalType: "address",
name: "_owner",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "greetingSetter",
type: "address",
},
{
indexed: false,
internalType: "string",
name: "newGreeting",
type: "string",
},
{
indexed: false,
internalType: "bool",
name: "premium",
type: "bool",
},
{
indexed: false,
internalType: "uint256",
name: "value",
type: "uint256",
},
],
name: "GreetingChange",
type: "event",
},
{
inputs: [],
name: "greeting",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "owner",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "premium",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "string",
name: "_newGreeting",
type: "string",
},
],
name: "setGreeting",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "totalCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
name: "userGreetingCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "withdraw",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
stateMutability: "payable",
type: "receive",
},
],
inheritedFunctions: {},
},
},
11155111: {
MyContract: {
address: "0xd7036EDDDA3743Aa577DC24ca817f3F85099bDbD",
abi: [
{
inputs: [
{
internalType: "address",
name: "_owner",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "previousOwner",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "OwnershipTransferred",
type: "event",
},
{
inputs: [],
name: "increaseCounter",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "owner",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "renounceOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "totalCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "transferOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
name: "userGreetingCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "withdraw",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
stateMutability: "payable",
type: "receive",
},
],
inheritedFunctions: {
owner: "@openzeppelin/contracts/access/Ownable.sol",
renounceOwnership: "@openzeppelin/contracts/access/Ownable.sol",
transferOwnership: "@openzeppelin/contracts/access/Ownable.sol",
},
},
YourContract: {
address: "0xE009aea21af005e6B531B5f4a8f909C64A0c596d",
abi: [
{
inputs: [
{
internalType: "address",
name: "_owner",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "greetingSetter",
type: "address",
},
{
indexed: false,
internalType: "string",
name: "newGreeting",
type: "string",
},
{
indexed: false,
internalType: "bool",
name: "premium",
type: "bool",
},
{
indexed: false,
internalType: "uint256",
name: "value",
type: "uint256",
},
],
name: "GreetingChange",
type: "event",
},
{
inputs: [],
name: "greeting",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "owner",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "premium",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "string",
name: "_newGreeting",
type: "string",
},
],
name: "setGreeting",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "totalCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
name: "userGreetingCounter",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "withdraw",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
stateMutability: "payable",
type: "receive",
},
],
inheritedFunctions: {},
},
},
} as const;
export default externalContracts satisfies GenericContractsDeclaration;
3 . Copy pasted this in index.tsx
import { useEffect, useState } from "react";
import type { NextPage } from "next";
import { parseEther } from "viem";
import { useAccount, useWalletClient } from "wagmi";
import { MetaHeader } from "~~/components/MetaHeader";
import { Address, InputBase } from "~~/components/scaffold-eth";
import {
useScaffoldContract,
useScaffoldContractRead,
useScaffoldContractWrite,
useScaffoldEventHistory,
} from "~~/hooks/scaffold-eth";
const Home: NextPage = () => {
const { address: connectedAddress } = useAccount();
const [inputGreetings, setInputGreetings] = useState("");
const { writeAsync: writeSetGreetings } = useScaffoldContractWrite({
contractName: "YourContract",
functionName: "setGreeting",
args: [inputGreetings],
value: parseEther("0.01"),
});
const { data: totalCounter } = useScaffoldContractRead({
contractName: "YourContract",
functionName: "totalCounter",
watch: true,
});
const { data: walletClient } = useWalletClient();
const { data: YourContract } = useScaffoldContract({
contractName: "YourContract",
walletClient,
});
useEffect(() => {
const fetchTotalCounter = async () => {
console.log("YourContract", YourContract);
const value = await YourContract?.read.totalCounter();
console.log("totalCounter", value);
};
fetchTotalCounter();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { data: eventHistory } = useScaffoldEventHistory({
contractName: "YourContract",
eventName: "GreetingChange",
filters: { greetingSetter: connectedAddress },
fromBlock: 0n,
watch: true,
});
console.log("eventHistory", eventHistory);
return (
<>
<MetaHeader />
<div className="flex items-center flex-col flex-grow pt-10">
<div className="text-center mb-8">
<span className="block text-2xl mb-2">The connected address is</span>
<Address address={connectedAddress} />
</div>
<div className="px-5 space-y-4">
<h1 className="text-center mb-4">
<span className="block text-3xl mb-2">With Hooks</span>
</h1>
<h1 className="text-center mb-4">
<span className="block text-2xl mb-2">The totalCounter is</span>
<span className="block text-4xl font-bold">{totalCounter?.toString()}</span>
</h1>
<InputBase onChange={setInputGreetings} value={inputGreetings} placeholder="Set greetings" />
<button className="btn btn-primary btn-sm ml-12 mb-2" onClick={() => writeSetGreetings()}>
Set Greetings
</button>
<h1 className="text-center mt-8">
<span className="block text-3xl mb-2">Imperative</span>
</h1>
<button
className="btn btn-primary btn-sm"
onClick={async () => {
await YourContract?.write.setGreeting([inputGreetings], { value: parseEther("0.01") });
}}
>
Set Imperative Greetings
</button>
</div>
</div>
</>
);
};
export default Home;
If it get too complicated to handle maybe we can handle it in a different PR and even @damianmarti can help us with the issue with useScffoldEventHistory
there 🙌
So the TODO's after this PR might be :
- Placement of network switch button
- Handle bugs for
useScaffoldContract
anduseScaffoldEventHistory
- Handle autocompletions even if there are different contract names
Thanks again Samuel and others for reviews 🙌
Good find 👍 |
Ohh shit, my bad...yup it works nicely 🙌
Yup makes sense, but still sometimes it still works a bit weirdly : Screen.Recording.2023-11-30.at.11.10.35.AM.movI think maybe it's worth handling in different PR nicely, will create an issue for it Tysm all merging this 🙌 !! |
This is a great job @sverps. This feature was a long time coming! Thanks all |
Fixes #599
Updates
scaffold.config.ts
to allow configuring multiple target networks like so:The first value is the default and will be the network selected upon launching the app.