Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 119 additions & 57 deletions server.js
Original file line number Diff line number Diff line change
@@ -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}`);
});