Skip to content

Configuration & Setup

1. Environment Configuration

Required Environment Variables

# Blockchain RPC Endpoints
RPC_URL_MAINNET=https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY
RPC_URL_POLYGON=https://polygon-mainnet.g.alchemy.com/v2/YOUR-API-KEY
RPC_URL_ARBITRUM=https://arb-mainnet.g.alchemy.com/v2/YOUR-API-KEY
RPC_URL_BASE=https://base-mainnet.g.alchemy.com/v2/YOUR-API-KEY
RPC_URL_HEMI=https://hemi-mainnet.g.alchemy.com/v2/YOUR-API-KEY
 
# API Configuration
ABSINTHE_API_URL=https://adapters.absinthe.network
ABSINTHE_API_KEY=your-absinthe-api-key
ABS_CONFIG='{"balanceFlushIntervalHours":6,"dexProtocols":[{"type":"uniswap-v2","chainId":1,"toBlock":0,"protocols":[{"name":"pepe-weth","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":17046833,"pricingStrategy":"coingecko","token0":{"coingeckoId":"pepe","decimals":18},"token1":{"coingeckoId":"weth","decimals":18},"preferredTokenCoingeckoId":"token1"}]},{"type":"izumi","chainId":42161,"toBlock":0,"protocols":[{"name":"weth-hemitbtc","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":1276815,"pricingStrategy":"coingecko","token0":{"coingeckoId":"weth","decimals":18},"token1":{"coingeckoId":"btc","decimals":8},"preferredTokenCoingeckoId":"token1"},{"name":"vusd-weth","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":1274620,"pricingStrategy":"coingecko","token0":{"coingeckoId":"vesper-vdollar","decimals":18},"token1":{"coingeckoId":"weth","decimals":18},"preferredTokenCoingeckoId":"token1"}]}],"txnTrackingProtocols":[{"type":"printr","name":"printr-base","contractAddress":"0xbdc9a5b600e9a10609b0613b860b660342a6d4c0","factoryAddress":"0x33128a8fc17869897dce68ed026d694621f6fdfd","chainId":8453,"toBlock":0,"fromBlock":30000000},{"type":"vusd-mint","name":"vusd-mint","contractAddress":"0xFd22Bcf90d63748288913336Cd38BBC0e681e298","chainId":1,"toBlock":0,"fromBlock":22017054},{"type":"demos","name":"demos","contractAddress":"0x70468f06cf32b776130e2da4c0d7dd08983282ec","chainId":43111,"toBlock":0,"fromBlock":1993447},{"type":"voucher","name":"voucher","contractAddress":"0xa26b04b41162b0d7c2e1e2f9a33b752e28304a49","chainId":1,"toBlock":0,"fromBlock":21557766}],"stakingProtocols":[{"type":"hemi","name":"hemi-staking","contractAddress":"0x4f5e928763cbfaf5ffd8907ebbb0dabd5f78ba83","chainId":43111,"toBlock":0,"fromBlock":2025621},{"type":"vusd-bridge","name":"vusd-bridge","contractAddress":"0x5eaa10F99e7e6D177eF9F74E519E319aa49f191e","chainId":1,"toBlock":0,"fromBlock":22695105}],"univ3Protocols":[{"type":"uniswap-v3","chainId":1,"factoryAddress":"0x1f98431c8ad98523631ae4a59f267346ea31f984","factoryDeployedAt":12369621,"positionsAddress":"0xc36442b4a4522e871399cd717abdd847ab11fe88","toBlock":0,"poolDiscovery":true,"trackPositions":true,"trackSwaps":true,"pools":[{"name":"pepe-weth-0.3","contractAddress":"0x11950d141ecb863f01007add7d1a342041227b58","fromBlock":13609065,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"PEPE","decimals":18},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"},{"name":"wepe-weth-0.3","contractAddress":"0xa3c2076eb97d573cc8842f1db1ecdf7b6f77ba27","fromBlock":12376729,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"WEPE","decimals":18},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"},{"name":"usdc-weth-0.3","contractAddress":"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640","fromBlock":1620250931,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"USDC","decimals":6},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"}]}],"zebuProtocols":[{"type":"zebu","name":"zebu-new","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xD71954165a026924cA771C53164FB0a781c54C83","chainId":137,"fromBlock":61059459},{"name":"xyz-2","contractAddress":"0x3e4768dB375094b753929B7A540121d970fcb24e","chainId":137,"fromBlock":61059459},{"name":"xyz-3","contractAddress":"0x5859Ff44A3BDCD00c7047E68B94e93d34aF0fd71","chainId":8453,"fromBlock":15286409},{"name":"xyz-4","contractAddress":"0xE3EB2347bAE4E2C6905D7B832847E7848Ff6938c","chainId":137,"fromBlock":61695150},{"name":"xyz-5","contractAddress":"0x19633c8006236f6c016a34B9ca48e98AD10418B4","chainId":137,"fromBlock":64199277},{"name":"xyz-6","contractAddress":"0x0c18F35EcfF53b7c587bD754fc070b683cB9063B","chainId":8453,"fromBlock":20328800},{"name":"xyz-7","contractAddress":"0xDD4d9ae148b7c821b8157828806c78BD0FeCE8C4","chainId":137,"fromBlock":73490308}]},{"type":"zebu","name":"zebu-legacy","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xd7829F0EFC16086a91Cf211CFbb0E4Ef29D16BEE","chainId":8453,"fromBlock":27296063}]}]}'
 
# Price Data
COINGECKO_API_KEY=your-coingecko-api-key
 
# Database Configuration
DB_URL=postgresql://username:password@localhost:5432/database
REDIS_URL="redis://localhost:6379"

2. Setup Instructions

Prerequisites

  • Node.js (v20+)
  • pnpm package manager
  • PostgreSQL database
  • API keys for required services

Installation Steps

  1. Clone and Install
git clone https://github.com/AbsintheLabs/absinthe-adapters.git
cd absinthe-adapters
pnpm install
  1. Environment Setup
cp .env.example .env
# Edit .env with your configuration
  1. Configuration Setup
ABS_CONFIG='{"balanceFlushIntervalHours":6,"dexProtocols":[{"type":"uniswap-v2","chainId":1,"toBlock":0,"protocols":[{"name":"pepe-weth","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":17046833,"pricingStrategy":"coingecko","token0":{"coingeckoId":"pepe","decimals":18},"token1":{"coingeckoId":"weth","decimals":18},"preferredTokenCoingeckoId":"token1"}]},{"type":"izumi","chainId":42161,"toBlock":0,"protocols":[{"name":"weth-hemitbtc","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":1276815,"pricingStrategy":"coingecko","token0":{"coingeckoId":"weth","decimals":18},"token1":{"coingeckoId":"btc","decimals":8},"preferredTokenCoingeckoId":"token1"},{"name":"vusd-weth","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":1274620,"pricingStrategy":"coingecko","token0":{"coingeckoId":"vesper-vdollar","decimals":18},"token1":{"coingeckoId":"weth","decimals":18},"preferredTokenCoingeckoId":"token1"}]}],"txnTrackingProtocols":[{"type":"printr","name":"printr-base","contractAddress":"0xbdc9a5b600e9a10609b0613b860b660342a6d4c0","factoryAddress":"0x33128a8fc17869897dce68ed026d694621f6fdfd","chainId":8453,"toBlock":0,"fromBlock":30000000},{"type":"vusd-mint","name":"vusd-mint","contractAddress":"0xFd22Bcf90d63748288913336Cd38BBC0e681e298","chainId":1,"toBlock":0,"fromBlock":22017054},{"type":"demos","name":"demos","contractAddress":"0x70468f06cf32b776130e2da4c0d7dd08983282ec","chainId":43111,"toBlock":0,"fromBlock":1993447},{"type":"voucher","name":"voucher","contractAddress":"0xa26b04b41162b0d7c2e1e2f9a33b752e28304a49","chainId":1,"toBlock":0,"fromBlock":21557766}],"stakingProtocols":[{"type":"hemi","name":"hemi-staking","contractAddress":"0x4f5e928763cbfaf5ffd8907ebbb0dabd5f78ba83","chainId":43111,"toBlock":0,"fromBlock":2025621},{"type":"vusd-bridge","name":"vusd-bridge","contractAddress":"0x5eaa10F99e7e6D177eF9F74E519E319aa49f191e","chainId":1,"toBlock":0,"fromBlock":22695105}],"univ3Protocols":[{"type":"uniswap-v3","chainId":1,"factoryAddress":"0x1f98431c8ad98523631ae4a59f267346ea31f984","factoryDeployedAt":12369621,"positionsAddress":"0xc36442b4a4522e871399cd717abdd847ab11fe88","toBlock":0,"poolDiscovery":true,"trackPositions":true,"trackSwaps":true,"pools":[{"name":"pepe-weth-0.3","contractAddress":"0x11950d141ecb863f01007add7d1a342041227b58","fromBlock":13609065,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"PEPE","decimals":18},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"},{"name":"wepe-weth-0.3","contractAddress":"0xa3c2076eb97d573cc8842f1db1ecdf7b6f77ba27","fromBlock":12376729,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"WEPE","decimals":18},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"},{"name":"usdc-weth-0.3","contractAddress":"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640","fromBlock":1620250931,"feeTier":3000,"pricingStrategy":"internal-twap","token0":{"symbol":"USDC","decimals":6},"token1":{"symbol":"WETH","decimals":18},"preferredTokenCoingeckoId":"token1"}]}],"zebuProtocols":[{"type":"zebu","name":"zebu-new","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xD71954165a026924cA771C53164FB0a781c54C83","chainId":137,"fromBlock":61059459},{"name":"xyz-2","contractAddress":"0x3e4768dB375094b753929B7A540121d970fcb24e","chainId":137,"fromBlock":61059459},{"name":"xyz-3","contractAddress":"0x5859Ff44A3BDCD00c7047E68B94e93d34aF0fd71","chainId":8453,"fromBlock":15286409},{"name":"xyz-4","contractAddress":"0xE3EB2347bAE4E2C6905D7B832847E7848Ff6938c","chainId":137,"fromBlock":61695150},{"name":"xyz-5","contractAddress":"0x19633c8006236f6c016a34B9ca48e98AD10418B4","chainId":137,"fromBlock":64199277},{"name":"xyz-6","contractAddress":"0x0c18F35EcfF53b7c587bD754fc070b683cB9063B","chainId":8453,"fromBlock":20328800},{"name":"xyz-7","contractAddress":"0xDD4d9ae148b7c821b8157828806c78BD0FeCE8C4","chainId":137,"fromBlock":73490308}]},{"type":"zebu","name":"zebu-legacy","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xd7829F0EFC16086a91Cf211CFbb0E4Ef29D16BEE","chainId":8453,"fromBlock":27296063}]}]}'

What is ABS_CONFIG?

  • ABS_CONFIG is a single environment variable that holds your entire protocol configuration as a minified JSON string.
  • This config controls which protocols, pools, contracts, and chains your adapter will track and how it will behave.
  • It is parsed and validated at runtime using Zod schemas to ensure correctness.

How to Use It

A. Generate Your Config

  1. Start with the Example

    • The repository provides an abs_config.example.json file with a comprehensive template.
    • Edit this file to add, remove, or modify protocol sections as needed for your deployment.
  2. Minify the JSON

    • Remove all whitespace and newlines to make it a single-line string.
    • You can use tools like jsonformatter.org or run:
      • cat abs_config.example.json | jq -c
  3. Wrap in Single Quotes

    • The final string should be wrapped in single quotes ('...') when setting it in your .env file.

B. Set in Your Environment

  • Open your .env file and add or update the ABS_CONFIG line:
    • ABS_CONFIG='{"balanceFlushIntervalHours":6,"dexProtocols":[...]}'
  • Tip: If you don’t set ABS_CONFIG, the code will fall back to abs_config.example.json (see below).

How the Code Uses It

  • On startup, the code checks for ABS_CONFIG in your environment.
  • If present, it parses and validates it using the Zod schema.
  • If not present, it loads abs_config.example.json from disk.
  • If neither is available, it throws an error.

Relevant code snippet:

if (envResult.data.ABS_CONFIG) {
  configData = JSON.parse(envResult.data.ABS_CONFIG);
} else {
  // fallback to abs_config.example.json
  configData = JSON.parse(fs.readFileSync(configFilePath, 'utf8'));
}
const txnTrackingProtocolSchema = z.object({
  type: z.string(),
  name: z.string(),
  contractAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Contract address must be a valid Ethereum address'),
  factoryAddress: z
    .string()
    .regex(/^0x[a-fA-F0-9]{40}$/, 'Factory address must be a valid Ethereum address')
    .optional(),
  chainId: z.number(),
  toBlock: z.number(),
  fromBlock: z.number(),
});

Ensures required fields exist and are correctly typed. Validates contractAddress (and optional factoryAddress) are checksummed-like hex strings. Enforces numeric chainId, fromBlock, toBlock.

// After configSchema.parse → configResult.data.txnTrackingProtocols is trusted.
// We enrich each item with chain metadata + RPC URL for downstream processors.
 
const txnTrackingProtocols: ValidatedTxnTrackingProtocolConfig[] = configResult.data.txnTrackingProtocols.map(
  txnTrackingProtocol => {
    const chainId = txnTrackingProtocol.chainId;
    const chainKey = getChainEnumKey(chainId);
    if (!chainKey) {
      throw new Error(`${chainId} is not a supported chainId.`);
    }
 
    const chainName = ChainName[chainKey];
    const chainShortName = ChainShortName[chainKey];
    const chainArch = ChainType.EVM;
    const gatewayUrl = GatewayUrl[chainKey];
 
    // Selects the correct RPC URL from env for this chain (e.g. HEMI/BASE/MAINNET/POLYGON).
    const rpcUrl = getRpcUrlForChain(chainId, envResult.data);
 
    return {
      // original config fields
      type: txnTrackingProtocol.type as TxnTrackingProtocol,
      toBlock: txnTrackingProtocol.toBlock,
      fromBlock: txnTrackingProtocol.fromBlock,
      name: txnTrackingProtocol.name,
      contractAddress: txnTrackingProtocol.contractAddress,
      factoryAddress: txnTrackingProtocol.factoryAddress,
 
      // derived metadata
      chainArch,
      chainId,
      gatewayUrl,
      chainShortName,
      chainName,
      rpcUrl,
    };
  },
);

Why Use This Pattern?

  • Portability: You can deploy the same codebase to multiple environments (dev, staging, prod) with different configs, just by changing the env var.
  • No Code Changes: Add new pools, protocols, or chains by editing the config, not the code.
  • Validation: The Zod schema ensures your config is correct before the app starts.

How to Add New Config Parameters

  • Just add new fields to your config JSON and update the Zod schema if needed.
  • For example, to add a new protocol, add a new object to the relevant array (dexProtocols, stakingProtocols, etc.).
  • If you add a new top-level field, extend the Zod schema in types/schema.ts to validate it.

Best Practices

  • Always validate your config (run the app once locally to check for errors).
  • Keep a copy of your config (in a secure place, not just in .env).
  • Minify before pasting to avoid parsing errors.
  • Use version control for your config files (but don’t commit secrets).

Example Workflow

  1. Edit abs_config.example.json to your needs.

  2. Minify it.

  3. Paste into .env as ABS_CONFIG='...'.

  4. Add the necessary schema parsing function to absinthe-adapters/packages/common/src/types/schema.ts (Like shown in example above)

  5. Add the validateEnv function to absinthe-adapters/packages/common/src/utils/validateEnv.ts (Like shown in example above)

  6. Start your app.

  7. Database/Redis Setup
# For local development
docker-compose up -d postgres
 
# Or configure external database
  1. Code Generation
cd projects/uniswapv2
pnpm typegen
pnpm codegen
pnpm migration
  1. Run Development
pnpm dev

API Integration

1. Absinthe API Client

Client Configuration

const apiClient = new AbsintheApiClient({
  baseUrl: env.baseConfig.absintheApiUrl,
  apiKey: env.baseConfig.absintheApiKey,
  minTime: 90, // Rate limiting
});

Data Transmission

// Send transaction events
await apiClient.send(transactions);
 
// Send time-weighted balance events
await apiClient.send(balances);

2. Rate Limiting & Retry Logic

Bottleneck Configuration

  • Rate Limiting: 90ms minimum between requests
  • Queue Management: Automatic request queuing
  • Retry Logic: Exponential backoff for failed requests

Error Handling

try {
  await apiClient.send(data);
} catch (error) {
  console.error('API transmission failed:', error);
  // Automatic retry with exponential backoff
}