skills_all/swapper-integration/SKILL.md
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.
npx skillsauth add activer007/ordinary-claude-skills swapper-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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:
API Documentation
.env.baseSupported Networks
API Behavior
Brand Assets
Reference Materials (helpful but optional)
Action: 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:
Read the official docs (link from Phase 1)
Study the Swagger/OpenAPI spec (if available)
Key things to verify:
Try making a test curl request if possible to see real responses.
Now that you understand the API, see how existing swappers work:
# 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.ts
Pay 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.ts
Check @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:
export type TradeQuoteStep = {
// ... existing fields
[swapperName]Specific?: {
depositAddress: string
swapId: number
// ... other swapper-specific fields
}
}
Add to SwapperSpecificMetadata type (for swap storage):
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:
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:
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:
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)d. Access in status check (packages/swapper/src/swappers/[Swapper]/endpoints.ts):
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
Update these files to register your new swapper:
packages/swapper/src/constants.ts
SwapperName enumswappers recordpackages/swapper/src/index.ts
packages/swapper/src/types.ts
CSP Headers (if swapper calls external API):
headers/csps/defi/swappers/[SwapperName].ts:
import type { Csp } from '../../../types'
export const csp: Csp = {
'connect-src': ['https://api.[swapper].com'],
}
headers/csps/index.ts:
import { csp as [swapperName] } from './defi/swappers/[SwapperName]'
export const csps = [
// ... other csps
[swapperName],
]
UI Integration (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'case SwapperName.[SwapperName]:
return <Image src={[swapperName]Icon} />
c. Add feature flag (REQUIRED):
src/state/slices/preferencesSlice/preferencesSlice.tsFeatureFlags type:
export type FeatureFlags = {
// ...
[SwapperName]Swap: boolean
}
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:
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.tsfeatureFlags: {
// ... other flags
[SwapperName]Swap: false,
}
Configuration:
Environment variables - Follow naming conventions (e.g., Bebop):
.env (base/production - both API key and feature flag OFF):
# Bebop
VITE_BEBOP_API_KEY=
VITE_FEATURE_BEBOP_SWAP=false
.env.development (development - feature flag ON):
# 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):
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:
# 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 issuetools
Generate typed TypeScript SDKs for AI agents to interact with MCP servers. Converts verbose JSON-RPC curl commands to clean function calls (docs.createDocument() vs curl). Auto-detects MCP tools from server modules, generates TypeScript types and client methods, creates runnable example scripts. Use when: building MCP-enabled applications, need typed programmatic access to MCP tools, want Claude Code to manage apps via scripts, eliminating manual JSON-RPC curl commands, validating MCP inputs/outputs, or creating reusable agent automation.
testing
Generate structured task lists from specs or requirements. IMPORTANT: After completing ANY spec via ExitSpecMode, ALWAYS ask the user: "Would you like me to generate a task list for this spec?" Use when user confirms or explicitly requests task generation from a plan/spec/PRD.
tools
Create compelling story-format summaries using UltraThink to find the best narrative framing. Support multiple formats - 3-part narrative, n-length with inline links, abridged 5-line, or comprehensive via Foundry MCP. USE WHEN user says 'create story explanation', 'narrative summary', 'explain as a story', or wants content in Daniel's conversational first-person voice.
testing
Navigate through the original three-world shamanic technology. Deploy when soul retrieval, power animal guidance, or journey between realms emerges. Deeply respectful of Tungus, Buryat, Yakut, Evenki traditions. Use for consciousness navigation, NOT cultural appropriation.