A Node.js client for interacting with Chainlink's cNGN/USD price feed oracle on Base mainnet. This implementation provides real-time exchange rate monitoring, event-driven updates, and historical data queries for the Nigerian Naira to US Dollar pair.
- Overview
- Features
- Prerequisites
- Installation
- Docker Installation
- Configuration
- Usage
- API Reference
- HTTP API Endpoints
- Events
- Examples
- Security Considerations
- Troubleshooting
- Contributing
- License
This client interfaces with Chainlink's cNGN/USD price feed oracle deployed on Base mainnet at address 0xdfbb5Cbc88E382de007bfe6CE99C388176ED80aD. The oracle provides reliable, decentralized exchange rate data for the Nigerian Naira (NGN) against the US Dollar (USD).
Chainlink's cNGN oracle is part of their comprehensive price feed network, providing:
- Decentralized data aggregation from multiple sources
- High reliability with multiple node operators
- Tamper-resistant price information
- Regular updates based on market conditions
- π Real-time Price Monitoring - Continuous polling of current exchange rates
- π‘ Event-Driven Architecture - Listen to price update events in real-time
- π Historical Data Query - Access past price updates and events
- οΈ Error Handling - Robust error management and retry logic
- π Detailed Logging - Comprehensive logging for debugging and monitoring
Before you begin, ensure you have the following installed:
- Node.js v16.0.0 or higher
- npm or yarn package manager
- Git for version control
You'll also need:
- RPC endpoint (default uses public Base RPC)
- Clone the repository
git clone https://github.com/wrappedcbdc/cngn-price-oracle.git
cd cngn-price-oracle- Install dependencies
npm installor with yarn:
yarn install- Set up environment variables
cp .env.example .env- Clone and configure
git clone https://github.com/wrappedcbdc/cngn-price-oracle.git
cd cngn-price-oracle
cp .env.example .env- Build and run with Docker Compose
# Start both API server and monitoring service
docker-compose up -d
# Or start only the API server
docker-compose up -d cngn-oracle-api
# View logs
docker-compose logs -f
# Stop services
docker-compose downBuild the image:
docker build -t cngn-oracle .Run API server:
docker run -d \
--name cngn-oracle-api \
-p 3000:3000 \
-e ORACLE_CONTRACT_ADDRESS=0xdfbb5Cbc88E382de007bfe6CE99C388176ED80aD \
-e RPC_URL=https://mainnet.base.org \
cngn-oracleRun monitoring service:
docker run -d \
--name cngn-oracle-monitor \
-e ORACLE_CONTRACT_ADDRESS=0xdfbb5Cbc88E382de007bfe6CE99C388176ED80aD \
-e RPC_URL=https://mainnet.base.org \
cngn-oracle npm startUsing environment file:
docker run -d \
--name cngn-oracle-api \
-p 3000:3000 \
--env-file .env \
cngn-oracleDocker commands:
# View logs
docker logs -f cngn-oracle-api
# Stop container
docker stop cngn-oracle-api
# Remove container
docker rm cngn-oracle-api
# Execute commands inside container
docker exec -it cngn-oracle-api npm run devCreate a .env file in the root directory with the following variables:
# Required
ORACLE_CONTRACT_ADDRESS=0xdfbb5Cbc88E382de007bfe6CE99C388176ED80aD
# Optional (defaults provided)
RPC_URL=https://mainnet.base.org
POLLING_INTERVAL=30000 # Price check interval in milliseconds
API_PORT=3000 # API server portThe client is configured for Base mainnet by default. Network details:
| Parameter | Value |
|---|---|
| Network | Base Mainnet |
| Chain ID | 8453 |
| Currency | ETH |
| Oracle | Chainlink cNGN/USD |
| Contract | 0xdfbb5Cbc88E382de007bfe6CE99C388176ED80aD |
# Run the oracle client
npm start
# Run in development mode with auto-restart
npm run dev# Start the API server
npm run api # production mode
npm run dev:api # development mode with auto-restartconst { NGNUSDOracle } = require('./oracle');
const { ethers } = require('ethers');
const rpcUrl = "https://mainnet.base.org";
const provider = new ethers.JsonRpcProvider(rpcUrl);
async function main() {
// Initialize the oracle client
const oracle = new NGNUSDOracle(provider);
// Get current price
const price = await oracle.getCurrentPrice();
console.log(`Current rate: ${price.formattedPrice}`);
// Start monitoring with events
oracle.setupEventListeners();
await oracle.monitorPrice(60000); // Check every minute
}
main().catch(console.error);new NGNUSDOracle(provider)Creates a new instance of the NGN/USD oracle client.
Parameters:
provider(ethers.Provider): An ethers.js provider connected to Base mainnet
Fetches the current NGN/USD exchange rate.
Returns: Promise<Object>
{
price: 1450.50, // Numeric price
decimals: 8, // Price decimals
timestamp: 1699564800, // Unix timestamp
formattedPrice: "1 USD = 1450.50 NGN" // Human-readable format
}Retrieves detailed information about the latest price update round.
Returns: Promise<Object>
{
roundId: "18446744073709562776",
answer: 145050000000, // Raw price with decimals
startedAt: 1699564800,
updatedAt: 1699564800,
answeredInRound: "18446744073709562776"
}Initializes event listeners for real-time updates.
oracle.setupEventListeners();Starts continuous price monitoring.
Parameters:
intervalMs(number): Polling interval in milliseconds (default: 60000)
Queries past price update events.
Parameters:
fromBlock(number|string): Starting block number or 'latest'toBlock(number|string): Ending block number or 'latest'
Returns: Promise<Array> of event objects
The project includes a REST API server (implemented in api.js) that exposes HTTP endpoints for easy integration with web applications and external services.
Health check endpoint to verify API server status.
Response:
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00.000Z",
"oracle": "initialized"
}Retrieves the current NGN/USD exchange rate with comprehensive price data.
Response:
{
"success": true,
"data": {
"usdToNgn": 1450.50,
"ngnToUsd": 0.000689,
"formattedPrice": "1 USD = 1450.50 NGN",
"reversePrice": "1 NGN = 0.000689 USD",
"decimals": 8,
"description": "NGN/USD",
"timestamp": "2024-01-15T10:30:00.000Z"
}
}Fetches detailed information about the latest price update round.
Response:
{
"success": true,
"data": {
"roundId": "18446744073709562776",
"usdToNgn": 1450.50,
"ngnToUsd": 0.000689,
"formattedPrice": "1 USD = 1450.50 NGN",
"reversePrice": "1 NGN = 0.000689 USD",
"updatedAt": 1699564800,
"updatedAtFormatted": "2024-01-15T10:30:00.000Z",
"answeredInRound": "18446744073709562776"
}
}Converts a specified USD amount to NGN using the current exchange rate.
Parameters:
amount(path parameter): USD amount to convert (must be a valid number)
Example: /api/convert/usd-to-ngn/100
Response:
{
"success": true,
"data": {
"usdAmount": 100,
"ngnAmount": 145050,
"rate": 1450.50,
"formattedResult": "100 USD = 145050.00 NGN",
"timestamp": "2024-01-15T10:30:00.000Z"
}
}Converts a specified NGN amount to USD using the current exchange rate.
Parameters:
amount(path parameter): NGN amount to convert (must be a valid number)
Example: /api/convert/ngn-to-usd/145050
Response:
{
"success": true,
"data": {
"ngnAmount": 145050,
"usdAmount": 100.000000,
"rate": 0.000689,
"formattedResult": "145050 NGN = 100.000000 USD",
"timestamp": "2024-01-15T10:30:00.000Z"
}
}Retrieves historical price update events from the blockchain.
Parameters:
blocks(query parameter, optional): Number of blocks to query (default: 50)
Example: /api/history?blocks=100
Response:
{
"success": true,
"data": {
"events": [
{
"roundId": "18446744073709562776",
"price": 1450.50,
"formattedPrice": "1 USD = 1450.50 NGN",
"updatedAt": 1699564800,
"updatedAtFormatted": "2024-01-15T10:30:00.000Z",
"blockNumber": 12345678,
"transactionHash": "0x..."
}
],
"count": 25,
"blocksQueried": 100
}
}Error Response Format:
All endpoints return errors in the following format:
{
"success": false,
"error": "Error description",
"message": "Detailed error message"
}The oracle emits the following events:
Emitted when the oracle price is updated.
oracle.contract.on('AnswerUpdated', (current, roundId, updatedAt, event) => {
console.log('New price:', current);
console.log('Round ID:', roundId);
console.log('Updated at:', updatedAt);
});async function checkPrice() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const oracle = new NGNUSDOracle(provider);
const { formattedPrice } = await oracle.getCurrentPrice();
console.log(formattedPrice);
}async function analyzeHistory() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const oracle = new NGNUSDOracle(provider);
const events = await oracle.queryHistoricalEvents(1000);
events.forEach(event => {
console.log(`${event.updatedAtFormatted}: ${event.formattedPrice}`);
});
}async function priceAlerts() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const oracle = new NGNUSDOracle(provider);
const threshold = 1500; // Alert if 1 USD > 1500 NGN
oracle.contract.on('AnswerUpdated', async (current, roundId, updatedAt) => {
const decimals = await oracle.contract.decimals();
const rate = Number(current) / Math.pow(10, Number(decimals));
if (rate > threshold) {
console.log(`π¨ ALERT: NGN/USD rate exceeded ${threshold}!`);
// Send notification, email, etc.
}
});
}-
RPC Security
- Use private RPC endpoints for production
- Implement rate limiting to avoid DOS
- Monitor for unusual activity
-
Best Practices
// Good: Using environment variable for RPC
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const oracle = new NGNUSDOracle(provider);Error: Could not connect to Base mainnet
Solution: Check your internet connection and RPC endpoint availability.
Error: Contract method not found
Solution: Verify you're using the correct contract ABI.
Enable debug logging:
const provider = new ethers.JsonRpcProvider(rpcUrl);
const oracle = new NGNUSDOracle(provider);async function healthCheck() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const oracle = new NGNUSDOracle(provider);
try {
await oracle.getCurrentPrice();
console.log('β
Oracle is healthy');
} catch (error) {
console.error('β Oracle health check failed:', error);
}
}We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Install development dependencies
npm install --save-dev
# Run tests
npm test
# Run linter
npm run lint
# Build documentation
npm run docsThis project is licensed under the MIT License - see the LICENSE file for details.
- Chainlink Documentation
- Base Network Documentation
- Ethers.js Documentation
- Oracle Contract on Basescan
- Base Bridge
- Chainlink Price Feeds
- Open an issue on GitHub
- Email: contact@cngn.co
Disclaimer: This software is provided "as is" without warranty of any kind. Always test thoroughly before using in production environments.