AppKit and WalletKit have arrived. Explore the all-new product stacks.
Learn more
Blog Home
Network
|
August 24, 2023
The WalletConnect logo
WalletConnect
Ethereum Provider vs. Universal Provider: Uncovering the differences

As you explore the WalletConnect docs, you’ll come across two distinct providers that may leave you wondering: which one is the best fit for my specific needs? Deciding between the two can be quite the task, but don’t worry!

In this blog post, I’ll guide you through both options and shed light on their differences.

By the time you reach the end of this article, you’ll have a clear and comprehensive understanding of each provider, helping you to make an informed decision about which one suits your dapp perfectly!

Before we begin, it’s important to emphasize that WalletConnect v2.0 is chain-agnostic, thanks to its adherence to CAIP-25.

Chain Agnostic Improvement Proposals are like Ethereum Improvement Proposals (EIPs), but for various chains. This means that your dapp is not limited to the Ethereum Ecosystem. In fact, as of the time of writing, WalletConnect v2.0 supports 300+ chains! You can check them all out here.

Now that we know what CAIP-25 is and how that affects your requirements, let’s discuss what Universal Provider is.

Universal Provider

Imagine this. You’re a developer creating a multi-platform messaging app that supports various messaging services like WhatsApp, Telegram, Signal, and iMessage.

Universal Provider acts as a bridge that connects your app to these messaging platforms. With an app like this, users can communicate seamlessly across multiple messaging services without the need to switch between different apps.

This integration theoretically makes the messaging experience smoother and more efficient for everyone, as users can conveniently send messages to their friends and family on different platforms, all from within your unified messaging app.

Now coming back to Universal Provider, it allows you to connect and communicate with multiple chains, making your development journey smoother and more efficient.

Great! Now that we know what it is and why you might need it, it’s time to jump to Ethereum Provider and shed light on the title of this post.

Ethereum Provider

Ethereum Provider is essentially an abstraction over Universal Provider specifically for Ethereum-based chains. Remember CAIP-25 and how it makes multi-chain possible? Well, Ethereum Provider is Universal Provider preconfigured for Ethereum and chains built on Ethereum.

In fact, you can check the source code and see how it uses Universal Provider under the hood.

Ethereum Provider eliminates the need for manual configurations, seamlessly enabling exclusive support for Ethereum chains without hassle.

Demo

Now that we have a clear understanding between the two providers, let’s put it to a quick practical test! We will build two example dapps, one with Ethereum Provider and another with Universal Provider.

With Ethereum Provider

We are going to use Vite React Typescript template for these demos. Let’s get the prerequisites installed first!

yarn create vite walletconnect-ethereum-provider --template react-ts
cd walletconnect-ethereum-provider
yarn add @walletconnect/ethereum-provider @walletconnect/modal ethers@5.7.1

We installed three dependencies here

  • @ethers@5.7.1 which will help us interact with Ethereum
  • @walletconnect/ethereum-provider the belle of the ball, this is what’s going to help us use WalletConnect
  • @walletconnect/modal since we want to delegate the QR Code modal handling part, we are installing WalletConnectModal. Think of Web3Modal but more barebones. We could also skip this step and handle the QRCode ourselves, which I’ll go through briefly in this Demo.

Awesome, so far so good! Let’s move ahead now.

First, we need to initialize a provider through Ethereum Provider.

Go to App.tsx and add this:

const provider = await EthereumProvider.init({
  projectId: "<YOUR_PROJECT_ID>",
  chains: [1],
  methods: ["eth_signTypedData"],
  showQrModal: true,
  qrModalOptions: {
    themeMode: "light",
  },
});

Okay, a bunch of stuff happened here. Let’s go through it quickly.

  • projectId: To use WalletConnect v2.0, we need a project ID. Not only does this let you keep track of your projects more efficiently, you can see real-time metrics, submit your dapp to WalletConnect Explorer, and much more for free! Go to https://cloud.walletconnect.com and create a new project and copy your project ID.
  • chains: We are passing which chains we want to interact with. This field is an array of chainIds. You can pass in more chains like Polygon (137), Optimism (10), etc, since WalletConnect v2.0 is multi-chain by design. You can find more chain IDs from a website like ChainList.
  • methods: By default Ethereum Provider will pass in two methods for you, personal_sign and eth_sendTransaction. If you want to use more RPC methods, you can pass them here!
  • showQRModal: Since we installed @walletconnect/modal, setting showQRModal to true allows Ethereum Provider to automatically hide or show WalletConnectModal.
  • qrModalOptions: This allows further customization of WalletConnectModal. Check out the full list of fields you can customize here.

Now that we have initialized Ethereum Provider, let’s pass it to ethers.

const ethersWeb3Provider = new ethers.providers.Web3Provider(provider);

Next, we need a function to help the user get connected with our UI somehow.

const [connected, setConnected] = useState(false);
const connect = () => {
    provider.connect().then(() => {
      setConnected(true);
    });
  };

provider.connect() makes our lives so much easier here! Since it’s a promise, we can use .then to know when we got connected successfully and we toggle a state variable to reflect that change.

Perfect! Believe it or not, we’re already halfway there.

Now, let’s make a function to fetch our balance once we’re connected.

const getBalance = async () => {
  // Fetching raw balance with ethers
    const balanceFromEthers = await ethersWeb3Provider
      .getSigner(provider.accounts[0])
      .getBalance();

  // Converting it into a human readable format
    const remainder = balanceFromEthers.mod(1e14);
    setBalance(ethers.utils.formatEther(balanceFromEthers.sub(remainder)));
  };

We are getting the balance from our signer. Now to fetch it, our signer also needs to know the account it’s connected with, hence, the provider.accounts[0], i.e., the first account we got connected with. The next part is just making it look nice so we can understand what the balance actually says. ethers provides a bunch of useful utility functions to make our life easier, we’re going to use one such function called formatEther here.

Let’s add another function to sign a message with eth_signTypedData.

const callEthSign = async () => {
  const signerAddress = provider.accounts[0];
  const response = await provider.signer.request({
    method: "eth_signTypedData",
    params: [signerAddress, JSON.stringify(typedData)],
  });
  console.log("Signature(eth_signTypedData):", response);
};

This is what the typedData constant looks like:

const typedData = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      Person: [
        { name: "name", type: "string" },
        { name: "wallet", type: "address" },
      ],
      Mail: [
        { name: "from", type: "Person" },
        { name: "to", type: "Person" },
        { name: "contents", type: "string" },
      ],
    },
    primaryType: "Mail",
    domain: {
      name: "Ether Mail",
      version: "1",
      chainId: 1,
      verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
    },
    message: {
      from: {
        name: "Cow",
        wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
      },
      to: { name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" },
      contents: "Hello, Bob!",
    },
  };

Now that we have all the necessary building blocks in place, let’s show it to our user!

if (connected) {
    return (
      <>
        <button onClick={getBalance}>Balance</button>
        <button onClick={callEthSign}>Sign</button>
        <button onClick={refresh}>Refresh</button>
        <p>balance: {balance} ETH</p>
      </>
    );
  }
  return <button onClick={connect}>Connect with ethereum-provider</button>;

Remember how I mentioned we can handle the QR code generation ourselves? This is where I talk more about that. Feel free to skip this step since we are already using WalletConnectModal.

provider.on("display_uri", (uri) => {
  console.log("display_uri", uri);
});

You can use third-party module like qrcode to generate a QR Code from the display uri! This is how a lot of “Connect Kit” libraries do it, like Rainbow’s RainbowKit, Family’s ConnectKit, and believe it or not, both Web3Modal and WalletConnectModal! Pretty cool, right?

Alright, back to our demo.

With everything setup, this is what your dapp should look like:

Amazing! Looks good so far, let’s click the button and scan the QR Code next. You can do this with your favorite mobile wallet

Once we do that, this is what it should look like:

Amazing!! Give yourself a pat on the back if you made it this far, quite a feat! Now for the cool part, click on sign and see a signing request pop up in your wallet.

That brings us to the end of setting up a demo app with Ethereum Provider.

You can check out the sandbox/code for this demo here.

With Universal Provider

For convenience’s sake, let’s make a copy of the Ethereum Provider example and change relevant sections.

But before we do that, let’s get all the new modules installed! (also since we’ve forked the demo, it might be worthwhile to remove @walletconnect/ethereum-provider while we’re at it.

yarn add @walletconnect/universal-provider

Firstly, (spoiler alert) we’re going to use projectId a lot here, so it’s best to declare it as its own constant.

const projectId = "<YOUR-PROJECT-ID>";

Let’s move on to initializing the modal now.

const modal = new WalletConnectModal({
  projectId,
});

aaand the provider

const provider = await UniversalProvider.init({
  logger: "info",
  relayUrl: "wss://relay.walletconnect.com",
  projectId: projectId,
  metadata: {
    name: "WalletConnect Universal Provider",
    description: "WalletConnect Universal Provider Demo",
    url: "<https://walletconnect.com/>",
    icons: ["<https://avatars.githubusercontent.com/u/37784886>"],
  },
});

Woah let’s slow down. What happened here? What’s logger? Whats relayUrl?

Here’s what the fields mean:

  • logger: Sets a logging level to show in console. For example, if you set it to info, like we have, you should see a lot more data showing up in your console than if you set it to “error” let’s say.
  • relayUrl: WalletConnect v2.0 works using websockets. It’s end-to-end encrypted and a pretty secure way to connect your dapp with your wallet. In fact, this is the genie that does all the magic between your dapp and your wallet and helps them communicate with each other. To do this, we need to have a websocket server to “relay” your message to and fro, and that’s where relayUrl comes into play. For this example, we’ve used WalletConnect’s official relay socket server.
  • projectId: By this point, you most likely already know what projectId is.
  • metadata: This is what your dapp looks like in your wallet (the details you see while connecting)

Nice! The next step is pretty much the same as ethereumProvider, i.e., passing this to ethers.

const ethersWeb3Provider = new ethers.providers.Web3Provider(provider);

Let’s roll back a bit and talk about the optional feature I mentioned in Ethereum Provider, display_uri. For Universal Provider, this is no longer optional and, actually, pretty important for our dapp to function!

provider.on("display_uri", (uri: string) => {
  console.log("display_uri", uri);
  modal?.closeModal();
  modal?.openModal({ uri });
});

The main features added here are the last two lines in the callback function. Here’s how the flow goes. If a modal window is already open for some reason, it’s closed and a new one is shown with the uri that was generated.

Alright, let’s now move on to the connecting logic.

const connect = () => {
    provider
      .connect({
        namespaces: {
          eip155: {
            methods: ["eth_sendTransaction", "eth_signTypedData"],
            chains: ["eip155:1"],
            events: ["chainChanged", "accountsChanged"],
            rpcMap: {
              1: `https://rpc.walletconnect.com/v1/?chainId=eip155:1&projectId=${projectId}`,
            },
          },
        },
      })
      .then(async () => {
        setConnected(true);
        const acc = await ethersWeb3Provider.listAccounts();
        setAccounts(acc);
        console.log(acc);
        modal?.closeModal();
      });
  };

Quite a jump from provider.connect() eh?

So for universal provider you need to mention something called namespaces. What is that you might be asking? Remember how I mentioned WalletConnect v2.0 is multi-chain by design? This is where we actually get to see how that is relevant.

Let’s say you wanted to connect to a non-EVM chain as well. That’s totally possible thanks to CAIP-10! You could pass in, let’s say, another config object inside a cosmos key and configure it accordingly and it should work as intended. Cool stuff!

The rest of the fields inside the eip155 key are pretty much the same, except rpcMap, where you pass the RPC for WalletConnect to use for that specific chain. If you passed eip155:80001 as an example under eip155 you could pass another RPC url like this:

rpcMap: {
  1: `https://rpc.walletconnect.com/v1/?chainId=eip155:1&projectId=${projectId}`,
	80001: `https://rpc.walletconnect.com/v1/?chainId=eip155:80001&projectId=${projectId}`,
},

Okay enough of that, what happened to only entering setConnected(true); inside the then block and it just working? Well, this is advanced territory, with great power comes great responsibilities!

We need to manually handle how the modal behaves here. We also need something else, the accounts we are connected with, so while we’re at it, might as well fetch those! Here’s the useState definition for the same

const [accounts, setAccounts] = useState<string[]>([]);

Notice how we manually closed the modal in the last line? Yep, we have total control over the modal now!

Also, now that we are storing the accounts in an accounts variable, let’s replace all provider.accounts definition with accounts.

Well, the rest is, pretty much exactly the same!

You can check out the sandbox/code for this demo over here.

Conclusion

In this post, we dove into the world of WalletConnect providers, exploring the key differences between the Ethereum Provider and Universal Provider.

The Ethereum Provider acts as a tailored solution, streamlining the configuration process exclusively for Ethereum-based chains, while the Universal Provider proves to be a versatile option, effortlessly connecting your app to multiple chains.

To better understand these providers, we used a real-life example of creating a multi-platform messaging app.

On the other hand, we saw how the Ethereum Provider offers an abstraction over the Universal Provider, optimized specifically for Ethereum-based chains. It eliminates the need for manual configurations, enabling exclusive support for Ethereum chains without any hassle.

Throughout the blog, we demonstrated how to set up demos with both providers, showcasing how they are used. Armed with this knowledge, you can confidently choose the provider that best suits your app’s needs, whether it’s for multi-chain support or a seamless experience within the Ethereum ecosystem.

You are now equipped to revolutionize your dapp development, leveraging the power of WalletConnect providers to create innovative and interconnected dapps.

Happy building!

Recommended Articles
More articles
alt=""

Build what's next.
Build with WalletConnect.

Get started
© 2024 WalletConnect, Inc.