Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ VITE_FEATURE_ABSTRACT=false
VITE_FEATURE_HEMI=false
VITE_FEATURE_SONIC=false
VITE_FEATURE_UNICHAIN=false
VITE_FEATURE_BOB=false
VITE_FEATURE_BOB=true
VITE_FEATURE_MODE=false
VITE_FEATURE_SONEIUM=false
VITE_FEATURE_SEI=false
Expand All @@ -116,7 +116,7 @@ VITE_FEATURE_TON=true
VITE_FEATURE_EARN_TAB=true
VITE_FEATURE_ACROSS_SWAP=true
VITE_FEATURE_DEBRIDGE_SWAP=true
VITE_FEATURE_BOB_GATEWAY_SWAP=false
VITE_FEATURE_BOB_GATEWAY_SWAP=true
VITE_FEATURE_USERBACK=true
VITE_FEATURE_AGENTIC_CHAT=false
VITE_FEATURE_MM_NATIVE_MULTICHAIN=false
Expand Down
2 changes: 0 additions & 2 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ VITE_FEATURE_WORLDCHAIN=true
VITE_FEATURE_HEMI=true
VITE_FEATURE_SONIC=true
VITE_FEATURE_UNICHAIN=true
VITE_FEATURE_BOB=true
VITE_FEATURE_BOB_GATEWAY_SWAP=true
VITE_FEATURE_MODE=true
VITE_FEATURE_SONEIUM=true
VITE_FEATURE_TON=true
Expand Down
2 changes: 1 addition & 1 deletion packages/swap-widget/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/swap-widget",
"version": "0.3.0",
"version": "0.4.0",
"description": "Embeddable swap widget using ShapeShift API",
"repository": "https://github.com/shapeshift/web",
"license": "MIT",
Expand Down
43 changes: 39 additions & 4 deletions packages/swap-widget/src/components/InputStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { ReceiveAddressRow } from './ReceiveAddressRow'
type InputStepProps = {
displayValues: SwapDisplayValues
onOpenTokenModal: (type: 'sell' | 'buy') => void
onSellAmountChange: (value: string) => void
onSellAmountChange: (value: string, sellAssetUsdPrice?: string) => void
onToggleSellFiat: (sellAssetUsdPrice?: string) => void
onSwapTokens: () => void
onSelectRate: (rate: TradeRate) => void
onButtonClick: () => void
Expand All @@ -23,6 +24,7 @@ export const InputStep = ({
displayValues,
onOpenTokenModal,
onSellAmountChange,
onToggleSellFiat,
onSwapTokens,
onSelectRate,
onButtonClick,
Expand All @@ -41,6 +43,7 @@ export const InputStep = ({
isSellBalanceLoading,
isBuyBalanceLoading,
sellUsdValue,
sellAssetUsdPrice,
buyUsdValue,
sellChainInfo,
buyChainInfo,
Expand All @@ -67,6 +70,8 @@ export const InputStep = ({
selectedRate,
sellAmount,
sellAmountBaseUnit,
isSellAmountFiat,
sellAmountFiat,
isSellAssetEvm,
isSellAssetUtxo,
isSellAssetSolana,
Expand Down Expand Up @@ -114,16 +119,17 @@ export const InputStep = ({
</div>

<div className='ssw-input-row'>
{isSellAmountFiat && <span className='ssw-fiat-prefix'>$</span>}
<input
type='text'
className='ssw-amount-input'
placeholder='0'
value={sellAmount}
value={isSellAmountFiat ? sellAmountFiat : sellAmount}
onChange={e => {
const raw = e.target.value.replace(/[^0-9.]/g, '')
const parts = raw.split('.')
const sanitized = parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : raw
onSellAmountChange(sanitized)
onSellAmountChange(sanitized, sellAssetUsdPrice)
}}
/>
<button
Expand Down Expand Up @@ -156,7 +162,36 @@ export const InputStep = ({
</div>

<div className='ssw-section-footer'>
<span className='ssw-usd-value'>{sellUsdValue}</span>
{sellAssetUsdPrice ? (
<button
type='button'
className='ssw-usd-value ssw-usd-value-toggle'
onClick={() => onToggleSellFiat(sellAssetUsdPrice)}
>
<span>
{isSellAmountFiat
? `≈ ${
sellAmountBaseUnit
? formatAmount(sellAmountBaseUnit, sellAsset.precision, 6)
: '0'
} ${sellAsset.symbol}`
: sellUsdValue}
</span>
<svg
width='12'
height='12'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
aria-hidden='true'
>
<path d='M7 16V4M7 4L3 8M7 4l4 4M17 8v12M17 20l4-4M17 20l-4-4' />
</svg>
</button>
) : (
<span className='ssw-usd-value' />
)}
{hasAnyWalletAddress &&
(isSellBalanceLoading ? (
<span className='ssw-balance-skeleton' />
Expand Down
25 changes: 25 additions & 0 deletions packages/swap-widget/src/components/SwapWidget.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,31 @@
color: var(--ssw-text-muted);
}

.ssw-usd-value-toggle {
background: none;
border: none;
padding: 0;
font: inherit;
font-size: 13px;
color: var(--ssw-text-muted);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
}

.ssw-usd-value-toggle:hover {
color: var(--ssw-text-secondary);
}

.ssw-fiat-prefix {
font-size: 32px;
font-weight: 500;
color: var(--ssw-text-primary);
/* Counteract the row's 12px gap so the $ sits snug against the amount */
margin-right: -8px;
}

.ssw-swap-divider {
display: flex;
justify-content: center;
Expand Down
5 changes: 5 additions & 0 deletions packages/swap-widget/src/components/SwapWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { SwapWalletContextValue } from '../contexts/SwapWalletContext'
import { SwapWalletProvider } from '../contexts/SwapWalletContext'
import { useBitcoinSigning } from '../hooks/useBitcoinSigning'
import { useEvmSigning } from '../hooks/useEvmSigning'
import { useSellFiatSync } from '../hooks/useSellFiatSync'
import { useSolanaSigning } from '../hooks/useSolanaSigning'
import { useStatusPolling } from '../hooks/useStatusPolling'
import { useSwapApproval } from '../hooks/useSwapApproval'
Expand Down Expand Up @@ -73,6 +74,7 @@ const SwapWidgetContent = ({
handleSellAssetSelect,
handleBuyAssetSelect,
handleSellAmountChange,
handleToggleSellFiat,
handleSelectRate,
handleSlippageChange,
handleButtonClick,
Expand All @@ -82,6 +84,7 @@ const SwapWidgetContent = ({
useSwapApproval()
useSwapExecution()
useStatusPolling({ apiClient, onSwapSuccess, onSwapError, refetchSellBalance, refetchBuyBalance })
useSellFiatSync(displayValues.sellAssetUsdPrice)

const widgetStyle = useMemo(() => {
if (!themeConfig) return undefined
Expand Down Expand Up @@ -169,6 +172,7 @@ const SwapWidgetContent = ({
displayValues={displayValues}
onOpenTokenModal={setTokenModalType}
onSellAmountChange={handleSellAmountChange}
onToggleSellFiat={handleToggleSellFiat}
onSwapTokens={handleSwapTokens}
onSelectRate={handleSelectRate}
onButtonClick={handleButtonClick}
Expand Down Expand Up @@ -215,6 +219,7 @@ const SwapWidgetContent = ({
}
allowedChainIds={(tokenModalType === 'buy' ? buyFilters : sellFilters).allowedChainIds}
allowedAssetIds={(tokenModalType === 'buy' ? buyFilters : sellFilters).allowedAssetIds}
allowShapeshiftRedirect={allowShapeshiftRedirect}
/>

<SettingsModal
Expand Down
29 changes: 26 additions & 3 deletions packages/swap-widget/src/components/TokenSelectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useAllMarketData } from '../hooks/useMarketData'
import { useSolanaSigning } from '../hooks/useSolanaSigning'
import { SwapMachineCtx } from '../machines/SwapMachineContext'
import type { Asset, AssetId, ChainId } from '../types'
import { isWidgetExecutableChainId, isWidgetSupportedChainId } from '../types'

const VISIBLE_BUFFER = 10

Expand All @@ -35,6 +36,7 @@ type TokenSelectModalProps = {
disabledChainIds?: ChainId[]
allowedChainIds?: ChainId[]
allowedAssetIds?: AssetId[]
allowShapeshiftRedirect: boolean
}

const isNativeAsset = (assetId: string): boolean => {
Expand Down Expand Up @@ -69,6 +71,7 @@ export const TokenSelectModal = ({
disabledChainIds = [],
allowedChainIds,
allowedAssetIds,
allowShapeshiftRedirect,
}: TokenSelectModalProps) => {
useLockBodyScroll(isOpen)
const [searchQuery, setSearchQuery] = useState('')
Expand Down Expand Up @@ -111,7 +114,13 @@ export const TokenSelectModal = ({
}, [allowedAssetIds, allAssets])

const filteredChains = useMemo(() => {
let enabledChains = chains.filter(chain => !disabledChainIds.includes(chain.chainId))
let enabledChains = chains.filter(
chain => isWidgetSupportedChainId(chain.chainId) && !disabledChainIds.includes(chain.chainId),
)

if (!allowShapeshiftRedirect) {
enabledChains = enabledChains.filter(chain => isWidgetExecutableChainId(chain.chainId))
}

if (allowedChainIds && allowedChainIds.length > 0) {
enabledChains = enabledChains.filter(chain => allowedChainIds.includes(chain.chainId))
Expand All @@ -125,14 +134,27 @@ export const TokenSelectModal = ({

const lowerQuery = chainSearchQuery.toLowerCase()
return enabledChains.filter(chain => chain.name.toLowerCase().includes(lowerQuery))
}, [chains, chainSearchQuery, disabledChainIds, allowedChainIds, allowedAssetChainIds])
}, [
chains,
chainSearchQuery,
disabledChainIds,
allowedChainIds,
allowedAssetChainIds,
allowShapeshiftRedirect,
])

const filteredAssets = useMemo(() => {
let assets = allAssets.filter(
asset =>
!disabledAssetIds.includes(asset.assetId) && !disabledChainIds.includes(asset.chainId),
isWidgetSupportedChainId(asset.chainId) &&
!disabledAssetIds.includes(asset.assetId) &&
!disabledChainIds.includes(asset.chainId),
)

if (!allowShapeshiftRedirect) {
assets = assets.filter(asset => isWidgetExecutableChainId(asset.chainId))
}

if (allowedAssetIds && allowedAssetIds.length > 0) {
assets = assets.filter(asset => allowedAssetIds.includes(asset.assetId))
}
Expand Down Expand Up @@ -173,6 +195,7 @@ export const TokenSelectModal = ({
disabledChainIds,
allowedAssetIds,
allowedChainIds,
allowShapeshiftRedirect,
])

const initialAssetPrecisions = useMemo(() => {
Expand Down
10 changes: 10 additions & 0 deletions packages/swap-widget/src/config/appkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import {
bitcoin,
bsc,
gnosis,
hyperEvm,
katana,
mainnet,
megaeth,
monad,
optimism,
plasma,
polygon,
solana,
} from '@reown/appkit/networks'
Expand All @@ -26,6 +31,11 @@ const EVM_NETWORKS: readonly AppKitNetwork[] = [
avalanche,
bsc,
gnosis,
monad,
megaeth,
hyperEvm,
plasma,
katana,
]

const ALL_NETWORKS: readonly AppKitNetwork[] = [...EVM_NETWORKS, bitcoin, solana]
Expand Down
10 changes: 10 additions & 0 deletions packages/swap-widget/src/constants/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import {
dogecoin,
ethereum,
gnosis,
hyperevm,
katana,
litecoin,
mayachain,
megaeth,
monad,
optimism,
plasma,
polygon,
solana,
thorchain,
Expand All @@ -29,6 +34,11 @@ const BASE_ASSETS_BY_CHAIN_ID: Record<ChainId, Asset> = {
[avax.chainId]: avax,
[bnbsmartchain.chainId]: bnbsmartchain,
[gnosis.chainId]: gnosis,
[monad.chainId]: monad,
[megaeth.chainId]: megaeth,
[hyperevm.chainId]: hyperevm,
[plasma.chainId]: plasma,
[katana.chainId]: katana,
[bitcoin.chainId]: bitcoin,
[bitcoincash.chainId]: bitcoincash,
[dogecoin.chainId]: dogecoin,
Expand Down
34 changes: 33 additions & 1 deletion packages/swap-widget/src/constants/viemChains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
import type { Chain, WalletClient } from 'viem'
import { arbitrum, avalanche, base, bsc, gnosis, mainnet, optimism, polygon } from 'viem/chains'
import { defineChain } from 'viem'
import {
arbitrum,
avalanche,
base,
bsc,
gnosis,
hyperEvm,
katana,
mainnet,
monad,
optimism,
plasma,
polygon,
} from 'viem/chains'

const megaeth = defineChain({
id: 4326,
name: 'MegaETH',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: { default: { http: ['https://mainnet.megaeth.com/rpc'] } },
blockExplorers: {
default: { name: 'MegaETH Explorer', url: 'https://megaeth.blockscout.com' },
},
})

export const VIEM_CHAINS_BY_ID: Record<number, Chain> = {
[mainnet.id]: {
Expand Down Expand Up @@ -34,6 +58,14 @@ export const VIEM_CHAINS_BY_ID: Record<number, Chain> = {
...avalanche,
rpcUrls: { default: { http: ['https://api.avalanche.shapeshift.com/api/v1/jsonrpc'] } },
},
[monad.id]: { ...monad, rpcUrls: { default: { http: ['https://rpc.monad.xyz'] } } },
[megaeth.id]: megaeth,
[hyperEvm.id]: {
...hyperEvm,
rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },
},
[plasma.id]: { ...plasma, rpcUrls: { default: { http: ['https://rpc.plasma.to'] } } },
[katana.id]: { ...katana, rpcUrls: { default: { http: ['https://rpc.katana.network'] } } },
}

export const addChainToWallet = async (client: WalletClient, chain: Chain): Promise<void> => {
Expand Down
Loading
Loading