Backend REST API for StackSave - A DeFi savings platform integrating with Ethereum smart contracts.
- Complete REST API for goal management (create, deposit, withdraw)
- Smart contract integration using ethers.js v6
- Token faucet support for test tokens
- User goal tracking
- APY information for supported currencies
- TypeScript for type safety
- CORS enabled for Flutter mobile app integration
- Node.js with Express.js
- TypeScript for type safety
- Ethers.js v6 for blockchain interaction
- Tenderly Fork (Mainnet fork for testing)
- Node.js 18+ installed
- npm or yarn
- Install dependencies:
npm install- Configure environment variables:
Edit Backend/.env and set your private key:
PRIVATE_KEY=your_private_key_hereAll other variables (contract addresses, RPC URL) are already configured for the deployed Tenderly fork.
Development mode (with auto-reload):
npm run devProduction mode:
npm run build
npm startThe server will start on http://localhost:3000
GET /health- Check server status
POST /api/goals
Content-Type: application/json
{
"name": "Vacation Fund",
"currency": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"mode": 0,
"targetAmount": "1000",
"durationInDays": 90,
"donationPercentage": 500
}Parameters:
name- Goal namecurrency- Token address (USDC, DAI, or WETH)mode- 0 for Lite (Aave), 1 for Pro (Morpho)targetAmount- Target amount in token unitsdurationInDays- Duration in daysdonationPercentage- Donation % in basis points (500 = 5%)
Response:
{
"success": true,
"data": {
"txHash": "0x...",
"goalId": 1,
"explorer": "https://dashboard.tenderly.co/tx/0x..."
}
}GET /api/goals/:goalIdResponse:
{
"success": true,
"data": {
"goal": {
"id": 1,
"owner": "0x...",
"currency": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"mode": 0,
"targetAmount": "1000000000",
"duration": 7776000,
"donationPercentage": 500,
"depositedAmount": "500000000",
"createdAt": 1704067200,
"lastDepositTime": 1704067200,
"status": 0,
"statusText": "Active"
},
"currentValue": "502000000",
"yieldEarned": "2000000"
}
}GET /api/users/:address/goalsResponse:
{
"success": true,
"data": {
"address": "0x...",
"goals": [...],
"total": 3
}
}POST /api/goals/:goalId/deposit
Content-Type: application/json
{
"amount": "100"
}Response:
{
"success": true,
"data": {
"txHash": "0x...",
"goalId": 1,
"amount": "100",
"explorer": "https://dashboard.tenderly.co/tx/0x..."
}
}POST /api/goals/:goalId/withdrawResponse:
{
"success": true,
"data": {
"txHash": "0x...",
"goalId": 1,
"explorer": "https://dashboard.tenderly.co/tx/0x..."
}
}POST /api/goals/:goalId/withdraw-earlyNote: 2% penalty applied on early withdrawals
Response:
{
"success": true,
"data": {
"txHash": "0x...",
"goalId": 1,
"penalty": "2%",
"explorer": "https://dashboard.tenderly.co/tx/0x..."
}
}GET /api/goals/currencies/listResponse:
{
"success": true,
"data": {
"currencies": [
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"liteAPY": "3.50",
"proAPY": "5.20"
},
...
]
}
}POST /api/faucet/claim
Content-Type: application/json
{
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}Response:
{
"success": true,
"data": {
"txHash": "0x...",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"explorer": "https://dashboard.tenderly.co/tx/0x..."
}
}GET /api/faucet/can-claim/:address/:tokenAddressResponse:
{
"success": true,
"data": {
"canClaim": true,
"nextClaimTime": 0,
"nextClaimDate": null
}
}GET /api/faucet/balance/:address/:tokenAddressResponse:
{
"success": true,
"data": {
"address": "0x...",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"balance": "1000.0"
}
}All on Ethereum Mainnet (via Tenderly Fork):
- USDC:
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 - DAI:
0x6B175474E89094C44Da98b954EedeAC495271d0F - WETH:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Deployed on Tenderly Mainnet Fork:
- StackSave:
0xa9EDF625508bE4AcE93d3013B0cC4A5c3BD69F1a - Yield Router:
0x26eDe7de9AD22F05D283FAB6436E50016b60bDdF - Token Faucet:
0x81782AE5663A590A8758996a8c1a20956279f888 - USDC Vault:
0x08677300cbdF89d2C0b55Ae124eB3e7ae70b21C1 - DAI Vault:
0x4D489e902D14B05E1cC00dd53334C18eD24c9a73 - WETH Vault:
0x340E9C7B5b7BedbE15cAC0E9A98e44CF324eb511
All endpoints return consistent error responses:
{
"success": false,
"error": "Error message here"
}Common HTTP status codes:
200- Success201- Created (new goal)400- Bad Request (validation error)404- Not Found500- Internal Server Error
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> createGoal() async {
final response = await http.post(
Uri.parse('http://localhost:3000/api/goals'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'name': 'Vacation Fund',
'currency': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'mode': 0,
'targetAmount': '1000',
'durationInDays': 90,
'donationPercentage': 500,
}),
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
print('Goal created: ${data['data']['goalId']}');
print('Tx: ${data['data']['explorer']}');
}
}Future<List<dynamic>> getUserGoals(String address) async {
final response = await http.get(
Uri.parse('http://localhost:3000/api/users/$address/goals'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['data']['goals'];
}
return [];
}Backend/
├── src/
│ ├── config/
│ │ └── contracts.ts # Contract addresses and ABIs
│ ├── services/
│ │ ├── blockchain.service.ts # Ethers.js provider/wallet
│ │ └── contract.service.ts # Smart contract wrappers
│ ├── controllers/
│ │ ├── goals.controller.ts # Goal endpoints
│ │ └── faucet.controller.ts # Faucet endpoints
│ ├── routes/
│ │ ├── goals.routes.ts # Goal routes
│ │ ├── users.routes.ts # User routes
│ │ └── faucet.routes.ts # Faucet routes
│ ├── middleware/
│ │ └── errorHandler.ts # Error handling
│ └── server.ts # Main Express app
├── dist/ # Compiled JavaScript
├── .env # Environment variables
├── tsconfig.json # TypeScript config
└── package.json
- The backend uses the deployer wallet's private key for transactions
- All transactions are sent to Tenderly mainnet fork (Chain ID 8)
- Token approvals are handled automatically in deposit operations
- Goal IDs are parsed from GoalCreated events
- APY values are converted from basis points to percentages
This backend provides a complete REST API for the StackSave DeFi savings platform. It abstracts all blockchain complexity, making it easy for the Flutter mobile app to interact with smart contracts through simple HTTP requests.
Key features:
- Full smart contract integration with automatic token approvals
- Test token faucet for easy testing
- Comprehensive error handling and validation
- Explorer links in all transaction responses for transparency
- Type-safe TypeScript implementation
The API is production-ready and deployed on the Tenderly mainnet fork for realistic testing with real DeFi protocols (Aave, Morpho Blue).