Connecting a Discord server to a Solana DAO
I travelled around North America this past Spring. It was a good opportunity to find a routine that allowed me to enjoy travelling while working on a small project. I had been thinking about community DAOs for a while. Solana is a good option for a low-stakes online community that doesn't hold large assets. Solana transactions are fast and cheap to confirm while using a Proof of Stake blockchain that doesn't harm the environment. These properties are more fitting for an application used by all kinds of end-users.
Discord is the most common town hall for online communities. I wanted to use existing tooling and find the best way to connect a Discord server to a Solana DAO. The DAO would manage the community's assets and the rules to function. Discord would hold the conversation. Throughout the post, I'll outline my research and the solution I went with. The priority was to find the tradeoffs that worked best for an online community:
Becoming a member should be accessible: cheap and easy.
The community should be open. Anyone can join and start collaborating.
Members should feel empowered and that they belong.
Certain leadership to enforce rules and foster a collaborative environment.
Why
I want to work on different experiments to try out new tools that could help build better online communities. In this case, I wanted to go deeper into DAOs, NFTs, and Solana. The plan was to understand where these technologies stand today, whether they could be useful for online communities, and if so, how.
I had certain hypotheses of how they could come in handy:
We can move community management to a decentralized structure. by untethering a community from the application. The community itself is represented as a DAO and can then pick any application stack to organize itself. Decentralization provides the added benefit of making communities more resilient by reducing points of failure, including censorship resistance.
Crypto makes it possible for a DAO to receive, hold, and use different kinds of assets. Online communities should be able to work with digital-native assets. As the assets are smart contracts, they are programmable so actions can occur right after a vote passes.
The community's NFT can be their canonical membership and authentication mechanism. They are open standards, making it easier for different applications to support them leading to more interoperability. This way, membership is determined by holding the NFT and participating in an application (like Discord) depends on the NFT, but said application can’t affect one’s membership in the actual community.
We can tie community membership to the community's economic value. If the community is considered valuable, the assets they create, like NFTs, will be valuable. This way, becoming a member means minting or buying an NFT, which could bring income to the community. Also, members will be interested in increasing the value of the community thus fostering their participation.
Setting up a DAO
There are many ways to set up a DAO in the Solana ecosystem. Solana is several years newer than Ethereum so the different protocols and applications aren't as far along. There's still a very active developer community working hard to close the gap.
The first major decision was deciding if being a member of the DAO would depend on fungible or non-fungible tokens. Fungible tokens seemed like an easier approach as they worked better with the DAO tooling that exists today in Solana. I preferred NFTs as they are a better abstraction for community membership. One NFT can be one vote.
For DAOs, I looked into Squads. It has the best UI for a DAO in Solana. Its focus is on small teams that have a clear goal. It didn't fit the community use case, and it didn't work with NFTs.
SPL DAOs were more flexible. They are the official standard for DAOs. They provide greater flexibility when configuring your DAO and they had just added support for NFT-based DAOs. The existing UI to manage them feels experimental, which isn’t a problem for what I’m building here.
With that decided, it’s time to create the NFTs and an SPL DAO.
Setting Up Candy Machine
In Solana, the standard for NFTs is Metaplex. There's a recent proposal called NFToken with a different approach that uses fewer accounts to set up, but there's not much of an ecosystem around it at this point.
Metaplex is a wrapper around SPL tokens (the standard for tokens in Solana) that adds some extra metadata. There are different options to set up minting with Metaplex. I wanted to focus on something easy to set up and use, but that also makes sense for community membership.
Metaplex has something called a Candy Machine. Once it's set up, eligible users can go to the Candy Machine and mint an NFT from the collection. One way to determine if someone is eligible is with a whitelist token. The community creators, or the DAO itself, can hold these whitelist tokens and send them to accepted members.
Let's get started creating an SPL token for the Candy Machine whitelist. We will mint the same amount of tokens as NFTs there will be (10 in this example).
Install the Solana CLI tools: https://docs.solana.com/cli/install-solana-cli-tools
spl-token create-token --decimals 0
This should output something like:
Creating token 4CkDhhN7AgDc2BhAwiUrebfgCwERsuceffyfM9D7Dji3
This creates a new token with 0 decimals. We only want to use this token to whitelist eligible users, so it should be 1 token per user. There's no need for the token to be divisible.
spl-token create-account 4CkDhhN7AgDc2BhAwiUrebfgCwERsuceffyfM9D7Dji3
This creates a new Solana account derived from our main account that is used to store the whitelist token.
spl-token mint 4CkDhhN7AgDc2BhAwiUrebfgCwERsuceffyfM9D7Dji3 10
Now we mint 10 whitelist tokens to our new derived account.
We can either manage these tokens ourselves or send them to the DAO we will create in a bit.
Once we have our whitelist tokens set up, we can create the Candy Machine. We need a configuration file for the Candy Machine itself, and a folder that holds our assets and their metadata. Follow the links to understand in more detail how they work.
My configuration file looks like this:
{
"price": 0,
"number": 10,
"gatekeeper": null,
"solTreasuryAccount": "our solana address",
"splTokenAccount": null,
"splToken": null,
"goLiveDate": "25 Dec 2021 00:00:00 GMT",
"endSettings": null,
"whitelistMintSettings": {
"mode" : { "burnEveryTime": true },
"mint": "whitelist token mint e.g. 4CkDhhN7AgDc2BhAwiUrebfgCwERsuceffyfM9D7Dji3",
"presale" : false,
"discountPrice" : null
},
"hiddenSettings": null,
"storage": "arweave",
"ipfsInfuraProjectId": null,
"ipfsInfuraSecret": null,
"awsS3Bucket": null,
"nftStorageKey": null,
"noRetainAuthority": false,
"noMutable": false
}
A few things to note:
The
price
is 0 as this NFT is not intended to be sold. It's a token of membership.The
number
is the total number of NFTs that will belong to this collection. It must be the same as the number of whitelist tokens we created.The
whitelistMintSettings
configures our whitelist tokens. We should set mint to the mint address of our whitelist token. We also want the whitelist token to be burnt after a mint occurs.
For the NFT assets, we need a folder where each asset is named after a number starting from 0 to the total number of NFTs minus one. In our case with 10 NFTs, they would go from 0 to 9. We need both the asset and the metadata for that asset. We end up with a 0.png and a 0.json. In my case, I made all images and the metadata the same, only changing the number. Here's an example configuration:
{
"name": "DAO Membership #1",
"symbol": "DAO",
"description": "Membership Token to DAO",
"seller_fee_basis_points": 0,
"image": "0.png",
"external_url": "https://yourwebsite.com/details/0",
"attributes": [
{
"value": "Membership #1",
"trait_type": "UNIQUENESS"
}
],
"collection": {
"name": "DAO Membership #1",
"family": "DAO Membership"
},
"properties": {
"files": [
{
"uri": "0.png",
"type": "image/png"
}
],
"creators": [
{
"share": 100,
"address": "our Solana address"
}
]
}
Now that we have our configuration ready, let's create our Candy Machine.
Download the Metaplex repository and set it up: https://docs.metaplex.com/guides/archived/candy-machine-v2/getting-started
Upload the Candy Machine
ts-node js/packages/cli/src/candy-machine-v2-cli.ts upload -e devnet -k /path/to/our/solana/key -cp configuration-file.json -c test ./assets
test
is the cache name. We will use this with other commands.We are using
devnet
. In production, usemainnet-beta
.
Set the collection for all the unminted NFTs:
ts-node js/packages/cli/src/candy-machine-v2-cli.ts set_collection -e devnet -k /path/to/our/solana/key -c test -m 28amzxoBSiivJgoZ7VX1it9pU11b5nze9M7iM6tMAQE3
28amzxoBSiivJgoZ7VX1it9pU11b5nze9M7iM6tMAQE3
is the Collection mint address. We get this address from the output of the first command.
Verify upload:
ts-node js/packages/cli/src/candy-machine-v2-cli.ts verify_upload -e devnet -k /path/to/our/solana/key -c test
Now it's up to the community to mint the NFTs. Only the update authority or accounts with the whitelist token can mint them. There are 2 ways to mint:
With the Candy Machine CLI:
ts-node js/packages/cli/src/candy-machine-v2-cli.ts mint_one_token -e devnet -k /path/to/our/solana/key -c test
Using the Candy Machine UI. This can be set up following this link: https://docs.metaplex.com/candy-machine-v2/mint-frontend#setting-up
Metaplex now provides a CLI called Sugar to create and manage a Candy Machine. It's probably easier to use so make sure to check it out.
Setting up the DAO
With our Candy Machine collection set up, we can now configure an SPL DAO and attach it to the collection. The collection must be verified and the NFTs need to be verified for that collection.
The team behind SPL DAO have a UI at https://realms.today that is used to set up and manage a DAO. They provide instructions to create an NFT DAO at: https://docs.realms.today/DAO-Management/createing-DAOs/NFT-Community-DAO. Make sure the UI runs in the same Solana network you used when setting up the Metaplex collection. It's either mainnet-beta or devnet. If you have any doubts, feel free to drop into their Discord, they are very helpful.
Making a Discord Bot
With our DAO setup, we now have an organization that can create votes and trigger on-chain actions based on the results. We are missing how the community will communicate and collaborate. We'll use Discord for that. We want to make it easy for new members to come in and start participating, but to also reward members who prove themselves to be valuable members of the group. Some channels will be public, and others will be private to the members that hold the NFT. How to organize this is up to each community.
There's a Discord bot by Grape called Grape Access that handles gatekeeping based on different token types, including Metaplex NFTs. Though it may have a lot of features, getting started with it is a long manual process.
I needed something simpler for both community creators and members. I built a small bot called Solana DAO Verify Bot (oh yeah, I gave the name a lot of thought 😅). My requirements were:
Link a Solana account to a Discord user.
There can only be one Solana wallet assigned to a Discord user and vice versa. This is to avoid using an NFT in a Solana wallet with multiple Discord users.
I built it in Node.JS and you can find the code here. The repo is divided into the server that acts as the bot and a small website to interact with the bot.
Starting with the server, we are actually running 3 different small applications:
A Discord bot that listens to commands and responds to them. It checks if a Discord user has verified their Solana wallet, and, if so, if it holds the necessary NFT for that Discord server.
An API used by the frontend to do the verification process that links a Solana account to a DIscord user.
A daemon that runs every 24h and checks if the stored Solana addresses still hold the NFTs. If not, those users lose access to the private channels.
I'm running it in a Node.JS droplet on Digital Ocean. The first 2 mini-apps run together, but the daemon is run separately. I'm using pm2 for both. The website is set up in Vercel, with auto-deploys with every push I make to the main branch. I set up a CNAME record on my domain pointing to Vercel, making the website accessible at solanadaoverify.albertoelias.me.
Setting up Solana DAO Verify Bot
Once you have your NFT collection and a Discord server set up, it's time to invite the bot into your server and configure it.
Invite the bot by following this link.
Configure the permissions for the bot:
Go to your Discord server settings.
Go to Roles.
Drag the Solana Verify role, which is only used by the bot, to the top. This way, other roles and permissions won't override it.
Set up a new role for verified users:
In the Roles settings, like before, create a new Role.
When creating a new channel, you can limit access to it to members with this new Role. To do so, click the gear icon by the new channel name:
In Permissions, remove the View Channel permission for @everyone.
In Permissions, add the verified role and add the View Channel permission.
We need the ID of this new Role. For that, you need to set your Discord into Developer Mode:
Go to your User Settings on Discord.
Go to Advanced.
Enable Developer Mode.
You can now go to the Role and Right-Click or click the 3 vertical dots to copy the ID.
You can, optionally, create a channel for your Discord members to communicate with the bot. This way, they won't be spamming bot commands in other more productive channels.
To finish, run the
?setup
command for the bot:?setup collectionID roleID solana-network
collectionID
is the address of the Metaplex collection.roleID
is the ID of the new role we created.solana-network
needs to be the same network we used to create both the NFTs and the DAO. It can bedevnet
ormainnet-beta
.
If all went well, the bot will let you know. If you have any issues, send me a DM!
Verifying your Discord User
If a member of your Discord server now wants to verify their Discord account and associate it with a Solana wallet, they can use the website. The process is simple:
Sign in with Discord.
Sign a message with your Solana wallet.
The frontend will then send the verification to the server. It will work if the Discord account and the Solana wallet have not been verified previously.
Go to the Discord server that you want to be verified in, and, in the correct channel, send the command
?verify
.If the Solana wallet holds a valid NFT, the Discord user will receive the appropriate role.
Extra points: Adding Mozilla Hubs
Text is a great way for people to communicate in an asynchronous manner from any device. But a community can make good use of hanging out in person in real-time as it provides richer interactions. This is where Mozilla Hubs comes in.
Now we can have specific channels that only those with the verified role can access. With the Mozilla Hubs Discord Bot, we can attach a Discord channel to a Mozilla Hubs room. The permissions from the Discord channel are passed on into the Hubs room. That means that if we limit a channel to those that have the role, the room will be limited in the same way.
To set up the bot, I followed the instructions on their Github and I'll detail here how I did it:
Invite the bot to your server using this link.
Create a new channel that will be synced to a Hubs room. Some people call it #hubs or #meetup.
Click the gear icon by the new channel to go to its settings.
In Integrations => Webhooks, create a new Webhook and call it "Hubs".
In Permissions, add the Hubs bot. You need to assign the permissions mentioned in the docs.
Type
!hubs create
which will create a new Hubs room and assign it to this channel.In Permissions, remove the View Channel permission for @everyone.
In Permissions, add the verified role and add the View Channel permission.
Now the channel should only be accessible to those with the verified role, and so should the Hubs room.
Open Questions
We now have a whitelist token that we can distribute to new members of our community. Then they can mint an NFT from our Candy Machine. From then on, they can be active members of the DAO we created.
During this process, I found different issues that remain unanswered with existing technology. We don't want to create a DAO or NFTs for the sake of it. We want to figure out if there's a real use case here.
The first question I asked myself is how open the community should be. There are all kinds of communities out there, and different structures meet different needs. Also, different structures require different tools. I didn't want to build a universal tool, so I needed to think about which kind of community I wanted to cater for. An open community can incentivise spam and lower-quality conversations. A closed community makes it hard for new ideas to flow in. I prefer communities that are as open as possible, where it's easy for newcomers to join. But the setup we built makes no sense for an open community, as the NFTs act as a gatekeeper. With that in mind, I prioritised making the process as simple as possible so these communities can be accessible.
When configuring voting in the DAO, the number of NFTs in the collection marks the maximum number of votes that can be submitted. Imagine you create a collection of 1000 NFTs and, as you accept new members, the number of available spots goes down little by little. If you create a vote when only 100 NFTs have been minted, and the quorum is set to 50%, a quorum will never be reached as at most 100 votes will be submitted. It's important to have a small quorum at the beginning so votes can pass.
Voting in the DAO depends on people owning the NFT. One NFT = One Vote. We cannot avoid people hoarding NFTs to control the votes. The DAO setup would need to change to make it one vote per address that holds at least one NFT. In an ideal world, we could verify that 2 different addresses definitely belong to different people.
While we strive for an open community, we want existing members to feel comfortable and safe. A community needs to enforce certain rules. In extreme cases where a person is working against the community, for the sake of the group, there needs to be a mechanism to kick them out. This is not possible with existing tools. There would need to be a way for the authority behind an NFT collection to block a specific NFT from being used. Another option is a blacklist in the DAO that blocks certain addresses from participating. With some extra work, this could be done.
To sum it up
Discord is a very powerful tool for communities. It supports all kinds of integrations that allow communities to adapt the tool to their needs. In this experiment, I wanted to find the best way to bring a community DAO into Discord. We also created a virtual world that is only accessible by members of the DAO. By following this process, a community is no longer a chat room in Discord. A community can become its own digital-native entity outside of any centralized tool. It can also take advantage of cryptocurrencies to manage all sorts of assets and bring real value to the community.
This is no perfect solution. There are several open questions that the existing technology does not answer, or that require a decent amount of work. The tooling is getting better quickly though. There's now a new CLI to create Candy Machines, and I hope this post and the bot I made can help communities too. Seeing the progress in the space, I believe now is already a great time to make use of the advantages of Web3.
Please let me know what you think. If you try it out and bump into any errors, hit me up on Twitter or open an issue on Github. I’m currently working on another community project. I hope to share some details soon, but first, I’m off to Tanzania to try to climb Mount Kilimanjaro, a life-long dream of mine.
Bonus: Master Edition vs Candy Machine
This is out of scope for this post, but in case you're interested in other options on how to set up the community NFT, read along.
Another option I considered, instead of a Candy Machine mint, is creating a Master Edition. It's an abstraction supported by Metaplex. It's an NFT that allows its owner to print Editions. Editions are limited copies of a Master Edition. Imagine a print that has 100 editions, that's something that's now easy to set up with Metaplex.
Once you have a Master Edition, you can pass it on to the DAO. Once a new member is accepted, the DAO can have an instruction that prints a new Edition and sends it to the new member. I think this is a very interesting approach and would be worth pursuing. I decided not to go down this route for 2 reasons:
It's more complex to set up. Creating each Edition is more manual than just creating a Collection for a bunch of NFTs in a Candy Machine.
The SPL DAO requires a Collection. We can create a Collection from a Master Edition, so it should work, but there are a lot more docs for Candy Machine making it easier to follow the process.
In case you're interested in trying this out, here are the instructions to create a Master Edition.
Run the Metaplex web server:
cd metaplex/js
yarn install && yarn bootstrap && yarn start
Use the Web App to create an NFT marking it as a parent collection.
To create Editions we are going to use the tools from a different repo:
git clone https://github.com/metaplex-foundation/metaplex-program-library
cd metaplex-program-library/token-metadata
cargo build
./target/debug/metaplex-token-metadata-test-client --keypair /path/to/our/solana/key mint_new_edition_from_master_edition_via_token --mint 4tUo7VCMk4xb9pEJjUWifhPScwrpyA2kDthTYbDoeBoq
--mint
is the address of the Master Edition created from the UI.