Implement a cosmos-sdk chain faucet API #1

Merged
nabarun merged 7 commits from pm-add-faucet into main 2024-07-18 05:50:32 +00:00
4 changed files with 1283 additions and 27 deletions
Showing only changes of commit 15eb41a8e6 - Show all commits

2
.gitignore vendored
View File

@ -128,3 +128,5 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
db

View File

@ -8,7 +8,8 @@
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"typescript": "^5.5.3", "@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-semistandard": "^15.0.1", "eslint-config-semistandard": "^15.0.1",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
@ -16,10 +17,13 @@
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^5.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.1", "ts-node": "^10.9.2",
"@typescript-eslint/parser": "^5.47.1" "typescript": "^5.5.3"
}, },
"dependencies": { "dependencies": {
"@cosmjs/proto-signing": "^0.32.4",
"@cosmjs/stargate": "^0.32.4",
"@keyv/sqlite": "^3.6.7",
"express": "^4.19.2" "express": "^4.19.2"
}, },
"scripts": { "scripts": {

View File

@ -1,11 +1,85 @@
import express from 'express'; import express from 'express';
import Keyv from 'keyv';
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'
};
// Initialize KV store with SQLite backend
const keyv = new Keyv({
store: new KeyvSqlite({
uri: `sqlite://${config.dbPath}`,
table: 'keyv'
})
});
const app = express(); const app = express();
const port = 3000; app.use(express.json());
app.get('/', (req, res) => { async function sendTokens (recipientAddress: string, amount: string): Promise<string> {
res.send('Hello World!'); const wallet = await DirectSecp256k1Wallet.fromKey(
Buffer.from(config.faucetKey, 'hex'),
config.prefix
);
const [faucetAccount] = await wallet.getAccounts();
const client = await SigningStargateClient.connectWithSigner(config.rpcEndpoint, wallet, { gasPrice: GasPrice.fromString(`0.01${config.denom}`) });
const result = await client.sendTokens(
faucetAccount.address,
recipientAddress,
[{ denom: config.denom, amount: amount }],
'auto',
'Faucet transfer'
);
return result.transactionHash;
}
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.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' });
}
}); });
app.listen(port, () => { const PORT = process.env.PORT || 3000;
return console.log(`Express is listening at http://localhost:${port}`); app.listen(PORT, () => {
console.log(`Faucet server running on port ${PORT}`);
}); });

1214
yarn.lock

File diff suppressed because it is too large Load Diff