Skip to content

Conversation

@NeOMakinG
Copy link
Collaborator

@NeOMakinG NeOMakinG commented Jan 17, 2026

Description

Adds DeDust swapper integration for TON blockchain to enable native TON swaps within ShapeShift.

Key changes:

  • Implemented DeDust swapper with multi-hop routing support through TON
  • Added support for both STABLE and VOLATILE pool types to find best rates
  • Integrated with existing swapper infrastructure and feature flag system
  • Added CSP configuration for DeDust API endpoints
  • Includes rate sanity checks to reject catastrophically bad routes

Issue (if applicable)

closes #

Risk

Low-Medium Risk

This is behind a feature flag (DedustSwap) and only affects TON blockchain swaps. The implementation follows existing swapper patterns.

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

TON blockchain swaps using DeDust protocol. Only activated when feature flag is enabled.

Testing

Engineering

  1. Enable DedustSwap feature flag via /flags route
  2. Test TON swaps with various token pairs
  3. Verify multi-hop routing works correctly
  4. Check that bad routes are rejected

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Screenshots (if applicable)

https://jam.dev/c/e7c722ac-e2b1-45ea-b5ec-531b3c8e9c0b

Summary by CodeRabbit

Release Notes

  • New Features
    • Added DeDust as a new swapper option supporting TON and jetton asset exchanges with single and multi-hop routing capabilities
    • Introduced a feature flag to control DeDust availability (disabled by default, configurable per environment)
    • Integrated DeDust branding into the swapper UI

✏️ Tip: You can customize this high-level summary in your review settings.

Minimoi and others added 24 commits January 17, 2026 19:15
…to swapper package

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…in types

Added dedustSpecific optional property to TradeQuoteStep type with:
- poolAddress: DeDust pool contract address
- sellAssetAddress: TON native or jetton address for sell asset
- buyAssetAddress: TON native or jetton address for buy asset
- sellAmount: Amount being sold
- minBuyAmount: Minimum amount to receive (with slippage)
- gasBudget: Recommended gas for the transaction
- quoteTimestamp: When the quote was created

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…specific

Add DeDust-specific types for the swapper implementation:
- DedustSupportedChainId - type alias for TON mainnet
- dedustSupportedChainIds - const array of supported chains
- DedustAssetAddress - type for DeDust asset addresses (native/jetton)
- DedustAssetValidationResult - discriminated union for asset validation
- DedustQuote - quote data from DeDust pool estimation
- DedustTradeSpecific - trade-specific metadata for quote steps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…K client

- Add dedustClientManager with singleton pattern for TonClient4 and Factory
- Add DEDUST_TON_V4_ENDPOINT and gas budget constants
- Factory initialized from MAINNET_FACTORY_ADDR from @dedust/sdk

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ion and rate calculation

Add helper utilities for DeDust swapper:
- isTonAsset: Check if asset is on TON chain
- assetToDedustAddress: Convert ShapeShift asset to DeDust address format
- validateTonAssets: Validate both assets are on TON and can be converted
- calculateRate: Calculate exchange rate with precision conversion
- slippageDecimalToBps: Convert slippage decimal to basis points
- calculateMinBuyAmount: Calculate minimum buy amount after slippage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…te fetch

Implements getTradeQuote function for DeDust swapper that:
- Validates TON assets using existing helpers
- Gets pool from DeDust factory for the trading pair
- Fetches estimated swap output from the pool
- Calculates rate, slippage, and min buy amount
- Returns TradeQuote with dedustSpecific metadata

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… fetching

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…entation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…nsaction

Add DedustSwapper.ts following the StonfiSwapper pattern for TON transaction
delegation. The swapper implements executeTonTransaction which delegates to
signAndBroadcastTransaction for TON chain swap execution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…appers map

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…itialState

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… and getEnabledSwappers

- Add SwapperName.DeDust to isCrossAccountTradeSupported (returns false)
- Add DedustSwap to destructured FeatureFlags in getEnabledSwappers
- Add DeDust entry to return object in getEnabledSwappers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add the DeDust swapper feature flag to environment configuration:
- .env: VITE_FEATURE_DEDUST_SWAP=false (default base)
- .env.development: VITE_FEATURE_DEDUST_SWAP=true (enabled for dev)
- .env.production: VITE_FEATURE_DEDUST_SWAP=false (disabled for prod)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…d save to SwapperIcon directory

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ran yarn install to install newly added dependencies including @dedust/sdk
and @ton/ton packages required for the DeDust swapper integration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fixed import ordering in endpoints.ts
- Fixed object destructuring formatting
- Applied ESLint auto-fixes to DedustSwapper files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix VaultNative swap payload: build manually using opcode 0xea06185d
  (VaultNative doesn't have static createSwapPayload like VaultJetton)
- Fix VaultJetton swap: move deadline into swapParams object
- Fix pool.getEstimatedSwapOut: open pool with client.open() first
- Add type assertion for discriminated union narrowing in validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes:
- Add pool readiness check (ReadinessStatus.READY) in getTradeQuote.ts
- Add pool readiness check (ReadinessStatus.READY) in getTradeRate.ts
- Remove unused VaultNative import from endpoints.ts

Critical security fix: Pool readiness must be verified before any swap
operation to prevent potential fund loss as per DeDust SDK requirements.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Adds DeDust swapper integration for TON blockchain including feature flags, environment configuration, CSP headers, complete swapper implementation with trade routing and multi-hop support, type definitions, UI icons, and state management wiring.

Changes

Cohort / File(s) Summary
Environment Configuration
.env, .env.development, .env.production
Adds feature flag VITE_FEATURE_DEDUST_SWAP across environments (false by default, true in development).
Security Headers
headers/csps/defi/swappers/Dedust.ts, headers/csps/index.ts
Creates CSP configuration for DeDust with connect-src to https://mainnet-v4.tonhubapi.com and registers it in the CSP index.
Package Dependencies
packages/swapper/package.json
Adds @dedust/sdk and @ton/ton dependencies.
Type System Updates
packages/swapper/src/types.ts
Adds SwapperName.DeDust enum variant and dedustSpecific optional field to TradeQuoteStep with pool and swap metadata.
Swapper Core Implementation
packages/swapper/src/swappers/DedustSwapper/DedustSwapper.ts
Implements minimal dedustSwapper with executeTonTransaction delegating to sign and broadcast.
Swapper API — Endpoints
packages/swapper/src/swappers/DedustSwapper/endpoints.ts
Implements dedustApi with getUnsignedTonTransaction (retry logic, native/jetton swap builders), getTonTransactionFees, and checkTradeStatus with comprehensive error handling.
Swapper API — Trade Routing
packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeQuote.ts, getTradeRate.ts
Implements trade quote and rate functions with direct pool queries, optional multi-hop TON-based routing (SELL→TON→BUY), and route selection logic.
Swapper Type Definitions
packages/swapper/src/swappers/DedustSwapper/types.ts
Defines DedustAssetAddress, DedustQuote, DedustTradeSpecific, DedustSwapHop, and related types for Dedust swap modeling.
Swapper Utilities
packages/swapper/src/swappers/DedustSwapper/utils/constants.ts, dedustClient.ts, helpers.ts
Adds constants (slippage, gas budgets, endpoints), client manager with singleton caching, and helpers (asset validation, rate calculation, slippage conversion).
Swapper Module Exports
packages/swapper/src/swappers/DedustSwapper/index.ts
Re-exports swapper API, implementation, types, and constants.
Swapper Registration
packages/swapper/src/constants.ts
Registers SwapperName.DeDust in swappers map and adds DEFAULT_DEDUST_SLIPPAGE_DECIMAL_PERCENTAGE constant with getter.
Application Configuration
src/config.ts
Adds VITE_FEATURE_DEDUST_SWAP boolean validator to environment config.
State Management
src/state/helpers.ts
Updates isCrossAccountTradeSupported and getEnabledSwappers to recognize SwapperName.DeDust gated by feature flag.
Preferences & Mocks
src/state/slices/preferencesSlice/preferencesSlice.ts, src/test/mocks/store.ts
Adds DedustSwap boolean field to FeatureFlags type and initializes it in preferences and mock store.
UI Components
src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsx
Imports and maps DedustIcon for SwapperName.DeDust in icon selector.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #11552 — Both PRs add a new swapper integration (feature flag, CSP entry, package dependencies, SwapperName enum entry, swapper implementation with endpoints, types, utilities, and state feature flag) following the same integration pattern for different providers.
  • PR #11240 — Both PRs add a new swapper feature (DeDust for TON vs. Cetus for SUI) and make analogous code-level changes to core files (environment configuration, swapper constants, types, state helpers, preferences, and module structure).
  • PR #11500 — Both PRs extend swapper registration by modifying packages/swapper/src/constants.ts to integrate a new swapper with its configuration and default slippage settings.

Suggested reviewers

  • gomesalexandre
  • premiumjibles

Poem

🐰 A DeDust swapper hops onto the scene,
Multi-hop routing, the best ever seen!
TON assets swapping with grace and care,
Feature flags wave through the air!
Another layer of swap-magick is here! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: dedust swapper ton implementation' accurately and clearly summarizes the main change: adding DeDust swapper support for TON blockchain. It is specific, concise, and directly reflects the PR's primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-claude/003-feat-add-dedust-swapper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@NeOMakinG NeOMakinG changed the title feat: dedux swapper ton implementation feat: dedust swapper ton implementation Jan 17, 2026
Minimoi and others added 4 commits January 18, 2026 00:47
The quote fetching was failing because jetton addresses were being
incorrectly cast as Address types instead of properly parsed.

Changed from: dedustAddress.address as unknown as Address
Changed to: tonAddress(dedustAddress.address)

This uses the @ton/core address() function to properly parse the
string address into an Address object that the DeDust SDK requires.

Fixes: DeDust quotes not appearing in the Available Quotes list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds Content-Security-Policy connect-src directive for
https://mainnet-v4.tonhubapi.com which is required for
DeDust SDK to make on-chain quote calculations.

- Created headers/csps/defi/swappers/Dedust.ts
- Added dedust CSP to headers/csps/index.ts exports

QA Fix Session: 0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…requested)

The DeDust swapper was only querying VOLATILE pools, which caused extremely
poor rates for stablecoin pairs like USDE/USDT. VOLATILE pools use the
constant product formula (x*y=k) which is not optimal for stable pairs.

Changes:
- Query both PoolType.STABLE and PoolType.VOLATILE pools in parallel
- Compare output amounts and use the pool with the better rate
- Store poolType in dedustSpecific for transaction building
- Add DedustPoolType type to types

For stablecoin pairs, STABLE pools use StableSwap curves optimized for
near 1:1 trading, providing much better rates.

Fixes: Bad quote rate for USDE→USDT (was 0.000089 instead of ~1.0)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…requested)

Addresses QA feedback about selecting the best pool rate by implementing
multi-hop routing through TON for jetton-to-jetton swaps.

Changes:
- Added multi-hop routing through TON (sellAsset → TON → buyAsset)
- Compares direct pool rates with multi-hop route and selects the best
- Added DedustSwapHop type to track hop details for multi-hop swaps
- Multi-hop swaps have 2x gas budget to cover both transactions
- Multi-hop swaps have 60s estimated execution time (vs 30s for direct)

This improves rates for pairs that don't have direct pools with good
liquidity (e.g., USDT → SUSDE can route through TON for better rates).

QA Fix Session: 0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Minimoi and others added 2 commits January 18, 2026 02:34
…-requested)

Problem:
- DeDust multi-hop routing through TON was returning terrible rates
  for stablecoin pairs (e.g., 2.8 USDE for 10,000 USDT)
- This happened when there was no direct pool, and multi-hop gave
  a catastrophically bad rate

Solution:
- Add rate sanity check for multi-hop routes without direct pools
- Only use multi-hop if output is at least 1% of input value
- This prevents users from accidentally accepting terrible rates

Fixes:
- getTradeQuote.ts: Add rate sanity check before returning multi-hop
- getTradeRate.ts: Same fix for rate fetching

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove .auto-claude-security.json, .auto-claude-status, and .claude_settings.json files that were accidentally committed. Also remove .auto-claude/ from .gitignore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@NeOMakinG NeOMakinG marked this pull request as ready for review January 18, 2026 16:15
@NeOMakinG NeOMakinG requested a review from a team as a code owner January 18, 2026 16:15
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.env.development (1)

95-103: Fix dotenv-linter key ordering.

dotenv-linter warns this key should be placed before VITE_FEATURE_KATANA. Reordering avoids lint noise.

🧹 Suggested reorder
 VITE_FEATURE_CETUS_SWAP=true
 VITE_FEATURE_AVNU_SWAP=true
+VITE_FEATURE_DEDUST_SWAP=true
 VITE_FEATURE_NEAR=true
 VITE_FEATURE_KATANA=true
 VITE_FEATURE_YIELD_XYZ=true
 VITE_FEATURE_TON=true
 VITE_FEATURE_STONFI_SWAP=true
-VITE_FEATURE_DEDUST_SWAP=true
 VITE_FEATURE_YIELDS_PAGE=true
 VITE_FEATURE_EARN_TAB=true
 VITE_FEATURE_YIELD_MULTI_ACCOUNT=true
.env (1)

79-87: Fix dotenv-linter key ordering.

dotenv-linter expects VITE_FEATURE_DEDUST_SWAP to appear before VITE_FEATURE_JUPITER_SWAP.

🧹 Suggested reorder
 VITE_FEATURE_CHAINFLIP_SWAP=true
 VITE_FEATURE_CHAINFLIP_SWAP_DCA=true
 VITE_FEATURE_COWSWAP=true
 VITE_FEATURE_THOR_SWAP=true
 VITE_FEATURE_ZRX_SWAP=true
+VITE_FEATURE_DEDUST_SWAP=false
 VITE_FEATURE_JUPITER_SWAP=true
 VITE_FEATURE_MAYA_SWAP=true
-VITE_FEATURE_DEDUST_SWAP=false
🤖 Fix all issues with AI agents
In `@headers/csps/defi/swappers/Dedust.ts`:
- Around line 1-4: The CSP object export named csp in Dedust.ts is missing
required DeDust SDK hosts; update the 'connect-src' array in the exported csp to
include 'https://api.dedust.io' and 'https://hub.dedust.io' so it becomes
['https://mainnet-v4.tonhubapi.com', 'https://api.dedust.io',
'https://hub.dedust.io'] ensuring the SDK can call its API and Developer Hub.

In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeQuote.ts`:
- Around line 195-239: The current getTradeQuote always attempts multi-hop via
tryGetMultiHopRoute regardless of caller intent; update getTradeQuote to read
the allowMultiHop flag from CommonTradeQuoteInput and only call
tryGetMultiHopRoute when allowMultiHop is true and neither asset is TON (i.e.
replace the current sellIsTon || buyIsTon ? Promise.resolve(null) :
tryGetMultiHopRoute(...) branch with a condition that also checks
allowMultiHop). Ensure the referenced symbols are getTradeQuote,
tryGetMultiHopRoute, sellIsTon, buyIsTon and the input property allowMultiHop so
multi‑hop routing is skipped when the caller disallows it.
- Around line 261-266: The 1% sanity check compares raw base units and ignores
differing token precisions, causing false rejects/accepts; update the logic in
getTradeQuote around minAcceptableRatio/isReasonableRate to normalize amounts by
token precisions before comparison: obtain the input and output token decimals
(e.g., from the route or trade quote input), scale multiHopRoute.amountOut and
amountIn to the same precision (using BigInt pow10 of the decimals difference)
and then apply the minAcceptableRatio check (isReasonableRate = scaledAmountOut
* minAcceptableRatio >= scaledAmountIn); ensure you handle both cases where
output has more or fewer decimals and keep using BigInt arithmetic.

In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeRate.ts`:
- Around line 83-113: The code in getTradeRate.ts is swallowing exceptions
(catch blocks that return null) when calling factory.getPool, client.open,
pool.getReadinessStatus, and pool.getEstimatedSwapOut; update the try/catch
blocks to catch the error object (catch (err)) and log the error with context
(include poolType, sellDedustAsset, buyDedustAsset and any pool address if
available) before returning null so SDK/network failures are visible; use the
module's existing logger (or console.error if none) and include
ReadinessStatus/PoolType context when logging.
- Around line 250-266: The 1% sanity check currently multiplies
multiHopRoute.amountOut by minAcceptableRatio and compares to amountIn using raw
base units (in functions referencing multiHopRoute, bestDirectQuote, amountIn,
minAcceptableRatio), which mis-handles differing token decimals; instead compute
a normalized rate/amount (use the existing calculateRate or equivalent helper to
convert amounts to a common precision or convert amountOut to the input token's
base) and then apply the 1% threshold against the normalized values (replace the
raw check in the else branch with a normalized isReasonableRate using
calculateRate or converted amountOut before setting useMultiHop).
🧹 Nitpick comments (2)
packages/swapper/src/swappers/DedustSwapper/utils/constants.ts (1)

1-4: Align supported chain IDs with the {sell, buy} shape used by swappers.

Keeping this as a simple array can drift from the SupportedChainIds contract used by filterAssetIdsBySellable/filterBuyAssetsBySellAssetId. Consider exporting the structured object to avoid mismatches. As per coding guidelines, align supported chain IDs with the standard SupportedChainIds pattern.

♻️ Proposed refactor
 import { KnownChainIds } from '@shapeshiftoss/types'
+import type { SupportedChainIds } from '../../../types'
 
-export const DEDUST_SUPPORTED_CHAIN_IDS = [KnownChainIds.TonMainnet] as const
+export const DEDUST_SUPPORTED_CHAIN_IDS: SupportedChainIds = {
+  sell: [KnownChainIds.TonMainnet],
+  buy: [KnownChainIds.TonMainnet],
+}
packages/swapper/src/swappers/DedustSwapper/endpoints.ts (1)

20-107: Add explicit return types for swap message builders.

The repo guidelines require explicit return types for all TypeScript functions. Rather than defining TonRawMessage locally as shown in the diff, import it from the existing type in @shapeshiftoss/chain-adapters which already defines this type:

♻️ Proposed refactor
 import { Asset as DedustAsset, JettonRoot, VaultJetton } from '@dedust/sdk'
 import { toAddressNList } from '@shapeshiftoss/chain-adapters'
+import type { ton } from '@shapeshiftoss/chain-adapters'
 import { TxStatus } from '@shapeshiftoss/unchained-client'
 import type { Address } from '@ton/core'
 import { address as tonAddress, beginCell, toNano } from '@ton/core'

-const buildNativeTonSwapMessage = async (
+const buildNativeTonSwapMessage = async (
   poolAddress: string,
   sellAmount: string,
   minBuyAmount: string,
   gasBudget: string,
-) => {
+): Promise<ton.TonRawMessage> => {

-const buildJettonSwapMessage = async (
+const buildJettonSwapMessage = async (
   from: string,
   poolAddress: string,
   sellAssetAddress: string,
   sellAmount: string,
   minBuyAmount: string,
   gasBudget: string,
-) => {
+): Promise<ton.TonRawMessage> => {

Comment on lines +1 to +4
import type { Csp } from '../../../../types'

export const csp: Csp = {
'connect-src': ['https://mainnet-v4.tonhubapi.com'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for DeDust references in the codebase to understand implementation
rg -i "dedust" --type ts --type tsx --type js --type json -A 3 -B 1

Repository: shapeshift/web

Length of output: 85


🏁 Script executed:

# Look for other CSP files to understand the pattern
fd -e ts -p "*/csps/*" | head -20

Repository: shapeshift/web

Length of output: 345


🏁 Script executed:

# Check if there are any package.json references to dedust
rg "dedust" package.json -i

Repository: shapeshift/web

Length of output: 40


🌐 Web query:

DeDust SDK API endpoints required hosts documentation

💡 Result:

Here are the DeDust hosts you should allow/whitelist for the SDK and API to function:

  • api.dedust.io (DeDust API endpoints). [1]
  • docs.dedust.io (official SDK/API docs). [2]
  • hub.dedust.io (Developer Hub / SDK pages). [3]
  • mainnet-v4.tonhubapi.com (TON RPC/endpoint used in SDK examples). [2]

Sources:
[1] DeDust API endpoint list (api.dedust.io).
[2] DeDust docs / Getting Started and SDK examples (docs.dedust.io).
[3] DeDust Developer Hub (hub.dedust.io).


🏁 Script executed:

# Search for DeDust implementation files
fd -type f -name "*[Dd]edust*"

Repository: shapeshift/web

Length of output: 228


🏁 Script executed:

# Search for any DeDust SDK instantiation or API calls
rg "dedust|DeDust" -i --type ts --type js -A 3

Repository: shapeshift/web

Length of output: 50370


🏁 Script executed:

# Search for any API calls to dedust.io domains
rg "dedust\.io|api\.dedust|hub\.dedust|docs\.dedust" -i

Repository: shapeshift/web

Length of output: 40


🏁 Script executed:

# Check what the DeDust SDK actually imports and uses
grep -r "@dedust/sdk" --include="*.ts" -A 2 | head -40

Repository: shapeshift/web

Length of output: 1546


🏁 Script executed:

# Check for `@dedust/sdk` in package.json
rg "@dedust/sdk" package.json -A 1 -B 1

Repository: shapeshift/web

Length of output: 40


🏁 Script executed:

# Look for SDK documentation or README in node_modules-like references
find . -name "dedust*" -o -name "*.md" | grep -i dedust | head -20

Repository: shapeshift/web

Length of output: 217


Add missing DeDust SDK hosts to CSP.

The CSP is incomplete. DeDust SDK requires additional hosts beyond mainnet-v4.tonhubapi.com:

  • https://api.dedust.io (required for SDK API endpoints)
  • https://hub.dedust.io (required for Developer Hub)

Update the CSP entry to:

'connect-src': ['https://mainnet-v4.tonhubapi.com', 'https://api.dedust.io', 'https://hub.dedust.io'],

Without these, the SDK will encounter CSP violations at runtime.

🤖 Prompt for AI Agents
In `@headers/csps/defi/swappers/Dedust.ts` around lines 1 - 4, The CSP object
export named csp in Dedust.ts is missing required DeDust SDK hosts; update the
'connect-src' array in the exported csp to include 'https://api.dedust.io' and
'https://hub.dedust.io' so it becomes ['https://mainnet-v4.tonhubapi.com',
'https://api.dedust.io', 'https://hub.dedust.io'] ensuring the SDK can call its
API and Developer Hub.

Comment on lines +195 to +239
export const getTradeQuote = async (input: CommonTradeQuoteInput): Promise<TradeQuoteResult> => {
const {
sellAsset,
buyAsset,
sellAmountIncludingProtocolFeesCryptoBaseUnit,
receiveAddress,
accountNumber,
slippageTolerancePercentageDecimal,
} = input

const validation = validateTonAssets(sellAsset, buyAsset)
if (!validation.isValid) {
return validation.error as TradeQuoteResult
}

const { sellAssetAddress, buyAssetAddress } = validation
const client = dedustClientManager.getClient()
const factory = dedustClientManager.getFactory()

try {
const sellDedustAsset = dedustAddressToAsset(sellAssetAddress)
const buyDedustAsset = dedustAddressToAsset(buyAssetAddress)
const amountIn = BigInt(sellAmountIncludingProtocolFeesCryptoBaseUnit)

// Check if either asset is TON (native) - skip multi-hop if so
const sellIsTon = sellAssetAddress.type === 'native'
const buyIsTon = buyAssetAddress.type === 'native'

// Try direct pools (STABLE and VOLATILE) in parallel with multi-hop route
// STABLE pools use StableSwap curves optimized for stablecoin pairs
// VOLATILE pools use standard constant product (x*y=k) formula
const [stableQuote, volatileQuote, multiHopRoute] = await Promise.all([
tryGetPoolQuote(factory, client, PoolType.STABLE, sellDedustAsset, buyDedustAsset, amountIn),
tryGetPoolQuote(
factory,
client,
PoolType.VOLATILE,
sellDedustAsset,
buyDedustAsset,
amountIn,
),
// Only try multi-hop if neither asset is TON
sellIsTon || buyIsTon
? Promise.resolve(null)
: tryGetMultiHopRoute(factory, client, sellDedustAsset, buyDedustAsset, amountIn),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Respect allowMultiHop to avoid unexpected routing.
Multi‑hop is attempted even when the caller disallows it. This can override user/flow intent.

🛠️ Proposed fix
-  const {
-    sellAsset,
-    buyAsset,
-    sellAmountIncludingProtocolFeesCryptoBaseUnit,
-    receiveAddress,
-    accountNumber,
-    slippageTolerancePercentageDecimal,
-  } = input
+  const {
+    sellAsset,
+    buyAsset,
+    sellAmountIncludingProtocolFeesCryptoBaseUnit,
+    receiveAddress,
+    accountNumber,
+    slippageTolerancePercentageDecimal,
+    allowMultiHop,
+  } = input
...
-      sellIsTon || buyIsTon
+      !allowMultiHop || sellIsTon || buyIsTon
         ? Promise.resolve(null)
         : tryGetMultiHopRoute(factory, client, sellDedustAsset, buyDedustAsset, amountIn),
🤖 Prompt for AI Agents
In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeQuote.ts`
around lines 195 - 239, The current getTradeQuote always attempts multi-hop via
tryGetMultiHopRoute regardless of caller intent; update getTradeQuote to read
the allowMultiHop flag from CommonTradeQuoteInput and only call
tryGetMultiHopRoute when allowMultiHop is true and neither asset is TON (i.e.
replace the current sellIsTon || buyIsTon ? Promise.resolve(null) :
tryGetMultiHopRoute(...) branch with a condition that also checks
allowMultiHop). Ensure the referenced symbols are getTradeQuote,
tryGetMultiHopRoute, sellIsTon, buyIsTon and the input property allowMultiHop so
multi‑hop routing is skipped when the caller disallows it.

Comment on lines +261 to +266
// No direct pool - check if multi-hop rate is reasonable
// Reject if output is less than 1% of input (accounting for same-decimal assets)
// This catches catastrophic routing failures like getting 2.8 for 10,000
const minAcceptableRatio = BigInt(100) // 1% = 1/100
const isReasonableRate = multiHopRoute.amountOut * minAcceptableRatio >= amountIn
useMultiHop = isReasonableRate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the 1% sanity check to account for differing precisions.
The guard compares base units directly, so assets with different precisions can be incorrectly rejected (or accepted).

🛠️ Proposed fix
-        const minAcceptableRatio = BigInt(100) // 1% = 1/100
-        const isReasonableRate = multiHopRoute.amountOut * minAcceptableRatio >= amountIn
+        const precisionDelta = buyAsset.precision - sellAsset.precision
+        const scale = BigInt(10) ** BigInt(Math.abs(precisionDelta))
+        const amountInInBuyPrecision =
+          precisionDelta >= 0 ? amountIn * scale : amountIn / scale
+        const minAcceptableRatio = 100n // 1% = 1/100
+        const isReasonableRate =
+          multiHopRoute.amountOut * minAcceptableRatio >= amountInInBuyPrecision
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// No direct pool - check if multi-hop rate is reasonable
// Reject if output is less than 1% of input (accounting for same-decimal assets)
// This catches catastrophic routing failures like getting 2.8 for 10,000
const minAcceptableRatio = BigInt(100) // 1% = 1/100
const isReasonableRate = multiHopRoute.amountOut * minAcceptableRatio >= amountIn
useMultiHop = isReasonableRate
// No direct pool - check if multi-hop rate is reasonable
// Reject if output is less than 1% of input (accounting for same-decimal assets)
// This catches catastrophic routing failures like getting 2.8 for 10,000
const precisionDelta = buyAsset.precision - sellAsset.precision
const scale = BigInt(10) ** BigInt(Math.abs(precisionDelta))
const amountInInBuyPrecision =
precisionDelta >= 0 ? amountIn * scale : amountIn / scale
const minAcceptableRatio = 100n // 1% = 1/100
const isReasonableRate =
multiHopRoute.amountOut * minAcceptableRatio >= amountInInBuyPrecision
useMultiHop = isReasonableRate
🤖 Prompt for AI Agents
In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeQuote.ts`
around lines 261 - 266, The 1% sanity check compares raw base units and ignores
differing token precisions, causing false rejects/accepts; update the logic in
getTradeQuote around minAcceptableRatio/isReasonableRate to normalize amounts by
token precisions before comparison: obtain the input and output token decimals
(e.g., from the route or trade quote input), scale multiHopRoute.amountOut and
amountIn to the same precision (using BigInt pow10 of the decimals difference)
and then apply the minAcceptableRatio check (isReasonableRate = scaledAmountOut
* minAcceptableRatio >= scaledAmountIn); ensure you handle both cases where
output has more or fewer decimals and keep using BigInt arithmetic.

Comment on lines +83 to +113
try {
const poolContract = await factory.getPool(poolType, [sellDedustAsset, buyDedustAsset])

if (!poolContract) {
return null
}

const pool = client.open(poolContract)
const readinessStatus = await pool.getReadinessStatus()

if (readinessStatus !== ReadinessStatus.READY) {
return null
}

const { amountOut } = await pool.getEstimatedSwapOut({
assetIn: sellDedustAsset,
amountIn,
})

if (amountOut <= 0n) {
return null
}

return {
amountOut,
poolAddress: pool.address.toString(),
poolType: poolType === PoolType.STABLE ? 'STABLE' : 'VOLATILE',
}
} catch {
return null
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Log unexpected pool/route errors instead of swallowing them.

Line 111-112 and Line 190-191 return null without logging, which can hide SDK/network failures during rate discovery. Consider logging the error with pool type/assets before returning null. As per coding guidelines, avoid silently catching exceptions.

Also applies to: 131-192

🤖 Prompt for AI Agents
In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeRate.ts`
around lines 83 - 113, The code in getTradeRate.ts is swallowing exceptions
(catch blocks that return null) when calling factory.getPool, client.open,
pool.getReadinessStatus, and pool.getEstimatedSwapOut; update the try/catch
blocks to catch the error object (catch (err)) and log the error with context
(include poolType, sellDedustAsset, buyDedustAsset and any pool address if
available) before returning null so SDK/network failures are visible; use the
module's existing logger (or console.error if none) and include
ReadinessStatus/PoolType context when logging.

Comment on lines +250 to +266
// Only use multi-hop if:
// 1. We have a direct pool quote to compare against, AND multi-hop is better
// 2. OR if there's no direct pool, the multi-hop rate must be reasonable (>= 1% of input)
// This prevents returning catastrophically bad routes (e.g., 2.8 USDE for 10,000 USDT)
let useMultiHop = false
if (multiHopRoute) {
if (bestDirectQuote) {
// Compare multi-hop vs direct - use multi-hop only if it's better
useMultiHop = multiHopRoute.amountOut > bestDirectQuote.amountOut
} else {
// No direct pool - check if multi-hop rate is reasonable
// Reject if output is less than 1% of input (accounting for same-decimal assets)
// This catches catastrophic routing failures like getting 2.8 for 10,000
const minAcceptableRatio = BigInt(100) // 1% = 1/100
const isReasonableRate = multiHopRoute.amountOut * minAcceptableRatio >= amountIn
useMultiHop = isReasonableRate
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Multi-hop sanity check compares raw base units; can reject valid routes.

Line 261-265 compares amounts without normalizing for asset precision, so pairs with different decimals can be incorrectly rejected. Normalize the comparison (e.g., via calculateRate) before applying the 1% threshold.

🐛 Suggested fix (normalize before applying the 1% guard)
-        const minAcceptableRatio = BigInt(100) // 1% = 1/100
-        const isReasonableRate = multiHopRoute.amountOut * minAcceptableRatio >= amountIn
+        const multiHopRate = Number(
+          calculateRate(
+            multiHopRoute.amountOut.toString(),
+            sellAmountIncludingProtocolFeesCryptoBaseUnit,
+            buyAsset.precision,
+            sellAsset.precision,
+          ),
+        )
+        const isReasonableRate = Number.isFinite(multiHopRate) && multiHopRate >= 0.01
         useMultiHop = isReasonableRate
🤖 Prompt for AI Agents
In `@packages/swapper/src/swappers/DedustSwapper/swapperApi/getTradeRate.ts`
around lines 250 - 266, The 1% sanity check currently multiplies
multiHopRoute.amountOut by minAcceptableRatio and compares to amountIn using raw
base units (in functions referencing multiHopRoute, bestDirectQuote, amountIn,
minAcceptableRatio), which mis-handles differing token decimals; instead compute
a normalized rate/amount (use the existing calculateRate or equivalent helper to
convert amounts to a common precision or convert amountOut to the input token's
base) and then apply the 1% threshold against the normalized values (replace the
raw check in the else branch with a normalized isReasonableRate using
calculateRate or converted amountOut before setting useMultiHop).

@NeOMakinG NeOMakinG marked this pull request as draft January 19, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants