Created
May 7, 2023 13:41
-
-
Save TheJazzDev/ade773846287a8616a1450d7c7afcef8 to your computer and use it in GitHub Desktop.
thejazzdev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { discordRequest } from './utils'; | |
export const hasGuildCommands = async (appId, guildId, commands) => { | |
if (guildId === '' || appId === '') return; | |
commands.forEach((c) => hasGuildCommand(appId, guildId, c)); | |
}; | |
const hasGuildCommand = async (appId, guildId, command) => { | |
const endpoint = `applications/${appId}/guilds/${guildId}/commands`; | |
try { | |
const res = await discordRequest(endpoint, { method: 'GET' }); | |
const data: object[] = await res.data; | |
if (data) { | |
const installedNames = data.map((c) => c['name']); | |
if (!installedNames.includes(command['name'])) { | |
console.log(`[FAUCET]Installing "${command['name']}"`); | |
installGuildCommand(appId, guildId, command); | |
} | |
} | |
} catch (err) { | |
console.error(err); | |
} | |
}; | |
export const installGuildCommand = async (appId, guildId, command) => { | |
const endpoint = `applications/${appId}/guilds/${guildId}/commands`; | |
try { | |
await discordRequest(endpoint, { method: 'POST', body: command }); | |
} catch (err) { | |
console.error(err); | |
} | |
}; | |
export const CREATE_FAUCET_COMMAND = { | |
name: 'faucet', | |
description: 'Command to give testnet token', | |
options: [ | |
{ | |
name: 'address', | |
description: 'Your address', | |
type: 3, | |
required: true, | |
}, | |
], | |
type: 1, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dotenv/config'; | |
import { verifyKey } from 'discord-interactions'; | |
import axios from 'axios'; | |
import * as zksync from 'zksync-web3'; | |
import * as ethers from 'ethers'; | |
import * as fs from 'fs/promises'; | |
const zkSyncProvider = new zksync.Provider('https://testnet.era.zksync.dev'); | |
const ethProvider = ethers.getDefaultProvider('goerli'); | |
const zkSyncWallet = new zksync.Wallet(process.env.WALLET_PRIVATE_KEY, zkSyncProvider, ethProvider); | |
const ethWallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY, ethProvider); | |
interface User { | |
userId: string; | |
lastFaucet: string; | |
} | |
interface Address { | |
address: string; | |
lastFaucet: string; | |
} | |
export const verifyDiscordRequest = (clientKey) => { | |
return (req, res, buf, encoding) => { | |
const signature = req.get('X-Signature-Ed25519'); | |
const timestamp = req.get('X-Signature-Timestamp'); | |
const isValidRequest = verifyKey(buf, signature, timestamp, clientKey); | |
if (!isValidRequest) { | |
res.status(401).send('Bad request signature'); | |
throw new Error('Bad request signature'); | |
} | |
}; | |
}; | |
export const discordRequest = async (endpoint, options) => { | |
const url = 'https://discord.com/api/v10/' + endpoint; | |
if (options.body) options.body = JSON.stringify(options.body); | |
let res; | |
if (options.method && options.method === 'GET') { | |
res = await axios.get(url, { | |
headers: { | |
Authorization: `Bot ${process.env.DISCORD_TOKEN}`, | |
'Content-Type': 'application/json; charset=UTF-8', | |
}, | |
}); | |
} | |
if (options.method && options.method === 'POST') { | |
try { | |
res = await axios.post(url, { | |
headers: { | |
Authorization: `Bot ${process.env.DISCORD_TOKEN}`, | |
'Content-Type': 'application/json; charset=UTF-8', | |
}, | |
...options, | |
}); | |
} catch (error) { | |
console.log(error); | |
} | |
} | |
if (res.status !== 200) { | |
console.log(res.status); | |
throw new Error(JSON.stringify(res)); | |
} | |
return res; | |
}; | |
export const sendToken = async (userId: string, address: string) => { | |
const amount = ethers.utils.parseEther(process.env.TOKEN_AMOUNT); | |
try { | |
await zkSyncWallet.transfer({ | |
to: address, | |
token: zksync.utils.ETH_ADDRESS, | |
amount, | |
}); | |
} catch (err) { | |
console.error(err); | |
} | |
// await ethWallet.sendTransaction({ | |
// to: address, | |
// value: amount, | |
// }); | |
}; | |
const userInCooldown = async (userId: string) => { | |
const data: { users: User[]; addresses: Address[] } = await getObjectFromFile(); | |
const user = data.users.find((user) => user.userId === userId); | |
if (!user) return undefined; | |
if (Date.parse(user.lastFaucet) + 1000 * 60 * 60 * parseInt(process.env.COOLDOWN_IN_HOURS, 10) < new Date().getTime()) | |
return undefined; | |
return Date.parse(user.lastFaucet) + 1000 * 60 * 60 * parseInt(process.env.COOLDOWN_IN_HOURS, 10); | |
}; | |
const addressInCooldown = async (userAddress: string) => { | |
const data: { users: User[]; addresses: Address[] } = await getObjectFromFile(); | |
const address = data.addresses.find((address) => address.address === userAddress); | |
if (!address) return undefined; | |
if ( | |
Date.parse(address.lastFaucet) + 1000 * 60 * 60 * parseInt(process.env.COOLDOWN_IN_HOURS, 10) < | |
new Date().getTime() | |
) | |
return undefined; | |
return Date.parse(address.lastFaucet) + 1000 * 60 * 60 * parseInt(process.env.COOLDOWN_IN_HOURS, 10); | |
}; | |
export const inCooldown = async (userId: string, userAddress: string, whitelistedUsers: string[]) => { | |
const userCooldown = await userInCooldown(userId); | |
const addressCooldown = await addressInCooldown(userAddress); | |
if (userCooldown) return userCooldown; | |
// Bypass the wallet cooldown for whitelisted users | |
if (!whitelistedUsers.includes(userId) && addressCooldown) { | |
return addressCooldown; | |
} | |
return undefined; | |
}; | |
export const setCooldown = async (userId: string, userAddress: string) => { | |
const data: { users: User[]; addresses: Address[] } = await getObjectFromFile(); | |
data.users = data.users.filter((user) => user.userId !== userId); | |
data.addresses = data.addresses.filter((address) => address.address !== userAddress); | |
data.users.push({ userId, lastFaucet: new Date().toISOString() }); | |
data.addresses.push({ address: userAddress, lastFaucet: new Date().toISOString() }); | |
await writeObjectToFile(data); | |
}; | |
export const writeObjectToFile = async (data: any) => { | |
await fs.writeFile('data.json', JSON.stringify(data, null, '\t')); | |
}; | |
export const getObjectFromFile = async () => { | |
const data = await fs.readFile('data.json', { encoding: 'utf8' }); | |
return JSON.parse(data); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import express from 'express'; | |
import { InteractionResponseFlags, InteractionResponseType, InteractionType } from 'discord-interactions'; | |
import { inCooldown, sendToken, setCooldown } from '../utils'; | |
const router = express.Router(); | |
const WHITELISTED_USERS = JSON.parse(process.env.WHITELISTED_USERS); | |
router.post('/interactions', async (req, res) => { | |
const { type, id, data } = req.body; | |
if (type === InteractionType.PING) { | |
return res.send({ type: InteractionResponseType.PONG }); | |
} | |
if (type === InteractionType.APPLICATION_COMMAND) { | |
const { name } = data; | |
if (name === 'faucet' && id) { | |
const channelId = req.body.channel_id; | |
const userId = req.body.member.user.id; | |
const userAddress = req.body.data.options[0].value; | |
if (channelId !== process.env.CHANNEL_ID) { | |
return res.send({ | |
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, | |
data: { | |
content: `This command is only available in <#${process.env.CHANNEL_ID}>`, | |
flags: InteractionResponseFlags.EPHEMERAL, | |
}, | |
}); | |
} | |
if (!userAddress || !userAddress.startsWith('0x') || userAddress.length !== 42) { | |
return res.send({ | |
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, | |
data: { | |
content: `:warning: Invalid Wallet Address`, | |
flags: InteractionResponseFlags.EPHEMERAL, | |
}, | |
}); | |
} | |
const cooldown = await inCooldown(userId, userAddress, WHITELISTED_USERS); | |
if (cooldown) { | |
return res.send({ | |
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, | |
data: { | |
content: '', | |
embeds: [ | |
{ | |
fields: [], | |
color: 16711680, | |
description: `You are currently in cooldown mode.\nYou will be able to use the faucet again on <t:${( | |
cooldown / 1000 | |
).toFixed(0)}:F>`, | |
}, | |
], | |
components: [], | |
}, | |
}); | |
} | |
if (!WHITELISTED_USERS.includes(userId)) { | |
await setCooldown(userId, userAddress); | |
} | |
sendToken(userId, userAddress); | |
console.log(`Sent ${process.env.TOKEN_AMOUNT} ETH to ${userAddress} for user ${userId}`); | |
return res.send({ | |
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, | |
data: { | |
content: '', | |
embeds: [ | |
{ | |
fields: [], | |
color: 917248, | |
description: `Successfully sent 0.05 ETH to <@${userId}> with wallet address ${userAddress}`, | |
}, | |
], | |
components: [], | |
}, | |
}); | |
} | |
} | |
}); | |
export default router; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dotenv/config'; | |
import express from 'express'; | |
import { CREATE_FAUCET_COMMAND, hasGuildCommands } from './commands.js'; | |
import { verifyDiscordRequest } from './utils'; | |
import faucet from './commands/faucet'; | |
const app = express(); | |
const PORT = process.env.PORT || 5000; | |
app.use(express.json({ verify: verifyDiscordRequest(process.env.DISCORD_PUBLIC_KEY) })); | |
app.listen(PORT, () => { | |
console.log('[VastFluid Faucet Bot]Listening on port', PORT); | |
hasGuildCommands(process.env.APP_ID, process.env.GUILD_ID, [CREATE_FAUCET_COMMAND]); | |
}); | |
app.use('/', faucet); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment