diff --git a/.env.example b/.env.example
index 7d028477..f76e0244 100644
--- a/.env.example
+++ b/.env.example
@@ -4,3 +4,5 @@ SEGMENT_WRITE_KEY=segmentwritekey
SENTRY_DSN=sentrydsn
TRANSAK_API_KEY=transakapikey
WALLET_ENVIRONMENT=development
+
+SWAP_ENABLED=true
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 00000000..a6271e18
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+nodejs 16.13.0
+yarn 1.22.19
diff --git a/src/app/features/swap/index.ts b/src/app/features/swap/index.ts
new file mode 100644
index 00000000..a3f0e882
--- /dev/null
+++ b/src/app/features/swap/index.ts
@@ -0,0 +1 @@
+export * from './use-swap-feature';
diff --git a/src/app/features/swap/use-swap-feature.ts b/src/app/features/swap/use-swap-feature.ts
new file mode 100644
index 00000000..a5d8ac12
--- /dev/null
+++ b/src/app/features/swap/use-swap-feature.ts
@@ -0,0 +1,5 @@
+export function useSwapFeature() {
+ return {
+ swapIsEnabled: !!process.env.SWAP_ENABLED
+ };
+}
diff --git a/src/app/pages/home/components/home-actions.tsx b/src/app/pages/home/components/home-actions.tsx
index e120ef13..020b1ff4 100644
--- a/src/app/pages/home/components/home-actions.tsx
+++ b/src/app/pages/home/components/home-actions.tsx
@@ -2,17 +2,23 @@ import { Suspense } from 'react';
import { Stack, StackProps } from '@stacks/ui';
+import { useSwapFeature } from '@app/features/swap';
+
import { BuyButton } from './buy-button';
import { ReceiveButton } from './receive-button';
import { SendButton } from './send-button';
+import { SwapButton } from './swap-button';
export function HomeActions(props: StackProps) {
+ const { swapIsEnabled } = useSwapFeature();
+
return (
>}>
+ {swapIsEnabled && }
);
diff --git a/src/app/pages/home/components/swap-button.tsx b/src/app/pages/home/components/swap-button.tsx
new file mode 100644
index 00000000..4df2d705
--- /dev/null
+++ b/src/app/pages/home/components/swap-button.tsx
@@ -0,0 +1,26 @@
+import { useNavigate } from 'react-router-dom';
+
+import { ButtonProps } from '@stacks/ui';
+import { HomePageSelectors } from '@tests/selectors/home.selectors';
+
+import { RouteUrls } from '@shared/route-urls';
+
+import { PrimaryButton } from '@app/components/primary-button';
+
+import { HomeActionButton } from './tx-button';
+import { FiRefreshCw } from 'react-icons/fi';
+
+export function SwapButton(props: ButtonProps) {
+ const navigate = useNavigate();
+
+ return (
+ navigate(RouteUrls.Swap)}
+ {...props}
+ />
+ );
+}
diff --git a/src/app/pages/swap/index.ts b/src/app/pages/swap/index.ts
new file mode 100644
index 00000000..d4efaa7e
--- /dev/null
+++ b/src/app/pages/swap/index.ts
@@ -0,0 +1,2 @@
+export * from './swap';
+export * from './swap-routes';
diff --git a/src/app/pages/swap/swap-container.tsx b/src/app/pages/swap/swap-container.tsx
new file mode 100644
index 00000000..59afa6fd
--- /dev/null
+++ b/src/app/pages/swap/swap-container.tsx
@@ -0,0 +1,29 @@
+import { Outlet } from 'react-router-dom';
+
+import { Flex } from '@stacks/ui';
+
+import { whenPageMode } from '@app/common/utils';
+import { ModalHeader } from '@app/components/modal-header';
+import { useRouteHeader } from '@app/common/hooks/use-route-header';
+
+import { CENTERED_FULL_PAGE_MAX_WIDTH } from '@app/components/global-styles/full-page-styles';
+
+export function SwapContainer() {
+ useRouteHeader(, true);
+
+ return whenPageMode({
+ full: (
+
+
+
+ ),
+ popup: ,
+ });
+}
diff --git a/src/app/pages/swap/swap-routes.tsx b/src/app/pages/swap/swap-routes.tsx
new file mode 100644
index 00000000..e75ae760
--- /dev/null
+++ b/src/app/pages/swap/swap-routes.tsx
@@ -0,0 +1,20 @@
+import { Route } from "react-router-dom";
+
+import { RouteUrls } from "@shared/route-urls";
+import { AccountGate } from "@app/routes/account-gate";
+
+import { Swap } from "./swap";
+import { SwapContainer } from "./swap-container";
+
+export const swapRoutes = (
+ }>
+
+
+
+ }
+ />
+
+);
diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx
new file mode 100644
index 00000000..886aaad5
--- /dev/null
+++ b/src/app/pages/swap/swap.tsx
@@ -0,0 +1,19 @@
+import { Text } from "@app/components/typography";
+import { Flex } from "@stacks/ui";
+import { SwapCryptoAssetSelectors } from "@tests/selectors/swap.selectors";
+
+export function Swap() {
+ return (
+
+ Coming soon!
+
+ );
+}
diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx
index b0258374..3df56125 100644
--- a/src/app/routes/app-routes.tsx
+++ b/src/app/routes/app-routes.tsx
@@ -55,6 +55,7 @@ import { SendInscriptionSummary } from '@app/pages/send/ordinal-inscription/sent
import { sendCryptoAssetFormRoutes } from '@app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes';
import { SignOutConfirmDrawer } from '@app/pages/sign-out-confirm/sign-out-confirm';
import { StacksMessageSigningRequest } from '@app/pages/stacks-message-signing-request/stacks-message-signing-request';
+import { swapRoutes } from '@app/pages/swap';
import { TransactionRequest } from '@app/pages/transaction-request/transaction-request';
import { UnauthorizedRequest } from '@app/pages/unauthorized-request/unauthorized-request';
import { Unlock } from '@app/pages/unlock';
@@ -353,6 +354,18 @@ function useAppRoutes() {
}
/>
+ {
+ const { RpcSignBip322MessageRoute } = await import(
+ '@app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message'
+ );
+ return { Component: RpcSignBip322MessageRoute };
+ }}
+ />
+
+ {swapRoutes}
+
{/* Catch-all route redirects to onboarding */}
} />
diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts
index 25615a1a..08c073f7 100644
--- a/src/shared/route-urls.ts
+++ b/src/shared/route-urls.ts
@@ -106,4 +106,7 @@ export enum RouteUrls {
// Shared legacy and rpc request routes
RequestError = '/request-error',
UnauthorizedRequest = '/unauthorized-request',
+
+ // Swap routes
+ Swap = '/swap',
}
diff --git a/tests/fixtures/fixtures.ts b/tests/fixtures/fixtures.ts
index 9c59c6ec..9aef22ad 100644
--- a/tests/fixtures/fixtures.ts
+++ b/tests/fixtures/fixtures.ts
@@ -1,8 +1,11 @@
import { BrowserContext, test as base, chromium } from '@playwright/test';
+
import { GlobalPage } from '@tests/page-object-models/global.page';
import { HomePage } from '@tests/page-object-models/home.page';
import { OnboardingPage } from '@tests/page-object-models/onboarding.page';
import { SendPage } from '@tests/page-object-models/send.page';
+import { SwapPage } from '@tests/page-object-models/swap.page';
+
import path from 'path';
interface TestFixtures {
@@ -12,6 +15,7 @@ interface TestFixtures {
homePage: HomePage;
onboardingPage: OnboardingPage;
sendPage: SendPage;
+ swapPage: SwapPage
}
/**
@@ -55,4 +59,7 @@ export const test = base.extend({
sendPage: async ({ page }, use) => {
await use(new SendPage(page));
},
+ swapPage: async ({ page }, use) => {
+ await use(new SwapPage(page));
+ }
});
diff --git a/tests/page-object-models/home.page.ts b/tests/page-object-models/home.page.ts
index df7e8db8..595a9d9a 100644
--- a/tests/page-object-models/home.page.ts
+++ b/tests/page-object-models/home.page.ts
@@ -12,6 +12,8 @@ export class HomePage {
readonly drawerActionButton: Locator;
readonly receiveButton: Locator;
readonly sendButton: Locator;
+ readonly swapButton: Locator;
+
readonly testNetworkSelector: string = createTestSelector(
WalletDefaultNetworkConfigurationIds.testnet
);
@@ -21,12 +23,17 @@ export class HomePage {
this.drawerActionButton = page.getByTestId(HomePageSelectors.DrawerHeaderActionBtn);
this.receiveButton = page.getByTestId(HomePageSelectors.ReceiveCryptoAssetBtn);
this.sendButton = page.getByTestId(HomePageSelectors.SendCryptoAssetBtn);
+ this.swapButton = page.getByTestId(HomePageSelectors.SwapBtn);
}
async goToReceiveModal() {
await this.page.getByTestId(HomePageSelectors.ReceiveCryptoAssetBtn).click();
}
+ async goToSwapPage() {
+ await this.page.getByTestId(HomePageSelectors.SwapBtn).click();
+ }
+
// Open issue with Playwright's ability to copyToClipboard from legacy tests:
// https://github.com/microsoft/playwright/issues/8114#issuecomment-1103317576
// Also, an open issue to consistently determine `isMac` in the workaround:
diff --git a/tests/page-object-models/swap.page.ts b/tests/page-object-models/swap.page.ts
new file mode 100644
index 00000000..7011b743
--- /dev/null
+++ b/tests/page-object-models/swap.page.ts
@@ -0,0 +1,18 @@
+import { Locator, Page } from '@playwright/test';
+import { SwapCryptoAssetSelectors } from '@tests/selectors/swap.selectors';
+
+import { createTestSelector } from '@tests/utils';
+
+export class SwapPage {
+ readonly page: Page;
+
+ constructor(page: Page) {
+ this.page = page;
+ }
+
+ async waitForSendPageReady() {
+ await this.page.waitForSelector(createTestSelector(SwapCryptoAssetSelectors.SwapPageReady), {
+ state: 'attached',
+ });
+ }
+}
diff --git a/tests/selectors/home.selectors.ts b/tests/selectors/home.selectors.ts
index 1685184e..1990f1d1 100644
--- a/tests/selectors/home.selectors.ts
+++ b/tests/selectors/home.selectors.ts
@@ -8,4 +8,5 @@ export enum HomePageSelectors {
SendCryptoAssetBtn = 'send-crypto-asset-btn',
ActivityTabBtn = 'tab-activity',
BalancesTabBtn = 'tab-balances',
+ SwapBtn = 'swap-btn',
}
diff --git a/tests/selectors/swap.selectors.ts b/tests/selectors/swap.selectors.ts
new file mode 100644
index 00000000..537cd29e
--- /dev/null
+++ b/tests/selectors/swap.selectors.ts
@@ -0,0 +1,3 @@
+export enum SwapCryptoAssetSelectors {
+ SwapPageReady = 'swap-page-ready',
+}
diff --git a/tests/specs/swap/swap.spec.ts b/tests/specs/swap/swap.spec.ts
new file mode 100644
index 00000000..590f45ab
--- /dev/null
+++ b/tests/specs/swap/swap.spec.ts
@@ -0,0 +1,17 @@
+import { test } from '../../fixtures/fixtures';
+
+test.describe('swap', () => {
+ test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, swapPage }) => {
+ await globalPage.setupAndUseApiCalls(extensionId);
+ await onboardingPage.signInWithTestAccount(extensionId);
+ await homePage.enableTestMode();
+ await homePage.swapButton.click();
+ await swapPage.waitForSendPageReady();
+ });
+
+ test.describe('swap', () => {
+ test('that it shows swap page', async ({ sendPage }) => {
+ await test.expect(sendPage.page.getByText('Coming soon!')).toBeVisible();
+ });
+ })
+})