--- name: swapper-integration description: Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web. Activates when user wants to add, integrate, or implement support for a new swapper. Guides through research, implementation, and testing following established patterns. allowed-tools: Read, Write, Edit, Grep, Glob, Bash(yarn test:*), Bash(yarn lint:*), Bash(yarn type-check), Bash(yarn build:*) --- # Swapper Integration Skill You are helping integrate a new DEX aggregator, swapper, or bridge into ShapeShift Web. This skill guides you through the complete process from research to testing. ## When This Skill Activates Use this skill when the user wants to: - "Integrate [SwapperName] swapper" - "Add support for [Protocol]" - "Implement [DEX] integration" - "Add [Aggregator] as a swapper" ## Overview ShapeShift Web supports multiple swap aggregators through a unified swapper interface located in `packages/swapper/src/swappers/`. Each swapper follows consistent patterns, but has variations based on its type (EVM, Solana, cross-chain, gasless, etc.). **Your task**: Research existing swappers to understand patterns, then adapt them for the new integration. ## Workflow ### Phase 1: Information Gathering Before starting implementation, collect ALL required information from the user. **Use the `AskUserQuestion` tool to interactively gather this information with structured prompts.** **Ask the user for:** 1. **API Documentation** - Link to official API documentation (main docs) - Link to Swagger/OpenAPI specification (separate link, if available) - API base URL - Authentication method (API key? signature? none?) - **If API key needed**: Obtain production API key to add to `.env.base` - Rate limiting details 2. **Supported Networks** - Which blockchains (Ethereum, Polygon, Arbitrum, Solana, etc.)? - Any network-specific limitations? - Chain naming convention (e.g., "ethereum" vs "1" vs "mainnet") 3. **API Behavior** - **CRITICAL**: How does slippage work? (percentage like 1=1%? decimal like 0.01=1%? basis points like 100=1%?) - Does it require checksummed addresses? - How are native tokens handled? (special marker address? omit field?) - Minimum/maximum trade amounts? - Quote expiration time? 4. **Brand Assets** - Official swapper name (exact capitalization) - Logo/icon file or link (PNG preferred, 128x128 or larger) - Brand colors (optional) 5. **Reference Materials** (helpful but optional) - Example integrations on GitHub - Sample curl requests + responses from their dApp - Known quirks or gotchas - Community/support contact **Action**: Stop and gather this information before proceeding. Missing details cause bugs later. --- ### Phase 2: Research & Understanding **IMPORTANT**: Don't guess at implementation details. Research thoroughly before coding. #### Step 0: Study the API Documentation **Before looking at code**, understand the swapper's API: 1. **Read the official docs** (link from Phase 1) - How does the API work? - What endpoints are available? - What's the request/response format? - Any special requirements or quirks? 2. **Study the Swagger/OpenAPI spec** (if available) - Exact request parameters - Response schema - Error formats - Example requests/responses 3. **Key things to verify**: - How is slippage formatted in requests? - Are addresses checksummed in examples? - How are native tokens represented? - What does a successful quote response look like? - What error responses can occur? **Try making a test curl request** if possible to see real responses. #### Step 1: Explore Existing Swappers Now that you understand the API, see how existing swappers work: #### Step 1: Explore the swappers directory ```bash # List all existing swappers ls packages/swapper/src/swappers/ ``` You'll see swappers like: - BebopSwapper - ZrxSwapper - CowSwapper - PortalsSwapper - JupiterSwapper - ThorchainSwapper - And others... #### Step 2: Identify similar swappers Based on what you gathered in Phase 1, determine which swapper type yours is: **EVM Single-Hop** (most common): - Same-chain swaps only - Standard transaction signing - Examples: BebopSwapper, ZrxSwapper, PortalsSwapper **Gasless / Order-Based**: - Sign message instead of transaction - No gas fees - Examples: CowSwapper **Solana-Only**: - Solana ecosystem - Different execution model - Examples: JupiterSwapper **Cross-Chain / Multi-Hop**: - Bridge or cross-chain swaps - May have multiple steps - Examples: ThorchainSwapper, ChainflipSwapper **Bridge-Specific**: - Focus on bridging, not swapping - Examples: ArbitrumBridgeSwapper, RelaySwapper #### Step 3: Study similar swappers Pick 2-3 similar swappers and read their implementations: ``` # Example: If building an EVM aggregator, study these: @packages/swapper/src/swappers/BebopSwapper/BebopSwapper.ts @packages/swapper/src/swappers/BebopSwapper/endpoints.ts @packages/swapper/src/swappers/BebopSwapper/types.ts @packages/swapper/src/swappers/BebopSwapper/INTEGRATION.md @packages/swapper/src/swappers/ZrxSwapper/ZrxSwapper.ts @packages/swapper/src/swappers/PortalsSwapper/PortalsSwapper.ts ``` **Pay attention to:** - File structure - How they call their APIs - How they handle errors - How they calculate rates and fees - Special handling (checksumming, hex conversion, etc.) #### Step 4: Read supporting documentation Consult the skill's reference materials: - `@reference.md` - General swapper architecture and patterns - `@common-gotchas.md` - Critical bugs to avoid - `@examples.md` - Code templates --- ### Phase 3: Implementation Follow the pattern established by similar swappers. Don't reinvent the wheel. #### Step 1: Create directory structure Create `packages/swapper/src/swappers/[SwapperName]Swapper/` **For most EVM swappers**, create: ``` [SwapperName]Swapper/ ├── index.ts ├── [SwapperName]Swapper.ts ├── endpoints.ts ├── types.ts ├── get[SwapperName]TradeQuote/ │ └── get[SwapperName]TradeQuote.ts ├── get[SwapperName]TradeRate/ │ └── get[SwapperName]TradeRate.ts └── utils/ ├── constants.ts ├── [swapperName]Service.ts ├── fetchFrom[SwapperName].ts └── helpers/ └── helpers.ts ``` **Check** `@examples.md` for structure templates. #### Step 2: Implement core files **Order** (follow this sequence): 1. **`types.ts`**: Define TypeScript interfaces based on API responses 2. **`utils/constants.ts`**: Supported chains, default slippage, native token markers 3. **`utils/helpers/helpers.ts`**: Helper functions (validation, rate calculation) 4. **`utils/[swapperName]Service.ts`**: HTTP service wrapper 5. **`utils/fetchFrom[SwapperName].ts`**: API fetch functions 6. **`get[SwapperName]TradeQuote.ts`**: Quote logic 7. **`get[SwapperName]TradeRate.ts`**: Rate logic 8. **`endpoints.ts`**: Wire up SwapperApi interface 9. **`[SwapperName]Swapper.ts`**: Main swapper class 10. **`index.ts`**: Exports **Refer to** `@examples.md` for code templates. **Copy patterns** from similar existing swappers. #### Step 3: Add Swapper-Specific Metadata (ONLY if needed) **When is metadata needed?** - Deposit-to-address swappers (Chainflip, NEAR Intents) - need deposit address, swap ID for status polling - Order-based swappers (CowSwap) - need order ID for status tracking - Any swapper that requires tracking state between quote → execution → status polling **When is metadata NOT needed?** - Direct transaction swappers (Bebop, 0x, Portals) - transaction is built from quote, no async tracking needed - Same-chain aggregators where transaction hash is sufficient for status tracking - Most EVM-only swappers that return transaction data directly **If your swapper doesn't need async status polling or deposit addresses, skip this step!** **Three places to add metadata:** **a. Define types** (`packages/swapper/src/types.ts`): Add to `TradeQuoteStep` type: ```typescript export type TradeQuoteStep = { // ... existing fields [swapperName]Specific?: { depositAddress: string swapId: number // ... other swapper-specific fields } } ``` Add to `SwapperSpecificMetadata` type (for swap storage): ```typescript export type SwapperSpecificMetadata = { chainflipSwapId: number | undefined nearIntentsSpecific?: { depositAddress: string depositMemo?: string timeEstimate: number deadline: string } // Add your swapper's metadata here [swapperName]Specific?: { // ... fields needed for status polling } // ... other fields } ``` **b. Populate in quote** (`packages/swapper/src/swappers/[Swapper]/swapperApi/getTradeQuote.ts`): Store metadata in the TradeQuoteStep: ```typescript const tradeQuote: TradeQuote = { // ... other fields steps: [{ // ... step fields [swapperName]Specific: { depositAddress: response.depositAddress, swapId: response.id, // ... other data needed later } }] } ``` **c. Extract into swap** (TWO places required!): **Place 1**: `src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx` Add to metadata object around line 114-126: ```typescript metadata: { chainflipSwapId: firstStep?.chainflipSpecific?.chainflipSwapId, nearIntentsSpecific: firstStep?.nearIntentsSpecific, // Add your swapper's metadata extraction here: [swapperName]Specific: firstStep?.[swapperName]Specific, relayTransactionMetadata: firstStep?.relayTransactionMetadata, stepIndex: currentHopIndex, quoteId: activeQuote.id, streamingSwapMetadata: { ... } } ``` **Place 2**: `src/lib/tradeExecution.ts` (CRITICAL - often forgotten!) Add to metadata object around line 156-161: ```typescript metadata: { ...swap.metadata, chainflipSwapId: tradeQuote.steps[0]?.chainflipSpecific?.chainflipSwapId, nearIntentsSpecific: tradeQuote.steps[0]?.nearIntentsSpecific, // Add your swapper's metadata extraction here: [swapperName]Specific: tradeQuote.steps[0]?.[swapperName]Specific, relayTransactionMetadata: tradeQuote.steps[0]?.relayTransactionMetadata, stepIndex, } ``` **Why both places?** - `useTradeButtonProps` creates the initial swap (before wallet signature) - `tradeExecution` updates the swap during execution (after wallet signature, with actual tradeQuote) - If you only add to one place, metadata will be missing! **d. Access in status check** (`packages/swapper/src/swappers/[Swapper]/endpoints.ts`): ```typescript checkTradeStatus: async ({ config, swap }) => { const { [swapperName]Specific } = swap?.metadata ?? {} if (![swapperName]Specific?.swapId) { throw new Error('swapId is required for status check') } // Use metadata to poll API const status = await api.getStatus([swapperName]Specific.swapId) // ... } ``` **Example: NEAR Intents metadata flow** ``` 1. Quote: Store in step.nearIntentsSpecific.depositAddress 2. Swap creation: Extract to swap.metadata.nearIntentsSpecific 3. Status check: Read from swap.metadata.nearIntentsSpecific.depositAddress ``` #### Step 4: Register the swapper Update these files to register your new swapper: 1. **`packages/swapper/src/constants.ts`** - Add to `SwapperName` enum - Add to `swappers` record - Add default slippage 2. **`packages/swapper/src/index.ts`** - Export new swapper 3. **`packages/swapper/src/types.ts`** - Add API config fields (if not already done in metadata step) 4. **CSP Headers** (if swapper calls external API): - Create `headers/csps/defi/swappers/[SwapperName].ts`: ```typescript import type { Csp } from '../../../types' export const csp: Csp = { 'connect-src': ['https://api.[swapper].com'], } ``` - Register in `headers/csps/index.ts`: ```typescript import { csp as [swapperName] } from './defi/swappers/[SwapperName]' export const csps = [ // ... other csps [swapperName], ] ``` 5. **UI Integration** (`src/`): **a. Add swapper icon:** - Place icon file at: `src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/[swapper]-icon.png` - Recommended: 128x128 PNG with transparent background **b. Update SwapperIcon component:** - File: `src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsx` - Import icon: `import [swapperName]Icon from './[swapper]-icon.png'` - Add case to switch statement: ```typescript case SwapperName.[SwapperName]: return ``` **c. Add feature flag (REQUIRED):** - File: `src/state/slices/preferencesSlice/preferencesSlice.ts` - Add to `FeatureFlags` type: ```typescript export type FeatureFlags = { // ... [SwapperName]Swap: boolean } ``` - Add to initial state: ```typescript const initialState: Preferences = { featureFlags: { // ... [SwapperName]Swap: getConfig().VITE_FEATURE_[SWAPPER]_SWAP, } } ``` **d. Wire up feature flag:** - File: `src/state/helpers.ts` - Add to `isCrossAccountTradeSupported` function parameter and switch statement (if swapper supports cross-account) - Add to `getEnabledSwappers` function: ```typescript export const getEnabledSwappers = ( { [SwapperName]Swap, // Add to destructured parameters ...otherFlags }: FeatureFlags, ... ): Record => { return { // ... [SwapperName.[SwapperName]]: [SwapperName]Swap && (!isCrossAccountTrade || isCrossAccountTradeSupported(SwapperName.[SwapperName])), } } ``` **e. Update test mocks (REQUIRED):** - File: `src/test/mocks/store.ts` - Add feature flag to mock featureFlags object: ```typescript featureFlags: { // ... other flags [SwapperName]Swap: false, } ``` 6. **Configuration**: **Environment variables** - Follow naming conventions (e.g., Bebop): **`.env`** (base/production - both API key and feature flag OFF): ```bash # Bebop VITE_BEBOP_API_KEY= VITE_FEATURE_BEBOP_SWAP=false ``` **`.env.development`** (development - feature flag ON): ```bash # Bebop VITE_BEBOP_API_KEY=your-dev-api-key-here VITE_FEATURE_BEBOP_SWAP=true ``` **Naming pattern**: - API key: `VITE_[SWAPPER]_API_KEY` (in both `.env` and `.env.development`) - Feature flag: `VITE_FEATURE_[SWAPPER]_SWAP` (`.env` = false, `.env.development` = true) - Other config: `VITE_[SWAPPER]_BASE_URL` (if needed, both files) - **Config file** (`src/config.ts`): ```typescript export const getConfig = (): Config => ({ // ... VITE_[SWAPPER]_API_KEY: import.meta.env.VITE_[SWAPPER]_API_KEY || '', VITE_[SWAPPER]_BASE_URL: import.meta.env.VITE_[SWAPPER]_BASE_URL || '', VITE_FEATURE_[SWAPPER]_SWAP: parseBoolean(import.meta.env.VITE_FEATURE_[SWAPPER]_SWAP), }) ``` #### Step 4: Check common gotchas **Before testing**, review `@common-gotchas.md` to avoid known bugs: - Slippage format issues - Address checksumming - Hex value conversion - Response parsing errors - Rate vs quote affiliate fee deltas **Fix these proactively!** --- ### Phase 4: Testing & Validation Run validation commands: ```bash # Type checking yarn type-check # Linting yarn lint # Build yarn build:swapper ``` **All must pass** before manual testing. **Manual testing checklist**: - [ ] Can fetch quotes successfully - [ ] Rates display correctly - [ ] Approval flow works (if needed) - [ ] Transaction execution completes - [ ] Error handling works (bad chain, insufficient liquidity, etc.) - [ ] UI shows swapper correctly - [ ] Feature flag toggles swapper on/off See `@reference.md` for detailed testing strategies. --- ### Phase 5: Documentation Create `packages/swapper/src/swappers/[SwapperName]Swapper/INTEGRATION.md` Document: 1. API overview (URLs, auth, chains) 2. Key implementation details 3. Critical quirks or gotchas 4. Sample requests/responses 5. Testing notes **Use BebopSwapper's INTEGRATION.md as a template.** --- ## Key Principles 1. **Research, don't guess**: Study existing swappers before coding 2. **Copy patterns**: Don't reinvent - adapt what works 3. **Read gotchas**: Avoid bugs others already fixed 4. **Test thoroughly**: Type check, lint, build, manual test 5. **Document quirks**: Help future maintainers ## Success Criteria Integration is complete when: ✅ All validation commands pass (type-check, lint, build) ✅ Swapper appears in UI when feature flag is enabled ✅ Can successfully fetch quotes and execute trades ✅ Error cases handled gracefully ✅ Integration documentation written ✅ Code follows patterns from similar swappers ## Reference Files - `@reference.md` - Swapper architecture and patterns - `@examples.md` - Code templates - `@common-gotchas.md` - Critical bugs to avoid ## Need Help? If stuck: 1. Read similar swapper implementations 2. Check `@common-gotchas.md` for your specific issue 3. Grep for similar patterns in existing swappers 4. Ask user for clarification on API behavior