From 6436ec1bb9072d819ac653e0cb5f57257d2f6759 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Fri, 12 Jul 2024 16:07:02 +0530 Subject: [PATCH] Read config from a config file and refactor code --- .eslintrc.json | 2 +- environments/local.toml | 11 +++ package.json | 5 +- src/index.ts | 162 +++++++++++++++++++++++++--------------- yarn.lock | 5 ++ 5 files changed, 121 insertions(+), 64 deletions(-) create mode 100644 environments/local.toml diff --git a/.eslintrc.json b/.eslintrc.json index eee46dc..d07134b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,4 +24,4 @@ } ] } -} \ No newline at end of file +} diff --git a/environments/local.toml b/environments/local.toml new file mode 100644 index 0000000..bb3457b --- /dev/null +++ b/environments/local.toml @@ -0,0 +1,11 @@ +[upstream] + rpcEndpoint = "http://localhost:26657" + chainId = "laconic_9000-1" + denom = "photon" + prefix = "laconic" + faucetKey = "" + +[server] + transferAmount = 1000000 + dailyLimit = 3000000 + dbDir = "db" diff --git a/package.json b/package.json index ddaf9cb..9887ced 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,6 @@ "name": "laconic-testnet-faucet", "version": "0.1.0", "main": "dist/index.js", - "repository": "git@github.com:deep-stack/laconic-testnet-faucet.git", - "author": "Prathamesh Musale ", "license": "UNLICENSED", "private": true, "devDependencies": { @@ -24,7 +22,8 @@ "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", "@keyv/sqlite": "^3.6.7", - "express": "^4.19.2" + "express": "^4.19.2", + "toml": "^3.0.0" }, "scripts": { "lint": "eslint .", diff --git a/src/index.ts b/src/index.ts index 7a96995..5c1632a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,45 +1,120 @@ import express from 'express'; import Keyv from 'keyv'; +import fs from 'fs'; +import path from 'path'; +import toml from 'toml'; import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; import KeyvSqlite from '@keyv/sqlite'; -// TODO: Take from a config file -const config = { - rpcEndpoint: 'https://rpc.example.com', - faucetKey: 'your faucet key here', - chainId: 'laconic_9000-1', - denom: 'photon', - amount: '1000000', - prefix: 'laconic', - dailyLimit: '3000000', - dbPath: './db/faucet_data.sqlite' -}; +const CONFIG_PATH = 'environments/local.toml'; +const FAUCET_DATA_FILE = 'faucet_data.sqlite'; -// Initialize KV store with SQLite backend -const keyv = new Keyv({ - store: new KeyvSqlite({ - uri: `sqlite://${config.dbPath}`, - table: 'keyv' - }) -}); +interface Config { + upstream: { + rpcEndpoint: string + chainId: string + denom: string + prefix: string + faucetKey: string + }, + server: { + transferAmount: string + dailyLimit: string + dbDir: string + } +} -const app = express(); -app.use(express.json()); +async function main (): Promise { + // Read and parse the configuration + const configFile = fs.readFileSync(CONFIG_PATH, 'utf-8'); + const config: Config = toml.parse(configFile); + console.log('Config: ', JSON.stringify(config, null, 2)); + + const keyv = await initKVStore(config.server.dbDir); + + const app = express(); + app.use(express.json()); + + app.post('/faucet', async (req, res) => { + const { address } = req.body; + + if (!address) { + return res.status(400).json({ error: 'Address is required' }); + } + + // Check rate limit + const now = Date.now(); + const today = new Date(now).toISOString().split('T')[0]; + const key = `${address}:${today}`; + const currentAmount = await keyv.get(key) || '0'; + + if (BigInt(currentAmount) + BigInt(config.server.transferAmount) > BigInt(config.server.dailyLimit)) { + return res.status(429).json({ error: 'Limit exceeded' }); + } + + try { + const txHash = await sendTokens(config, address, String(config.server.transferAmount)); + console.log(`Sent tokens to address: ${address}, txHash: ${txHash}`); + + // Update rate limit + await keyv.set(key, (BigInt(currentAmount) + BigInt(config.server.transferAmount)).toString(), 86400000); // 24 hours TTL + + res.json({ success: true, txHash }); + } catch (error) { + console.error('Error sending tokens:', error); + res.status(500).json({ error: 'Failed to send tokens' }); + } + }); + + const PORT = process.env.PORT || 3000; + app.listen(PORT, () => { + console.log(`Faucet server running on port ${PORT}`); + }); +} + +async function initKVStore (dbDir: string): Promise { + const dbDirPath = path.resolve(dbDir); + + // Create the database dir if it doesn't exist + fs.mkdir(dbDirPath, { recursive: true }, (err) => { + if (err) { + if (err.code !== 'EEXIST') { + console.error(`Error creating db directory: ${err.message}`); + } + } + + console.log(`Using DB directory '${dbDirPath}'`); + }); + + // Initialize KV store with SQLite backend + return new Keyv({ + store: new KeyvSqlite({ + uri: `sqlite://${path.resolve(dbDirPath, FAUCET_DATA_FILE)}`, + table: 'keyv' + }) + }); +} + +async function sendTokens (config: Config, recipientAddress: string, amount: string): Promise { + let faucetKey = config.upstream.faucetKey; + if (faucetKey.startsWith('0x')) { + faucetKey = faucetKey.slice(2); + } -async function sendTokens (recipientAddress: string, amount: string): Promise { const wallet = await DirectSecp256k1Wallet.fromKey( - Buffer.from(config.faucetKey, 'hex'), - config.prefix + Buffer.from(faucetKey, 'hex'), + config.upstream.prefix ); const [faucetAccount] = await wallet.getAccounts(); - const client = await SigningStargateClient.connectWithSigner(config.rpcEndpoint, wallet, { gasPrice: GasPrice.fromString(`0.01${config.denom}`) }); + + const client = await SigningStargateClient.connectWithSigner(config.upstream.rpcEndpoint, wallet, { gasPrice: GasPrice.fromString(`0.01${config.upstream.denom}`) }); const result = await client.sendTokens( faucetAccount.address, recipientAddress, - [{ denom: config.denom, amount: amount }], + [{ denom: config.upstream.denom, amount: amount }], 'auto', 'Faucet transfer' ); @@ -47,39 +122,6 @@ async function sendTokens (recipientAddress: string, amount: string): Promise { - const { address } = req.body; - - if (!address) { - return res.status(400).json({ error: 'Address is required' }); - } - - // Check rate limit - const now = Date.now(); - const today = new Date(now).toISOString().split('T')[0]; - const key = `${address}:${today}`; - const currentAmount = await keyv.get(key) || '0'; - - if (BigInt(currentAmount) + BigInt(config.amount) > BigInt(config.dailyLimit)) { - return res.status(429).json({ error: 'Limit exceeded' }); - } - - try { - const txHash = await sendTokens(address, config.amount); - - console.log(`Sent tokens to address ${address}`); - - // Update rate limit - await keyv.set(key, (BigInt(currentAmount) + BigInt(config.amount)).toString(), 86400000); // 24 hours TTL - - res.json({ success: true, txHash }); - } catch (error) { - console.error('Error sending tokens:', error); - res.status(500).json({ error: 'Failed to send tokens' }); - } -}); - -const PORT = process.env.PORT || 3000; -app.listen(PORT, () => { - console.log(`Faucet server running on port ${PORT}`); +main().catch(err => { + console.log(err); }); diff --git a/yarn.lock b/yarn.lock index 89a5cb0..1e6f65c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3076,6 +3076,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"