Create a Solana dApp from scratch

Integrating with Solana wallets

Episode 8
1 month ago
20 min read

At this point, we've got a nice user interface to send and read tweets but nothing is wired to our Solana program. In addition, we have no way of knowing which wallet is connected in the user's browser. So let's fix that.

In this episode, we'll focus on integrating our frontend with Solana wallet providers such as Phantom or Solfare so we can send transactions on behalf of a user. Once we'll have access to the connected wallet, we'll be able to create a "Program" object just like we did in our tests.

Okay, let's get started!

Install Solana wallet libraries

Fortunately for us, Solana has a few official JavaScript libraries that can help us integrate with many wallet providers out there.

These libraries are available on GitHub and there are even packages for Vue 3! So let's install these libraries right now. We'll need one for the core "wallet adapter" logic, one for the Vue 3 logic, one for the Vue 3 components and one for importing all supported wallets.

Run the following inside your app directory to install them.

npm install @solana/wallet-adapter-base \
    @solana/wallet-adapter-vue \
    @solana/wallet-adapter-vue-ui \
    @solana/wallet-adapter-wallets

Use the wallet provider

With these libraries installed, the first thing we need to do is use the WalletProvider component. This component is a little special and does not render anything other than its content. However, it provides data to its descendants using the provide/inject API of VueJS. If you're familiar with React, this is very similar to using a React context. Take the following example.

<template>
    <wallet-provider>
        <some-other-component></some-other-component>
    </wallet-provider>
</template>

This code is visually equivalent to using <some-other-component> directly in the template. However, that component will now have access to all the data needed to get the connected wallet and even methods to initiate such connection. Later in the episode, we will explain what we get and how to get it but, for now, let's see how to set up that WalletProvider component.

Inside the script part of your App.vue component, add the following lines.

import { useRoute } from 'vue-router'
import TheSidebar from './components/TheSidebar'
import { getPhantomWallet, getSolflareWallet } from '@solana/wallet-adapter-wallets'
import { WalletProvider } from '@solana/wallet-adapter-vue'

const route = useRoute()

const wallets = [
    getPhantomWallet(),
    getSolflareWallet(),
]

This does two things.

  1. It imports two wallet providers: Phantom and Solflare. It then registers these two wallet providers inside a wallets array. Whilst we'll only use these two in this series, note that you can use any of the supported wallet providers listed here. To add some more, import the relevant method from @solana/wallet-adapter-wallets and add it to the wallets array.
  2. It imports the WalletProvider component so we can use it in our template.

Next, we need to give that wallets array to the WalletProvider component so it knows which wallet providers we want to support. Additionally, we'll use the auto-connect mode on that component, such that, it will automatically try to reconnect the user's wallet on page refresh.

To do all that, we need to wrap the content of our App.vue template inside a WalletProvider component and provide the following props.

<template>
    <wallet-provider :wallets="wallets" auto-connect>
        <div class="w-full max-w-3xl lg:max-w-4xl mx-auto">

            <!-- Sidebar. -->
            <the-sidebar class="py-4 md:py-8 md:pl-4 md:pr-8 fixed w-20 md:w-64"></the-sidebar>

            <!-- Main -->
            <main class="flex-1 border-r border-l ml-20 md:ml-64 min-h-screen">
                <header class="flex space-x-6 items-center justify-between px-8 py-4 border-b">
                    <div class="text-xl font-bold" v-text="route.name"></div>
                </header>
                <router-view></router-view>
            </main>
        </div>
    </wallet-provider>
</template>

Because we're wrapping all of the content of our App.vue component inside a WalletProvider component, every component inside our application will now have access to wallet data. On top of that, they will have access to various methods enabling the user to select a wallet provider and connect to it.

Fortunately, these wallet libraries also provide UI components that handle all of that for us.

Use wallet UI components

From the wallet libraries we imported earlier, one of them — @solana/wallet-adapter-vue-ui — also provide VueJS components that allow the user to select a wallet provider and connect to it. It contains a button to initiate the process, a modal to select the wallet provider and a dropdown that can be used once connected to copy your address, change provider or disconnect.

All of that can be added to your application through the following components.

<wallet-modal-provider>
    <wallet-multi-button></wallet-multi-button>
</wallet-modal-provider>

The <wallet-modal-provider> component provides a little context for showing and hiding modals whilst the <wallet-multi-button> component contains all of the design and logic to let users connect their wallets.

Currently, we have a fake "Select a wallet" button on the sidebar. Thus, let's replace it with the two components above to connect our wallets for real.

Inside the script part of the TheSidebar.vue component, add the following line to import the components.

import { WalletMultiButton, WalletModalProvider } from '@solana/wallet-adapter-vue-ui'

Then, use them inside the template part and remove the fake button.

- <!-- TODO: Connect wallet -->
- <div class="bg-pink-500 text-center w-full text-white rounded-full px-4 py-2">
-     Select a wallet
- </div>
+ <wallet-modal-provider>
+     <wallet-multi-button></wallet-multi-button>
+ </wallet-modal-provider>

Last but not least, we need to import some CSS to style these components properly. Add the following line to your main.js file. It's important to add it before our main.css file so we can make some design tweaks in the next section.

// CSS.
import '@solana/wallet-adapter-vue-ui/styles.css'
import './main.css'

// ...

Awesome! At this point you should be able to compile your application — using npm run serve — and connect your wallet! 🎉

Note that, if you don't have a wallet or a browser extension such as Phantom installed yet, don't worry about it, we'll tackle that in a minute. But first, let's have a look at what our wallet button looks like.

Screenshot of the application with the &quot;Connect Wallet&quot; modal opened.

Screenshot of the application with a connected wallet and the dropdown menu opened.

Overall, not so bad but the style doesn't really match the rest of our application and the dropdown is not properly aligned so let's fix that.

Update the design of the wallet button

Fortunately for us, all the UI components provided by the @solana/wallet-adapter-vue-ui library use CSS classes that we can override to tweak their style.

So let's do that. Open your main.css file and add the following lines at the end of the file.

.wallet-adapter-dropdown {
    @apply w-full;
}

.wallet-adapter-button {
    @apply rounded-full w-full;
}

.wallet-adapter-button-trigger {
    @apply bg-pink-500 justify-center !important;
}

.wallet-adapter-dropdown-list {
    @apply top-auto bottom-full md:top-full md:bottom-auto md:left-0 md:right-auto;
}

.wallet-adapter-dropdown-list-active {
    @apply transform -translate-y-3 md:translate-y-3;
}

The @apply directive allows us to write CSS using TailwindCSS classes for convenience. Aside from that, we're just updating some CSS classes.

Okay, let's have a look at our wallet button now.

Screenshot of the application with a connected wallet and the dropdown menu opened with the new design.

Much better! 🎨

Connect your wallet

It is worth mentioning that the wallet you're going to use in your browser is usually different from the wallet we created earlier in this series to run our tests in the console. The former is typically your "real" wallet whereas the latter is just the wallet your local machine uses to run tests or use CLI tools. They can be the same if you want them to be but I prefer to keep them separate.

Now, if you already have a wallet registered in a wallet provider such as Phantom or Solflare, you should already be good to go. If you're using another wallet provider, feel free to add it to the wallets array we defined earlier.

However, if you don't have a wallet or a wallet provider installed as a browser extension, then you'll need to do this to interact with your application. For that purpose, I recommend installing Phantom in your browser. It's a very popular wallet provider and has a friendly user interface. Once installed, you can follow the steps to create a new wallet directly on the Phantom extension. Be sure to store your recovery phrase someplace safe since it can recover your full wallet including its private key.

By default, your wallet will show you the money or assets you have in the "mainnet" cluster. The "mainnet" cluster is basically the real cluster where real money is kept. However, the same wallet can be used in other clusters such as "devnet" — a live cluster with fake money to test things — or "localnet" — your local cluster.

As such, if you want to see your money or assets in other clusters, you may do this by changing a setting in your wallet provider. In Phantom, you can do this by clicking on the cog icon, then going to the "Change Network" setting and selecting your cluster here.

Three screenshots of the Phantom app to show how to change the network.

Note that changing this setting is optional as it only affects the assets displayed by the wallet provider. It does not affect which cluster our application sends transactions to. We will configure this within our code a bit later in this article.

Access wallet data

At this point, users can connect their wallet to our application and we can access that data within our VueJS component. But how do we access that data and what do we actually get from it?

How and what?

Let's start with the "how".

You can access the data provided by the WalletProvider component by using the useWallet composable from the @solana/wallet-adapter-vue library.

import { useWallet } from '@solana/wallet-adapter-vue'
const data = useWallet()

As long as you call useWallet() within a component, you will have access to properties and methods regarding the connected wallet.

So what do we actually get from useWallet()?

  • wallet. Potentially the most interesting piece of information for us is the user's connected wallet. If the user has connected a wallet, this will be an object containing its public key. Otherwise, this property will be null.
  • ready, connected, connecting and disconnecting. These are useful booleans for us to understand which state we are in. For instance, we can use the connected boolean to know if the user has connected its wallet or not.
  • The select, connect and disconnect methods enable us to select, connect to and disconnect from a wallet provider. We don't need to use these methods directly since they are already being used by the wallet UI components we imported earlier.
  • The sendTransaction, signTransaction, signAllTransactions and signMessage methods enable us to sign messages and/or transactions on behalf of the connected wallet. Whilst we will not use them directly, Anchor requires some of these methods inside its wallet object.

Anchor wallet

As you can see, useWallet() gives us lots of granular information that can be used to interact with the connected wallet. Because of that, the wallet object it provides is not compatible with Anchor's definition of a wallet. If you remember the following diagram from episode 5, you can see that Anchor uses its own "Wallet" object to interact with the connected wallet and sign transactions on its behalf.

Diagram from episode 5 showing the incompatibility with the wallet provided by &quot;useWallet()&quot;.

In order to get an object compatible with Anchor's definition of a wallet, we can use yet another composable called useAnchorWallet. This will return a wallet object that can sign transactions.

import { useAnchorWallet } from '@solana/wallet-adapter-vue'
const wallet = useAnchorWallet()

And just like that, we can connect our previous Anchor diagram with our brand new wallet integration.

Previous diagram using &quot;useAnchorWallet()&quot; to connect &quot;WalletAdapter&quot; with the &quot;Wallet&quot; object from Anchor.

Reactive variables in VueJS

I'd like to take a little break here to talk about reactive variables in Vue 3. If you're not familiar with them, some of the code you'll read later could be a little confusing.

Most of the properties we've listed above are reactive and wrapped inside Ref objects. If you're not familiar with Vue's Ref variables, they ensure the content of a variable is passed by reference and not by value.

Pass by reference vs pass by value animation using a cup of coffee and its content to illustrate the two.

This means, by having a reference of a Ref variable, we can mutate its content and any code using that variable can be notified of such change. To access the content of a Ref variable, you must access its value property — e.g. wallet.value — unless you're using that variable inside a VueJS template, in which case, VueJS automatically does that for you. You can read more about them in Vue's documentation or — if you're used to React — this might help.

Here's a little example to summarise how we can access Ref variables. Inside the script part, we use value. Inside the template part, we don't.

<script setup>
import { ref } from 'vue'
const name = ref('Loris')
console.log(name.value) // Outputs: Loris
</script>

<template>
  <div>{{ name }}</div> <!-- Displays: Loris -->
</template>

Use wallet data in components

Okay, let's put what we've learned into practice.

At the moment, anyone can see the form that allows users to send tweets. However, that form should only be visible to users that have connected their wallets so let's fix that.

In the script part of the TweetForm.vue component, import the useWallet composable.

import { computed, ref, toRefs } from 'vue'
import { useAutoresizeTextarea, useCountCharacterLimit, useSlug } from '@/composables'
import { sendTweet } from '@/api'
import { useWallet } from '@solana/wallet-adapter-vue'

Then, in the template part of the component — Under "Permissions" — update the following line.

  // Permissions.
- const connected = ref(true) // TODO: Check connected wallet.
+ const { connected } = useWallet()

This will use the connected variable from the wallet data instead of being always true like it was before.

If you look inside the template of that component, you can see that this connected variable is used to toggle which HTML we are showing to the user: either the form or an empty state.

<template>
    <div v-if="connected" class="px-8 py-4 border-b">
        <!-- Form here... -->
    </div>

    <div v-else class="px-8 py-4 bg-gray-50 text-gray-500 text-center border-b">
        Connect your wallet to start tweeting...
    </div>
</template>

And that's it! Now, only users with connected wallets can see the tweet form.

Let's do another one. This time, we'll make sure the profile page is not visible on the sidebar if you're not connected.

In the script part of TheSidebar.vue component, import and call useWallet to access the connected variable.

import { WalletMultiButton, WalletModalProvider } from '@solana/wallet-adapter-vue-ui'
import { useWallet } from '@solana/wallet-adapter-vue'
const { connected } = useWallet()

Then, inside the template, look for the comment that says "TODO: Check connected wallet". Under that comment, replace v-if="true" with v-if="connected" and voilà! You can also remove that "TODO" comment now.

<template>
    <aside class="flex flex-col items-center md:items-stretch space-y-2 md:space-y-4">
        <!-- ... -->
        <div class="flex flex-col items-center md:items-stretch space-y-2">
          	<!-- ... -->
            <router-link v-if="connected" :to="{ name: 'Profile' }" ...>
                <!-- ... -->
            </router-link>
        </div>
        <div class="fixed bottom-8 right-8 md:static w-48 md:w-full">
            <wallet-modal-provider>
                <wallet-multi-button></wallet-multi-button>
            </wallet-modal-provider>
        </div>
    </aside>
</template>

To recap, here's what you should see if you have a connected wallet.

Screenshot of the home page when a wallet is connected. We can see the profile page link in the sidebar and the tweet form on the home page.

And here's what you should see if you don't. I.e. no profile page and no tweet form.

Screenshot of the home page when no wallet is connected. We can no longer see the profile page link in the sidebar nor the tweet form on the home page that now says &quot;Connect your wallet to start tweeting...&quot;.

We need more data

Okay, let's take a deep breath and see what we've accomplished so far in this episode.

  • We imported a WalletProvider that provides everything we need to connect a wallet and access its data.
  • We imported some UI components that make use of that to allow users to connect their wallets.
  • We accessed the data provided by the WalletProvider using useWallet() in various components.
  • We found out that we can use useAnchorWallet() to obtain a wallet object compatible with Anchor.

So if we refer to our diagram that represents all entities needed for Anchor to create a Program, we can see that we currently only have one piece of the puzzle: the "Wallet".

Previous diagram where &quot;WalletAdapter&quot; points to Anchor's &quot;Wallet&quot; object but this time &quot;Wallet&quot; is highlighted in red to show that we've only got this piece of information so far.

In order for us to have everything we need to interact with our Solana program, we need to fill the rest of the puzzle. Fortunately for us, the "Wallet" piece was the most difficult piece to find since we needed to integrate with wallet providers which we've now done.

Anchor refers to this whole picture as a "Workspace" because it gives us everything we need to work with our program.

Provide a workspace

Okay, let's fill the missing pieces of the puzzle and create our workspace. We'll create a new useWorkspace.js file inside the composables folder and register it inside composables/index.js.

export * from './useAutoresizeTextarea'
export * from './useCountCharacterLimit'
export * from './useFromRoute'
export * from './useSlug'
export * from './useWorkspace'

Inside the useWorkspace.js composable, we'll use the provide/inject API from VueJS to inject data to our components the same way WalletProvider does. For that, we need an initWorkspace method that initialises our context and a useWorkspace method that access it. Here's how we can do this using VueJS.

import { inject, provide } from 'vue'

const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    provide(workspaceSymbol, {
        // Provided data here...
    })
}

Let's start simple, by importing the connected Anchor wallet and providing it as data. That way, we don't need to use the other composables to access the connected wallet. We'll have everything in one place.

import { inject, provide } from 'vue'
import { useAnchorWallet } from '@solana/wallet-adapter-vue'

const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    const wallet = useAnchorWallet()

    provide(workspaceSymbol, {
        wallet,
    })
}

The next thing we need is a Connection object. For that, we simply need to know which cluster — or network — we want to interact with. For now, we'll continue to develop our application locally. Therefore, we'll hardcode our localhost URL which is: http://127.0.0.1:8899. We'll have a more dynamic way to handle this in the future when we'll deploy to devnet.

Previous diagram with a new &quot;localhost&quot; node pointing to the &quot;Cluster&quot; node and the array says &quot;hardcoded&quot;.

So let's create a new Connection object using this cluster URL and provide it as data as well.

import { inject, provide } from 'vue'
import { useAnchorWallet } from '@solana/wallet-adapter-vue'
import { Connection } from '@solana/web3.js'

const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    const wallet = useAnchorWallet()
    const connection = new Connection('http://127.0.0.1:8899')

    provide(workspaceSymbol, {
        wallet,
        connection,
    })
}

We know that Connection + Wallet = Provider so we can now create a new Provider object. However, this provider object needs to be a computed property so that it is recreated when the wallet property changes — e.g. it is disconnected or connected as another wallet.

Here's how we can achieve this using VueJS. Notice how we access the wallet using wallet.value inside the computed method.

import { inject, provide, computed } from 'vue'
import { useAnchorWallet } from '@solana/wallet-adapter-vue'
import { Connection } from '@solana/web3.js'
import { Provider } from '@project-serum/anchor'

const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    const wallet = useAnchorWallet()
    const connection = new Connection('http://127.0.0.1:8899')
    const provider = computed(() => new Provider(connection, wallet.value))

    provide(workspaceSymbol, {
        wallet,
        connection,
        provider,
    })
}

Next, we need to access the IDL file which is the JSON file representing the structure of our program. This file is auto-generated in the target folder of the root of our project so let's access it directly from there.

Note that this will not work when the app is deployed to a server on its own since the target folder will be empty but we will take care of that later on when we deploy to devnet.

import { inject, provide, computed } from 'vue'
import { useAnchorWallet } from '@solana/wallet-adapter-vue'
import { Connection } from '@solana/web3.js'
import { Provider } from '@project-serum/anchor'
import idl from '../../../target/idl/solana_twitter.json'

const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    const wallet = useAnchorWallet()
    const connection = new Connection('http://127.0.0.1:8899')
    const provider = computed(() => new Provider(connection, wallet.value))

    provide(workspaceSymbol, {
        wallet,
        connection,
        provider,
    })
}

Finally, because IDL + Provider = Program, we can now create our program object. We'll use a computed property here as well because provider is also reactive.

On top of asking for the idl and the provider objects, creating a Program also requires its address as an instance of PublicKey. Fortunately for us, the IDL file already contains that information under idl.metadata.address. We just need to wrap this in a PublicKey object and feed it to the program.

⛔️ Warning: The metadata.address variable containing our program ID will only be available after running anchor deploy because that's when Anchor knows which address the program was deployed to. So if you run anchor build without running anchor deploy, you will end up with the following error: Cannot read properties of undefined (reading 'address').

And there we have it! The final code of our useWorkspace.js composable that gives us access to everything we need to interact with our Solana program.

import { inject, provide, computed } from 'vue'
import { useAnchorWallet } from '@solana/wallet-adapter-vue'
import { Connection, PublicKey } from '@solana/web3.js'
import { Provider, Program } from '@project-serum/anchor'
import idl from '../../../target/idl/solana_twitter.json'

const programID = new PublicKey(idl.metadata.address)
const workspaceSymbol = Symbol()

export const useWorkspace = () => inject(workspaceSymbol)

export const initWorkspace = () => {
    const wallet = useAnchorWallet()
    const connection = new Connection('http://127.0.0.1:8899')
    const provider = computed(() => new Provider(connection, wallet.value))
    const program = computed(() => new Program(idl, programID, provider.value))

    provide(workspaceSymbol, {
        wallet,
        connection,
        provider,
        program,
    })
}

Now, all we need to do is call that initWorkspace method inside a component so it will provide the workspace data to all of its descendants.

In the components folder, create a new WorkspaceProvider.vue component and paste the following code.

<script setup>
import { initWorkspace } from '@/composables'
initWorkspace()
</script>

<template>
    <slot></slot>
</template>

Next, inside our App.vue component, we'll import that WorkspaceProvider we've just created and use it in the template. It should wrap all of the content of our template except the WalletProvider component which should be rendered first so the WorkspaceProvider component can access its data.

<script setup>
import { useRoute } from 'vue-router'
import TheSidebar from './components/TheSidebar'
import { getPhantomWallet, getSolflareWallet } from '@solana/wallet-adapter-wallets'
import { WalletProvider } from '@solana/wallet-adapter-vue'
import WorkspaceProvider from '@/components/WorkspaceProvider'

// ...
</script>

<template>
    <wallet-provider :wallets="wallets" auto-connect>
        <workspace-provider>
            <!-- ... -->
        </workspace-provider>
    </wallet-provider>
</template>

Phew, all done! We can now access the workspace data from any component of our application.

Use the workspace

Before we wrap up this article, let's have a quick look at how we can access that workspace data in our components.

We'll take that opportunity to update the wallet address on the profile page.

If you open the PageProfile.vue component, you should see a public key hardcoded in the template.

<div v-if="true" class="border-b px-8 py-4 bg-gray-50">
    B1AfN7AgpMyctfFbjmvRAvE1yziZFDb9XCwydBjJwtRN
</div>

Now that we have access to the real connected wallet, let's replace this with its public key.

<script setup>
import { ref, watchEffect } from 'vue'
import { fetchTweets } from '@/api'
import TweetForm from '@/components/TweetForm'
import TweetList from '@/components/TweetList'
import { useWorkspace } from '@/composables'

const tweets = ref([])
const loading = ref(true)
const { wallet } = useWorkspace()

// ...
</script>

<template>
    <div v-if="wallet" class="border-b px-8 py-4 bg-gray-50">
        {{ wallet.publicKey.toBase58() }}
    </div>
    <tweet-form @added="addTweet"></tweet-form>
    <tweet-list :tweets="tweets" :loading="loading"></tweet-list>
</template>

As you can see, we:

  1. Imported the useWorkspace composable.
  2. Extracted any variable needed from useWorkspace() — here, the wallet object.
  3. Used the wallet object inside our template to display its public key in base 58 format.

This was a simple example, but we're now able to do much more than that. Just like we were in our tests, we can now access the program object and use its various APIs to interact with our Solana program which is exactly what we will be doing in the next couple of episodes.

Conclusion

First of all, well done for following this far! Honestly, this series has been quite a journey and I'm super happy to see many of you looking forward to the next episodes.

Whilst integrating with wallets has been made super easy for us by the wallet-adapters repository and the JavaScript libraries it provides, we still had to set them up properly and understand various concepts along the way. But look at what we've got now:

  • Users can connect their wallets.
  • Our app can access data regarding the connected wallet and act accordingly.
  • Our app has access to a full workspace allowing it to interact with our Solana program the same way we were in our tests.

That's a massive progress from our mock application that wasn't doing anything before!

As usual, you can access the code for this episode in the branch below and compare it with the previous episode.

View Episode 8 on GitHub

Compare with Episode 7

In the next episode, we will replace the mock data from our api files and use our brand new workspace to fetch real tweets from our Solana program.

← Previous episode
Scaffolding the frontend

Discussions

Author avatar
Kevin Connors
1 month ago

There is some issue with the latest versions of the libraries used in this episode; when running

npm install @solana/wallet-adapter-base \ @solana/wallet-adapter-vue \ @solana/wallet-adapter-vue-ui \ @solana/wallet-adapter-wallets

you will get the latest versions. Be sure to use the versions specifically on the GitHub repo or this code won't work.

💖 1

Discussion

Integrating with Solana wallets
Author avatar
Kevin Connors
1 month ago

There is some issue with the latest versions of the libraries used in this episode; when running

npm install @solana/wallet-adapter-base \ @solana/wallet-adapter-vue \ @solana/wallet-adapter-vue-ui \ @solana/wallet-adapter-wallets

you will get the latest versions. Be sure to use the versions specifically on the GitHub repo or this code won't work.

💖 1
Author avatar
columbbus
1 month ago

Can you explain why that is? And what would be the solution. I mean installing old version seems like a good way to get through the tutorial, but it's not an optimal solution.

💖 1

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
James
1 week ago

Was getting a transparent background on the Select Wallet button:

button, [type='button'], [type='reset'], [type='submit'] {
    background-color: transparent;
}

Easiest fix is adding class="bg-indigo-500" to the <wallet-multi-button> component.

💖 0

Discussion

Integrating with Solana wallets
Author avatar
James
1 week ago

Was getting a transparent background on the Select Wallet button:

button, [type='button'], [type='reset'], [type='submit'] {
    background-color: transparent;
}

Easiest fix is adding class="bg-indigo-500" to the <wallet-multi-button> component.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member

Would you like to chime in?

You must be a member to start a new discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member