Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/microck-swapper-integration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 301% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 147% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 141% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 137% | 0% |
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.
Use this skill when the user wants to:
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.
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:
.env.baseAction: Stop and gather this information before proceeding. Missing details cause bugs later.
IMPORTANT: Don't guess at implementation details. Research thoroughly before coding.
Before looking at code, understand the swapper's API:
Try making a test curl request if possible to see real responses.
Now that you understand the API, see how existing swappers work:
bash# List all existing swappers ls packages/swapper/src/swappers/
You'll see swappers like:
Based on what you gathered in Phase 1, determine which swapper type yours is:
EVM Single-Hop (most common):
Gasless / Order-Based:
Solana-Only:
Cross-Chain / Multi-Hop:
Bridge-Specific:
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.tsPay attention to:
Consult the skill's reference materials:
@reference.md - General swapper architecture and patterns@common-gotchas.md - Critical bugs to avoid@examples.md - Code templatesFollow the pattern established by similar swappers. Don't reinvent the wheel.
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.tsCheck @examples.md for structure templates.
Order (follow this sequence):
types.ts: Define TypeScript interfaces based on API responsesutils/constants.ts: Supported chains, default slippage, native token markersutils/helpers/helpers.ts: Helper functions (validation, rate calculation)utils/[swapperName]Service.ts: HTTP service wrapperutils/fetchFrom[SwapperName].ts: API fetch functionsget[SwapperName]TradeQuote.ts: Quote logicget[SwapperName]TradeRate.ts: Rate logicendpoints.ts: Wire up SwapperApi interface[SwapperName]Swapper.ts: Main swapper classindex.ts: ExportsRefer to @examples.md for code templates. Copy patterns from similar existing swappers.
When is metadata needed?
When is metadata NOT needed?
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:
typescriptexport type TradeQuoteStep = { // ... existing fields [swapperName]Specific?: { depositAddress: string swapId: number // ... other swapper-specific fields } }
Add to SwapperSpecificMetadata type (for swap storage):
typescriptexport 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:
typescriptconst 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:
typescriptmetadata: { 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:
typescriptmetadata: { ...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)d. Access in status check (packages/swapper/src/swappers/[Swapper]/endpoints.ts):
typescriptcheckTradeStatus: 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.depositAddressUpdate these files to register your new swapper:
packages/swapper/src/constants.tsSwapperName enumswappers recordpackages/swapper/src/index.tspackages/swapper/src/types.tsheaders/csps/defi/swappers/[SwapperName].ts:typescript import type { Csp } from '../../../types'
export const csp: Csp = { 'connect-src': 'https://api.swapper].com'], }
headers/csps/index.ts:typescript import { csp as swapperName] } from './defi/swappers/SwapperName]'
export const csps = // ... other csps swapperName], ]
src/):a. Add swapper icon:
src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/[swapper]-icon.pngb. Update SwapperIcon component:
src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsximport [swapperName]Icon from './[swapper]-icon.png'typescript case SwapperName.[SwapperName]: return <Image src={[swapperName]Icon} />
c. Add feature flag (REQUIRED):
src/state/slices/preferencesSlice/preferencesSlice.tsFeatureFlags type:typescript export type FeatureFlags = { // ... [SwapperName]Swap: boolean }
typescript const initialState: Preferences = { featureFlags: { // ... [SwapperName]Swap: getConfig().VITE_FEATURE_[SWAPPER]_SWAP, } }
d. Wire up feature flag:
src/state/helpers.tsisCrossAccountTradeSupported function parameter and switch statement (if swapper supports cross-account)getEnabledSwappers function:typescript export const getEnabledSwappers = ( { [SwapperName]Swap, // Add to destructured parameters ...otherFlags }: FeatureFlags, ... ): Record<SwapperName, boolean> => { return { // ... [SwapperName.[SwapperName]]: [SwapperName]Swap && (!isCrossAccountTrade || isCrossAccountTradeSupported(SwapperName.[SwapperName])), } }
e. Update test mocks (REQUIRED):
src/test/mocks/store.tstypescript featureFlags: { // ... other flags [SwapperName]Swap: false, }
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:
VITE_[SWAPPER]_API_KEY (in both .env and .env.development)VITE_FEATURE_[SWAPPER]_SWAP (.env = false, .env.development = true)VITE_[SWAPPER]_BASE_URL (if needed, both files)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), })
Before testing, review @common-gotchas.md to avoid known bugs:
Fix these proactively!
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:
See @reference.md for detailed testing strategies.
Create packages/swapper/src/swappers/[SwapperName]Swapper/INTEGRATION.md
Document:
Use BebopSwapper's INTEGRATION.md as a template.
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.md - Swapper architecture and patterns@examples.md - Code templates@common-gotchas.md - Critical bugs to avoidIf stuck:
@common-gotchas.md for your specific issue| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | pass→pass | 16,132 | 12,140 | -25% | 1 | 1 | 0% | 3,216 | 6,272 | +95% | 0 | 0 | — |
case-03 | fail→fail | 16,116 | 4,976 | -69% | 1 | 1 | 0% | 3,360 | 5,456 | +62% | 0 | 0 | — |
case-13 | fail→pass | 6,979 | 2,257 | -68% | 1 | 1 | 0% | 1,257 | 5,043 | +301% | 0 | 0 | — |
case-01 | fail→pass | 20,097 | 10,299 | -49% | 1 | 1 | 0% | 3,808 | 6,580 | +73% | 0 | 0 | — |
case-02 | fail→fail | 22,228 | 12,106 | -46% | 1 | 1 | 0% | 3,952 | 7,009 | +77% | 0 | 0 | — |
case-04 | pass→pass | 11,750 | 3,910 | -67% | 1 | 1 | 0% | 1,998 | 5,412 | +171% | 0 | 0 | — |
case-05 | fail→pass | 13,966 | 4,981 | -64% | 1 | 1 | 0% | 2,301 | 5,678 | +147% | 0 | 0 | — |
case-06 | pass→pass | 7,191 | 2,646 | -63% | 1 | 1 | 0% | 1,295 | 5,152 | +298% | 0 | 0 | — |
case-07 | fail→pass | 12,084 | 3,136 | -74% | 1 | 1 | 0% | 2,202 | 5,311 | +141% | 0 | 0 | — |
case-18 | pass→fail | 16,428 | 4,680 | -72% | 1 | 1 | 0% | 2,758 | 4,847 | +76% | 0 | 0 | — |
case-08 | fail→pass | 13,273 | 3,470 | -74% | 1 | 1 | 0% | 2,227 | 5,274 | +137% | 0 | 0 | — |
case-09 | fail→pass | 9,959 | 5,186 | -48% | 1 | 1 | 0% | 1,701 | 5,643 | +232% | 0 | 0 | — |
case-10 | pass→pass | 12,141 | 5,046 | -58% | 1 | 1 | 0% | 2,051 | 5,521 | +169% | 0 | 0 | — |
case-11 | fail→pass | 10,864 | 3,099 | -71% | 1 | 1 | 0% | 2,038 | 5,246 | +157% | 0 | 0 | — |
case-12 | fail→pass | 9,046 | 2,621 | -71% | 1 | 1 | 0% | 1,488 | 5,119 | +244% | 0 | 0 | — |
case-14 | fail→pass | 10,666 | 6,551 | -39% | 1 | 1 | 0% | 1,920 | 5,894 | +207% | 0 | 0 | — |
case-15 | pass→pass | 9,336 | 3,446 | -63% | 1 | 1 | 0% | 1,695 | 5,178 | +205% | 0 | 0 | — |
case-16 | fail→pass | 11,810 | 5,396 | -54% | 1 | 1 | 0% | 1,998 | 5,478 | +174% | 0 | 0 | — |
case-17 | fail→fail | 8,797 | 5,425 | -38% | 1 | 1 | 0% | 1,436 | 5,604 | +290% | 0 | 0 | — |
case-20 | pass→pass | 15,596 | 9,137 | -41% | 1 | 1 | 0% | 2,940 | 6,384 | +117% | 0 | 0 | — |
case-21 | fail→pass | 12,261 | 3,179 | -74% | 1 | 1 | 0% | 2,178 | 5,181 | +138% | 0 | 0 | — |
case-22 | pass→pass | 8,540 | 4,488 | -47% | 1 | 1 | 0% | 1,498 | 5,371 | +259% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.