Swapper Integration
Microck/ordinary-claude-skills
Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web.
Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web.
$ npx skills add shapeshift/web --skill swapper-integration -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install shapeshift/web swapper-integration --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/swapper-integration .claude/skills/swapper-integration && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .claude/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integrationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add shapeshift/web --skill swapper-integration -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install shapeshift/web swapper-integration --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/swapper-integration .agents/skills/swapper-integration && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .agents/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shapeshift/web --skill swapper-integration -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install shapeshift/web swapper-integration --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/swapper-integration .cursor/skills/swapper-integration && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .cursor/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/shapeshift/web.git --path .claude/skills/swapper-integration--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add shapeshift/web --skill swapper-integration -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install shapeshift/web swapper-integration --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/swapper-integration .gemini/skills/swapper-integration && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .gemini/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install shapeshift/web swapper-integrationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add shapeshift/web --skill swapper-integration -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/swapper-integration .github/skills/swapper-integration && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .github/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shapeshift/web --skill swapper-integration -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install shapeshift/web swapper-integration --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/swapper-integration .opencode/skills/swapper-integration && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "swapper-integration" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/swapper-integration into .opencode/skills/swapper-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "swapper-integration", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
swapper-integrationIntegrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web.
Swapper Integration is an agent skill from shapeshift/web. 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. (project)
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. The skill folder holds 4 other files (for example `NEAR_INTENTS_RESEARCH.md`, `common-gotchas.md` and `examples.md`).
The licence is MIT.
6 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 52aebb2. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
ReadWriteEditGrepGlobWebFetchWebSearchBash(pnpm run test:*)Bash(pnpm run lint:*)Bash(pnpm run type-check)…and 3 more on the same allowed-tools line.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
VITE_XYZ_API_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Swapper Integration loads about 11k tokens when it runs. Until then it costs about 82 tokens; SKILL.md has 2,282 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
`.env` (production - both OFF):`.env.development` (development - flag ON):Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from shapeshift/web at commit 52aebb2, republished under its MIT licence (© shapeshift). 2,282 words, ~11,390 tokens.
.claude/skills/swapper-integration/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.You are an expert at integrating DEX aggregators, swappers, and bridge protocols into ShapeShift Web. This skill guides you through the complete process from API research to production-ready implementation.
Use this skill when the user wants to:
ShapeShift Web is a decentralized crypto exchange aggregator that supports multiple swap providers through a unified interface. Each swapper implements standardized TypeScript interfaces (Swapper and SwapperApi) but has variations based on blockchain type (EVM, UTXO, Solana, Sui, Tron) and swapper model (direct transaction, deposit-to-address, gasless order-based).
Core Architecture:
packages/swapper/src/swappers/Swapper (execution) + SwapperApi (quotes/rates/status)transactionData (a TxBuildData variant) built at quote time. Execution and the public api consume the quote payload as-is — static data is set at quote time, only dynamic data (gas price, solana priority fee, nonce, blockhash) is fetched at execution.helpers.ts, shared getXTradeContext.ts, discriminated getXStepData.ts, thin getTradeQuote/getTradeRate arm wrappers. AcrossSwapper is the spec in code form; the authoritative conventions rubric lives in .claude/skills/swapper-rate-quote-review/SKILL.md — read it alongside this skill.Your Role: Research → Implement → Test → Document, following battle-tested patterns from 18 existing swapper integrations.
BEFORE asking the user for anything, proactively research the swapper online:
Search for official documentation:
Search: "[SwapperName] API documentation"
Search: "[SwapperName] developer docs"
Search: "[SwapperName] swagger api"Find their website and look for:
Fetch their API docs using WebFetch:
Research chain support:
Search: "[SwapperName] supported chains"
Search: "[SwapperName] which blockchains"Find existing integrations:
Search: "github [SwapperName] integration example"
Search: "[SwapperName] typescript sdk"Then, compile what you found and ask the user ONLY for what you couldn't find or need confirmation on.
Use the AskUserQuestion tool to gather missing information with structured prompts.
Based on your Phase 0 research, ask the user for:
API Access (if needed):
Chain Support Confirmation:
Critical API Behaviors (if not clear from docs):
Brand Assets:
Known Issues:
Example Multi-Question Prompt:
AskUserQuestion({
questions: [
{
question: "Do we have an API key for [Swapper]?",
header: "API Key",
multiSelect: false,
options: [
{ label: "Yes, I have it", description: "I'll provide the API key" },
{ label: "No, but we can get one", description: "I'll obtain an API key" },
{ label: "No API key needed", description: "API is public/unauthenticated" }
]
},
{
question: "Which chains should we support initially?",
header: "Chain Support",
multiSelect: true,
options: [
{ label: "Ethereum", description: "Ethereum mainnet" },
{ label: "Polygon", description: "Polygon PoS" },
{ label: "Arbitrum", description: "Arbitrum One" },
{ label: "All supported chains", description: "Enable all chains the API supports" }
]
}
]
})IMPORTANT: Study existing swappers BEFORE writing any code. This prevents reimplementing solved problems.
Based on API research, determine the swapper type. Every category produces the same canonical
structure — the category only changes what the quote's transactionData variant is and how the
context/step data derive it.
EVM Direct Transaction (Most Common):
ZrxSwapper, PortalsSwapper, BebopSwapper (EVM arm), DebridgeSwapper, AcrossSwappertransactionData: { type: 'evm', chainId, to, data, value, gasLimit } — the
gasLimit is ALWAYS set (provider-supplied, or estimated-and-set by getEvmNetworkFeeCryptoBaseUnit){to, data, value, gas} transaction objectDeposit-to-Address (Cross-Chain/Async):
BobGatewaySwapper (order resolved once up front), ChainflipSwapper
(deposit channel opened quote-side), NearIntentsSwappertransactionData (the transfer we build) PLUS a
swapperMetadata union member holding the tracking id / deposit addressGasless Order-Based:
CowSwapper — transactionData: { type: 'cowswap', chainId, orderToSign },
getUnsignedEvmMessage is a thin reader, executeEvmMessage signs + POSTs the orderSolana:
transactionData: { type: 'solana_instructions', instructions, addressLookupTableAddresses } with the static compute unit limit set at quote time via
withComputeUnitLimit (measured simulation × per-swapper margin); execution fetches only the
dynamic priority fee. Canonical: the solana arms of AcrossSwapper/ButterSwap/RelaySwapper.transactionData: { type: 'solana_serialized_tx', serializedTx } — co-sign as-is, never rebuild. Canonical:
BebopSwapper solana arm.Multi-Chain:
switch (chainNamespace) in step data with BOTH arms
inline per case. Canonical: ButterSwap (evm/utxo/solana/tron), RelaySwapper, NearIntentsSwapper.Chain-Specific (Sui/Tron/Starknet/TON):
transactionData); execution re-derives from
swapperMetadata or provider re-fetch. Canonical: CetusSwapper (sui), SunioSwapper (tron —
the one migrated tron example), AvnuSwapper (starknet), StonfiSwapper (ton). New chain-specific
swappers still get the full context split (Cetus/Stonfi prove it applies without an executable payload).Read the conventions rubric first: .claude/skills/swapper-rate-quote-review/SKILL.md — it is
the authoritative spec for the structure below and its edge cases.
Then read Across — the reference implementation:
packages/swapper/src/swappers/AcrossSwapper/
├── index.ts # Barrel: exports { acrossApi, acrossSwapper } at minimum
├── AcrossSwapper.ts # Swapper interface (shared executors)
├── endpoints.ts # SwapperApi: scoped input casts + shared chain exec utils
├── getTradeQuote/
│ └── getTradeQuote.ts # Quote arm wrapper: assertQuoteAddresses → context → step data → Trade[]
├── getTradeRate/
│ └── getTradeRate.ts # Rate arm wrapper: owns ?? default-address fallbacks → Trade[]
└── utils/
├── types.ts # API types + scoped AcrossTrade{Quote,Rate}Input aliases
├── helpers.ts # PURE helpers: assertValidTrade, address mappers, fee fallbacks
├── acrossService.ts # HTTP client with cache + API key injection
├── fetchAcrossTrade.ts # API wrappers
├── getAcrossTradeContext.ts # Shared core: fetch + derivations, ZERO quoteOrRate checks
└── getAcrossStepData.ts # Discriminated rate/quote step data (StepDataArgs, overloaded)Then read 1-2 swappers of your category (see canonical examples above).
Critical things to note while reading:
StepDataArgs<Base, RateExtra, QuoteExtra> generic and the overloaded step data returnsmakeNetworkFeeEstimationFailedErr / makeTradeStepBuildFailedErr / makeSwapErrorRighttransactionData variant the quote carries, and what (if anything) goes in swapperMetadataimport { Err, Ok } from '@sniptt/monads'
import { makeSwapErrorRight } from '../../../utils'
// ALWAYS return Result<T, SwapErrorRight>, NEVER throw
const result = await someOperation()
if (result.isErr()) {
return Err(makeSwapErrorRight({
message: 'What went wrong',
code: TradeQuoteError.QueryFailed,
details: { context: 'here' }
}))
}
return Ok(result.unwrap())import { createCache, makeSwapperAxiosServiceMonadic } from '../../../utils'
const maxAge = 5 * 1000 // 5 seconds
const cachedUrls = ['/quote', '/price'] // which endpoints to cache
const serviceBase = createCache(maxAge, cachedUrls, {
timeout: 10000,
headers: {
'Accept': 'application/json',
'x-api-key': config.VITE_XYZ_API_KEY
}
})
export const xyzService = makeSwapperAxiosServiceMonadic(serviceBase)For chain adapters and swappers that directly interact with RPC endpoints or APIs:
import PQueue from 'p-queue'
// In constructor or module scope:
private requestQueue: PQueue = new PQueue({
intervalCap: 1, // 1 request per interval
interval: 50, // 50ms between requests
concurrency: 1, // 1 concurrent request at a time
})
// Wrap all external API/RPC calls:
const quote = await this.requestQueue.add(() =>
swapperService.get('/quote', { params })
)
// For provider calls in chain adapters:
const balance = await this.requestQueue.add(() =>
this.provider.getBalance(address)
)When to use: Any swapper or chain adapter making direct RPC/API calls (especially public endpoints) Example implementations: MonadChainAdapter, PlasmaChainAdapter
import { getInputOutputRate } from '../../../utils'
const rate = getInputOutputRate({
sellAmountCryptoBaseUnit,
buyAmountCryptoBaseUnit,
sellAsset,
buyAsset
})Follow this EXACT order to avoid rework:
mkdir -p packages/swapper/src/swappers/[SwapperName]Swapper/{getTradeQuote,getTradeRate,utils}Canonical structure (mirror Across exactly):
[SwapperName]Swapper/
├── index.ts # Barrel: { [swapperName]Api, [swapperName]Swapper } at minimum
├── [SwapperName]Swapper.ts # Swapper interface (shared executors)
├── endpoints.ts # SwapperApi wiring
├── types.ts # Scoped input aliases + metadata type (or utils/types.ts)
├── getTradeQuote/
│ └── getTradeQuote.ts # Quote arm wrapper
├── getTradeRate/
│ └── getTradeRate.ts # Rate arm wrapper
└── utils/
├── constants.ts # Supported chains, native marker, defaults
├── helpers.ts # PURE helpers only (flat file, not helpers/helpers.ts)
├── [swapperName]Service.ts # HTTP client with cache + API key injection
├── fetch[SwapperName]Trade.ts # API wrappers
├── get[SwapperName]TradeContext.ts # Shared core
└── get[SwapperName]StepData.ts # Discriminated rate/quote step data2a. types.ts - API TypeScript Types
Define types EXACTLY matching the API response (log actual API responses to verify!):
import type { Address, Hex } from 'viem'
// Request types
export type [Swapper]QuoteRequest = {
sellToken: Address
buyToken: Address
sellAmount: string
slippage: number // NOTE: document what format! (percentage, decimal, basis points)
takerAddress: Address
receiverAddress?: Address
chainId: number
}
// Response types (match API exactly!)
export type [Swapper]QuoteResponse = {
// Copy structure from actual API response
buyAmount: string
sellAmount: string
transaction: {
to: Address
data: Hex
value: Hex
gas?: Hex
}
// ... rest of response
}
// Constants
export const [SWAPPER]_SUPPORTED_CHAIN_IDS: Record<number, string> = {
1: 'ethereum',
137: 'polygon',
42161: 'arbitrum',
// ...
}2b. utils/constants.ts - Configuration
import type { AssetId, ChainId } from '@shapeshiftoss/caip'
import { ethChainId, polygonChainId, arbitrumChainId } from '@shapeshiftoss/caip'
import type { Address } from 'viem'
export const SUPPORTED_CHAIN_IDS = [
ethChainId,
polygonChainId,
arbitrumChainId,
] as const
export type [Swapper]SupportedChainId = (typeof SUPPORTED_CHAIN_IDS)[number]
// Native token marker (if API uses one)
export const NATIVE_TOKEN_MARKER = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as Address
// Dummy address for rates (when no wallet connected)
export const DUMMY_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Address
// Default slippage if none provided
export const DEFAULT_SLIPPAGE_PERCENTAGE = '0.5' // 0.5%2c. utils/helpers.ts - Pure Helper Functions (incl. assertValidTrade)
import { fromAssetId, type AssetId } from '@shapeshiftoss/caip'
import { isToken } from '@shapeshiftoss/utils'
import { getAddress, type Address } from 'viem'
import { NATIVE_TOKEN_MARKER, SUPPORTED_CHAIN_IDS } from '../constants'
// Check if chain is supported
export const isSupportedChainId = (chainId: string): boolean => {
return SUPPORTED_CHAIN_IDS.includes(chainId as any)
}
// Convert assetId to token address (with native token handling)
export const assetIdToToken = (assetId: AssetId): Address => {
if (!isToken(assetId)) {
return NATIVE_TOKEN_MARKER // Native token (ETH, MATIC, etc.)
}
const { assetReference } = fromAssetId(assetId)
return getAddress(assetReference) // Checksum ERC20 address
}
// Convert ShapeShift chainId to API chain identifier
export const chainIdToChainRef = (chainId: string): string => {
switch (chainId) {
case ethChainId:
return 'ethereum' // or '1' or 'mainnet' depending on API
case polygonChainId:
return 'polygon'
// ...
default:
throw new Error(`Unsupported chainId: ${chainId}`)
}
}
// Calculate rate from amounts
import { getInputOutputRate } from '../../../../utils'
export { getInputOutputRate } // Re-export for use in quote/rate files2d. utils/[swapperName]Service.ts - HTTP Service
import { createCache, makeSwapperAxiosServiceMonadic } from '../../../utils'
import type { SwapperConfig } from '../../../types'
// Cache for 5 seconds (adjust based on API)
const maxAge = 5 * 1000
// Which endpoints to cache (usually /quote and /price)
const cachedUrls = ['/quote', '/price']
export const [swapperName]ServiceFactory = (config: SwapperConfig) => {
const axiosConfig = {
timeout: 10000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...(config.VITE_[SWAPPER]_API_KEY && {
'x-api-key': config.VITE_[SWAPPER]_API_KEY
})
}
}
const serviceBase = createCache(maxAge, cachedUrls, axiosConfig)
return makeSwapperAxiosServiceMonadic(serviceBase)
}
export type [Swapper]Service = ReturnType<typeof [swapperName]ServiceFactory>2e. utils/fetchFrom[SwapperName].ts - API Wrappers
import { type AssetId } from '@shapeshiftoss/caip'
import { bn } from '@shapeshiftoss/utils'
import { Err, Ok, type Result } from '@sniptt/monads'
import { getAddress, type Address } from 'viem'
import { makeSwapErrorRight } from '../../../utils'
import { TradeQuoteError, type SwapErrorRight } from '../../../types'
import type { [Swapper]Service } from './[swapperName]Service'
import type { [Swapper]QuoteRequest, [Swapper]QuoteResponse } from '../types'
import { assetIdToToken, chainIdToChainRef } from './helpers'
// Base URL for API
const BASE_URL = 'https://api.[swapper].com'
export type FetchQuoteParams = {
sellAssetId: AssetId
buyAssetId: AssetId
sellAmountCryptoBaseUnit: string
chainId: string
takerAddress: string
receiverAddress: string
slippageTolerancePercentageDecimal: string
affiliateBps: string
}
export const fetchQuote = async (
params: FetchQuoteParams,
service: [Swapper]Service
): Promise<Result<[Swapper]QuoteResponse, SwapErrorRight>> => {
try {
const {
sellAssetId,
buyAssetId,
sellAmountCryptoBaseUnit,
chainId,
takerAddress,
receiverAddress,
slippageTolerancePercentageDecimal,
affiliateBps
} = params
// Convert to API format
const sellToken = assetIdToToken(sellAssetId)
const buyToken = assetIdToToken(buyAssetId)
const chainRef = chainIdToChainRef(chainId)
// CRITICAL: Convert slippage to API format
// ShapeShift format: 0.005 = 0.5%
// Check API docs for their format!
const slippagePercentage = bn(slippageTolerancePercentageDecimal)
.times(100) // If API expects 0.5 for 0.5%
.toNumber()
// Checksum addresses (CRITICAL for many APIs)
const checksummedTakerAddress = getAddress(takerAddress)
const checksummedReceiverAddress = getAddress(receiverAddress)
const requestBody: [Swapper]QuoteRequest = {
sellToken,
buyToken,
sellAmount: sellAmountCryptoBaseUnit,
slippage: slippagePercentage,
takerAddress: checksummedTakerAddress,
receiverAddress: checksummedReceiverAddress,
chainId: chainRef,
// Add affiliate if supported
...(affiliateBps !== '0' && { affiliateBps })
}
const maybeResponse = await service.post<[Swapper]QuoteResponse>(
`${BASE_URL}/quote`,
requestBody
)
if (maybeResponse.isErr()) {
return Err(maybeResponse.unwrapErr())
}
const { data: response } = maybeResponse.unwrap()
// Validate response has required fields
if (!response.buyAmount || !response.transaction) {
return Err(
makeSwapErrorRight({
message: 'Invalid response from API',
code: TradeQuoteError.InvalidResponse,
details: { response }
})
)
}
return Ok(response)
} catch (error) {
return Err(
makeSwapErrorRight({
message: 'Failed to fetch quote',
code: TradeQuoteError.QueryFailed,
cause: error
})
)
}
}
// For rates (no wallet needed)
export type FetchPriceParams = Omit<FetchQuoteParams, 'takerAddress' | 'receiverAddress'> & {
receiveAddress: string | undefined
}
export const fetchPrice = async (
params: FetchPriceParams,
service: [Swapper]Service
): Promise<Result<[Swapper]QuoteResponse, SwapErrorRight>> => {
// Use dummy address if no wallet connected
const address = params.receiveAddress
? getAddress(params.receiveAddress)
: DUMMY_ADDRESS
// IMPORTANT: Use same affiliate for both quote and rate to avoid delta!
return fetchQuote(
{
...params,
takerAddress: address,
receiverAddress: address
},
service
)
}2f. utils/get[SwapperName]TradeContext.ts - Shared Core
The context holds everything BOTH arms share: the provider fetch (when both arms hit the same
endpoint - Across/Debridge model) or just the assembly (when arms fetch differently - Zrx/Portals
model), error mapping, derived amounts, protocolFees, and the step data args. It contains ZERO
quoteOrRate checks and takes already-resolved addresses as params.
type [Swapper]TradeContext = {
tradeCommon: TradeCommon // id, rate, affiliateBps, slippage, swapperName...
stepCommon: Omit<TradeStepCommon, 'feeData'> // amounts, assets, allowanceContract, source...
protocolFees: QuoteFeeData['protocolFees']
stepDataArgs: Omit<Get[Swapper]StepDataArgs, 'type' | 'input'> // also omit arm-divergent extras
}Rules:
allowanceContract is '' when there is no approval target, never undefinedswapperMetadata (if any) is set here or in the quote wrapper - see Step 3Result - provider errors map to TradeQuoteError codes (QueryFailed, NoRouteFound,
SellAmountBelowMinimum...), never throw2g. utils/get[SwapperName]StepData.ts - Discriminated Step Data
The heart of the rate/quote split. Uses the shared StepDataArgs<Base, RateExtra, QuoteExtra>
generic from types.ts: Base carries deps + sellAsset + everything derived in the context;
the Rate/Quote generics carry arm-specific extras derived in the wrappers (e.g. chainflip's quote
depositAddress). Declare TWO overloads over one implementation so callers get precise per-arm
types:
type [Swapper]RateStepData = { networkFeeCryptoBaseUnit: string }
type [Swapper]QuoteStepData = { transactionData: TxBuildData; networkFeeCryptoBaseUnit: string }
export function get[Swapper]StepData(
args: Extract<Get[Swapper]StepDataArgs, { type: 'rate' }>,
): Promise<Result<[Swapper]RateStepData, SwapErrorRight>>
export function get[Swapper]StepData(
args: Extract<Get[Swapper]StepDataArgs, { type: 'quote' }>,
): Promise<Result<[Swapper]QuoteStepData, SwapErrorRight>>
export async function get[Swapper]StepData(
args: Get[Swapper]StepDataArgs,
): Promise<Result<[Swapper]RateStepData | [Swapper]QuoteStepData, SwapErrorRight>> { ... }The non-negotiable rules (see the review skill for full nuance):
transactionData - TradeRateStep bans it at the type levelmakeNetworkFeeEstimationFailedErr(context, cause) - NEVER a provider-fee fallback (execution
needs the same fee data). Unbuildable provider payloads (decode failure, missing fields) fail via
makeTradeStepBuildFailedErr(context, cause)throw - validation misses and unsupported-namespace default cases return Err;
try/catch is scoped ONLY around the external adapter/estimation calltransactionData.gasLimit is always set - pass the transactionData to
getEvmNetworkFeeCryptoBaseUnit (utils/evm), which prices a provider-supplied gasLimit as-is or
estimates-and-sets the buffered limit in place. Route ALL EVM fee math through itomitComputeBudgetInstructions), estimate via getSolanaNetworkFeeCryptoBaseUnit, then set the
static compute unit limit with withComputeUnitLimit({ instructions, computeUnits, includeComputeBudget, computeBudget }) using a per-swapper exported
[SWAPPER]_SOLANA_COMPUTE_BUDGET (margin measured against live drift){ type: 'utxo', to, opReturnData?, value } via getUtxoNetworkFeeCryptoBaseUnit;
guard genuinely-optional memo fields (estimation won't catch their absence)switch (chainNamespace) with both arms inline per case
(ButterSwap canonical) - never a separate rate helper that re-switches on namespace2h. Arm Wrappers - getTradeQuote/getTradeQuote.ts + getTradeRate/getTradeRate.ts
Thin assembly, returning Trade[] (Ok([trade])) so endpoints wire them directly:
// Quote wrapper: addresses guarded BEFORE any provider request
export const getTradeQuote = async (
input: [Swapper]TradeQuoteInput, // the scoped alias - see types.ts below
deps: SwapperDeps,
): Promise<Result<TradeQuote[], SwapErrorRight>> => {
const { accountNumber } = input
const maybeAddresses = assertQuoteAddresses(input)
if (maybeAddresses.isErr()) return Err(maybeAddresses.unwrapErr())
const { sendAddress, receiveAddress } = maybeAddresses.unwrap()
const maybeContext = await get[Swapper]TradeContext({ input, deps, from: sendAddress, ... })
if (maybeContext.isErr()) return Err(maybeContext.unwrapErr())
const { tradeCommon, stepCommon, protocolFees, stepDataArgs } = maybeContext.unwrap()
const maybeStepData = await get[Swapper]StepData({ ...stepDataArgs, type: 'quote', input })
if (maybeStepData.isErr()) return Err(maybeStepData.unwrapErr())
const { transactionData, networkFeeCryptoBaseUnit } = maybeStepData.unwrap()
const tradeQuote: TradeQuote = {
...tradeCommon,
quoteOrRate: 'quote',
receiveAddress,
steps: [{
...stepCommon,
accountNumber,
transactionData,
feeData: { networkFeeCryptoBaseUnit, protocolFees },
}],
}
return Ok([tradeQuote])
}Rate wrapper differences:
?? default/dummy address fallbacks (rate-only - a quote must NEVER request a provider
route with a defaulted address)accountNumber from the input (input.accountNumber - set when a wallet is
connected, undefined walletless; this feeds approval-before-quote flows). Do NOT hardcode
accountNumber: undefinedtransactionData on the step, quoteOrRate: 'rate'Result: no TradeQuoteStep | TradeRateStep unions, no as TradeQuoteStep casts, no scattered
input.quoteOrRate === 'quote' checks anywhere.
2i. Scoped Input Aliases - types.ts
EVERY swapper (even single-chain) defines scoped input aliases - unions of ONLY the supported
Get<Chain>Trade{Quote,Rate}Input members - and casts ONCE at the endpoint boundary:
export type [Swapper]TradeQuoteInput = GetEvmTradeQuoteInput | GetSolanaTradeQuoteInput
export type [Swapper]TradeRateInput = GetEvmTradeRateInput | GetSolanaTradeRateInputThe wrappers and context take the scoped alias; step data's input stays the wide
GetTradeRateInput/GetTradeQuoteInput (dictated by StepDataArgs). 'supportsEIP1559' in input
narrowing discriminates EVM members from the rest (chainId comparison does NOT narrow the union).
2j. endpoints.ts - SwapperApi Wiring
export const [swapperName]Api: SwapperApi = {
getTradeQuote: (input, deps) => getTradeQuote(input as [Swapper]TradeQuoteInput, deps),
getTradeRate: (input, deps) => getTradeRate(input as [Swapper]TradeRateInput, deps),
// Use the SHARED per-chain executors - do not hand-roll unless the swapper genuinely deviates
getUnsignedEvmTransaction, // from '../../utils/evm' - appends permit2 signature if present
getEvmTransactionFees, // from '../../utils/evm'
getUnsignedUtxoTransaction, // from '../../utils/utxo'
getUtxoTransactionFees,
getUnsignedSolanaTransaction, // from '../../utils/solana' - reads the static limit, fetches priority fee
getSolanaTransactionFees,
checkTradeStatus: async ({ config, swap }) => {
if (!swap) throw new Error('Missing swap')
// Read tracking data via getSwapMetadata (throws on mismatch - matches all swappers)
const { swapId } = getSwapMetadata(swap.metadata.swapperMetadata, '[swapperName]')
// ...poll the provider...
return {
status, // TxStatus
buyTxHash,
// The protocol's own tracker page, constructed HERE next to the provider response:
swapperTxId, // display id (native swap id, relayer hash, order uid)
swapperTxLink, // fully-formed URL (e.g. scan.chainflip.io/swaps/<id>)
message,
}
},
}For plain same-chain EVM swappers, checkTradeStatus: checkEvmSwapStatus (shared) suffices.
2k. [SwapperName]Swapper.ts - Swapper Interface
import { executeEvmTransaction } from '../../utils'
import type { Swapper } from '../../types'
export const [swapperName]Swapper: Swapper = {
executeEvmTransaction, // and/or executeSolanaTransaction etc. - shared executors
}Custom execution logic (e.g. CowSwap's order POST) lives here.
2l. index.ts - Barrel
Every swapper barrel exports at minimum its api + swapper def, so constants.ts imports one line
per swapper:
export { [swapperName]Api } from './endpoints'
export { [swapperName]Swapper } from './[SwapperName]Swapper'
export * from './types'Skip this step if execution and status tracking need nothing beyond the transaction hash (plain same-chain EVM swappers).
Implement it if status polling or execution needs a provider-side identifier (deposit address, order id, swap id, quote id).
The mechanism is the SwapperMetadata discriminated union - a single swapperMetadata field on the
step. There is NO web-side wiring: buildSwapMetadata carries it onto the persisted swap
automatically, and consumers read it with getSwapMetadata.
a. Define the union member in the swapper's types.ts:
export type [Swapper]Metadata = {
name: '[swapperName]' // the union discriminant
swapId: string // whatever tracking data status/exec needs - keep it minimal,
depositAddress: string // every field must have a read site (no write-only fields)
}b. Register it in packages/swapper/src/types.ts's SwapperMetadata union.
c. Set it at quote time (context or quote wrapper):
steps: [{ ...stepCommon, accountNumber, transactionData, swapperMetadata: { name: '[swapperName]', swapId, depositAddress }, ... }]d. Read it wherever needed - status polling and chain-specific execution:
const { swapId } = getSwapMetadata(swap.metadata.swapperMetadata, '[swapperName]') // status
const { depositAddress } = getSwapMetadata(step.swapperMetadata, '[swapperName]') // exec4a. packages/swapper/src/types.ts - Add Config Fields + SwapperName
export enum SwapperName {
// ... existing
[SwapperName] = '[Display Name]',
}
export type SwapperConfig = {
// ... existing fields
VITE_[SWAPPER]_API_KEY: string
}(SwapperName lives in types.ts, not constants.ts.)
4b. packages/swapper/src/constants.ts - Register Swapper
One barrel import per swapper, spread into the record:
import { [swapperName]Api, [swapperName]Swapper } from './swappers/[SwapperName]Swapper'
export const swappers: Record<SwapperName, (SwapperApi & Swapper) | undefined> = {
// ... existing
[SwapperName.[SwapperName]]: {
...[swapperName]Swapper,
...[swapperName]Api,
},
}Also add the swapper's default slippage to getDefaultSlippageDecimalPercentageForSwapper if it
differs from the default.
4c. packages/swapper/src/index.ts - Root Barrel
Re-export the swapper directory: export * from './swappers/[SwapperName]Swapper'
4c-bis. Public API + Swap Widget enablement (deliberate, separate decisions)
ENABLED_SWAPPER_NAMES in packages/public-api/src/constants.ts. Before enabling, confirm the
quote's transactionData variant is serialized by
packages/public-api/src/routes/quote/extractTransactionData.ts + the zod schemas - a variant
the extractor doesn't handle ships silently non-executable quotes.SwapperName enum
(packages/swap-widget/src/types/index.ts - members commented out = disabled) is the
widget allowlist; also add icon/color entries in packages/swap-widget/src/constants/swappers.ts
if enabling there. Only enable swappers the widget can actually execute.4d. CSP Headers (if swapper calls external API)
Create headers/csps/defi/swappers/[SwapperName].ts:
import type { Csp } from '../../../types'
export const csp: Csp = {
'connect-src': [
'https://api.[swapper].com',
'https://api.[swapper].io', // add all API domains
]
}Register in headers/csps/index.ts:
import { csp as [swapperName] } from './defi/swappers/[SwapperName]'
export const csps = [
// ... other csps
[swapperName],
]Add to src/state/slices/preferencesSlice/preferencesSlice.ts:
export type FeatureFlags = {
// ... existing
BebopSwap: boolean // Example: use PascalCase swapper name + "Swap" suffix
}
const initialState: Preferences = {
featureFlags: {
// ... existing
BebopSwap: getConfig().VITE_FEATURE_BEBOP_SWAP
}
}In src/state/helpers.ts:
Add to isCrossAccountTradeSupported (if supported):
export const isCrossAccountTradeSupported = (swapperName: SwapperName): boolean => {
switch (swapperName) {
case SwapperName.Bebop: // Use enum value, not placeholder
return true // or false if not supported
// ...
}
}Add to getEnabledSwappers:
export const getEnabledSwappers = (
{
BebopSwap, // ADD THIS - destructure the flag directly
// ... other existing flags like ChainflipSwap, ThorSwap, etc.
}: FeatureFlags,
isCrossAccountTrade: boolean,
isSolBuyAssetId: boolean
): Record<SwapperName, boolean> => {
return {
// ... existing
[SwapperName.Bebop]:
BebopSwap &&
(!isCrossAccountTrade || isCrossAccountTradeSupported(SwapperName.Bebop))
}
}In src/test/mocks/store.ts:
featureFlags: {
// ... existing
BebopSwap: false // Use actual flag name, not placeholder
}In UI:
Add icon: src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/[swapper]-icon.png
Update SwapperIcon.tsx:
import [swapperName]Icon from './[swapper]-icon.png'
const SwapperIcon = ({ swapperName }: Props) => {
switch (swapperName) {
// ... existing
case SwapperName.[SwapperName]:
return <Image src={[swapperName]Icon} />
}
}.env (production - both OFF):
# [Swapper Name]
VITE_[SWAPPER]_API_KEY=
VITE_FEATURE_[SWAPPER]_SWAP=false.env.development (development - flag ON):
# [Swapper Name]
VITE_[SWAPPER]_API_KEY=your-dev-api-key-here
VITE_FEATURE_[SWAPPER]_SWAP=trueAdd to src/config.ts:
export const getConfig = (): Config => ({
// ... existing
VITE_[SWAPPER]_API_KEY: import.meta.env.VITE_[SWAPPER]_API_KEY || '',
VITE_FEATURE_[SWAPPER]_SWAP: parseBoolean(import.meta.env.VITE_FEATURE_[SWAPPER]_SWAP)
})BEFORE testing, check for these critical bugs:
getAddress() from viemfromHex() for tx.value, tx.gas, tx.gasPriceaffiliateBps to BOTH quote and rate endpointstransactionData.gasLimit ends up set - via
getEvmNetworkFeeCryptoBaseUnit, never inline gas mathassertQuoteAddresses before any provider request; rate-only address
defaults never leak into quotesErr, try/catch scoped to adapter calls only4a. Automated Checks
# Type checking (MUST pass)
pnpm run type-check
# Linting (MUST pass)
pnpm run lint
# Build swapper package (MUST pass)
pnpm run build:swapper
# Build web (SHOULD pass, may have unrelated errors)
pnpm run build:webFix ALL type errors and lint errors before manual testing.
4b. Manual Testing Checklist
4c. Edge Cases
Create packages/swapper/src/swappers/[SwapperName]Swapper/INTEGRATION.md:
# [Swapper Name] Integration
## Overview
- **Website**: https://[swapper].com
- **API Docs**: https://docs.[swapper].com
- **Supported Chains**: Ethereum, Polygon, Arbitrum, ...
- **Type**: EVM Direct Transaction / Deposit-to-Address / Gasless
## API Details
- **Base URL**: `https://api.[swapper].com`
- **Authentication**: API key in `x-api-key` header
- **Rate Limiting**: X requests per second
- **Endpoints**:
- `POST /quote` - Get executable quote
- `GET /price` - Get rate without wallet
## Implementation Notes
### Slippage Format
API expects **percentage** (1 = 1%). ShapeShift internal format is decimal (0.01 = 1%), so we multiply by 100.
### Address Format
API requires **EIP-55 checksummed** addresses. We use `getAddress()` from viem.
### Native Token Handling
API uses marker address `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` for native tokens (ETH, MATIC, etc.).
### Response Format
```json
{
"buyAmount": "1000000",
"sellAmount": "500000000",
"transaction": {
"to": "0x...",
"data": "0x...",
"value": "0x0",
"gas": "0x5208"
}
}fromHex()/quote and /price to avoid rate deltabestPrice route
---
## Contract Enforcement
**After implementation**, verify your work against the contract at
`.claude/contracts/swapper-integration.md`. The contract contains the
authoritative registration, testing, and completion checklists that must
all pass before the integration is complete.
## Critical Success Factors
1. **Research First**: Understand API thoroughly BEFORE coding
2. **Copy Patterns**: Adapt proven patterns from similar swappers
3. **Type Safety**: Use strict TypeScript types, avoid `any`
4. **Monadic Errors**: ALWAYS return `Result<T, SwapErrorRight>`, never throw
5. **Test Gotchas**: Proactively fix known bugs (slippage, checksumming, hex conversion)
6. **Feature Flag**: Always behind flag for gradual rollout
7. **Documentation**: Write INTEGRATION.md with quirks and gotchas
## Completion Checklist
Before considering integration complete:
**Code Quality**:
- [ ] Package type check passes (`npx tsc --noEmit -p packages/swapper/tsconfig.esm.json` - the
root `-p packages/swapper` config checks ZERO files and always passes; never trust it)
- [ ] All lint checks pass (`pnpm run lint`)
- [ ] No `any` types used
- [ ] All errors handled monadically; no throws in step data/context
- [ ] Rates carry no transactionData; quote wrapper guards addresses via assertQuoteAddresses
**Functionality**:
- [ ] Can fetch quotes successfully
- [ ] Can fetch rates without wallet
- [ ] Approval flow works (if needed)
- [ ] Transaction execution succeeds
- [ ] Status polling works (if applicable)
- [ ] Native token swaps work
- [ ] Error cases handled gracefully
**Integration**:
- [ ] SwapperName added in types.ts; registered in constants.ts via the swapper barrel
- [ ] Barrel exports { api, swapper }; root index.ts re-exports the directory
- [ ] Scoped [Swapper]Trade{Quote,Rate}Input aliases with the cast at the endpoint boundary
- [ ] SwapperMetadata union member registered (if tracking data needed)
- [ ] CSP headers added
- [ ] Feature flag implemented
- [ ] Test mocks updated
- [ ] Swapper icon added to UI
- [ ] Environment variables configured
- [ ] Public api enablement decided (ENABLED_SWAPPER_NAMES + wire variant serialization verified)
- [ ] Swap widget enablement decided (widget SwapperName enum + icon map)
**Documentation**:
- [ ] INTEGRATION.md created
- [ ] API quirks documented
- [ ] Known issues listed
- [ ] Testing notes included
**Testing**:
- [ ] Manual testing completed
- [ ] Rate vs quote delta verified (< 0.1%)
- [ ] Cross-account trades tested (if supported)
- [ ] Edge cases tested (min/max amounts, errors)
## Common Errors & Solutions
**"Taker address not checksummed"**
→ Use `getAddress(address)` from viem before sending to API
**"Number '0x...' is not a valid decimal"**
→ Convert hex to decimal: `fromHex(value as Hex, 'bigint').toString()`
**"Sell amount lower than fee"**
→ Check response parsing, likely accessing wrong field structure
**Large rate vs quote delta**
→ Pass same `affiliateBps` to both `/quote` and `/price` endpoints
**Quote succeeds but execution throws 'missing gas limit in evm transaction'**
→ The quote arm didn't route through `getEvmNetworkFeeCryptoBaseUnit` with the transactionData - it
estimates-and-sets the buffered gasLimit in place when the provider omits gas
**"$0 showing in UI"**
→ Response parsing bug, log actual response and verify structure
**"Transaction fails with slippage exceeded"**
→ Wrong slippage format sent to API (check docs for percentage/decimal/bps)
**Type error: "Property 'xyz' does not exist on type"**
→ Define proper TypeScript types matching actual API response
**"Cannot read property 'chainId' of undefined"**
→ Check null safety, add optional chaining or validation
---
## Need Help?
1. Read similar swapper implementations in packages/swapper/src/swappers/
2. Review the gotchas and patterns documented throughout this skill
3. Grep for similar patterns: `grep -r "pattern" packages/swapper/src/swappers/`
4. Ask user for API behavior clarification
5. Test with curl to verify API responses
---
**Remember**: Most bugs come from assumptions about API behavior. ALWAYS verify with actual API calls and log responses!© shapeshift, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 4 other files in .claude/skills/swapper-integration of shapeshift/web.
Open the folder on GitHubat commit 52aebb2
Swapper Integration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Swapper Integration this skillshapeshift/web | 206 | — | ~11k | Automated safety check: Notes | MIT | |
| Swapper IntegrationMicrock/ordinary-claude-skills | 401 | — | ~4.3k | Automated safety check: Notes | Custom licence | |
| Integration Testingthedaviddias/Front-End-Checklist | 74k | — | ~514 | Automated safety check: Pass | MIT | |
| Protocolsio Integrationdavila7/claude-code-templates | 32k | 10 repos | ~3.7k | Automated safety check: Pass | MIT | |
| API Integrationsickn33/agentic-awesome-skills | 47k | 1 repos | ~1.3k | Automated safety check: Pass | MIT | |
| Labarchive Integrationdavila7/claude-code-templates | 32k | 10 repos | ~2.3k | Automated safety check: Pass | MIT |
Microck/ordinary-claude-skills
Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web.
thedaviddias/Front-End-Checklist
A skill your agent uses when reviewing CI coverage, automated checks, or test strategy related to Write integration tests for key workflows.
davila7/claude-code-templates
Integration with protocols.io API for managing scientific protocols.
sickn33/agentic-awesome-skills
Designs event-driven architectures, webhook systems, API chaining flows, ETL pipelines, and integration patterns between services.
davila7/claude-code-templates
Electronic lab notebook API integration. An agent skill from davila7/claude-code-templates.
alsk1992/CloddsBot
Cross-chain DEX market intelligence - trending tokens, gainers, losers, volume, stats across all chains and protocols
shapeshift/web
Comprehensive React and Next.js performance optimization guide with 40+ rules for eliminating waterfalls, optimizing bundles, and improving rendering.
shapeshift/web
Run a quality benchmark of the /translate skill by selecting stratified test keys, capturing ground truth, translating, judging with sub-agents, and compiling a regression report.
shapeshift/web
Create a new qabot E2E test fixture interactively. An agent skill from shapeshift/web.
shapeshift/web
Translate new/changed English UI strings into all supported languages using a translate-review-refine pipeline.
shapeshift/web
Integrate a new blockchain as a second-class citizen in ShapeShift Web.
shapeshift/web
Run QA tests using agent-browser and post results to the qabot dashboard.
Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web. Swapper Integration is an agent skill from shapeshift/web.) into ShapeShift Web.
Swapper Integration fits situations like: implement support for a new swapper.
Run `npx skills add shapeshift/web --skill swapper-integration -a claude-code`. Or copy the skill folder (.claude/skills/swapper-integration in shapeshift/web) into .claude/skills/swapper-integration in your project. Claude Code loads it when a task matches its description.
Run `npx skills add shapeshift/web --skill swapper-integration -a codex`. Or copy the skill folder (.claude/skills/swapper-integration in shapeshift/web) into .agents/skills/swapper-integration in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add shapeshift/web --skill swapper-integration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/swapper-integration, .gemini/skills/swapper-integration, .github/skills/swapper-integration and .opencode/skills/swapper-integration in your project.
Going by SKILL.md and its folder, Swapper Integration needs the command-line tools its instructions call (pnpm) and credentials named VITE_XYZ_API_KEY. Our summary lists: A credential in VITE_XYZ_API_KEY. Its frontmatter pre-approves these tools: Read, Write, Edit, Grep, Glob, WebFetch, WebSearch, Bash(pnpm run test:*), Bash(pnpm run lint:*), Bash(pnpm run type-check), Bash(pnpm run build:*), Bash(gh pr:*), AskUserQuestion.
SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Swapper Integration is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 46k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Swapper Integration: Swapper Integration (Microck/ordinary-claude-skills, 401 stars), Integration Testing (thedaviddias/Front-End-Checklist, 74k stars), Protocolsio Integration (davila7/claude-code-templates, 32k stars) and API Integration (sickn33/agentic-awesome-skills, 47k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
shapeshift (a GitHub organization) maintains it in shapeshift/web, which has 206 GitHub stars. The repository holds 7 skills in this directory. The repository was last updated on October 7, 2026.
Source: shapeshift/web on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.