From ef063b77a07ca7ce24edccdce8d65f8f0e1016e7 Mon Sep 17 00:00:00 2001 From: Corey-Code <37006206+Corey-Code@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:56:30 -0400 Subject: [PATCH 1/5] Add the BeeZee VDL buy and sell route lock. --- src/lib/swap/vdlSwapConfig.ts | 67 ++++++++++++++++++++++++++ tests/lib/swap/vdlSwapConfig.test.ts | 70 ++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/lib/swap/vdlSwapConfig.ts create mode 100644 tests/lib/swap/vdlSwapConfig.test.ts diff --git a/src/lib/swap/vdlSwapConfig.ts b/src/lib/swap/vdlSwapConfig.ts new file mode 100644 index 0000000..818cadd --- /dev/null +++ b/src/lib/swap/vdlSwapConfig.ts @@ -0,0 +1,67 @@ +export const VDL_CHAIN_ID = 'beezee-1'; +export const VDL_DENOM = 'factory/bze13gzq40che93tgfm9kzmkpjamah5nj0j73pyhqk/uvdl'; +export const VDL_SWAP_TITLE = 'Buy / Sell VDL'; + +export type VdlSwapMode = 'buy' | 'sell'; + +export interface SkipDefaultRoute { + srcChainId?: string; + srcAssetDenom?: string; + destChainId?: string; + destAssetDenom?: string; +} + +export interface SkipAssetFilter { + source?: Record; + destination?: Record; +} + +export interface VdlSwapConfig { + defaultRoute: SkipDefaultRoute; + filter: SkipAssetFilter; + filterOut: SkipAssetFilter; +} + +const vdlOnly: Record = { + [VDL_CHAIN_ID]: [VDL_DENOM], +}; + +export function vdlSwapCopy(mode: VdlSwapMode): string { + switch (mode) { + case 'buy': + return 'Pay with any asset. Receive VDL.'; + case 'sell': + return 'Pay with VDL. Receive any asset.'; + default: { + const unreachable: never = mode; + return unreachable; + } + } +} + +export function vdlSwapConfig(mode: VdlSwapMode): VdlSwapConfig { + switch (mode) { + case 'buy': + return { + defaultRoute: { + destChainId: VDL_CHAIN_ID, + destAssetDenom: VDL_DENOM, + }, + filter: { destination: vdlOnly }, + filterOut: { source: vdlOnly }, + }; + case 'sell': + return { + defaultRoute: { + srcChainId: VDL_CHAIN_ID, + srcAssetDenom: VDL_DENOM, + }, + filter: { source: vdlOnly }, + filterOut: { destination: vdlOnly }, + }; + default: { + const unreachable: never = mode; + return unreachable; + } + } +} diff --git a/tests/lib/swap/vdlSwapConfig.test.ts b/tests/lib/swap/vdlSwapConfig.test.ts new file mode 100644 index 0000000..ea53826 --- /dev/null +++ b/tests/lib/swap/vdlSwapConfig.test.ts @@ -0,0 +1,70 @@ +import { vdlSwapConfig, vdlSwapCopy, VDL_CHAIN_ID, VDL_DENOM, VDL_SWAP_TITLE } from '@/lib/swap/vdlSwapConfig'; + +describe('vdlSwapConfig', () => { + it('buys VDL on BeeZee and leaves the pay-with side open', () => { + const config = vdlSwapConfig('buy'); + + expect(config.defaultRoute).toEqual({ + destChainId: VDL_CHAIN_ID, + destAssetDenom: VDL_DENOM, + }); + expect(config.defaultRoute.srcChainId).toBeUndefined(); + expect(config.defaultRoute.srcAssetDenom).toBeUndefined(); + expect(config.defaultRoute).not.toHaveProperty('amountIn'); + expect(config.defaultRoute).not.toHaveProperty('amountOut'); + + expect(config.filter.destination).toEqual({ + [VDL_CHAIN_ID]: [VDL_DENOM], + }); + expect(config.filter.source).toBeUndefined(); + + expect(config.filterOut.source).toEqual({ + [VDL_CHAIN_ID]: [VDL_DENOM], + }); + expect(config.filterOut.destination).toBeUndefined(); + }); + + it('sells BeeZee VDL and leaves the receive side open', () => { + const config = vdlSwapConfig('sell'); + + expect(config.defaultRoute).toEqual({ + srcChainId: VDL_CHAIN_ID, + srcAssetDenom: VDL_DENOM, + }); + expect(config.defaultRoute.destChainId).toBeUndefined(); + expect(config.defaultRoute.destAssetDenom).toBeUndefined(); + expect(config.defaultRoute).not.toHaveProperty('amountIn'); + expect(config.defaultRoute).not.toHaveProperty('amountOut'); + + expect(config.filter.source).toEqual({ + [VDL_CHAIN_ID]: [VDL_DENOM], + }); + expect(config.filter.destination).toBeUndefined(); + + expect(config.filterOut.destination).toEqual({ + [VDL_CHAIN_ID]: [VDL_DENOM], + }); + expect(config.filterOut.source).toBeUndefined(); + }); + + it('does not lock the same side for buy and sell', () => { + const buy = vdlSwapConfig('buy'); + const sell = vdlSwapConfig('sell'); + + expect(buy.filter.destination).toBeDefined(); + expect(buy.filter.source).toBeUndefined(); + expect(sell.filter.source).toBeDefined(); + expect(sell.filter.destination).toBeUndefined(); + }); + + it('uses the BeeZee factory denom', () => { + expect(VDL_CHAIN_ID).toBe('beezee-1'); + expect(VDL_DENOM).toBe('factory/bze13gzq40che93tgfm9kzmkpjamah5nj0j73pyhqk/uvdl'); + }); + + it('returns the page copy', () => { + expect(VDL_SWAP_TITLE).toBe('Buy / Sell VDL'); + expect(vdlSwapCopy('buy')).toBe('Pay with any asset. Receive VDL.'); + expect(vdlSwapCopy('sell')).toBe('Pay with VDL. Receive any asset.'); + }); +}); From 247baa1f58d9dfcd7edbf8967a2d876f330c46a3 Mon Sep 17 00:00:00 2001 From: Corey-Code <37006206+Corey-Code@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:08:10 -0400 Subject: [PATCH 2/5] Let the swap page buy and sell BeeZee VDL. --- src/popup/pages/Swap.tsx | 93 +++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/src/popup/pages/Swap.tsx b/src/popup/pages/Swap.tsx index 43ba9fd..59144b4 100644 --- a/src/popup/pages/Swap.tsx +++ b/src/popup/pages/Swap.tsx @@ -1,5 +1,5 @@ -import React, { useMemo, useCallback, useRef } from 'react'; -import { Box, HStack, Text, useToast, IconButton } from '@chakra-ui/react'; +import React, { useMemo, useCallback, useRef, useState } from 'react'; +import { Box, HStack, Text, useToast, IconButton, Button } from '@chakra-ui/react'; import { ArrowBackIcon } from '@chakra-ui/icons'; import { Widget } from '@skip-go/widget'; import { useWalletStore } from '@/store/walletStore'; @@ -7,8 +7,6 @@ import { useNetworkStore } from '@/store/networkStore'; import { networkRegistry, isCosmosNetwork, - isEvmNetwork, - isSvmNetwork, getHealthyEndpoint, } from '@/lib/networks'; import type { CosmosNetworkConfig, SvmNetworkConfig } from '@/lib/networks'; @@ -17,6 +15,12 @@ import { fromBech32 } from '@cosmjs/encoding'; import { createWalletClient, defineChain, http, type Hex } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { Keypair, PublicKey } from '@solana/web3.js'; +import { + vdlSwapConfig, + vdlSwapCopy, + VDL_SWAP_TITLE, + type VdlSwapMode, +} from '@/lib/swap/vdlSwapConfig'; /** * Vidulum affiliate fee configuration. @@ -51,6 +55,8 @@ const SwapPage: React.FC = ({ onBack }) => { const { getEnabledNetworks } = useNetworkStore(); const toast = useToast(); const lastErrorToastRef = useRef<{ message: string; timestamp: number } | null>(null); + const [mode, setMode] = useState('buy'); + const swapConfig = useMemo(() => vdlSwapConfig(mode), [mode]); /** * Build connected addresses map for ALL Cosmos chains in the registry. @@ -283,35 +289,6 @@ const SwapPage: React.FC = ({ onBack }) => { return adapterLike as any; }, []); - /** - * Build the source filter so the widget only shows chains/assets - * the user has enabled in their network preferences. - */ - const sourceFilter = useMemo>(() => { - const filter: Record = {}; - const enabledNetworks = getEnabledNetworks(); - - for (const network of enabledNetworks) { - if (isCosmosNetwork(network)) { - if (connectedAddresses[network.id]) { - filter[network.id] = undefined; - } - } else if (isEvmNetwork(network)) { - const skipChainId = String(network.chainId); - if (connectedAddresses[skipChainId]) { - filter[skipChainId] = undefined; - } - } else if (isSvmNetwork(network)) { - const skipChainId = getSkipSvmChainId(network); - if (connectedAddresses[skipChainId]) { - filter[skipChainId] = undefined; - } - } - } - - return filter; - }, [getEnabledNetworks, connectedAddresses]); - /** * Build chainIdsToAffiliates for Vidulum's fee collection. * Derives the fee recipient address on each enabled Cosmos chain from the @@ -550,7 +527,7 @@ const SwapPage: React.FC = ({ onBack }) => { {/* Header */} - + } @@ -560,38 +537,54 @@ const SwapPage: React.FC = ({ onBack }) => { onClick={onBack} /> - Cross-Chain Swap + {VDL_SWAP_TITLE} + + + + + + {vdlSwapCopy(mode)} + {/* Skip Go Widget */} Date: Wed, 21 Oct 2026 12:28:22 -0400 Subject: [PATCH 3/5] Rename the dashboard swap entry to Buy / Sell VDL. --- src/popup/pages/Dashboard.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/popup/pages/Dashboard.tsx b/src/popup/pages/Dashboard.tsx index d423110..36e4334 100644 --- a/src/popup/pages/Dashboard.tsx +++ b/src/popup/pages/Dashboard.tsx @@ -56,6 +56,7 @@ import NetworkManagerModal from '../components/NetworkManagerModal'; import IBCTransferModal from '../components/IBCTransferModal'; import { useNetworkStore } from '@/store/networkStore'; import { fetchIBCConnections, IBCChannel } from '@/lib/cosmos/ibc-connections'; +import { VDL_SWAP_TITLE } from '@/lib/swap/vdlSwapConfig'; interface DashboardProps { onNavigateToStaking?: () => void; @@ -803,7 +804,7 @@ const Dashboard: React.FC = ({ {/* Scrollable Content */} - {/* Cross Chain Swaps Button */} + {/* Buy / Sell VDL */} From 7cdf9b371bff61c0ef66f34523ff983f2ae2481c Mon Sep 17 00:00:00 2001 From: Corey-Code <37006206+Corey-Code@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:24:46 -0400 Subject: [PATCH 4/5] Lock the Skip flip control to the Buy or Sell side. --- src/lib/swap/vdlSwapConfig.ts | 4 ++++ src/popup/pages/Swap.tsx | 2 ++ tests/lib/swap/vdlSwapConfig.test.ts | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/src/lib/swap/vdlSwapConfig.ts b/src/lib/swap/vdlSwapConfig.ts index 818cadd..a8f1fc4 100644 --- a/src/lib/swap/vdlSwapConfig.ts +++ b/src/lib/swap/vdlSwapConfig.ts @@ -9,6 +9,8 @@ export interface SkipDefaultRoute { srcAssetDenom?: string; destChainId?: string; destAssetDenom?: string; + srcLocked?: boolean; + destLocked?: boolean; } export interface SkipAssetFilter { @@ -46,6 +48,7 @@ export function vdlSwapConfig(mode: VdlSwapMode): VdlSwapConfig { defaultRoute: { destChainId: VDL_CHAIN_ID, destAssetDenom: VDL_DENOM, + destLocked: true, }, filter: { destination: vdlOnly }, filterOut: { source: vdlOnly }, @@ -55,6 +58,7 @@ export function vdlSwapConfig(mode: VdlSwapMode): VdlSwapConfig { defaultRoute: { srcChainId: VDL_CHAIN_ID, srcAssetDenom: VDL_DENOM, + srcLocked: true, }, filter: { source: vdlOnly }, filterOut: { destination: vdlOnly }, diff --git a/src/popup/pages/Swap.tsx b/src/popup/pages/Swap.tsx index 59144b4..e928ac1 100644 --- a/src/popup/pages/Swap.tsx +++ b/src/popup/pages/Swap.tsx @@ -580,6 +580,8 @@ const SwapPage: React.FC = ({ onBack }) => { experimentalFeatures: ['hyperlane', 'cctp', 'eureka'], allowMultiTx: true, allowUnsafe: false, + // Workaround for a @skip-go/widget DOM nesting warning in RoutePreferenceSelector + // (div rendered inside p); keeping goFast false avoids rendering that section. goFast: false, }} batchSignTxs={true} diff --git a/tests/lib/swap/vdlSwapConfig.test.ts b/tests/lib/swap/vdlSwapConfig.test.ts index ea53826..c83db6c 100644 --- a/tests/lib/swap/vdlSwapConfig.test.ts +++ b/tests/lib/swap/vdlSwapConfig.test.ts @@ -7,7 +7,9 @@ describe('vdlSwapConfig', () => { expect(config.defaultRoute).toEqual({ destChainId: VDL_CHAIN_ID, destAssetDenom: VDL_DENOM, + destLocked: true, }); + expect(config.defaultRoute.srcLocked).toBeUndefined(); expect(config.defaultRoute.srcChainId).toBeUndefined(); expect(config.defaultRoute.srcAssetDenom).toBeUndefined(); expect(config.defaultRoute).not.toHaveProperty('amountIn'); @@ -30,7 +32,9 @@ describe('vdlSwapConfig', () => { expect(config.defaultRoute).toEqual({ srcChainId: VDL_CHAIN_ID, srcAssetDenom: VDL_DENOM, + srcLocked: true, }); + expect(config.defaultRoute.destLocked).toBeUndefined(); expect(config.defaultRoute.destChainId).toBeUndefined(); expect(config.defaultRoute.destAssetDenom).toBeUndefined(); expect(config.defaultRoute).not.toHaveProperty('amountIn'); From 0ccf1e8bc83e30fdcd5f2e1d2d0813b75aef9ed4 Mon Sep 17 00:00:00 2001 From: Corey-Code <37006206+Corey-Code@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:58:37 -0400 Subject: [PATCH 5/5] Call Skip directly so Buy and Sell VDL can quote. --- src/lib/swap/vdlSwapConfig.ts | 7 +++++++ src/popup/pages/Swap.tsx | 4 ++-- tests/lib/swap/vdlSwapConfig.test.ts | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/lib/swap/vdlSwapConfig.ts b/src/lib/swap/vdlSwapConfig.ts index a8f1fc4..78a71bb 100644 --- a/src/lib/swap/vdlSwapConfig.ts +++ b/src/lib/swap/vdlSwapConfig.ts @@ -2,6 +2,13 @@ export const VDL_CHAIN_ID = 'beezee-1'; export const VDL_DENOM = 'factory/bze13gzq40che93tgfm9kzmkpjamah5nj0j73pyhqk/uvdl'; export const VDL_SWAP_TITLE = 'Buy / Sell VDL'; +/** + * @skip-go/client appends `/v2/...` to this origin. + * Skip allows browser CORS, so the wallet calls it directly. + * Do not add `/v2` here, and do not point this at a hostname with no DNS record. + */ +export const SKIP_GO_API_URL = 'https://api.skip.build'; + export type VdlSwapMode = 'buy' | 'sell'; export interface SkipDefaultRoute { diff --git a/src/popup/pages/Swap.tsx b/src/popup/pages/Swap.tsx index e928ac1..862067b 100644 --- a/src/popup/pages/Swap.tsx +++ b/src/popup/pages/Swap.tsx @@ -16,6 +16,7 @@ import { createWalletClient, defineChain, http, type Hex } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { Keypair, PublicKey } from '@solana/web3.js'; import { + SKIP_GO_API_URL, vdlSwapConfig, vdlSwapCopy, VDL_SWAP_TITLE, @@ -29,7 +30,6 @@ import { */ const VIDULUM_FEE_BPS = import.meta.env.VIDULUM_FEE_BPS || ''; const VIDULUM_FEE_BASE_ADDRESS = import.meta.env.VIDULUM_FEE_BASE_ADDRESS || ''; -const SKIP_API_URL = 'https://api.vidulum.app/skip/api/skip/v2'; const skipSvmChainIdByNetworkId: Record = { 'solana-mainnet': 'solana', @@ -574,7 +574,7 @@ const SwapPage: React.FC = ({ onBack }) => { { it('buys VDL on BeeZee and leaves the pay-with side open', () => { @@ -71,4 +78,10 @@ describe('vdlSwapConfig', () => { expect(vdlSwapCopy('buy')).toBe('Pay with any asset. Receive VDL.'); expect(vdlSwapCopy('sell')).toBe('Pay with VDL. Receive any asset.'); }); + + it('calls Skip directly so the client can append /v2', () => { + expect(SKIP_GO_API_URL).toBe('https://api.skip.build'); + expect(SKIP_GO_API_URL.endsWith('/v2')).toBe(false); + expect(SKIP_GO_API_URL.includes('api.vidulum.app')).toBe(false); + }); });