From 6b23a27faf2d913c0d97ca242a4b268efc9248e2 Mon Sep 17 00:00:00 2001 From: Christian Lempa Date: Fri, 7 Nov 2025 17:04:44 +0300 Subject: [PATCH] Update server.js --- server.js | 176 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 57 deletions(-) diff --git a/server.js b/server.js index 1ba7b6a..c5b2685 100644 --- a/server.js +++ b/server.js @@ -1,88 +1,150 @@ +// server.js +'use strict'; + const express = require('express'); -const bodyParser = require('body-parser'); const Web3 = require('web3'); const config = require('./config.json'); -const walletPrivateKey = process.env.walletPrivateKey; -const web3 = new Web3('https://mainnet.infura.io/v3/_your_api_key_here_'); +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// --- Web3 / Accounts --------------------------------------------------------- +const INFURA_KEY = process.env.INFURA_KEY || '_your_api_key_here_'; +const RPC_URL = process.env.RPC_URL || `https://mainnet.infura.io/v3/${INFURA_KEY}`; +const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || process.env.walletPrivateKey; -web3.eth.accounts.wallet.add(walletPrivateKey); +if (!WALLET_PRIVATE_KEY) { + throw new Error('WALLET_PRIVATE_KEY is required'); +} + +const web3 = new Web3(RPC_URL); +web3.eth.accounts.wallet.clear(); +web3.eth.accounts.wallet.add(WALLET_PRIVATE_KEY); const myWalletAddress = web3.eth.accounts.wallet[0].address; +// --- Contracts --------------------------------------------------------------- const cEthAddress = config.cEthAddress; const cEthAbi = config.cEthAbi; -const cEthContract = new web3.eth.Contract(cEthAbi, cEthAddress); +const cEth = new web3.eth.Contract(cEthAbi, cEthAddress); -const app = express(); -const port = 3000; - -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); - -app.route('/protocol-balance/eth/').get((req, res) => { - cEthContract.methods.balanceOfUnderlying(myWalletAddress).call() - .then((result) => { - const balanceOfUnderlying = web3.utils.fromWei(result); - return res.send(balanceOfUnderlying); - }).catch((error) => { - console.error('[protocol-balance] error:', error); - return res.sendStatus(400); - }); +// --- Helpers ----------------------------------------------------------------- +const toBN = web3.utils.toBN; + +// Convert a decimal string to base units BN with given decimals (e.g., 8 for cTokens) +function decimalToUnits(amountStr, decimals) { + if (typeof amountStr !== 'string') amountStr = String(amountStr); + if (!/^\d+(\.\d+)?$/.test(amountStr)) { + throw new Error('Invalid numeric amount'); + } + const [ints, fracs = ''] = amountStr.split('.'); + if (fracs.length > decimals) { + // trim extra precision instead of rounding to avoid overflows on-chain + const trimmed = fracs.slice(0, decimals); + return toBN(ints).mul(toBN(10).pow(toBN(decimals))).add(toBN(trimmed.padEnd(decimals, '0'))); + } + return toBN(ints).mul(toBN(10).pow(toBN(decimals))).add(toBN(fracs.padEnd(decimals, '0'))); +} + +function isNumberLike(v) { + return typeof v === 'string' ? /^\d+(\.\d+)?$/.test(v) : typeof v === 'number' && Number.isFinite(v); +} + +// Default gas config (override via env if needed) +const GAS_PRICE_WEI = process.env.GAS_PRICE_WEI || null; // e.g., '20000000000' +const GAS_LIMIT = process.env.GAS_LIMIT || 500000; + +// --- Routes ------------------------------------------------------------------ + +// Underlying ETH balance supplied in the protocol for this wallet +app.get('/protocol-balance/eth/', async (req, res) => { + try { + const result = await cEth.methods.balanceOfUnderlying(myWalletAddress).call({ from: myWalletAddress }); + const balanceEth = web3.utils.fromWei(result, 'ether'); + return res.send(balanceEth); + } catch (error) { + console.error('[protocol-balance] error:', error); + return res.sendStatus(400); + } }); -app.route('/wallet-balance/eth/').get((req, res) => { - web3.eth.getBalance(myWalletAddress).then((result) => { - const ethBalance = web3.utils.fromWei(result); +// Wallet ETH balance +app.get('/wallet-balance/eth/', async (req, res) => { + try { + const result = await web3.eth.getBalance(myWalletAddress); + const ethBalance = web3.utils.fromWei(result, 'ether'); return res.send(ethBalance); - }).catch((error) => { + } catch (error) { console.error('[wallet-balance] error:', error); return res.sendStatus(400); - }); -}); - -app.route('/wallet-balance/ceth/').get((req, res) => { - cEthContract.methods.balanceOf(myWalletAddress).call().then((result) => { - const cTokenBalance = result / 1e8; - return res.send(cTokenBalance.toString()); - }).catch((error) => { - console.error('[wallet-ctoken-balance] error:', error); - return res.sendStatus(400); - }); + } }); -app.route('/supply/eth/:amount').get((req, res) => { - if (isNaN(req.params.amount)) { +// Wallet cETH balance (8 decimals) +app.get('/wallet-balance/ceth/', async (req, res) => { + try { + const result = await cEth.methods.balanceOf(myWalletAddress).call(); + // result is in base units (1e8) + const whole = toBN(result).div(toBN(1e8)).toString(); + const frac = toBN(result).mod(toBN(1e8)).toString().padStart(8, '0').replace(/0+$/, ''); + return res.send(frac ? `${whole}.${frac}` : whole); + } catch (error) { + console.error('[wallet-ctoken-balance] error:', error); return res.sendStatus(400); } +}); + +// Supply ETH -> mint cETH +app.get('/supply/eth/:amount', async (req, res) => { + try { + const { amount } = req.params; + if (!isNumberLike(amount)) return res.sendStatus(400); + + const valueWei = web3.utils.toWei(String(amount), 'ether'); + + const tx = cEth.methods.mint(); + const txData = { + from: myWalletAddress, + value: valueWei, + gas: GAS_LIMIT, + }; + if (GAS_PRICE_WEI) txData.gasPrice = GAS_PRICE_WEI; - cEthContract.methods.mint().send({ - from: myWalletAddress, - gasLimit: web3.utils.toHex(500000), - gasPrice: web3.utils.toHex(20000000000), - value: web3.utils.toHex(web3.utils.toWei(req.params.amount, 'ether')) - }).then((result) => { + await tx.send(txData); return res.sendStatus(200); - }).catch((error) => { + } catch (error) { console.error('[supply] error:', error); return res.sendStatus(400); - }); + } }); -app.route('/redeem/eth/:cTokenAmount').get((req, res) => { - if (isNaN(req.params.cTokenAmount)) { - return res.sendStatus(400); - } +// Redeem cETH -> underlying ETH +app.get('/redeem/eth/:cTokenAmount', async (req, res) => { + try { + const { cTokenAmount } = req.params; + if (!isNumberLike(cTokenAmount)) return res.sendStatus(400); + + // cETH has 8 decimals + const amountUnits = decimalToUnits(String(cTokenAmount), 8); - cEthContract.methods.redeem(req.params.cTokenAmount * 1e8).send({ - from: myWalletAddress, - gasLimit: web3.utils.toHex(500000), - gasPrice: web3.utils.toHex(20000000000) - }).then((result) => { + const tx = cEth.methods.redeem(amountUnits.toString()); + const txData = { + from: myWalletAddress, + gas: GAS_LIMIT, + }; + if (GAS_PRICE_WEI) txData.gasPrice = GAS_PRICE_WEI; + + await tx.send(txData); return res.sendStatus(200); - }).catch((error) => { + } catch (error) { console.error('[redeem] error:', error); return res.sendStatus(400); - }); + } }); -app.listen(port, () => console.log(`API server running on port ${port}`)); +app.listen(port, () => { + console.log(`API server running on port ${port}`); +});