| 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.ts or endpoints.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:
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:
for (const trade of data.trades) {
const chainId = `eip155:${trade.chainId}`;
const assetId = `${chainId}/erc20:${trade.token}`;
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,
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
amount AND amountUsd
- The
amountUsd is already using CURRENT prices from their API
- Return fees as-is without enrichment
Amount Format: Check API - may need conversion
Example Structure:
const fees = data.fees.map(fee => ({
chainId: config.chainId,
assetId: buildAssetId(...),
service: 'swappername',
txHash: fee.txHash,
timestamp: fee.timestamp,
amount: fee.amount,
amountUsd: fee.amountUsdCurrent,
}))
return fees
⚠️ 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) AND amountUsd
- The
amountUsd is historical (from when swap occurred)
- No enrichment possible - we don't have real crypto amounts
Amount Format: Synthesized in base units
Example Structure:
const fees = data.swaps.map((swap) => {
const usdValue = swap.affiliateFeeValueUsd;
const usdcDecimals = 6;
const usdcWei = decimalToBaseUnit(usdValue, usdcDecimals);
return {
chainId: "eip155:1",
assetId: "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
service: "swappername",
txHash: "",
timestamp: swap.timestamp,
amount: usdcWei,
amountUsd: usdValue,
};
});
return fees;
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
amount from on-chain transfers
- Enrichment calculates USD value using CURRENT prices
Amount Format: On-chain data is always in base units (wei)
Example Structure:
const events = await getPortalEventsFromExplorer(
config,
startTimestamp,
endTimestamp,
);
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,
};
} else {
return {
chainId: config.chainId,
assetId: buildAssetId(config.chainId, event.inputToken),
service: "swappername",
txHash: event.txHash,
timestamp: event.timestamp,
amount: calculateFallbackFee(event.inputAmount),
};
}
}),
);
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 - Export getFees function
[swappername].ts - Main implementation
types.ts - TypeScript types for API responses
constants.ts - API URLs, keys, and affiliate identifier from Step 0
utils.ts - Helper functions (if needed)
In constants.ts, define the affiliate identifier you found in Step 0:
export const DAO_TREASURY = "0x...";
export const SHAPESHIFT_BROKER_ID = "shapeshift";
export const SHAPESHIFT_REFERRER = "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,
);
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++;
}
}
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);
}
const recentFees: Fees[] = [];
if (recentStart !== null) {
recentFees.push(...(await fetchFeesFromAPI(recentStart, endTimestamp)));
}
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`,
);
const allFees = [...cachedFees, ...newFees, ...recentFees];
return enrichFeesWithUsdPrices(allFees);
return allFees;
};
Key Implementation Notes:
-
Amount Normalization (CRITICAL!):
const amount = fee.amount;
const decimals = await assetDataService.getAssetDecimals(assetId);
const amount = decimalToBaseUnit(fee.amount, decimals);
How to test if API returns decimal or base units:
-
Cache Strategy:
- Cache by date for historical data (never changes)
- Don't cache "today" (prices update)
- Use
splitDateRange to 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
-
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
const { data } = await axios.get(API_URL, {
params: {
referrer: SHAPESHIFT_REFERRER,
startTimestamp,
endTimestamp,
},
});
const { data } = await axios.post(API_URL, {
query: GET_SWAPS_QUERY,
variables: {
brokerId: SHAPESHIFT_BROKER_ID,
startDate,
endDate,
},
});
const { data } = await axios.get(API_URL, {
headers: {
"api-key": SWAPPERNAME_API_KEY,
},
});
-
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
Step 3: Environment Variables
If API key required:
-
Add to apps/revenue-api/.env.example:
SWAPPERNAME_API_KEY=your_key_here
-
Add to server setup:
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",
"swappername",
];
-
Add to Promise.allSettled:
const results = await Promise.allSettled([
bebop.getFees(startTimestamp, endTimestamp),
swappername.getFees(startTimestamp, endTimestamp),
]);
File: apps/revenue-api/src/types.ts
Add to services array:
export const services = [
"bebop",
"swappername",
] as const;
Step 5: Frontend Integration
File: apps/revenue-dashboard/src/constants/services.ts
-
Add display label:
export const SERVICE_LABELS: Record<string, string> = {
swappername: "Swapper Display Name",
};
-
Add color (use a color not already taken):
export const SERVICE_COLORS: Record<string, string> = {
swappername: "#3b82f6",
};
-
Add to stack order (determines chart order):
export const SERVICE_STACK_ORDER = [
"thorchain",
"swappername",
];
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:backend
-
Test amount conversion (if using decimal format):
console.log(decimalToBaseUnit("2.5", 6));
-
Test full stack:
bun dev
-
Verify 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:
grep -r "thorchain.*mayachain.*chainflip" apps/revenue-dashboard/src/
grep -r "Service.*=.*{" apps/revenue-dashboard/src/
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:
const decimals = await assetDataService.getAssetDecimals(assetId);
const baseUnits = decimalToBaseUnit("2.5", decimals);
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);
Asset ID Format
Always use CAIP format:
`eip155:${chainId}/slip44:${slip44}`
`eip155:${chainId}/erc20:${address.toLowerCase()}`
`cosmos:${chainName}/slip44:${slip44}`
`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:
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 amounts
apps/revenue-api/src/affiliateRevenue/portals/portals.ts - On-chain (always base units)
apps/revenue-api/src/affiliateRevenue/cache.ts - Caching utilities
apps/revenue-api/src/affiliateRevenue/enrichment.ts - USD enrichment logic
apps/revenue-api/src/affiliateRevenue/utils.ts - decimalToBaseUnit() utility ⭐
Frontend:
apps/revenue-dashboard/src/constants/services.ts - Service display configuration
apps/revenue-dashboard/src/types/index.ts - TypeScript types
Documentation:
README.md - Environment variables, architecture