name: add-swapper-integration description: Guide for adding new DEX/swapper integrations to the ShapeShift Revenue Dashboard. Covers research, API investigation, implementation patterns, and frontend integration.
Add Swapper Integration Skill
This skill guides you through adding a new swapper/DEX integration to the ShapeShift Revenue Dashboard.
Overview
The revenue dashboard tracks affiliate fees from various DEX providers. Each integration collects fee data for a specific swapper and returns it in a standardized format. The main app charges a flat 55 BPS (0.55%) fee on all swaps.
Phase 1: Research & Discovery
Your first task is to understand how ShapeShift integrates with this swapper, then research available data sources.
Research Checklist
Step 0: Investigate ShapeShift Implementation (REQUIRED FIRST!)
Before researching APIs, you MUST understand how ShapeShift tracks affiliates for this swapper.
Location: /home/sean/Repos/shapeshift/packages/swapper/src/swappers/[SwapperName]Swapper/
Files to investigate:
- Main swapper file:
[SwapperName]Swapper.ts - Quote generation:
getTradeQuote/get[SwapperName]TradeQuote.ts - Endpoints:
endpoints.ts - Utils:
utils/*.ts
What to find:
- ✅ Affiliate identifier - What does ShapeShift use to identify itself?
- Affiliate code/ID (e.g., "shapeshift", "ss-partner-123")
- Referrer parameter (e.g.,
referrer=,source=,affiliate=) - Treasury/fee collection address (e.g.,
0x123...) - Broker ID (e.g., Chainflip uses broker ID)
- ✅ How it's configured - Look for:
- Hardcoded values in code
- Environment variables
- Constants in
utils/constants.tsorendpoints.ts
- ✅ Fee structure - Understand:
- Does the swapper collect fees for us?
- Do we specify a fee recipient address?
- Is there a fee BPS we configure?
Search patterns to use:
// In /home/sean/Repos/shapeshift/packages/swapper/src/swappers/[SwapperName]Swapper/
grep -r "affiliate" .
grep -r "referrer" .
grep -r "treasury" .
grep -r "fee.*recipient" .
grep -r "source" . | grep -i param
grep -r "SHAPESHIFT" .
Example findings from existing integrations:
THORChain/Maya - Treasury Address
Location: swappers/ThorchainSwapper/utils/constants.ts
export const DEFAULT_AFFILIATE_FEE_BPS = "55";
export const DAO_TREASURY_THORCHAIN =
"thor1xmaggkcln5m5fnha2780xrdrulmplvfrz6wj3l";
→ Conclusion: THORChain swaps specify a treasury address to receive fees
ZRX (0x) - API Key
Location: swappers/ZrxSwapper/endpoints.ts
'0x-api-key': getConfig().VITE_ZRX_API_KEY
→ Conclusion: 0x uses an API key that identifies ShapeShift as the integrator
Relay - Referrer Parameter
Location: swappers/RelaySwapper/getTradeQuote.ts
referrer: "0xSHAPESHIFT_ADDRESS";
→ Conclusion: Relay uses a referrer address parameter
Chainflip - Broker ID
Location: swappers/ChainflipSwapper/utils/constants.ts
export const CHAINFLIP_BROKER_ID = "shapeshift";
→ Conclusion: Chainflip uses a broker ID string
Output from Step 0:
Document your findings before proceeding:
## ShapeShift Integration Analysis for [Swapper Name]
**Affiliate Identifier Type:** [Treasury Address / API Key / Referrer Param / Broker ID / Other]
**Identifier Value:** [The actual value used, e.g., "thor1x..." or "shapeshift"]
**Location in Code:** [File path and line number]
**How It's Used:** [Brief description of how the identifier is passed to the swapper]
**Fee Configuration:** [Any fee BPS or parameters configured]
**Key Code Snippet:**
```typescript
// Paste relevant code here
```
Conclusion: [How this affects our revenue tracking approach]
---
#### Step 1: Explore Available APIs
- Search for official API documentation
- Look for affiliate/partner APIs
- Check for transaction/trade history APIs
- Look for analytics or reporting APIs
- Check if there's a GraphQL endpoint
- **Use the affiliate identifier from Step 0 to test filtering!**
**⚠️ CRITICAL - API Cost Requirements:**
- ✅ **MUST be completely FREE** with no time limits
- ❌ **NO paid APIs**
- ❌ **NO time-limited free trials** (e.g., "14-day free trial then $99/month")
- ✅ Free tier with reasonable rate limits is acceptable
- ✅ Free tier that requires API key but no payment is acceptable
- If no free API exists, proceed to on-chain analysis (Step 3)
#### Step 2: Identify Data Availability
For each API you find, determine:
- ✅ Can filter by affiliate/referrer ID? **Use the identifier from Step 0!**
- ✅ Can filter by time range (start/end timestamps or dates)?
- ✅ What fee data is available?
- Direct fee amounts in crypto?
- Fee amounts in USD?
- Volume data we can calculate fees from?
- ✅ What format is the data in?
- **Decimal format** (e.g., "2.5" USDC, "0.001" ETH)?
- **Base units** (e.g., "2500000" wei USDC, "1000000000000000" wei ETH)?
- ✅ Pagination support?
- ✅ Rate limits?
- ✅ Requires API key?
**⚠️ CRITICAL - Performance Requirements:**
- ✅ **Must retrieve 30 days of data in under 15 seconds**
- ✅ **Batch queries preferred** (get all fees for date range in one/few calls)
- ❌ **Avoid per-transaction queries** (if 100 txs = 100 API calls, too slow!)
- ✅ Pagination is acceptable if page size is reasonable (e.g., 100-1000 items per page)
- ✅ GraphQL batch queries are good
- ❌ REST endpoints requiring one call per tx/day are problematic
**Performance Estimation:**
- You don't need to fetch full 30 days - estimate based on test queries
- Assume 50-200 transactions over 30 days (typical volume)
- Test a small query and calculate: `estimated_time = (api_response_time × number_of_calls_needed)`
- Examples:
- ✅ Single API call for date range = 1 second → **GOOD**
- ✅ Paginated (5 pages @ 200ms each) = 1 second → **GOOD**
- ❌ Per-transaction API (150 txs @ 100ms each) = 15 seconds → **BORDERLINE/BAD**
- ❌ Per-day + per-transaction lookups (30 days × 5 txs × 200ms) = 30 seconds → **TOO SLOW**
- Mark slow approaches as a major drawback in your research summary
#### Step 3: On-Chain Analysis (if no suitable API)
If no good API exists, investigate on-chain options:
- Use the **treasury address from Step 0** (if applicable)
- What contracts send fees to this address?
- Are there events/logs we can filter?
- Can we use block explorers (Etherscan, Blockscout)?
- What transaction data is available?
**RPC Providers:**
- ✅ **Check `/home/sean/Repos/shapeshift/.env` for RPC proxies**
- ShapeShift has private RPC endpoints configured for most chains
- These are **faster and more reliable** than public RPCs
- Look for env vars like `VITE_ETHEREUM_NODE_URL`, `VITE_POLYGON_NODE_URL`, etc.
- If available for your target chain, prefer these over public RPCs
#### Step 4: Test Your Findings
- Make sample API requests **using the affiliate identifier from Step 0**
- Verify data structure matches documentation
- Test filtering by date range
- Confirm fee data accuracy
- Check if crypto amounts AND/OR USD values are provided
- **CRITICAL: Test if amounts are in decimal or base units!**
### Integration Approaches (Ranked by Preference)
After research, you should determine which approach fits best:
#### **Approach 1: Direct Revenue/Fee API** ⭐⭐⭐⭐⭐
**Examples:** THORChain, Maya Protocol
**Characteristics:**
- Dedicated affiliate fee endpoint
- Returns raw crypto amounts (usually in base units)
- Clean, purpose-built API
- Easy time filtering
**Strengths:**
- Most accurate
- Easiest to implement
- **Best performance** - single/few API calls for entire date range
- Direct fee data
**Performance:** ⚡⚡⚡ Excellent (typically <2 seconds for 30 days)
**USD Enrichment:** ✅ **Use `enrichFeesWithUsdPrices()`**
- Integration returns crypto `amount` only
- Enrichment calculates USD value using **CURRENT** prices
- Most accurate revenue tracking
**Amount Format:** Usually base units (no conversion needed)
**Example Structure:**
```typescript
// API returns: { fees: [{ amount: "123456789", txId: "...", timestamp: 1234567890 }] }
const fees = data.fees.map(fee => ({
chainId: 'cosmos:thorchain-1',
assetId: 'cosmos:thorchain-1/slip44:931',
service: 'swappername',
txHash: fee.txId,
timestamp: fee.timestamp,
amount: fee.amount, // Already in base units (smallest denomination)
// No amountUsd - let enrichment handle it
}))
return enrichFeesWithUsdPrices(fees)
Approach 2: Transaction API with Fee Calculation ⭐⭐⭐⭐
Examples: Bebop, 0x (ZRX)
Characteristics:
- Trade/transaction history API
- May include fee BPS or calculated fees
- Sometimes provides historical USD values
- Can filter by affiliate/source
- Often returns amounts in DECIMAL format (needs conversion)
Strengths:
- Good data availability
- Usually has volume data
- Can verify fee calculations
- Good performance - batch queries with pagination
Drawbacks:
- May need to calculate fees ourselves (volume * BPS)
- Historical USD values (if provided) are less accurate
- Requires decimal-to-base-unit conversion
Performance: ⚡⚡⚡ Excellent (typically <3 seconds for 30 days with pagination)
USD Enrichment: ✅ Use enrichFeesWithUsdPrices()
- Integration returns crypto
amount(primary) - May optionally include
amountUsd(historical) as backup - Enrichment recalculates using CURRENT prices
- If enrichment fails, falls back to historical
amountUsd
Amount Format: ⚠️ Usually DECIMAL - requires conversion
Example Structure:
// API returns: { trades: [{ volume: 1000, partnerFeeBps: 55, partnerFeeNative: "2.5", token: "0x...", ... }] }
for (const trade of data.trades) {
const chainId = `eip155:${trade.chainId}`;
const assetId = `${chainId}/erc20:${trade.token}`;
// ⚠️ CRITICAL: API returns DECIMAL amount ("2.5" USDC)
// Must convert to base units (wei): "2.5" → "2500000" (for 6 decimals)
const decimals = await assetDataService.getAssetDecimals(assetId);
const amountInWei = decimalToBaseUnit(trade.partnerFeeNative, decimals);
fees.push({
chainId,
assetId,
service: "swappername",
txHash: trade.txHash,
timestamp: trade.timestamp,
amount: amountInWei, // Now in base units (wei)
amountUsd: trade.volumeUsd
? String(trade.volumeUsd * (trade.partnerFeeBps / 10000))
: undefined,
});
}
return enrichFeesWithUsdPrices(fees);
Approach 3: API with Current USD Pricing ⭐⭐⭐⭐
Examples: Relay
Characteristics:
- API already calculates USD using current/live prices
- Returns
amountUsdCurrent(not historical) - Also includes crypto amounts (may be decimal or base units)
Strengths:
- USD values already accurate
- No need for price enrichment
- Reduces API calls
- Good performance - batch queries
Drawbacks:
- Must verify API actually uses current prices (not historical)
- Less common pattern
- May still need decimal conversion for amounts
Performance: ⚡⚡⚡ Excellent (typically <3 seconds for 30 days)
USD Enrichment: ❌ Do NOT use enrichFeesWithUsdPrices()
- Integration returns both
amountANDamountUsd - The
amountUsdis already using CURRENT prices from their API - Return fees as-is without enrichment
Amount Format: Check API - may need conversion
Example Structure:
// API returns: { fees: [{ amount: "123456789", amountUsdCurrent: "100.50", ... }] }
const fees = data.fees.map(fee => ({
chainId: config.chainId,
assetId: buildAssetId(...),
service: 'swappername',
txHash: fee.txHash,
timestamp: fee.timestamp,
amount: fee.amount, // Check if in base units or decimal!
amountUsd: fee.amountUsdCurrent, // Already using current prices
}))
return fees // No enrichment!
⚠️ CRITICAL: You must TEST the API to confirm:
- It provides current USD values (not historical)
- The amount format (decimal vs base units)
Approach 4: USD-Only Data ⭐⭐⭐
Examples: Chainflip
Characteristics:
- API only provides USD values
- No crypto amount available
- Must reverse-engineer crypto amount (for stablecoins, use 1:1 ratio)
Strengths:
- Simple USD tracking
- Works when crypto amounts unavailable
- Good performance - batch queries
Drawbacks:
- Less accurate (historical USD values)
- Can't verify with on-chain data
- Loses native token information
Performance: ⚡⚡⚡ Excellent (typically <3 seconds for 30 days)
USD Enrichment: ❌ Do NOT use enrichFeesWithUsdPrices()
- Integration returns synthesized
amount(calculated from USD) ANDamountUsd - The
amountUsdis historical (from when swap occurred) - No enrichment possible - we don't have real crypto amounts
Amount Format: Synthesized in base units
Example Structure:
// API returns: { swaps: [{ affiliateFeeValueUsd: "10.50", ... }] }
// Chainflip only provides USD - must synthesize crypto amount
const fees = data.swaps.map((swap) => {
// Synthesize USDC amount from USD value (1:1 ratio for stablecoins)
// $10.50 USD = 10.50 USDC = 10,500,000 wei (6 decimals)
const usdValue = swap.affiliateFeeValueUsd;
const usdcDecimals = 6;
const usdcWei = decimalToBaseUnit(usdValue, usdcDecimals);
return {
chainId: "eip155:1",
assetId: "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
service: "swappername",
txHash: "",
timestamp: swap.timestamp,
amount: usdcWei, // Synthesized from USD
amountUsd: usdValue, // Historical USD value
};
});
return fees; // No enrichment - we don't have real amounts
Approach 5: On-Chain Analysis ⭐⭐
Examples: Portals
Characteristics:
- No suitable API available
- Scrape blockchain explorers for events/logs
- Filter transfers to treasury address
- Complex event decoding
Strengths:
- Works when no API exists
- Most trustless/verifiable
- Direct on-chain data
Drawbacks:
- Most complex implementation
- Slower performance - multiple API calls per transaction
- Requires block number lookups
- Explorer API rate limits
- Fallback fee calculations needed
Performance: ⚡⚡ Moderate (typically 5-10 seconds for 30 days, can be slower)
- WARNING: If approach requires looking up individual tx details, it can be too slow
- Per-tx lookups: 150 txs × 100ms = 15 seconds (borderline)
- Must implement carefully with batching/caching to stay under 15s limit
- TIP: Use ShapeShift's private RPC proxies (see
/home/sean/Repos/shapeshift/.env) for better performance than public RPCs
USD Enrichment: ✅ Use enrichFeesWithUsdPrices()
- Integration returns crypto
amountfrom on-chain transfers - Enrichment calculates USD value using CURRENT prices
Amount Format: On-chain data is always in base units (wei)
Example Structure:
// Scrape explorer for Portal events
const events = await getPortalEventsFromExplorer(
config,
startTimestamp,
endTimestamp,
);
// For each event, look up the actual token transfer to treasury
const fees = await Promise.all(
events.map(async (event) => {
const transfer = await getFeeTransferFromExplorer(config, event.txHash);
if (transfer) {
return {
chainId: config.chainId,
assetId: buildAssetId(config.chainId, transfer.token),
service: "swappername",
txHash: event.txHash,
timestamp: event.timestamp,
amount: transfer.amount, // Already in base units (from blockchain)
};
} else {
// Fallback: calculate from input amount
return {
chainId: config.chainId,
assetId: buildAssetId(config.chainId, event.inputToken),
service: "swappername",
txHash: event.txHash,
timestamp: event.timestamp,
amount: calculateFallbackFee(event.inputAmount), // 55 BPS of input
};
}
}),
);
return enrichFeesWithUsdPrices(fees);
Output Phase 1: Research Summary
After completing your research, provide a summary report:
## Research Summary for [Swapper Name]
### ShapeShift Integration (Step 0)
**Affiliate Identifier:** [Type and value]
**Found in ShapeShift code:** [File path]
**How fees are tracked:** [Treasury address / API key / Referrer param / etc.]
**Key insight:** [Brief explanation of how this affects our revenue tracking]
---
### Available Options
1. **Option 1: [Name]**
- **Type:** [Direct Revenue API / Transaction API / etc.]
- **Endpoint:** [URL]
- **Filtering:** [Time range support, affiliate ID, etc.]
- **Data Provided:** [Fee amounts, volumes, USD values]
- **Amount Format:** [Decimal / Base Units / Unknown - needs testing]
- **Cost:** [Free / Paid / Free trial only] ⚠️ Must be FREE!
- **API Key Required:** [Yes/No]
- **Performance Estimate:** [e.g., "1 call for 30 days ≈ 1s", "5 pages × 200ms ≈ 1s", "150 txs × 100ms ≈ 15s"]
- **Strengths:** [List]
- **Drawbacks:** [List - include performance issues if slow]
2. **Option 2: [Name]**
- [Same structure...]
### Recommendation
**Recommended Approach:** Option X - [Approach Name]
**Reasoning:**
- [Why this is best for our use case]
- [Alignment with our requirements]
- [Accuracy considerations]
**Performance:** [Estimated time to fetch 30 days] ⚡⚡⚡ / ⚡⚡ / ⚡
- [Brief justification - batch queries, pagination, per-tx, etc.]
- ✅ Meets 15-second requirement / ⚠️ Borderline / ❌ Too slow
**Amount Format:** [Decimal/Base Units] - [Conversion needed: Yes/No]
**USD Enrichment Strategy:**
- ✅ Use enrichFeesWithUsdPrices() / ❌ Return as-is
- **Rationale:** [Explain based on data available]
**Implementation Complexity:** [Low/Medium/High]
**Proceed?** [Wait for user confirmation before implementing]
Phase 2: Implementation
Once the user approves your recommended approach, proceed with implementation.
Step 1: Create Integration Directory
Location: apps/revenue-api/src/affiliateRevenue/[swappername]/
Required Files:
index.ts- ExportgetFeesfunction[swappername].ts- Main implementationtypes.ts- TypeScript types for API responsesconstants.ts- API URLs, keys, and affiliate identifier from Step 0utils.ts- Helper functions (if needed)
In constants.ts, define the affiliate identifier you found in Step 0:
// Example: Treasury address (THORChain/Maya)
export const DAO_TREASURY = "0x..."; // or 'thor1x...'
// Example: Broker ID (Chainflip)
export const SHAPESHIFT_BROKER_ID = "shapeshift";
// Example: Referrer address (Relay)
export const SHAPESHIFT_REFERRER = "0x...";
// Example: API key (0x)
export const SWAPPERNAME_API_KEY = process.env.SWAPPERNAME_API_KEY ?? "";
Step 2: Implement Core Integration
Standard getFees Pattern:
Every integration MUST follow this pattern:
export const getFees = async (
startTimestamp: number,
endTimestamp: number,
): Promise<Fees[]> => {
const startTime = Date.now();
const threshold = getCacheableThreshold();
const { cacheableDates, recentStart } = splitDateRange(
startTimestamp,
endTimestamp,
threshold,
);
// === CACHE LOOKUP ===
const cachedFees: Fees[] = [];
const datesToFetch: string[] = [];
let cacheHits = 0;
let cacheMisses = 0;
for (const date of cacheableDates) {
const cached = tryGetCachedFees("swappername", chainId, date);
if (cached) {
cachedFees.push(...cached);
cacheHits++;
} else {
datesToFetch.push(date);
cacheMisses++;
}
}
// === FETCH MISSING DATES ===
const newFees: Fees[] = [];
if (datesToFetch.length > 0) {
const fetchStart = getDateStartTimestamp(datesToFetch[0]);
const fetchEnd = getDateEndTimestamp(datesToFetch[datesToFetch.length - 1]);
const fetched = await fetchFeesFromAPI(fetchStart, fetchEnd);
const feesByDate = groupFeesByDate(fetched);
for (const date of datesToFetch) {
saveCachedFees("swappername", chainId, date, feesByDate[date] || []);
}
newFees.push(...fetched);
}
// === FETCH RECENT (UNCACHEABLE) DATA ===
const recentFees: Fees[] = [];
if (recentStart !== null) {
recentFees.push(...(await fetchFeesFromAPI(recentStart, endTimestamp)));
}
// === LOGGING ===
const totalFees = cachedFees.length + newFees.length + recentFees.length;
const duration = Date.now() - startTime;
console.log(
`[swappername] Total: ${totalFees} fees in ${duration}ms | Cache: ${cacheHits} hits, ${cacheMisses} misses`,
);
// === USD ENRICHMENT (conditional) ===
const allFees = [...cachedFees, ...newFees, ...recentFees];
// ✅ If using Approach 1, 2, or 5:
return enrichFeesWithUsdPrices(allFees);
// ❌ If using Approach 3 or 4:
return allFees;
};
Key Implementation Notes:
Amount Normalization (CRITICAL!):
// ⚠️ ALWAYS store amounts in smallest unit (wei, satoshis, etc.) // If API returns BASE UNITS (e.g., "2500000" for 2.5 USDC with 6 decimals): const amount = fee.amount; // Use directly // If API returns DECIMAL format (e.g., "2.5" for 2.5 USDC): const decimals = await assetDataService.getAssetDecimals(assetId); const amount = decimalToBaseUnit(fee.amount, decimals); // Convert to wei // Example conversions: // - "2.5" USDC (6 decimals) → "2500000" wei // - "0.001" ETH (18 decimals) → "1000000000000000" wei // - "1.0" BTC (8 decimals) → "100000000" satoshisHow to test if API returns decimal or base units:
// Make a test API call and check the amount // If you see small numbers like "0.5", "2.5" → DECIMAL format // If you see large numbers like "500000", "2500000" → BASE UNITS // Compare with actual token decimals to verifyCache Strategy:
- Cache by date for historical data (never changes)
- Don't cache "today" (prices update)
- Use
splitDateRangeto separate cacheable vs. recent - Cache key:
swappername:chainId:YYYY-MM-DD
Timestamps:
- API params use Unix timestamps (seconds)
- Internal storage uses Unix timestamps
- Cache keys use ISO date strings (YYYY-MM-DD)
Asset IDs:
- Follow CAIP format:
chainId/tokenType:tokenAddress - Native tokens:
eip155:1/slip44:60(ETH) - ERC20 tokens:
eip155:1/erc20:0x... - Cosmos chains:
cosmos:thorchain-1/slip44:931 - Use existing
getSlip44ForChain()utility
- Follow CAIP format:
Affiliate Identifier (from Step 0):
- ALWAYS use the affiliate identifier you found in Step 0
- Add it to
constants.ts(see example above) - Use it in your API calls to filter for ShapeShift's fees
// Example: Filtering by referrer const { data } = await axios.get(API_URL, { params: { referrer: SHAPESHIFT_REFERRER, // From Step 0 startTimestamp, endTimestamp, }, }); // Example: Filtering by broker ID const { data } = await axios.post(API_URL, { query: GET_SWAPS_QUERY, variables: { brokerId: SHAPESHIFT_BROKER_ID, // From Step 0 startDate, endDate, }, }); // Example: API key identifies us const { data } = await axios.get(API_URL, { headers: { "api-key": SWAPPERNAME_API_KEY, // From Step 0 }, });Error Handling:
- Use
withRetry()wrapper for API calls - Log errors with
console.error - Return empty array on complete failure
- Partial failures should be logged but not throw
- Use
Step 3: Environment Variables
If API key required:
Add to
apps/revenue-api/.env.example:SWAPPERNAME_API_KEY=your_key_hereAdd to server setup:
// In constants.ts export const SWAPPERNAME_API_KEY = process.env.SWAPPERNAME_API_KEY ?? "";Document in README under Environment Variables section
Step 4: Register Integration
File: apps/revenue-api/src/affiliateRevenue/index.ts
Import your module:
import * as swappername from "./swappername";Add to provider names array:
const providerNames: Service[] = [ "bebop", "butterswap", // ... existing providers "swappername", // Add here (alphabetical order preferred) ];Add to Promise.allSettled:
const results = await Promise.allSettled([ bebop.getFees(startTimestamp, endTimestamp), // ... existing providers swappername.getFees(startTimestamp, endTimestamp), // Add here ]);
File: apps/revenue-api/src/types.ts
Add to services array:
export const services = [
"bebop",
// ... existing
"swappername", // Add in alphabetical order
] as const;
Step 5: Frontend Integration
File: apps/revenue-dashboard/src/constants/services.ts
Add display label:
export const SERVICE_LABELS: Record<string, string> = { // ... existing swappername: "Swapper Display Name", };Add color (use a color not already taken):
export const SERVICE_COLORS: Record<string, string> = { // ... existing swappername: "#3b82f6", // Choose unique color };Add to stack order (determines chart order):
export const SERVICE_STACK_ORDER = [ "thorchain", // ... existing "swappername", // Add in desired display order ];
Available Colors (TailwindCSS):
#3b82f6- blue-500#a855f7- purple-500#ef4444- red-500#10b981- emerald-500#f59e0b- amber-500#06b6d4- cyan-500#ec4899- pink-500#14b8a6- teal-500#84cc16- lime-500#f97316- orange-500#6366f1- indigo-500#22c55e- green-500
Step 6: Testing
Test API calls locally:
bun dev:backendTest amount conversion (if using decimal format):
// Verify your conversion is correct // Example: "2.5" USDC → "2500000" (6 decimals) console.log(decimalToBaseUnit("2.5", 6)); // Should output "2500000"Test full stack:
bun devVerify data:
- Check browser network tab for API responses
- Verify revenue amounts are reasonable
- Check charts display correctly
- Confirm caching works (check logs)
- Verify amounts are in base units (large numbers like "2500000", not "2.5")
Test date ranges:
- Last 7 days
- Last 30 days
- Last 90 days
- Custom ranges
Step 7: Audit for Additional UI Updates
Search for hardcoded service references:
Run these checks to ensure nothing was missed:
# Search for service arrays/lists
grep -r "thorchain.*mayachain.*chainflip" apps/revenue-dashboard/src/
# Search for service type definitions
grep -r "Service.*=.*{" apps/revenue-dashboard/src/
# Search for service-specific styling
grep -r "switch.*service" apps/revenue-dashboard/src/
Common locations to check:
- Type definitions
- Chart components
- Table components
- Color/styling utilities
- Mock data files
- Test files
If you find any hardcoded lists or switch statements, update them to include the new swapper.
Step 8: Code Quality
Before submitting, ensure:
- ✅ No TypeScript errors:
bun type-check - ✅ No linting errors:
bun lint:fix - ✅ Follows existing code conventions
- ✅ Proper error handling
- ✅ Logging includes service name prefix
- ✅ Caching implemented correctly
- ✅ Amount normalization correct (all amounts in base units/wei)
- ✅ USD enrichment strategy correct for your approach
- ✅ All console.logs use
[swappername]prefix
Critical Reminders
Amount Format Decision Tree
What format does the API return amounts in?
├─ BASE UNITS (large numbers like "2500000")
│ └─ ✅ Use amount directly: amount = fee.amount
│
├─ DECIMAL (small numbers like "2.5", "0.001")
│ └─ ⚠️ Must convert to base units:
│ 1. Get asset decimals: const decimals = await assetDataService.getAssetDecimals(assetId)
│ 2. Convert: const amount = decimalToBaseUnit(fee.amount, decimals)
│
└─ UNKNOWN
└─ ⚠️ Test the API with known amounts and compare!
Example Test:
// If you see: { amount: "2.5", token: "0xUSDC..." }
// And USDC has 6 decimals
// Then 2.5 USDC should be stored as "2500000" (2.5 × 10^6)
const decimals = await assetDataService.getAssetDecimals(assetId);
const baseUnits = decimalToBaseUnit("2.5", decimals);
// baseUnits = "2500000" ✅
USD Enrichment Decision Tree
Do you have crypto amounts from the API?
├─ YES
│ └─ Does the API provide USD values?
│ ├─ NO → ✅ Use enrichFeesWithUsdPrices() (Approach 1)
│ └─ YES
│ └─ Are the USD values calculated with CURRENT prices or HISTORICAL prices?
│ ├─ CURRENT → ❌ Return as-is (Approach 3)
│ └─ HISTORICAL → ✅ Use enrichFeesWithUsdPrices() (Approach 2)
└─ NO
└─ ❌ Return as-is, synthesize crypto amounts from USD 1:1 (Approach 4)
Fee Calculation
If you need to calculate fees from volume:
const FEE_BPS = 55;
const FEE_BPS_DENOMINATOR = 10000;
const feeAmount = volume * (FEE_BPS / FEE_BPS_DENOMINATOR); // = volume * 0.0055
Asset ID Format
Always use CAIP format:
// EVM native
`eip155:${chainId}/slip44:${slip44}`
// EVM ERC20
`eip155:${chainId}/erc20:${address.toLowerCase()}`
// Cosmos native
`cosmos:${chainName}/slip44:${slip44}`
// Solana native
`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501`;
Caching Rules
- ✅ Cache complete days (00:00:00 to 23:59:59 UTC)
- ✅ Cache indefinitely (TTL handled by LRU)
- ❌ Don't cache partial days
- ❌ Don't cache "today"
- ✅ Use
splitDateRange()to separate cacheable vs recent
Success Checklist
Before marking the integration complete:
- Research phase completed with user approval
- Integration directory created with all files
- Core
getFeesfunction implemented - Amount normalization correct (all amounts in base units/wei)
- Correct USD enrichment strategy applied
- Caching implemented correctly
- Environment variables added (if needed)
- Backend type/service registration complete
- Frontend constants updated (labels, colors, order)
- No TypeScript errors (
bun type-check) - No linting errors (
bun lint:fix) - Tested with multiple date ranges
- Verified amounts are large numbers (wei), not decimal
- Logging includes service name prefix
- Additional UI locations audited and updated
- README updated with new provider (if needed)
Reference Files
Key files to reference during implementation:
ShapeShift Swapper Implementations (for Step 0):
/home/sean/Repos/shapeshift/packages/swapper/src/swappers/- All swapper implementations/home/sean/Repos/shapeshift/packages/swapper/src/swappers/ThorchainSwapper/- Treasury address example/home/sean/Repos/shapeshift/packages/swapper/src/swappers/ZrxSwapper/- API key example/home/sean/Repos/shapeshift/packages/swapper/src/swappers/RelaySwapper/- Referrer parameter example/home/sean/Repos/shapeshift/packages/swapper/src/swappers/ChainflipSwapper/- Broker ID example/home/sean/Repos/shapeshift/.env- RPC proxy endpoints for on-chain queries
Backend (Revenue Dashboard):
apps/revenue-api/src/affiliateRevenue/thorchain/thorchain.ts- Simple API (base units)apps/revenue-api/src/affiliateRevenue/bebop/bebop.ts- Decimal conversion example ⭐apps/revenue-api/src/affiliateRevenue/zrx/zrx.ts- Decimal conversion example ⭐apps/revenue-api/src/affiliateRevenue/relay/relay.ts- Current USD pricing (base units)apps/revenue-api/src/affiliateRevenue/chainflip/chainflip.ts- USD-only, synthesized amountsapps/revenue-api/src/affiliateRevenue/portals/portals.ts- On-chain (always base units)apps/revenue-api/src/affiliateRevenue/cache.ts- Caching utilitiesapps/revenue-api/src/affiliateRevenue/enrichment.ts- USD enrichment logicapps/revenue-api/src/affiliateRevenue/utils.ts-decimalToBaseUnit()utility ⭐
Frontend:
apps/revenue-dashboard/src/constants/services.ts- Service display configurationapps/revenue-dashboard/src/types/index.ts- TypeScript types
Documentation:
README.md- Environment variables, architecture