starryzone / starrybot-discord Goto Github PK
View Code? Open in Web Editor NEWA Discord bot using slash commands, and an ExpressJS backend to route offline-signing verification.
Home Page: https://starrybot.xyz
License: MIT License
A Discord bot using slash commands, and an ExpressJS backend to route offline-signing verification.
Home Page: https://starrybot.xyz
License: MIT License
As reported in the bugs channel in Discord, it sounds like The Strange Clan and Levana have run into and issue with this contract:
juno1uw3pxkga3dxhqs28rjgpgqkvcuedhmc7ezm2yxvvaey9yugfu3fq7ej2wv
I attempted to follow the normal process, using the button above, and could reproduce it.
TypeError: Cannot read properties of undefined (reading 'getTokenDetails')
at getTokenDetails (/Users/mikepurvis/ic/starrybot-discord/src/astrolabe/index.js:27:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Object.getConfig (/Users/mikepurvis/ic/starrybot-discord/src/commands/tokenAdd/createTokenRule.js:30:29)
at async Object.execute (/Users/mikepurvis/ic/starrybot-discord/src/utils/commands.js:46:9)
at async Wizard.execute (/Users/mikepurvis/ic/starrybot-discord/src/wizardware/wizard.js:76:14)
at async Wizardware.continue (/Users/mikepurvis/ic/starrybot-discord/src/wizardware/index.js:56:7)
at async Client.interactionCreate (/Users/mikepurvis/ic/starrybot-discord/src/handlers/interactionCreate.js:46:4)
TypeError: Cannot read properties of undefined (reading 'name')
at getTokenDetails (/Users/mikepurvis/ic/starrybot-discord/src/astrolabe/index.js:38:62)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Object.getConfig (/Users/mikepurvis/ic/starrybot-discord/src/commands/tokenAdd/createTokenRule.js:30:29)
at async Object.execute (/Users/mikepurvis/ic/starrybot-discord/src/utils/commands.js:46:9)
at async Wizard.execute (/Users/mikepurvis/ic/starrybot-discord/src/wizardware/wizard.js:76:14)
at async Wizardware.continue (/Users/mikepurvis/ic/starrybot-discord/src/wizardware/index.js:56:7)
at async Client.interactionCreate (/Users/mikepurvis/ic/starrybot-discord/src/handlers/interactionCreate.js:46:4)
This ticket should be done after #12
Allow an admin to edit a token rule, like changing the cw20 address or the minimum number tokens to hold before applying the role, etc.
There doesn't seem to be a wizard system in DiscordJS, so we'll need to build a small system that keeps track of a user's progress through a wizard.
We've added the env var TESTNET_ONLY but there are a few places where mainnet is checked and then testnet is automatically checked. let's ensure that all places referencing testnet and mainnet follow the rule
While StarryBot will grow into the cw20 (and cw721 and beyond) solution, we are talking to a Juno RPC and expecting cw20's from that network.
Let's make sure we're being explicit about this on the website, docs, README, etc.
To be perfectly honest, we may already have this functionality, but let's remove roles that no longer apply when someone runs /starry join
Right now the config is inside of db.js and it doesn't feel like the right place for it.
As StarryBot grows, we'd like to allow cw20 and cw721s from other Cosmos networks, or even different layer 1s. We want to make sure we're connecting to the right RPC, and it'd be best to have a cosmos_network
column that could help us connect to the right network and ensure we've Bech32-encoded the wallet address properly.
Using either buttons or some other means, let's have one step asking the user whether they'd like for StarryBot to remove all Discord roles created by it.
An admin should be able to have the choice here.
The epic in #8 covers add new token roles, but admins may want to be reminded of the rules set up.
This ticket is to create a slash command that displays all the rules for this Discord guild. It will be pulling information from the database.
Let's attempt to have this display in a table or something with a leading index. This way we can allow them to refer to particular roles they may want to edit or remove.
Old DAO DAO contracts are redirected to something like:
https://legacy.daodao.zone/dao/juno156vlvprfxc4yyu26ute4hu6tjq96pxgt5qqmm0zlt4y0khjetvhqdhmgdm
Which throws an error.
This ticket is lower priority to getting the current version of DAO DAO working, but we'll also want to make sure we can handle old DAO DAO contracts so it doesn't throw this error:
We have a fun way of signing a message, called a Saganism. It's a bunch of random words from Carl Sagan. You can see that here:
https://github.com/starryzone/starrybot-discord/blob/main/src/logic.js#L31
We haven't had any issues, but we'd like to also add in a normal seed phrase here just to ensure it can never be gamed.
So let's insert a seed phrase using near-seed-phrase
(https://www.npmjs.com/package/near-seed-phrase) because it's fast and not a huge priority.
You can see how I used this library in the near-crossword
repository.
(Note that in the crossword I'm actually using using the GitHub URL for dependency. So if you run into problems you can try that. https://github.com/mikedotexe/near-crossword/blob/e078b5e9027cac59cdcdf5a779ca64c6551c63af/package.json#L13)
So when this is complete you should have something like:
Orion's sword dispassionate extraterrestrial observer tingling of the spine network of wormholes prime number a billion trillion? Hearts of the stars vastness is bearable upset loud source damage mountain galaxy approve second census health evidence segment only through love intelligent beings tendrils of gossamer clouds Sea of Tranquility a mote of dust suspended in a sunbeam and billions upon billions upon billions upon billions upon billions upon billions upon billions.
Note that after the word "bearable" we have a 12-word seed phrase and then continue on with the rest of the Saganism.
It's possible we can look at knex migrations:
https://knexjs.org/#Migrations
When entering a non-existent cw20 shown like the screenshot below, I was expecting it to say:
No contract at that address. Probable black hole.
because of this part:
const getTokenDetails = async ({tokenAddress, network}) => {
console.log('aloha to of getTokenDetails')
let tokenDetails;
const tokenType = await getTokenType(tokenAddress);
try {
tokenDetails = await tokenType.getTokenDetails({tokenAddress, network});
} catch (e) {
// Throw a more specific error message if we can
if (e.message.includes('decoding bech32 failed')) {
throw 'Invalid address. Remember: first you copy, then you paste.';
} else if (e.message.includes('contract: not found')) {
throw 'No contract at that address. Probable black hole.';
} else if (e.message.includes('Error parsing into type')) {
throw 'That is a valid contract, but cosmic perturbations tell us it is not a cw20.';
} else {
console.warn(e.stack);
throw `Error message after trying to query ${tokenType.name}: ${e.message}`;
}
…
Saw this in the Render logs:
That's here:
starrybot-discord/src/utils/commands.js
Line 92 in 3bfe7dc
So something is up with buttons and setting the custom ID and probably the "next" assignment
It seems that this bug is specific to #84
See this workflow:
TypeError: this.error is not a function
at Wizard.execute (/Users/mike/Documents/starrybot-discord/src/wizardware/wizard.js:36:19)
at Wizardware.continue (/Users/mike/Documents/starrybot-discord/src/wizardware/index.js:46:18)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Client.messageCreate (/Users/mike/Documents/starrybot-discord/src/handlers/messageCreate.js:6:2)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
When you try to do /starry token-rule edit
you see:
No roles exist to edit!
Which is not ephemeral. Might be good to make ephemeral
We'll want to eventually have a better workflow than simply "kicking" StarryBot. We'd like to be able to cleanup anything we've done.
For instance, at the time of this writing, we automatically create the roles osmo-hodler
and juno-hodler
when the Discord bot is added. We have no opportunity to remove these roles and "clean up our campsite" when we kick the bot.
It would be advisable to have a slash command like /starry-uninstall
or something cuter that could clean up database data, roles, etc.
It seems that Anselm might have figured this out using a test shown here:
b873e85#diff-84cc21cba563e383127fa07f1be91efe212854c08719961c4095fff4a9f262c0R104
and:
b873e85#diff-84cc21cba563e383127fa07f1be91efe212854c08719961c4095fff4a9f262c0R115-R116
As we go through the UX flow, let's either use this or determine other options should we find limitations.
See the uses cases in the epic here:
#8
We'll want to have emoji responses be a common interactions, let's begin playing with this and have code that works for us.
See the uses cases in the epic here:
#8
We want current and future developers to have their own Discord bot, local daemon, local database, and local verify website.
Let's make sure to include details on how to set up a local verify website in the README, or docs, wherever we decide it fits best. I believe this is just making sure they have the repo:
https://github.com/starryzone/cosmos-webapp/tree/main
and the env var:
REACT_APP_STARRY_BACKEND
set to their localhost
Let's make sure we comb through our docs and readme and have attributed the sagan ipsum properly
I saw this in the logs:
Oct 16 09:36:49 AM Comparing 3 against 10
Oct 16 09:41:14 AM node:events:505
Oct 16 09:41:14 AM throw er; // Unhandled 'error' event
Oct 16 09:41:14 AM ^
Oct 16 09:41:14 AM
Oct 16 09:41:14 AM DiscordAPIError[50035]: Invalid Form Body
Oct 16 09:41:14 AM name[BASE_TYPE_REQUIRED]: This field is required
Oct 16 09:41:14 AM at SequentialHandler.runRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:743:15)
Oct 16 09:41:14 AM at processTicksAndRejections (node:internal/process/task_queues:96:5)
Oct 16 09:41:14 AM at async SequentialHandler.queueRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:549:14)
Oct 16 09:41:14 AM at async REST.request (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:988:22)
Oct 16 09:41:14 AM at async GuildChannelManager.create (/opt/render/project/src/node_modules/discord.js/src/managers/GuildChannelManager.js:149:18)
Oct 16 09:41:14 AM at async Client.guildCreate (/opt/render/project/src/src/handlers/guildCreate.js:42:27)
Oct 16 09:41:14 AM Emitted 'error' event on Client instance at:
Oct 16 09:41:14 AM at emitUnhandledRejectionOrErr (node:events:384:10)
Oct 16 09:41:14 AM at processTicksAndRejections (node:internal/process/task_queues:85:21) {
Oct 16 09:41:14 AM requestBody: {
Oct 16 09:41:14 AM files: undefined,
Oct 16 09:41:14 AM json: {
Oct 16 09:41:14 AM name: undefined,
Oct 16 09:41:14 AM topic: undefined,
Oct 16 09:41:14 AM type: undefined,
Oct 16 09:41:14 AM nsfw: undefined,
Oct 16 09:41:14 AM bitrate: undefined,
Oct 16 09:41:14 AM user_limit: undefined,
Oct 16 09:41:14 AM parent_id: undefined,
Oct 16 09:41:14 AM position: undefined,
Oct 16 09:41:14 AM permission_overwrites: undefined,
Oct 16 09:41:14 AM rate_limit_per_user: undefined,
Oct 16 09:41:14 AM rtc_region: undefined,
Oct 16 09:41:14 AM video_quality_mode: undefined,
Oct 16 09:41:14 AM available_tags: undefined,
Oct 16 09:41:14 AM default_reaction_emoji: undefined
Oct 16 09:41:14 AM }
Oct 16 09:41:14 AM },
Oct 16 09:41:14 AM rawError: {
Oct 16 09:41:14 AM code: 50035,
Oct 16 09:41:14 AM errors: {
Oct 16 09:41:14 AM name: {
Oct 16 09:41:14 AM _errors: [
Oct 16 09:41:14 AM {
Oct 16 09:41:14 AM code: 'BASE_TYPE_REQUIRED',
Oct 16 09:41:14 AM message: 'This field is required'
Oct 16 09:41:14 AM }
Oct 16 09:41:14 AM ]
Oct 16 09:41:14 AM }
Oct 16 09:41:14 AM },
Oct 16 09:41:14 AM message: 'Invalid Form Body'
Oct 16 09:41:14 AM },
Oct 16 09:41:14 AM code: 50035,
Oct 16 09:41:14 AM status: 400,
Oct 16 09:41:14 AM method: 'POST',
Oct 16 09:41:14 AM url: 'https://discord.com/api/v10/guilds/950805706142015538/channels'
Oct 16 09:41:14 AM }
So it looks like the "name" field is required.
I don't think we even need to create a channel, do we? Feels like we can remove this.
Let's move away from developing using the production database and instead use a local postgres instance.
Let's add a guide on how to do this properly for current and future developers.
Investigate whether we can handle an emoji reaction event when someone selects the first emoji before the others finish.
The Stargaze site has grown and changed a lot! Early on it was easy to go to the launchpad and click on a collection's image to give a url like:
https://app.stargaze.zone/launchpad/stars1lndsj2gufd292c35crv97ug2ncdcn9ys4s8e94wlxyeft6mt3k2svkwps9
Now there is a marketplace and a few different routes that people may find easier to use, given that there are so many great projects launching.
For instance, you can go to the Stargaze Marketplace, click on Bad Kids, and navigate your way to a page like this:
https://www.stargaze.zone/marketplace/stars19jq6mj84cnt9p7sagjxqf8hxtczwc8wlpuwe4sh62w45aheseues57n420
Currently starrybot is looking for a different path, like the first link above.
This shouldn't be hard, and it'll likely be a change in stargaze.js
If you were to try to paste the second link instead, it'll give you a not-great error:
So the task is to make sure we can handle whatever is the most convenient link for end users, which is no longer the Launchpad link.
Similar to this bot on Discord:
We could have a cute "activity update" for StarryBot, including a link to starrybot.xyz to help others who may want to try it. Perhaps we can also say, "try /starry help" or something
See this from the DiscordJS docs:
https://discordjs.guide/popular-topics/faq.html#how-do-i-set-both-status-and-activity-in-one-go
In the epic #8 we show a few workflows. One of them is this:
Which is an atomic task to tell the user what channels they now have access to given their new role(s)
We are replacing the starry-join
with starry join
the subcommand.
We (probably) need to remove it on the application level.
The way we added that was using this code:
const starryJoin = new SlashCommandBuilder()
.setName('starry-join')
.setDescription('Connect your account with Keplr');
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
await rest.put(
Routes.applicationCommands(client.application.id),
{ body: [starryJoin.toJSON()] },
);
So we'll want to find the opposite
The server did a quick crash-and-restart, resuming normal operation, but I've captured the log here:
Nov 8 08:04:37 AM { guildId: '950805706142015538', authorId: 'redacted' }
Nov 8 08:04:37 AM DiscordAPIError[10062]: Unknown interaction
Nov 8 08:04:37 AM at SequentialHandler.runRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:743:15)
Nov 8 08:04:37 AM at runMicrotasks (<anonymous>)
Nov 8 08:04:37 AM at processTicksAndRejections (node:internal/process/task_queues:96:5)
Nov 8 08:04:37 AM at async SequentialHandler.queueRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:549:14)
Nov 8 08:04:37 AM at async REST.request (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:988:22)
Nov 8 08:04:37 AM at async ChatInputCommandInteraction.reply (/opt/render/project/src/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:111:5)
Nov 8 08:04:37 AM at async Object.execute (/opt/render/project/src/src/utils/commands.js:236:9)
Nov 8 08:04:37 AM at async Wizard.execute (/opt/render/project/src/src/wizardware/wizard.js:76:14)
Nov 8 08:04:37 AM at async Wizardware.initiate (/opt/render/project/src/src/wizardware/index.js:31:5)
Nov 8 08:04:37 AM at async Object.execute (/opt/render/project/src/src/commands/index.js:59:9) {
Nov 8 08:04:37 AM requestBody: { files: [], json: { type: 4, data: [Object] } },
Nov 8 08:04:37 AM rawError: { message: 'Unknown interaction', code: 10062 },
Nov 8 08:04:37 AM code: 10062,
Nov 8 08:04:37 AM status: 404,
Nov 8 08:04:37 AM method: 'POST',
Nov 8 08:04:37 AM url: 'redacted'
Nov 8 08:04:37 AM }
Nov 8 08:04:37 AM node:events:505
Nov 8 08:04:37 AM throw er; // Unhandled 'error' event
Nov 8 08:04:37 AM ^
Nov 8 08:04:37 AM
Nov 8 08:04:37 AM DiscordAPIError[10062]: Unknown interaction
Nov 8 08:04:37 AM at SequentialHandler.runRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:743:15)
Nov 8 08:04:37 AM at runMicrotasks (<anonymous>)
Nov 8 08:04:37 AM at processTicksAndRejections (node:internal/process/task_queues:96:5)
Nov 8 08:04:37 AM at async SequentialHandler.queueRequest (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:549:14)
Nov 8 08:04:37 AM at async REST.request (/opt/render/project/src/node_modules/discord.js/node_modules/@discordjs/rest/dist/index.js:988:22)
Nov 8 08:04:37 AM at async ChatInputCommandInteraction.reply (/opt/render/project/src/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:111:5)
Nov 8 08:04:37 AM at async handleGuildCommands (/opt/render/project/src/src/handlers/interactionCreate.js:20:3)
Nov 8 08:04:37 AM Emitted 'error' event on Client instance at:
Nov 8 08:04:37 AM at emitUnhandledRejectionOrErr (node:events:384:10)
Nov 8 08:04:37 AM at processTicksAndRejections (node:internal/process/task_queues:85:21) {
Nov 8 08:04:37 AM requestBody: {
Nov 8 08:04:37 AM files: [],
Nov 8 08:04:37 AM json: {
Nov 8 08:04:37 AM type: 4,
Nov 8 08:04:37 AM data: {
Nov 8 08:04:37 AM content: 'There was an error while executing this command!',
Nov 8 08:04:37 AM tts: false,
Nov 8 08:04:37 AM nonce: undefined,
Nov 8 08:04:37 AM embeds: undefined,
Nov 8 08:04:37 AM components: undefined,
Nov 8 08:04:37 AM username: undefined,
Nov 8 08:04:37 AM avatar_url: undefined,
Nov 8 08:04:37 AM allowed_mentions: undefined,
Nov 8 08:04:37 AM flags: 64,
Nov 8 08:04:37 AM message_reference: undefined,
Nov 8 08:04:37 AM attachments: undefined,
Nov 8 08:04:37 AM sticker_ids: undefined,
Nov 8 08:04:37 AM thread_name: undefined
Nov 8 08:04:37 AM }
Nov 8 08:04:37 AM }
Nov 8 08:04:37 AM },
Nov 8 08:04:37 AM rawError: { message: 'Unknown interaction', code: 10062 },
Nov 8 08:04:37 AM code: 10062,
Nov 8 08:04:37 AM status: 404,
Nov 8 08:04:37 AM method: 'POST',
Nov 8 08:04:37 AM url: 'redacted'
Nov 8 08:04:37 AM }
When we lookup a user's cosmos wallet address to update their roles, we currently try to find any wallet address that gets used, in descending order of address. There are a few edgecases here:
We identified a handful of possible fixes to the DB implementation if any of these are an issue:
getCosmosHubAddressFromDiscordId
should also filter on the discord guild ID. In other words, we should allow users to associate one wallet per discord guild that they're joining fromcreated_at
column, so that we may use the one most likely to be up-to-dateSince we cannot have emoji responses and are perhaps limited in other ways we'd like to have a wizard, let's have the bot create a channel like starry-settings
where admins can use Application Commands (slash commands) without feeling like they're spamming a pubic channel.
Perhaps if an admin allowed to call /starry token-rule add
calls it in the public channel, it can reply with an ephemeral (private) message giving a link to this channel. (Or even creating the channel at that moment and then linking to it) saying, "hey buddy, let's keep this channel clean and talk about this over at #starry-discord"
This project's README is outdated. For instance, we're using Render instead of cloudflare and aren't using Winston. You also need to add the environment variable before running, for instance:
env DISCORD_TOKEN=O…4 yarn start
We'll want to update this
I think we'll need to add decimals column to the rolesSet function in db.js?
For when the cw20 flow gives us tokenInfo that includes the key decimals
related to #68
This must be a Discord API problem, as I've run the same code twice and seen the Discord API endpoint fail (with 404) and then succeed.
aloha systemChannelId undefined
starrybot has star(ry)ted.
/Users/mike/Documents/starrybot-discord/node_modules/discord.js/src/rest/RequestHandler.js:349
throw new DiscordAPIError(data, res.status, request);
^
DiscordAPIError: 404: Not Found
at RequestHandler.execute (/Users/mike/Documents/starrybot-discord/node_modules/discord.js/src/rest/RequestHandler.js:349:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async RequestHandler.push (/Users/mike/Documents/starrybot-discord/node_modules/discord.js/src/rest/RequestHandler.js:50:14)
at async ChannelManager.fetch (/Users/mike/Documents/starrybot-discord/node_modules/discord.js/src/managers/ChannelManager.js:114:18)
at async login (/Users/mike/Documents/starrybot-discord/src/discord.js:61:17) {
method: 'get',
path: '/channels',
code: 0,
httpStatus: 404,
requestData: { json: undefined, files: [] }
}
So anything using guild.systemChannelId
needs to be in a try/catch or else look for weirdness of it coming back undefined sometimes.
Similar to how we've got the Juno mainnet RPC endpoint here:
b873e85#diff-84cc21cba563e383127fa07f1be91efe212854c08719961c4095fff4a9f262c0R19
^ Actually, we should probably be clear that this is the testnet endoint
we can also have the testnet RPC endpoint as a constant somewhere. That endpoint is:
https://rpc.uni.juno.deuslabs.fi
A Juno mainnet endpoint we can use is:
https://rpc-juno.nodes.guru
Acceptance criteria for this ticket to close is when we have both endpoints as constants and there's a UX interaction such that when a user enters their cw20 token, we first look for it on mainnet. If it isn't there (we've probably used a try/catch) we'll see if it's on testnet. If it is on testnet, we'll want to have a confirmation button for the user confirming this, and make sure there's a database flag indicating we're using testnet.
This doesn't seem quite as relevant as we had hoped, but would be great to try out this testing framework:
https://cordejs.org/docs/expect
Other thoughts during our discussion would be to set up an instance of a bot on a shared server that auto-deploys on some branch like dev
or staging
and then we could have CI run these tests against that.
This ticket should be done after #12
Allow an admin to remove a token rule from the database. They would likely indicate by "row number" from the table we're displaying from the linked ticket above.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.