Learn how to create a professional REST API that fetches real-time cryptocurrency prices from Chainlink oracles and analyzes ERC-20 tokens on Ethereum
π Introduction
Ever wondered how DeFi applications get real-time cryptocurrency prices? Or how they interact with thousands of different tokens seamlessly? The answer lies in smart contract APIs that connect traditional web applications to blockchain data.
In this tutorial, I'll show you how to build a production-ready REST API that:
- π Fetches live ETH and BTC prices from Chainlink oracles
- πͺ Analyzes any ERC-20 token on Ethereum
- π Provides network diagnostics and health monitoring
- β‘ Handles errors gracefully with proper validation
By the end, you'll have a professional blockchain API that demonstrates real-world Web3 development skills.
π― What We're Building
Our API will have these endpoints:
GET /api # API information
GET /api/test/network # Network verification
GET /api/token/:address # ERC-20 token details
GET /api/chainlink/price/eth # Live ETH/USD price
GET /api/chainlink/price/btc # Live BTC/USD price
Tech Stack:
- Backend: Node.js + Express.js
- Blockchain: Web3.js for Ethereum interaction
- Data Sources: Chainlink Price Feeds + ERC-20 contracts
- Network: Ethereum Mainnet
π οΈ Prerequisites
Before we start, make sure you have:
- Node.js (v16+) installed
- Basic understanding of JavaScript and APIs
- Familiarity with blockchain concepts (helpful but not required)
- A code editor (VS Code recommended)
Don't worry if you're new to Web3 - I'll explain everything step by step!
π¦ Project Setup
Let's create our project and install dependencies:
# Create project directory
mkdir blockchain-api
cd blockchain-api
# Initialize package.json
yarn init -y
# Install dependencies
yarn add express web3 dotenv
# Install development dependencies
yarn add -D nodemon
# Create project structure
touch index.js .env .gitignore
Update your package.json
scripts:
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
Create .gitignore
:
node_modules/
.env
.DS_Store
π Environment Configuration
Create your .env
file:
# .env
WEB3_RPC_URL=https://eth.llamarpc.com
PORT=3000
π‘ Pro Tip: We're using a free RPC endpoint, but for production, consider Alchemy or Infura for better reliability and rate limits.
π» Code Implementation
Now let's build our API! Create index.js
:
require('dotenv').config();
const express = require('express');
const { Web3 } = require('web3');
const app = express();
const port = process.env.PORT || 3000;
// Initialize Web3 with RPC endpoint
const web3 = new Web3(process.env.WEB3_RPC_URL);
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// ERC-20 Token ABI (standard interface)
const ERC20_ABI = [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{"name": "", "type": "string"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{"name": "", "type": "string"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
];
// Chainlink Price Feed ABI
const CHAINLINK_PRICE_FEED_ABI = [
{
"inputs": [],
"name": "latestRoundData",
"outputs": [
{"internalType": "uint80", "name": "roundId", "type": "uint80"},
{"internalType": "int256", "name": "answer", "type": "int256"},
{"internalType": "uint256", "name": "startedAt", "type": "uint256"},
{"internalType": "uint256", "name": "updatedAt", "type": "uint256"},
{"internalType": "uint80", "name": "answeredInRound", "type": "uint80"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "description",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"stateMutability": "view",
"type": "function"
}
];
// Smart contract addresses on Ethereum Mainnet
const CONTRACTS = {
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
CHAINLINK_ETH_USD: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419',
CHAINLINK_BTC_USD: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c'
};
π Main API Endpoint
// API Information endpoint
app.get('/api', (req, res) => {
res.json({
message: 'Blockchain Smart Contract API',
version: '1.0.0',
endpoints: [
'GET /api/token/:address - Get ERC-20 token information',
'GET /api/chainlink/price/eth - Get ETH/USD price from Chainlink',
'GET /api/chainlink/price/btc - Get BTC/USD price from Chainlink',
'GET /api/test/network - Get network information'
]
});
});
π Network Verification
// Network diagnostics endpoint
app.get('/api/test/network', async (req, res) => {
try {
const blockNumber = await web3.eth.getBlockNumber();
const chainId = await web3.eth.getChainId();
const gasPrice = await web3.eth.getGasPrice();
// Verify we're on Ethereum Mainnet
const networkName = Number(chainId) === 1 ? 'Ethereum Mainnet' : `Unknown (Chain ID: ${chainId})`;
// Check contract existence
const ethContractCode = await web3.eth.getCode(CONTRACTS.CHAINLINK_ETH_USD);
const usdcContractCode = await web3.eth.getCode(CONTRACTS.USDC);
res.json({
network: {
chainId: chainId.toString(),
name: networkName,
blockNumber: blockNumber.toString(),
gasPrice: gasPrice.toString()
},
contracts: {
chainlinkEthUsd: {
address: CONTRACTS.CHAINLINK_ETH_USD,
hasCode: ethContractCode.length > 2
},
usdc: {
address: CONTRACTS.USDC,
hasCode: usdcContractCode.length > 2
}
}
});
} catch (error) {
res.status(500).json({
error: 'Network verification failed',
details: error.message
});
}
});
πͺ ERC-20 Token Analysis
// Get ERC-20 token information
app.get('/api/token/:address', async (req, res) => {
try {
const { address } = req.params;
// Validate Ethereum address format
if (!web3.utils.isAddress(address)) {
return res.status(400).json({
error: 'Invalid Ethereum address format'
});
}
// Create contract instance
const contract = new web3.eth.Contract(ERC20_ABI, address);
// Fetch token data in parallel for better performance
const [name, symbol, decimals, totalSupply] = await Promise.all([
contract.methods.name().call(),
contract.methods.symbol().call(),
contract.methods.decimals().call(),
contract.methods.totalSupply().call()
]);
res.json({
address,
name,
symbol,
decimals: decimals.toString(),
totalSupply: totalSupply.toString(),
// Convert to human-readable format
totalSupplyFormatted: (Number(totalSupply) / Math.pow(10, Number(decimals))).toLocaleString()
});
} catch (error) {
res.status(500).json({
error: 'Failed to fetch token information',
details: error.message
});
}
});
π° Chainlink Price Feeds
Here's where it gets exciting - real-time price data!
// Get ETH/USD price from Chainlink oracle
app.get('/api/chainlink/price/eth', async (req, res) => {
try {
const contract = new web3.eth.Contract(
CHAINLINK_PRICE_FEED_ABI,
CONTRACTS.CHAINLINK_ETH_USD
);
// Fetch latest price data
const [roundData, decimals, description] = await Promise.all([
contract.methods.latestRoundData().call(),
contract.methods.decimals().call(),
contract.methods.description().call()
]);
// Convert raw price to human-readable format
const price = Number(roundData.answer) / Math.pow(10, Number(decimals));
const lastUpdated = new Date(Number(roundData.updatedAt) * 1000);
res.json({
pair: 'ETH/USD',
price: price.toFixed(2),
rawPrice: roundData.answer.toString(),
decimals: decimals.toString(),
description,
lastUpdated: lastUpdated.toISOString(),
source: 'Chainlink Decentralized Oracle Network'
});
} catch (error) {
res.status(500).json({
error: 'Failed to fetch ETH/USD price',
details: error.message
});
}
});
// Get BTC/USD price from Chainlink oracle
app.get('/api/chainlink/price/btc', async (req, res) => {
try {
const contract = new web3.eth.Contract(
CHAINLINK_PRICE_FEED_ABI,
CONTRACTS.CHAINLINK_BTC_USD
);
const [roundData, decimals, description] = await Promise.all([
contract.methods.latestRoundData().call(),
contract.methods.decimals().call(),
contract.methods.description().call()
]);
const price = Number(roundData.answer) / Math.pow(10, Number(decimals));
const lastUpdated = new Date(Number(roundData.updatedAt) * 1000);
res.json({
pair: 'BTC/USD',
price: price.toFixed(2),
rawPrice: roundData.answer.toString(),
decimals: decimals.toString(),
description,
lastUpdated: lastUpdated.toISOString(),
source: 'Chainlink Decentralized Oracle Network'
});
} catch (error) {
res.status(500).json({
error: 'Failed to fetch BTC/USD price',
details: error.message
});
}
});
π Server Startup
// Start server with connection verification
app.listen(port, () => {
console.log(`π Blockchain API running on port ${port}`);
console.log(`π Endpoints available at http://localhost:${port}/api`);
// Test Web3 connection on startup
web3.eth.getBlockNumber()
.then(blockNumber => {
console.log(`β
Connected to Ethereum! Latest block: ${blockNumber}`);
})
.catch(error => {
console.error('β Web3 connection failed:', error.message);
});
});
π§ͺ Testing Your API
Start your server:
yarn dev
You should see:
π Blockchain API running on port 3000
π Endpoints available at http://localhost:3000/api
β
Connected to Ethereum! Latest block: 19785697
Test the endpoints:
1. API Information:
curl http://localhost:3000/api
2. Network Verification:
curl http://localhost:3000/api/test/network
3. Real-time ETH Price:
curl http://localhost:3000/api/chainlink/price/eth
4. USDC Token Analysis:
curl http://localhost:3000/api/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
π§ Understanding the Magic
Chainlink Oracles: Decentralized Price Feeds
Instead of relying on a single API that could fail or be manipulated, Chainlink aggregates price data from multiple independent nodes and data sources. This creates tamper-resistant, highly available price feeds that major DeFi protocols trust with billions of dollars.
ERC-20 Token Standard
Every ERC-20 token implements the same interface, which means our API can analyze any of the thousands of tokens on Ethereum using the same code. Pretty powerful!
Web3.js vs Traditional APIs
Unlike REST APIs that talk to centralized servers, Web3.js connects directly to the Ethereum blockchain. This means:
- β Censorship-resistant - No single point of failure
- β Transparent - All data is verifiable on-chain
- β Global - Same data available worldwide
- β Slower - Blockchain calls take more time than database queries
π¨ Production Considerations
Before deploying to production, consider:
Rate Limiting & Caching
// Add rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api', limiter);
Better RPC Providers
Free RPC endpoints have rate limits. For production:
- Alchemy - 300M compute units/month free
- Infura - 100k requests/day free
- QuickNode - High performance, paid plans
Error Handling & Monitoring
// Add comprehensive error logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.Console()
]
});
Input Validation
// Add request validation
const { body, validationResult } = require('express-validator');
app.get('/api/token/:address', [
param('address').isEthereumAddress().withMessage('Invalid Ethereum address')
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// ... rest of endpoint logic
});
π― What's Next?
Now that you have a working blockchain API, here are some ideas to expand it:
π₯ Advanced Features:
- Token balances - Check how much of any token an address holds
- DEX price data - Get prices from Uniswap, SushiSwap, etc.
- NFT metadata - Analyze ERC-721 and ERC-1155 tokens
- DeFi protocols - Integrate with Aave, Compound, Yearn
- WebSocket support - Real-time price streaming
- Historical data - Price charts and analytics
π‘οΈ Enterprise Features:
- Authentication - API keys and JWT tokens
- Database integration - Cache data for better performance
- Multi-chain support - Polygon, BSC, Arbitrum, Optimism
- Load balancing - Handle high traffic
- CI/CD pipeline - Automated testing and deployment
π‘ Key Takeaways
Building blockchain APIs opens up incredible possibilities:
- Real-time financial data without paying for expensive traditional APIs
- Global, censorship-resistant applications
- Composable services - build on top of existing DeFi protocols
- Transparent, verifiable data sources
The Web3 ecosystem is still early, which means there's huge opportunity for developers who understand how to bridge traditional web development with blockchain technology.
π Resources
π Deploy Your API
Ready to show the world? Deploy to:
- Vercel - Zero config Node.js hosting
- Railway - Simple deployment with databases
- Heroku - Classic PaaS with add-ons
- DigitalOcean App Platform - Affordable container hosting
What will you build next? Drop a comment with your ideas, and don't forget to β this article if it helped you get started with blockchain development!
Follow me for more Web3 development tutorials and blockchain deep dives! π
π» Complete Code
Here's the complete index.js
file for easy copy-paste:
require('dotenv').config();
const express = require('express');
const { Web3 } = require('web3');
const app = express();
const port = process.env.PORT || 3000;
// Initialize Web3 with RPC endpoint
const web3 = new Web3(process.env.WEB3_RPC_URL);
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// ERC-20 Token ABI (standard interface)
const ERC20_ABI = [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{"name": "", "type": "string"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{"name": "", "type": "string"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
];
// Chainlink Price Feed ABI
const CHAINLINK_PRICE_FEED_ABI = [
{
"inputs": [],
"name": "decimals",
"outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "description",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "latestAnswer",
"outputs": [{"internalType": "int256", "name": "", "type": "int256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "latestRoundData",
"outputs": [
{"internalType": "uint80", "name": "roundId", "type": "uint80"},
{"internalType": "int256", "name": "answer", "type": "int256"},
{"internalType": "uint256", "name": "startedAt", "type": "uint256"},
{"internalType": "uint256", "name": "updatedAt", "type": "uint256"},
{"internalType": "uint80", "name": "answeredInRound", "type": "uint80"}
],
"stateMutability": "view",
"type": "function"
}
];
// Smart contract addresses on Ethereum Mainnet
const CONTRACTS = {
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
CHAINLINK_ETH_USD: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419',
CHAINLINK_BTC_USD: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c'
};
// API Information endpoint
app.get('/api', (req, res) => {
res.json({
message: 'Blockchain Smart Contract API',
version: '1.0.0',
endpoints: [
'GET /api/token/:address - Get ERC-20 token information',
'GET /api/chainlink/price/eth - Get ETH/USD price from Chainlink',
'GET /api/chainlink/price/btc - Get BTC/USD price from Chainlink',
'GET /api/test/network - Get network information'
]
});
});
// Network diagnostics endpoint
app.get('/api/test/network', async (req, res) => {
try {
const blockNumber = await web3.eth.getBlockNumber();
const chainId = await web3.eth.getChainId();
const gasPrice = await web3.eth.getGasPrice();
// Verify we're on Ethereum Mainnet
let networkName = 'Unknown';
switch (Number(chainId)) {
case 1:
networkName = 'Ethereum Mainnet';
break;
case 11155111:
networkName = 'Sepolia Testnet';
break;
case 5:
networkName = 'Goerli Testnet';
break;
case 137:
networkName = 'Polygon Mainnet';
break;
default:
networkName = `Unknown (Chain ID: ${chainId})`;
}
// Check contract existence
const ethContractCode = await web3.eth.getCode(CONTRACTS.CHAINLINK_ETH_USD);
const btcContractCode = await web3.eth.getCode(CONTRACTS.CHAINLINK_BTC_USD);
const usdcContractCode = await web3.eth.getCode(CONTRACTS.USDC);
res.json({
network: {
chainId: chainId.toString(),
name: networkName,
blockNumber: blockNumber.toString(),
gasPrice: gasPrice.toString()
},
rpcUrl: process.env.WEB3_RPC_URL?.includes('alchemy') ? 'Alchemy' : 'Other',
contracts: {
usdc: {
address: CONTRACTS.USDC,
hasCode: usdcContractCode.length > 2,
codeLength: usdcContractCode.length
},
ethUsd: {
address: CONTRACTS.CHAINLINK_ETH_USD,
hasCode: ethContractCode.length > 2,
codeLength: ethContractCode.length
},
btcUsd: {
address: CONTRACTS.CHAINLINK_BTC_USD,
hasCode: btcContractCode.length > 2,
codeLength: btcContractCode.length
}
}
});
} catch (error) {
res.status(500).json({
error: 'Network verification failed',
details: error.message
});
}
});
// Get ERC-20 token information
app.get('/api/token/:address', async (req, res) => {
try {
const { address } = req.params;
// Validate Ethereum address format
if (!web3.utils.isAddress(address)) {
return res.status(400).json({
error: 'Invalid Ethereum address format'
});
}
// Create contract instance
const contract = new web3.eth.Contract(ERC20_ABI, address);
// Fetch token data in parallel for better performance
const [name, symbol, decimals, totalSupply] = await Promise.all([
contract.methods.name().call(),
contract.methods.symbol().call(),
contract.methods.decimals().call(),
contract.methods.totalSupply().call()
]);
res.json({
address,
name,
symbol,
decimals: decimals.toString(),
totalSupply: totalSupply.toString(),
// Convert to human-readable format
totalSupplyFormatted: (Number(totalSupply) / Math.pow(10, Number(decimals))).toLocaleString()
});
} catch (error) {
res.status(500).json({
error: 'Failed to fetch token information',
details: error.message
});
}
});
// Get ETH/USD price from Chainlink oracle
app.get('/api/chainlink/price/eth', async (req, res) => {
try {
console.log('Creating ETH/USD contract with address:', CONTRACTS.CHAINLINK_ETH_USD);
const contract = new web3.eth.Contract(
CHAINLINK_PRICE_FEED_ABI,
CONTRACTS.CHAINLINK_ETH_USD
);
console.log('Testing contract methods...');
// Try the simple latestAnswer method first
try {
const latestAnswer = await contract.methods.latestAnswer().call();
const decimals = await contract.methods.decimals().call();
const description = await contract.methods.description().call();
console.log('Simple method successful:', { latestAnswer, decimals, description });
const price = Number(latestAnswer) / Math.pow(10, Number(decimals));
res.json({
pair: 'ETH/USD',
price: price.toFixed(2),
rawPrice: latestAnswer.toString(),
decimals: decimals.toString(),
description,
method: 'latestAnswer',
source: 'Chainlink Decentralized Oracle Network',
contractAddress: CONTRACTS.CHAINLINK_ETH_USD
});
} catch (simpleError) {
console.log('Simple method failed, trying latestRoundData:', simpleError.message);
// Fallback to latestRoundData
const [roundData, decimals, description] = await Promise.all([
contract.methods.latestRoundData().call(),
contract.methods.decimals().call(),
contract.methods.description().call()
]);
console.log('latestRoundData successful:', { roundData, decimals, description });
const price = Number(roundData.answer) / Math.pow(10, Number(decimals));
const lastUpdated = new Date(Number(roundData.updatedAt) * 1000);
res.json({
pair: 'ETH/USD',
price: price.toFixed(2),
rawPrice: roundData.answer.toString(),
decimals: decimals.toString(),
description,
roundId: roundData.roundId.toString(),
lastUpdated: lastUpdated.toISOString(),
method: 'latestRoundData',
source: 'Chainlink Decentralized Oracle Network',
contractAddress: CONTRACTS.CHAINLINK_ETH_USD
});
}
} catch (error) {
console.error('ETH/USD Error details:', error);
res.status(500).json({
error: 'Failed to fetch ETH/USD price',
details: error.message,
contractAddress: CONTRACTS.CHAINLINK_ETH_USD
});
}
});
// Get BTC/USD price from Chainlink oracle
app.get('/api/chainlink/price/btc', async (req, res) => {
try {
console.log('Creating BTC/USD contract with address:', CONTRACTS.CHAINLINK_BTC_USD);
const contract = new web3.eth.Contract(
CHAINLINK_PRICE_FEED_ABI,
CONTRACTS.CHAINLINK_BTC_USD
);
const [roundData, decimals, description] = await Promise.all([
contract.methods.latestRoundData().call(),
contract.methods.decimals().call(),
contract.methods.description().call()
]);
const price = Number(roundData.answer) / Math.pow(10, Number(decimals));
const lastUpdated = new Date(Number(roundData.updatedAt) * 1000);
res.json({
pair: 'BTC/USD',
price: price.toFixed(2),
rawPrice: roundData.answer.toString(),
decimals: decimals.toString(),
description,
roundId: roundData.roundId.toString(),
lastUpdated: lastUpdated.toISOString(),
source: 'Chainlink Decentralized Oracle Network',
contractAddress: CONTRACTS.CHAINLINK_BTC_USD
});
} catch (error) {
console.error('BTC/USD Error details:', error);
res.status(500).json({
error: 'Failed to fetch BTC/USD price',
details: error.message,
contractAddress: CONTRACTS.CHAINLINK_BTC_USD
});
}
});
// Start server with connection verification
app.listen(port, () => {
console.log(`π Blockchain API running on port ${port}`);
console.log(`π Endpoints available at http://localhost:${port}/api`);
console.log(`π Web3 RPC: ${process.env.WEB3_RPC_URL || 'NOT SET'}`);
// Test Web3 connection on startup
web3.eth.getBlockNumber()
.then(blockNumber => {
console.log(`β
Connected to Ethereum! Latest block: ${blockNumber}`);
})
.catch(error => {
console.error('β Web3 connection failed:', error.message);
});
});
Package.json
{
"name": "blockchain-api",
"version": "1.0.0",
"description": "Production-ready blockchain API with Chainlink price feeds",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.18.2",
"web3": "^4.0.0",
"dotenv": "^16.0.3"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"keywords": ["blockchain", "ethereum", "web3", "api", "chainlink", "defi"],
"author": "Your Name",
"license": "MIT"
}
Environment (.env)
WEB3_RPC_URL=https://eth.llamarpc.com
PORT=3000
Gitignore (.gitignore)
node_modules/
.env
.DS_Store
Tags: #blockchain #ethereum #web3 #nodejs #javascript #api #defi #chainlink #cryptocurrency #smartcontracts
Top comments (0)