Carbon Backend, built with Nest.js, serves as a specialized backend solution for aggregating insights from Carbon smart contracts and delivering them through APIs. It provides a suite of APIs offering valuable insights, such as trading activity and history.
Before setting up Carbon Backend, ensure you have the following prerequisites:
- TimescaleDB: Ensure TimescaleDB is properly installed and running.
- Redis: Ensure Redis is properly installed and running.
- CoinGecko: Obtain an API key from CoinGecko.
- This repo is set up to use Coingecko's PRO API, if you have a free plan you will need to adjust the coingecko api url and authentication header.
- CoinMarketCap: Obtain an API key from CoinMarketCap.
- Codex: Obtain an API key from Codex.
- Python 3 (Optional): Required for the simulator.
To set up Carbon Backend, follow these steps:
-
Clone the repository:
git clone https://github.com/bancorprotocol/carbon-backend
-
Navigate to the project directory:
cd carbon-backend
-
Install dependencies:
npm install
-
Run database migrations:
After installing dependencies, run the following command to execute all migrations and prepare the database:
npm run migration:run
-
Configure environment variables:
Duplicate the
.env.example
file as.env
:cp .env.example .env
Provide the required values in the
.env
file. -
(Optional) If you wish to utilize the simulator feature, install the required Python packages:
pip install -r src/simulator/requirements.txt
-
(Optional) Database Seeding:
If you need to import data from an external database, you can use the seeding script:
a. Configure the database connection variables in your
.env
file:# External Database Configuration EXTERNAL_DATABASE_USERNAME=username EXTERNAL_DATABASE_PASSWORD=password EXTERNAL_DATABASE_HOST=host EXTERNAL_DATABASE_NAME=database_name # Local Database Configuration DATABASE_NAME=local_db_name DATABASE_USERNAME=username DATABASE_HOST=localhost DATABASE_PASSWORD=password
b. Run the seeding script:
npm run db:seed
This will import the database structure and data from the external database to your local database, excluding certain tables as configured in the seed script.
To run Carbon Backend:
npm start
On the first run, the application will sync each network to current state. This will heavily consume the RPC API urls, if you're using a free plan from Alchemy or another provider, you might be rate limited and the sync process will take some time.
If you're facing network issues when syncing the chain state, try reducing the parameters harvestEventsBatchSize
and harvestConcurrency
for each network in the deployment config on deployment.service.ts
. This will slow down the sync, but will be lighter on your network.
Access the API documentation by navigating to http://localhost:3000 in your browser.
Manually run the seed
function in src/historic-quote/historic-quote.service.ts
to populate the database with historic quotes for history-dependent functionalities such as the simulator.
To switch Carbon Backend's network for different deployments, follow these steps:
-
Replace Smart Contract Files:
- Replace files in
src/contracts/mainnet
with those from the new deployment.
- Replace files in
-
Modify CoinMarketCap Service:
- Adjust
src/coinmarketcap/coinmarketcap.service.ts
to align with the new network. - For guidance, check the CoinMarketCap API documentation.
- Adjust
-
Modify CoinGecko Service:
- Adjust
src/quote/coingecko.service.ts
to match the requirements of the new network. - Refer to the CoinGecko API documentation for assistance.
- Adjust
-
Customizing Networks and Exchange IDs:
To configure which networks are supported by Carbon Backend, make the following changes in
deployment.service.ts
andexchange-id-param.decorator.ts
.If you want to support multiple networks, update the following:
-
In
deployment.service.ts
:-
Update the
BlockchainType
andExchangeId
enums to reflect the networks you want to support:export enum BlockchainType { Ethereum = 'ethereum', Sei = 'sei-network', Celo = 'celo', Blast = 'blast', // Add or remove entries as needed } export enum ExchangeId { OGEthereum = 'ethereum', OGSei = 'sei', OGCelo = 'celo', OGBlast = 'blast', // Add or remove entries as needed }
-
Modify
initializeDeployments
with configuration for each network, includingexchangeId
,blockchainType
,rpcEndpoint
, and other network-specific values:private initializeDeployments(): Deployment[] { return [ { exchangeId: ExchangeId.OGEthereum, blockchainType: BlockchainType.Ethereum, rpcEndpoint: this.configService.get('ETHEREUM_RPC_ENDPOINT'), harvestEventsBatchSize: 2000000, harvestConcurrency: 10, multicallAddress: '0x5Eb3fa2DFECdDe21C950813C665E9364fa609bD2', startBlock: 17087000, gasToken: { name: 'Ethereum', symbol: 'ETH', address: NATIVE_TOKEN, }, }, // Repeat this block for each network ]; }
-
-
In
exchange-id-param.decorator.ts
:-
Adjust
extractExchangeId
to support dynamic handling for multiple networks:export function extractExchangeId(request: Request, exchangeIdParam?: string): ExchangeId { let exchangeId: ExchangeId; if (exchangeIdParam) { exchangeId = exchangeIdParam as ExchangeId; } else { let subdomain = request.hostname.split('.')[0]; if (subdomain.endsWith('-api')) { subdomain = subdomain.slice(0, -4); // Remove '-api' suffix } if (subdomain === 'api') { subdomain = ExchangeId.OGEthereum; // Adjust to your preferred default network } exchangeId = subdomain ? (subdomain as ExchangeId) : (ExchangeId.OGEthereum as ExchangeId); } if (!Object.values(ExchangeId).includes(exchangeId)) { throw new Error(`Invalid ExchangeId: ${exchangeId}`); } return exchangeId; }
-
If supporting only one network, simplify the configuration:
-
In
deployment.service.ts
:- Set a single
BlockchainType
andExchangeId
, and configureinitializeDeployments
for only that network.
- Set a single
-
In
exchange-id-param.decorator.ts
:-
Hardcode the
extractExchangeId
function to return only the supportedExchangeId
:export function extractExchangeId(request: Request, exchangeIdParam?: string): ExchangeId { const exchangeId = ExchangeId.OGEthereum; // Replace with the single supported ExchangeId if (exchangeIdParam && exchangeIdParam !== exchangeId) { throw new Error(`Unsupported ExchangeId: only ${exchangeId} is allowed`); } return exchangeId; }
-
-
Carbon Backend provides a command to automatically generate an Entity Relationship Diagram (ERD) from your TypeORM entities. This helps visualize the database structure and relationships between entities.
To generate the ERD:
npm run generate-erd
This command:
- Scans all TypeORM entity files
- Generates a Mermaid diagram definition
- Creates two files:
erd.mmd
: The Mermaid diagram definition fileerd.svg
: The rendered diagram in SVG format
The diagram includes:
- All entities with their properties
- Property types
- Primary key indicators
- Relationships between entities (one-to-one, one-to-many, many-to-many)
The Carbon Backend includes a notification system that sends alerts to Telegram channels.
Configure the notification system in your .env
file:
# Telegram Configuration
TELEGRAM_CHAT_ID=your-chat-id
# Google Cloud Tasks Configuration
QUEUE_NAME=bancor-alerts
QUEUE_LOCATION=europe-west2
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
GOOGLE_CLOUD_PROJECT=your-project-id
API_URL=https://your-api-url.com
# Network-Specific Thread IDs
ETHEREUM_TELEGRAM_BOT_TOKEN=your-bot-token
ETHEREUM_CARBON_THREAD_ID=123
ETHEREUM_FASTLANE_THREAD_ID=456
ETHEREUM_VORTEX_THREAD_ID=789
# Explorer URLs (for transaction links)
ETHEREUM_EXPLORER_URL=https://etherscan.io/tx/
ETHEREUM_WALLET_URL=https://app.carbondefi.xyz/wallet/
-
Locate Format Function In
TelegramService
, find the corresponding format function:private async formatExistingEventMessage( event: ExistingEvent, tokens: TokensByAddress, quotes: QuotesByAddress, deployment: Deployment, ): Promise<string> { // Modify the message format here return `Your modified message format`; }
-
Helper Methods Available
amountToken(amount: string, precision: number, token: Token)
: Format token amountsamountUSD(amount: string, precision: number, usdPrice: string, token: Token)
: Format USD amountsgetUsdRate(tokenAddress: string, quotes: QuotesByAddress, deployment: Deployment)
: Get token USD rateprintNumber(num: number, precision: number)
: Format numbers with precision
-
Update Event Types
// In src/events/event-types.ts export enum EventTypes { YourNewEvent = 'YourNewEvent', // ... other events }
-
Add Format Function
private async formatYourNewEventMessage( event: YourNewEvent, tokens: TokensByAddress, quotes: QuotesByAddress, deployment: Deployment, ): Promise<string> { return `New Event: ${event.name} Transaction: ${deployment.notifications.explorerUrl}${event.transactionHash}`; }
-
Register in Switch Statement
switch (eventType) { case EventTypes.YourNewEvent: message = await this.formatYourNewEventMessage(event, tokens, quotes, deployment); threadId = deployment.notifications.telegram.threads.yourThreadId; break; }
-
Register Services
// In NotificationService private registerEventServices() { this.eventServices.set(EventTypes.YourNewEvent, this.yourEventService); } // In NotificationController this.eventServiceMap = new Map([ [EventTypes.YourNewEvent, yourEventService], ]);
-
Configure Thread IDs
# In .env ETHEREUM_YOUR_THREAD_ID=123
NotificationService
processes events in batches from the blockchain- For each event found, it creates a task in Google Cloud Tasks queue
- Tasks trigger the
/notifications/telegram
endpoint NotificationController
retrieves the event data and passes it toTelegramService
TelegramService
formats the message based on event type and sends it to the appropriate Telegram thread
This system ensures reliable delivery of notifications even with high event volumes and provides network-specific configuration options.
Carbon Backend includes a sophisticated Merkl rewards processing system that distributes incentives to liquidity providers based on their strategy positions and market conditions. This system is designed to handle financial reward distribution with strict accuracy and temporal consistency requirements.
The Merkl rewards system implements an epoch-based reward distribution algorithm that:
- Processes campaigns in chronological epochs to ensure temporal consistency
- Maintains strategy state isolation to prevent cross-contamination between time periods
- Calculates reward eligibility based on liquidity proximity to market prices
- Applies configurable token weightings to incentivize specific assets
- Enforces campaign budget limits with automatic proportional scaling
- Uses deterministic processing for reproducible results
# REQUIRED: Security salt for deterministic seed generation
MERKL_SNAPSHOT_SALT=your-secret-salt-for-merkl-seed-generation
MERKL_SNAPSHOT_SALT
is required for secure seed generation in production. This salt ensures:
- Deterministic results: Same input data always produces the same rewards
- Security: Prevents manipulation of reward calculations through predictable randomness
- Reproducibility: Enables audit and verification of reward distributions
# OPTIONAL: Fixed seed for testing environments
MERKL_SNAPSHOT_SEED=fixed-test-seed-12345
Use Cases for Environment Variables:
Variable | Environment | Purpose | When to Use |
---|---|---|---|
MERKL_SNAPSHOT_SALT |
Production | Secure deterministic seed generation | Always required in production |
MERKL_SNAPSHOT_SEED |
Testing/Development | Fixed seed for reproducible tests | Only for testing and development |
MERKL_SNAPSHOT_SEED
in production as it overrides the secure transaction-based seed generation.
Example campaign creation:
INSERT INTO merkl_campaigns (
"blockchainType",
"exchangeId",
"pairId",
"rewardAmount",
"rewardTokenAddress",
"startDate",
"endDate",
"opportunityName",
"isActive"
) VALUES (
'ethereum',
'ethereum',
123,
'1000', -- 1000 USDT tokens
'0xdAC17F958D2ee523a2206206994597C13D831ec7', -- USDT token
'2024-01-01 00:00:00',
'2024-01-31 23:59:59',
'Ethereum Liquidity Incentives',
true
);
Required Campaign Fields:
blockchainType
: Must match deployment configurationexchangeId
: Must match deployment configurationpairId
: Valid pair ID from the pairs tablerewardAmount
: Total reward budget in token units (e.g., "100" for 100 tokens)rewardTokenAddress
: Contract address of the reward tokenstartDate
/endDate
: Campaign duration (must not overlap with existing campaigns for same pair)opportunityName
: Human-readable campaign descriptionisActive
: Should betrue
for active campaigns
- Never Cancel Active Campaigns: Once a campaign has started processing, it must not be cancelled or modified as per Merkl's requirements
- Complete Campaign Duration: Allow campaigns to run their full scheduled duration
- No Overlapping Campaigns: Ensure only one active campaign per pair at any time
- Budget Enforcement: The system automatically enforces campaign budget limits
The system uses configurable token weightings to control reward distribution:
// Example configuration in merkl-processor.service.ts
DEPLOYMENT_TOKEN_WEIGHTINGS: {
[ExchangeId.OGEthereum]: {
tokenWeightings: {
'0xdAC17F958D2ee523a2206206994597C13D831ec7': 0.7, // USDT - 70% weighting
'0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE': 1.8, // ETH - 180% weighting
},
whitelistedAssets: [], // Standard weighting tokens
defaultWeighting: 1, // Default for unlisted tokens
}
}
Weighting Effects:
0
: No rewards for this token0.5
: Half rewards compared to baseline1.0
: Standard reward amount2.0
: Double rewards compared to baseline
- Campaign Detection: System identifies active campaigns requiring processing
- Price Cache Creation: Builds comprehensive USD price cache for consistent rates
- Epoch Calculation: Determines which epochs need processing (unprocessed + recent updates)
- Temporal Isolation: Each epoch processes with its own isolated state
- Sub-Epoch Generation: Creates time-based snapshots within epochs
- Reward Distribution: Calculates rewards based on liquidity proximity to market prices
- Budget Enforcement: Applies proportional scaling if rewards exceed campaign limits
- Each epoch maintains completely isolated strategy states
- Prevents future events from affecting past reward calculations
- Ensures reproducible and auditable results
- Rewards distributed based on proximity to market price
- Closer to market price = higher reward eligibility
- Uses 2% tolerance zone around market prices
- Automatic tracking of distributed vs. total campaign amounts
- Proportional scaling when rewards would exceed budget
- Real-time budget enforcement prevents over-distribution
- Transaction-based seed generation for reproducible randomness
- Chronological event processing ensures consistent results
- All calculations use high-precision Decimal arithmetic
Processing merkl with epoch-based batching up to block X
: Processing startedFound X epoch batches to process
: Number of epochs to processProcessing epoch batch: campaign-X-epoch-Y
: Individual epoch processingSaved X sub-epoch records for epoch Y
: Successful epoch completion
No Active Campaigns Found
No active campaigns found for blockchain-exchangeId
- Solution: Verify campaigns exist in database and are marked as active
- Check: Campaign start/end dates are valid for current time
Missing Environment Variables
MERKL_SNAPSHOT_SALT environment variable is required for secure seed generation
- Solution: Add required environment variable to
.env
file - Critical: Never skip this in production environments
Budget Exceeded
Campaign X: Capping rewards from Y to Z
- Expected: System automatically scales rewards to fit budget
- Action: Monitor for frequent capping which may indicate configuration issues
- Environment Security: Protect
MERKL_SNAPSHOT_SALT
as a secret - Database Access: Restrict campaign creation to authorized personnel only
- Audit Trail: All reward calculations are logged and traceable
- Precision Requirements: Never modify Decimal arithmetic operations
- Temporal Integrity: Never bypass temporal isolation mechanisms
The system provides read-only API endpoints for reward data:
GET /:exchangeId/merkle/data
- Campaign and configuration dataGET /:exchangeId/merkle/rewards
- Reward distribution data
Note: No write endpoints are provided for security. All campaign management must be done via direct database access.
Carbon Backend is licensed under the MIT License.