diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index 981b71eb82..85fd2b576d 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -1611,11 +1611,13 @@ export const saveSettings = async ({ isDataSharingAllowed, isMemoValidationEnabled, isHideDustEnabled, + isOpenSidebarByDefault, }: { activePublicKey: string; isDataSharingAllowed: boolean; isMemoValidationEnabled: boolean; isHideDustEnabled: boolean; + isOpenSidebarByDefault: boolean; }): Promise => { let response = { allowList: DEFAULT_ALLOW_LIST, @@ -1629,6 +1631,7 @@ export const saveSettings = async ({ isSorobanPublicEnabled: false, isNonSSLEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, error: "", hiddenAssets: {}, }; @@ -1639,6 +1642,7 @@ export const saveSettings = async ({ isDataSharingAllowed, isMemoValidationEnabled, isHideDustEnabled, + isOpenSidebarByDefault, type: SERVICE_TYPES.SAVE_SETTINGS, }); } catch (e) { diff --git a/@shared/api/types/message-request.ts b/@shared/api/types/message-request.ts index 118de568f2..9eff6052ce 100644 --- a/@shared/api/types/message-request.ts +++ b/@shared/api/types/message-request.ts @@ -281,6 +281,7 @@ export interface SaveSettingsMessage extends BaseMessage { isHideDustEnabled: boolean; isMemoValidationEnabled: boolean; isDataSharingAllowed: boolean; + isOpenSidebarByDefault: boolean; } export interface SaveExperimentalFeaturesMessage extends BaseMessage { @@ -444,6 +445,20 @@ export interface MarkQueueActiveMessage extends BaseMessage { isActive: boolean; } +export interface SidebarRegisterMessage extends BaseMessage { + type: SERVICE_TYPES.SIDEBAR_REGISTER; + windowId: number; +} + +export interface SidebarUnregisterMessage extends BaseMessage { + type: SERVICE_TYPES.SIDEBAR_UNREGISTER; +} + +export interface OpenSidebarMessage extends BaseMessage { + type: SERVICE_TYPES.OPEN_SIDEBAR; + windowId: number; +} + export type ServiceMessageRequest = | FundAccountMessage | CreateAccountMessage @@ -508,4 +523,7 @@ export type ServiceMessageRequest = | GetCollectiblesMessage | ChangeCollectibleVisibilityMessage | GetHiddenCollectiblesMessage - | MarkQueueActiveMessage; + | MarkQueueActiveMessage + | SidebarRegisterMessage + | SidebarUnregisterMessage + | OpenSidebarMessage; diff --git a/@shared/api/types/types.ts b/@shared/api/types/types.ts index ca3fbb376c..6d8db072a4 100644 --- a/@shared/api/types/types.ts +++ b/@shared/api/types/types.ts @@ -110,6 +110,7 @@ export interface Response { recommendedFee: string; isNonSSLEnabled: boolean; isHideDustEnabled: boolean; + isOpenSidebarByDefault: boolean; activePublicKey: string; isAccountMismatch: boolean; assetVisibility: { @@ -185,6 +186,7 @@ export interface Preferences { isMemoValidationEnabled: boolean; networksList: NetworkDetails[]; isHideDustEnabled: boolean; + isOpenSidebarByDefault: boolean; error: string; } diff --git a/@shared/constants/services.ts b/@shared/constants/services.ts index 718f70f466..ad9c9445b9 100644 --- a/@shared/constants/services.ts +++ b/@shared/constants/services.ts @@ -63,6 +63,9 @@ export enum SERVICE_TYPES { CHANGE_COLLECTIBLE_VISIBILITY = "CHANGE_COLLECTIBLE_VISIBILITY", GET_HIDDEN_COLLECTIBLES = "GET_HIDDEN_COLLECTIBLES", MARK_QUEUE_ACTIVE = "MARK_QUEUE_ACTIVE", + SIDEBAR_REGISTER = "SIDEBAR_REGISTER", + SIDEBAR_UNREGISTER = "SIDEBAR_UNREGISTER", + OPEN_SIDEBAR = "OPEN_SIDEBAR", } export enum EXTERNAL_SERVICE_TYPES { diff --git a/extension/e2e-tests/accountHistory.test.ts b/extension/e2e-tests/accountHistory.test.ts index 71f8a5d7b0..dddf02e6b5 100644 --- a/extension/e2e-tests/accountHistory.test.ts +++ b/extension/e2e-tests/accountHistory.test.ts @@ -23,31 +23,60 @@ test("View Account History", async ({ page, extensionId, context }) => { }); test("View failed transaction", async ({ page, extensionId, context }) => { + test.slow(); + const mockAccountHistoryData = [ + { + amount: "0.0010000", + asset_code: "USDC", + asset_issuer: + "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + asset_type: "credit_alphanum4", + created_at: "2025-03-21T22:28:46Z", + from: "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY", + id: "164007621169153", + paging_token: "164007621169153", + source_account: + "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY", + to: "GCKUVXILBNYS4FDNWCGCYSJBY2PBQ4KAW2M5CODRVJPUFM62IJFH67J2", + transaction_attr: {}, + transaction_hash: + "686601028de9ddf40a1c24461a6a9c0415d60a39255c35eccad0b52ac1e700a5", + transaction_successful: false, + type: "payment", + type_i: 1, + }, + ]; + const stubOverrides = async () => { - await page.route("*/**/account-history/*", async (route) => { - const json = [ - { - amount: "0.0010000", - asset_code: "USDC", - asset_issuer: - "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", - asset_type: "credit_alphanum4", - created_at: "2025-03-21T22:28:46Z", - from: "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY", - id: "164007621169153", - paging_token: "164007621169153", - source_account: - "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY", - to: "GCKUVXILBNYS4FDNWCGCYSJBY2PBQ4KAW2M5CODRVJPUFM62IJFH67J2", - transaction_attr: {}, - transaction_hash: - "686601028de9ddf40a1c24461a6a9c0415d60a39255c35eccad0b52ac1e700a5", - transaction_successful: false, - type: "payment", - type_i: 1, - }, - ]; - await route.fulfill({ json }); + // Use addInitScript to patch window.fetch directly in the extension page. + // Playwright's route interception (context.route/page.route) does not reliably + // intercept fetch requests made from Chrome extension popup pages in CI headless mode. + // addInitScript injects code before page scripts run and is guaranteed to work. + await page.addInitScript((data: object[]) => { + const origFetch = window.fetch.bind(window); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).fetch = function (input: any, init: any) { + const urlStr: string = + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : input.url ?? ""; + if (urlStr.includes("/account-history/")) { + return Promise.resolve( + new Response(JSON.stringify(data), { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ); + } + return origFetch(input, init); + }; + }, mockAccountHistoryData); + + // Also register context.route as a network-level fallback + await context.route("*/**/account-history/*", async (route) => { + await route.fulfill({ json: mockAccountHistoryData }); }); }; @@ -55,6 +84,7 @@ test("View failed transaction", async ({ page, extensionId, context }) => { await page.getByTestId("nav-link-account-history").click(); await expect(page.getByTestId("history-item-amount-component")).toHaveText( "Mar 21", + { timeout: 30000 }, ); await expect(page.getByTestId("history-item-label")).toHaveText( "Transaction Failed", @@ -76,7 +106,7 @@ test("Hide create claimable balance spam", async ({ context, }) => { const stubOverrides = async () => { - await page.route("*/**/account-history/*", async (route) => { + await context.route("*/**/account-history/*", async (route) => { const json = [ { amount: "0.0010000", @@ -205,7 +235,7 @@ test("History row displays muxed address extracted from XDR for payment", async const envelopeXdr = tx.toXDR(); // Stub account history BEFORE login to ensure it catches all requests // (returns both base G address and muxed M address) - await page.route("**/account-history/**", async (route) => { + await context.route("**/account-history/**", async (route) => { const json = [ { amount: "1.0000000", @@ -307,7 +337,7 @@ test.skip("History row displays address extracted from XDR for createAccount", a await stubAccountBalances(page); await loginToTestAccount({ page, extensionId, context }); - await page.route("**/account-history/**", async (route) => { + await context.route("**/account-history/**", async (route) => { const json = [ { amount: "1.0000000", @@ -388,7 +418,7 @@ test("History row displays regular G address when no muxed address in XDR", asyn const stubOverrides = async () => { // Stub account history - await page.route("**/account-history/**", async (route) => { + await context.route("**/account-history/**", async (route) => { const json = [ { amount: "1.0000000", @@ -463,7 +493,7 @@ test.describe("Asset Diffs in Transaction History", () => { await stubTokenDetails(page); const stubOverrides = async () => { - await page.route("*/**/account-history/*", async (route) => { + await context.route("*/**/account-history/*", async (route) => { const json = [ { amount: "100.0000000", @@ -528,7 +558,7 @@ test.describe("Asset Diffs in Transaction History", () => { await stubTokenDetails(page); const stubOverrides = async () => { - await page.route("*/**/account-history/*", async (route) => { + await context.route("*/**/account-history/*", async (route) => { const json = [ { asset_type: "native", @@ -609,7 +639,7 @@ test.describe("Asset Diffs in Transaction History", () => { await stubTokenDetails(page); const stubOverrides = async () => { - await page.route("*/**/account-history/*", async (route) => { + await context.route("*/**/account-history/*", async (route) => { const json = [ { asset_type: "native", @@ -713,7 +743,7 @@ test.describe("Asset Diffs in Transaction History", () => { } }); - await page.route("*/**/account-history/*", async (route) => { + await context.route("*/**/account-history/*", async (route) => { const json = [ { amount: "1000000000000000000", diff --git a/extension/e2e-tests/helpers/stubs.ts b/extension/e2e-tests/helpers/stubs.ts index 05091dc378..c7a0c2150a 100644 --- a/extension/e2e-tests/helpers/stubs.ts +++ b/extension/e2e-tests/helpers/stubs.ts @@ -839,8 +839,8 @@ export const stubAccountBalancesWithUSDC = async (page: Page) => { }); }; -export const stubAccountHistory = async (page: Page) => { - await page.route("**/account-history/**", async (route) => { +export const stubAccountHistory = async (context: BrowserContext) => { + await context.route("**/account-history/**", async (route) => { const json = [ { _links: { @@ -2708,7 +2708,7 @@ export const stubAllExternalApis = async ( // Mercury/History endpoints // Note: Tests that need account history should call stubAccountHistory() instead // to provide their own test data - await stubAccountHistory(page); + await stubAccountHistory(context); await stubMercuryTransactions(page); // RPC and Soroban diff --git a/extension/public/background.ts b/extension/public/background.ts index 8241c1efa4..36ef17d494 100644 --- a/extension/public/background.ts +++ b/extension/public/background.ts @@ -3,6 +3,8 @@ import { initExtensionMessageListener, initInstalledListener, initAlarmListener, + initSidebarBehavior, + initSidebarConnectionListener, } from "background"; function main() { @@ -10,6 +12,8 @@ function main() { initExtensionMessageListener(); initInstalledListener(); initAlarmListener(); + initSidebarBehavior(); + initSidebarConnectionListener(); } main(); diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index f00335cb00..8c046a0a21 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -36,5 +36,12 @@ "128": "images/icon128.png" }, "permissions": ["storage", "alarms"], + "sidebar_action": { + "default_panel": "index.html?mode=sidebar", + "default_icon": { + "16": "images/icon16.png", + "32": "images/icon32.png" + } + }, "manifest_version": 2 } diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index a5cd24a56e..5a8a1124bc 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -34,6 +34,9 @@ "48": "images/icon48.png", "128": "images/icon128.png" }, - "permissions": ["storage", "alarms"], + "permissions": ["storage", "alarms", "sidePanel"], + "side_panel": { + "default_path": "index.html?mode=sidebar" + }, "manifest_version": 3 } diff --git a/extension/src/background/index.ts b/extension/src/background/index.ts index 8b949e913e..19eb0a9425 100644 --- a/extension/src/background/index.ts +++ b/extension/src/background/index.ts @@ -7,8 +7,13 @@ import { import { ExternalRequest, Response } from "@shared/api/types"; import { buildStore } from "background/store"; -import { popupMessageListener } from "./messageListener/popupMessageListener"; +import { + popupMessageListener, + clearSidebarWindowId, + setSidebarWindowId, +} from "./messageListener/popupMessageListener"; import { freighterApiMessageListener } from "./messageListener/freighterApiMessageListener"; +import { SIDEBAR_PORT_NAME } from "popup/components/SidebarSigningListener"; import { SESSION_ALARM_NAME, SessionTimer, @@ -24,6 +29,7 @@ import { dataStorageAccess, browserLocalStorage, } from "./helpers/dataStorageAccess"; +import { IS_OPEN_SIDEBAR_BY_DEFAULT_ID } from "constants/localStorageTypes"; import { ServiceMessageRequest } from "@shared/api/types/message-request"; import { BrowserStorageKeyStore, @@ -45,6 +51,24 @@ export const initContentScriptMessageListener = () => { }); }; +export const initSidebarConnectionListener = () => { + chrome.runtime.onConnect.addListener((port) => { + if (port.name !== SIDEBAR_PORT_NAME) return; + + // Sidebar sends its window ID as first message + port.onMessage.addListener((msg: { windowId: number }) => { + if (msg.windowId !== undefined) { + setSidebarWindowId(msg.windowId); + } + }); + + // When sidebar closes (for any reason), clear the window ID + port.onDisconnect.addListener(() => { + clearSidebarWindowId(); + }); + }); +}; + export const initExtensionMessageListener = () => { browser?.runtime?.onMessage?.addListener(async (request, sender) => { const sessionStore = await buildStore(); @@ -109,6 +133,18 @@ export const initInstalledListener = () => { browser?.runtime?.onInstalled.addListener(versionedMigration); }; +export const initSidebarBehavior = async () => { + const localStore = dataStorageAccess(browserLocalStorage); + const val = + ((await localStore.getItem(IS_OPEN_SIDEBAR_BY_DEFAULT_ID)) as boolean) ?? + false; + if (chrome.sidePanel?.setPanelBehavior) { + chrome.sidePanel + .setPanelBehavior({ openPanelOnActionClick: !!val }) + .catch((e) => console.error("Failed to set panel behavior:", e)); + } +}; + export const initAlarmListener = () => { browser?.alarms?.onAlarm.addListener(async ({ name }: { name: string }) => { const sessionStore = await buildStore(); diff --git a/extension/src/background/messageListener/freighterApiMessageListener.ts b/extension/src/background/messageListener/freighterApiMessageListener.ts index 0365b8e4ec..bccf0eea8d 100644 --- a/extension/src/background/messageListener/freighterApiMessageListener.ts +++ b/extension/src/background/messageListener/freighterApiMessageListener.ts @@ -61,10 +61,38 @@ import { FlaggedKeys, TransactionInfo } from "types/transactions"; import { authEntryQueue, blobQueue, + getSidebarWindowId, responseQueue, tokenQueue, transactionQueue, } from "./popupMessageListener"; +import { QUEUE_ITEM_TTL_MS } from "background/helpers/queueCleanup"; + +const SIDEBAR_NAVIGATE = "SIDEBAR_NAVIGATE"; + +const openSigningWindow = async (hashRoute: string, width?: number) => { + const sidebarWindowId = getSidebarWindowId(); + if (sidebarWindowId !== null) { + browser.runtime.sendMessage({ type: SIDEBAR_NAVIGATE, route: hashRoute }); + try { + if ((browser as any).sidebarAction) { + // Firefox + await (browser as any).sidebarAction.open(); + } else { + // Chrome and other Chromium browsers + await chrome.sidePanel.open({ windowId: sidebarWindowId }); + } + } catch (_) { + // ignore if unavailable + } + return null; + } + return browser.windows.create({ + url: chrome.runtime.getURL(`/index.html#${hashRoute}`), + ...WINDOW_SETTINGS, + ...(width !== undefined ? { width } : {}), + }); +}; import { DataStorageAccess } from "background/helpers/dataStorageAccess"; interface WindowParams { @@ -107,26 +135,32 @@ export const freighterApiMessageListener = ( const uuid = crypto.randomUUID(); const encodeOrigin = encodeObject({ tab, url: tabUrl, uuid }); - const window = await browser.windows.create({ - url: chrome.runtime.getURL(`/index.html#/grant-access?${encodeOrigin}`), - ...WINDOW_SETTINGS, - width: 400, - }); + const popup = await openSigningWindow(`/grant-access?${encodeOrigin}`); return new Promise((resolve) => { + if (popup === null) { + setTimeout( + () => + resolve({ + apiError: FreighterApiDeclinedError, + error: FreighterApiDeclinedError.message, + }), + QUEUE_ITEM_TTL_MS, + ); + } const response = async (url: string, publicKey?: string) => { // queue it up, we'll let user confirm the url looks okay and then we'll send publicKey // if we're good, of course if (url === tabUrl) { - /* + /* This timeout is a bit of a hack to ensure the window doesn't close before the promise resolves. Wrapping in a setTimeout queues up the wndows.remove action into the event loop, but allows the promise to resolve first. This is really only an issue in e2e tests as the e2e window closes too quickly to register a click. */ setTimeout(() => { - if (window.id) { + if (popup?.id) { // ensure the window is closed to prevent collisions with other popups - browser.windows.remove(window.id); + browser.windows.remove(popup.id); } }, 50); @@ -199,19 +233,14 @@ export const freighterApiMessageListener = ( tokenQueue.push({ token: tokenInfo, uuid, createdAt: Date.now() }); const encodedTokenInfo = encodeObject(tokenInfo); - const popup = await browser.windows.create({ - url: chrome.runtime.getURL( - `/index.html#/add-token?${encodedTokenInfo}`, - ), - ...WINDOW_SETTINGS, - }); + const popup = await openSigningWindow(`/add-token?${encodedTokenInfo}`); return new Promise((resolve) => { - if (!popup) { + if (popup === undefined) { resolve({ apiError: FreighterApiInternalError, }); - } else { + } else if (popup !== null) { browser.windows.onRemoved.addListener(() => resolve({ apiError: FreighterApiDeclinedError, @@ -351,20 +380,24 @@ export const freighterApiMessageListener = ( }); const encodedBlob = encodeObject(transactionInfo); - const popup = await browser.windows.create({ - url: chrome.runtime.getURL( - `/index.html#/sign-transaction?${encodedBlob}`, - ), - ...WINDOW_SETTINGS, - }); + const popup = await openSigningWindow(`/sign-transaction?${encodedBlob}`); return new Promise((resolve) => { - if (!popup) { + if (popup === undefined) { resolve({ // return 2 error formats: one for clients running older versions of freighter-api, and one to adhere to the standard wallet interface apiError: FreighterApiInternalError, error: FreighterApiInternalError.message, }); + } else if (popup === null) { + setTimeout( + () => + resolve({ + apiError: FreighterApiDeclinedError, + error: FreighterApiDeclinedError.message, + }), + QUEUE_ITEM_TTL_MS, + ); } else { browser.windows.onRemoved.addListener(() => resolve({ @@ -428,18 +461,24 @@ export const freighterApiMessageListener = ( blobQueue.push({ blob: blobData, uuid, createdAt: Date.now() }); const encodedBlob = encodeObject(blobData); - const popup = await browser.windows.create({ - url: chrome.runtime.getURL(`/index.html#/sign-message?${encodedBlob}`), - ...WINDOW_SETTINGS, - }); + const popup = await openSigningWindow(`/sign-message?${encodedBlob}`); return new Promise((resolve) => { - if (!popup) { + if (popup === undefined) { resolve({ // return 2 error formats: one for clients running older versions of freighter-api, and one to adhere to the standard wallet interface apiError: FreighterApiInternalError, error: FreighterApiInternalError.message, }); + } else if (popup === null) { + setTimeout( + () => + resolve({ + apiError: FreighterApiDeclinedError, + error: FreighterApiDeclinedError.message, + }), + QUEUE_ITEM_TTL_MS, + ); } else { browser.windows.onRemoved.addListener(() => resolve({ @@ -515,20 +554,26 @@ export const freighterApiMessageListener = ( authEntryQueue.push({ authEntry, uuid, createdAt: Date.now() }); const encodedAuthEntry = encodeObject(authEntry); - const popup = await browser.windows.create({ - url: chrome.runtime.getURL( - `/index.html#/sign-auth-entry?${encodedAuthEntry}`, - ), - ...WINDOW_SETTINGS, - }); + const popup = await openSigningWindow( + `/sign-auth-entry?${encodedAuthEntry}`, + ); return new Promise((resolve) => { - if (!popup) { + if (popup === undefined) { resolve({ // return 2 error formats: one for clients running older versions of freighter-api, and one to adhere to the standard wallet interface apiError: FreighterApiInternalError, error: FreighterApiInternalError.message, }); + } else if (popup === null) { + setTimeout( + () => + resolve({ + apiError: FreighterApiDeclinedError, + error: FreighterApiDeclinedError.message, + }), + QUEUE_ITEM_TTL_MS, + ); } else { browser.windows.onRemoved.addListener(() => resolve({ @@ -653,13 +698,17 @@ export const freighterApiMessageListener = ( const uuid = crypto.randomUUID(); const encodeOrigin = encodeObject({ tab, url: tabUrl, uuid }); - browser.windows.create({ - url: chrome.runtime.getURL(`/index.html#/grant-access?${encodeOrigin}`), - ...WINDOW_SETTINGS, - width: 400, - }); + await openSigningWindow(`/grant-access?${encodeOrigin}`, 400); return new Promise((resolve) => { + setTimeout( + () => + resolve({ + apiError: FreighterApiDeclinedError, + error: FreighterApiDeclinedError.message, + }), + QUEUE_ITEM_TTL_MS, + ); const response = async (url?: string) => { // queue it up, we'll let user confirm the url looks okay and then we'll say it's okay if (url === tabUrl) { diff --git a/extension/src/background/messageListener/handlers/loadSettings.ts b/extension/src/background/messageListener/handlers/loadSettings.ts index 00f32e3eb8..5f53b747fb 100644 --- a/extension/src/background/messageListener/handlers/loadSettings.ts +++ b/extension/src/background/messageListener/handlers/loadSettings.ts @@ -12,7 +12,10 @@ import { getOverriddenBlockaidResponse, } from "background/helpers/account"; import { DataStorageAccess } from "background/helpers/dataStorageAccess"; -import { DATA_SHARING_ID } from "constants/localStorageTypes"; +import { + DATA_SHARING_ID, + IS_OPEN_SIDEBAR_BY_DEFAULT_ID, +} from "constants/localStorageTypes"; import { getHiddenAssets } from "../helpers/get-hidden-assets"; export const loadSettings = async ({ @@ -28,6 +31,9 @@ export const loadSettings = async ({ const assetsLists = await getAssetsLists({ localStore }); const isNonSSLEnabled = await getIsNonSSLEnabled({ localStore }); const isHideDustEnabled = await getIsHideDustEnabled({ localStore }); + const isOpenSidebarByDefault = + ((await localStore.getItem(IS_OPEN_SIDEBAR_BY_DEFAULT_ID)) as boolean) ?? + false; const { hiddenAssets } = await getHiddenAssets({ localStore }); const overriddenBlockaidResponse = await getOverriddenBlockaidResponse({ localStore, @@ -46,6 +52,7 @@ export const loadSettings = async ({ assetsLists, isNonSSLEnabled, isHideDustEnabled, + isOpenSidebarByDefault, hiddenAssets, overriddenBlockaidResponse, }; diff --git a/extension/src/background/messageListener/handlers/saveSettings.ts b/extension/src/background/messageListener/handlers/saveSettings.ts index f0e7f6df16..d58e0260b2 100644 --- a/extension/src/background/messageListener/handlers/saveSettings.ts +++ b/extension/src/background/messageListener/handlers/saveSettings.ts @@ -12,6 +12,7 @@ import { DataStorageAccess } from "background/helpers/dataStorageAccess"; import { DATA_SHARING_ID, IS_HIDE_DUST_ENABLED_ID, + IS_OPEN_SIDEBAR_BY_DEFAULT_ID, IS_VALIDATING_MEMO_ID, } from "constants/localStorageTypes"; @@ -22,12 +23,27 @@ export const saveSettings = async ({ request: SaveSettingsMessage; localStore: DataStorageAccess; }) => { - const { isDataSharingAllowed, isMemoValidationEnabled, isHideDustEnabled } = - request; + const { + isDataSharingAllowed, + isMemoValidationEnabled, + isHideDustEnabled, + isOpenSidebarByDefault, + } = request; await localStore.setItem(DATA_SHARING_ID, isDataSharingAllowed); await localStore.setItem(IS_VALIDATING_MEMO_ID, isMemoValidationEnabled); await localStore.setItem(IS_HIDE_DUST_ENABLED_ID, isHideDustEnabled); + await localStore.setItem( + IS_OPEN_SIDEBAR_BY_DEFAULT_ID, + isOpenSidebarByDefault, + ); + + // Apply sidebar behavior immediately on Chrome + if (chrome.sidePanel?.setPanelBehavior) { + chrome.sidePanel + .setPanelBehavior({ openPanelOnActionClick: isOpenSidebarByDefault }) + .catch((e) => console.error("Failed to set panel behavior:", e)); + } const networkDetails = await getNetworkDetails({ localStore }); const isRpcHealthy = true; @@ -43,5 +59,8 @@ export const saveSettings = async ({ isSorobanPublicEnabled: featureFlags.useSorobanPublic, isNonSSLEnabled: await getIsNonSSLEnabled({ localStore }), isHideDustEnabled: await getIsHideDustEnabled({ localStore }), + isOpenSidebarByDefault: (await localStore.getItem( + IS_OPEN_SIDEBAR_BY_DEFAULT_ID, + )) as boolean ?? false, }; }; diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index f5f9292c96..558450ac21 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -16,6 +16,8 @@ import { RejectTransactionResponse, SignedHwPayloadResponse, MarkQueueActiveMessage, + SidebarRegisterMessage, + OpenSidebarMessage, } from "@shared/api/types/message-request"; import { SERVICE_TYPES } from "@shared/constants/services"; import { DataStorageAccess } from "background/helpers/dataStorageAccess"; @@ -92,6 +94,16 @@ import { getHiddenCollectibles } from "./handlers/getHiddenCollectibles"; const numOfPublicKeysToCheck = 5; +let sidebarWindowId: number | null = null; + +export const getSidebarWindowId = (): number | null => sidebarWindowId; +export const setSidebarWindowId = (id: number) => { + sidebarWindowId = id; +}; +export const clearSidebarWindowId = () => { + sidebarWindowId = null; +}; + export const responseQueue: ResponseQueue< | RequestAccessResponse | SignTransactionResponse @@ -553,6 +565,29 @@ export const popupMessageListener = ( return {}; } + case SERVICE_TYPES.OPEN_SIDEBAR: { + const { windowId } = request as OpenSidebarMessage; + return (async () => { + await chrome.sidePanel + .setOptions({ path: "index.html?mode=sidebar", enabled: true }) + .catch((e) => console.error("Failed to set sidebar options:", e)); + await chrome.sidePanel + .open({ windowId }) + .catch((e) => console.error("Failed to open sidebar:", e)); + return {}; + })(); + } + + case SERVICE_TYPES.SIDEBAR_REGISTER: { + sidebarWindowId = (request as SidebarRegisterMessage).windowId; + return {}; + } + + case SERVICE_TYPES.SIDEBAR_UNREGISTER: { + sidebarWindowId = null; + return {}; + } + default: return { error: "Message type not supported" }; } diff --git a/extension/src/constants/localStorageTypes.ts b/extension/src/constants/localStorageTypes.ts index 5223b2bd9a..6ce6a4760d 100644 --- a/extension/src/constants/localStorageTypes.ts +++ b/extension/src/constants/localStorageTypes.ts @@ -30,4 +30,5 @@ export const TEMPORARY_STORE_EXTRA_ID = "temporaryStoreExtra"; export const MOBILE_APP_BANNER_DISMISSED = "mobileAppBannerDismissed"; export const OVERRIDDEN_BLOCKAID_RESPONSE_ID = "overriddenBlockaidResponse"; export const COLLECTIBLES_ID = "collectibles"; +export const IS_OPEN_SIDEBAR_BY_DEFAULT_ID = "isOpenSidebarByDefault"; export const METRICS_USER_ID = "metrics_user_id"; diff --git a/extension/src/popup/Router.tsx b/extension/src/popup/Router.tsx index dc23fec8ad..93bd7d6250 100644 --- a/extension/src/popup/Router.tsx +++ b/extension/src/popup/Router.tsx @@ -64,6 +64,8 @@ import { Discover } from "popup/views/Discover"; import { Wallets } from "popup/views/Wallets"; import { DEV_SERVER } from "@shared/constants/services"; +import { isSidebarMode } from "popup/helpers/isSidebarMode"; +import { SidebarSigningListener } from "popup/components/SidebarSigningListener"; import { SettingsState } from "@shared/api/types"; import { SignMessage } from "./views/SignMessage"; @@ -164,6 +166,7 @@ const Layout = () => { export const Router = () => ( + {isSidebarMode() && } }> { + const navigate = useNavigate(); + + useEffect(() => { + // Open a long-lived port to the background. + // The background uses onDisconnect to reliably clear sidebarWindowId when sidebar closes. + const port = chrome.runtime.connect({ name: SIDEBAR_PORT_NAME }); + + // Send window ID so the background can register this sidebar + chrome.windows.getCurrent().then((win) => { + port.postMessage({ windowId: win.id }); + }); + + // In sidebar mode, window.close() would collapse the panel. Navigate home instead. + const originalClose = window.close.bind(window); + window.close = () => navigate(ROUTES.account); + + const handler = (message: { type: string; route: string }) => { + if (message.type === SIDEBAR_NAVIGATE) { + navigate(message.route); + } + }; + + chrome.runtime.onMessage.addListener(handler); + + return () => { + window.close = originalClose; + chrome.runtime.onMessage.removeListener(handler); + port.disconnect(); + }; + }, [navigate]); + + return null; +}; diff --git a/extension/src/popup/components/account/AccountHeader/index.tsx b/extension/src/popup/components/account/AccountHeader/index.tsx index 763634e5ac..ee907d06a4 100644 --- a/extension/src/popup/components/account/AccountHeader/index.tsx +++ b/extension/src/popup/components/account/AccountHeader/index.tsx @@ -11,7 +11,7 @@ import { ROUTES } from "popup/constants/routes"; import { LoadingBackground } from "popup/basics/LoadingBackground"; import { View } from "popup/basics/layout/View"; import { isActiveNetwork } from "helpers/stellar"; -import { navigateTo, openTab } from "popup/helpers/navigate"; +import { navigateTo, openTab, openSidebar } from "popup/helpers/navigate"; import { newTabHref } from "helpers/urls"; import { IdenticonImg } from "popup/components/identicons/IdenticonImg"; import { PunycodedDomain } from "popup/components/PunycodedDomain"; @@ -181,6 +181,19 @@ export const AccountHeader = ({ + {typeof globalThis.chrome?.sidePanel?.open === "function" && ( +
openSidebar()} + > + + {t("Sidebar mode")} + +
+ +
+
+ )}
openTab(newTabHref(ROUTES.account))} diff --git a/extension/src/popup/ducks/settings.ts b/extension/src/popup/ducks/settings.ts index e000be61aa..7da17788dc 100644 --- a/extension/src/popup/ducks/settings.ts +++ b/extension/src/popup/ducks/settings.ts @@ -58,6 +58,7 @@ const settingsInitialState: Settings = { networksList: DEFAULT_NETWORKS, isMemoValidationEnabled: true, isHideDustEnabled: true, + isOpenSidebarByDefault: false, error: "", }; @@ -124,12 +125,18 @@ export const saveSettings = createAsyncThunk< isDataSharingAllowed: boolean; isMemoValidationEnabled: boolean; isHideDustEnabled: boolean; + isOpenSidebarByDefault: boolean; }, { rejectValue: ErrorMessage; state: AppState } >( "settings/saveSettings", async ( - { isDataSharingAllowed, isMemoValidationEnabled, isHideDustEnabled }, + { + isDataSharingAllowed, + isMemoValidationEnabled, + isHideDustEnabled, + isOpenSidebarByDefault, + }, { getState, rejectWithValue }, ) => { let res = { @@ -139,6 +146,7 @@ export const saveSettings = createAsyncThunk< userNotification: { enabled: false, message: "" }, settingsState: SettingsState.IDLE, isHideDustEnabled: true, + isOpenSidebarByDefault: false, }; const activePublicKey = publicKeySelector(getState()); @@ -148,6 +156,7 @@ export const saveSettings = createAsyncThunk< isDataSharingAllowed, isMemoValidationEnabled, isHideDustEnabled, + isOpenSidebarByDefault, }); } catch (e) { console.error(e); @@ -365,6 +374,7 @@ const settingsSlice = createSlice({ assetsLists, isNonSSLEnabled, isHideDustEnabled, + isOpenSidebarByDefault, } = payload; state.allowList = allowList; state.isDataSharingAllowed = isDataSharingAllowed; @@ -376,6 +386,7 @@ const settingsSlice = createSlice({ state.assetsLists = assetsLists; state.isNonSSLEnabled = isNonSSLEnabled; state.isHideDustEnabled = isHideDustEnabled; + state.isOpenSidebarByDefault = isOpenSidebarByDefault; state.overriddenBlockaidResponse = payload.overriddenBlockaidResponse ?? null; state.settingsState = SettingsState.SUCCESS; @@ -424,9 +435,11 @@ const settingsSlice = createSlice({ isRpcHealthy, isSorobanPublicEnabled, isHideDustEnabled, + isOpenSidebarByDefault, overriddenBlockaidResponse, } = (action?.payload as typeof action.payload & { overriddenBlockaidResponse?: string | null; + isOpenSidebarByDefault?: boolean; }) || { ...initialState, }; @@ -440,6 +453,7 @@ const settingsSlice = createSlice({ isRpcHealthy, isSorobanPublicEnabled, isHideDustEnabled, + isOpenSidebarByDefault: isOpenSidebarByDefault ?? false, overriddenBlockaidResponse: overriddenBlockaidResponse ?? null, }; }); @@ -681,3 +695,8 @@ export const overriddenBlockaidResponseSelector = createSelector( settingsSelector, (settings) => settings.overriddenBlockaidResponse, ); + +export const isOpenSidebarByDefaultSelector = createSelector( + settingsSelector, + (settings) => settings.isOpenSidebarByDefault, +); diff --git a/extension/src/popup/helpers/isFullscreenMode.ts b/extension/src/popup/helpers/isFullscreenMode.ts index 4bcaaf55f2..6b0cd7882e 100644 --- a/extension/src/popup/helpers/isFullscreenMode.ts +++ b/extension/src/popup/helpers/isFullscreenMode.ts @@ -1,7 +1,9 @@ import { DEV_SERVER } from "@shared/constants/services"; import { POPUP_HEIGHT, POPUP_WIDTH } from "constants/dimensions"; +import { isSidebarMode } from "./isSidebarMode"; export const isFullscreenMode = () => window.innerHeight !== POPUP_HEIGHT && window.innerWidth !== POPUP_WIDTH && + !isSidebarMode() && !DEV_SERVER; diff --git a/extension/src/popup/helpers/isSidebarMode.ts b/extension/src/popup/helpers/isSidebarMode.ts new file mode 100644 index 0000000000..1100522554 --- /dev/null +++ b/extension/src/popup/helpers/isSidebarMode.ts @@ -0,0 +1,2 @@ +export const isSidebarMode = () => + new URLSearchParams(window.location.search).get("mode") === "sidebar"; diff --git a/extension/src/popup/helpers/navigate.ts b/extension/src/popup/helpers/navigate.ts index ef07030123..dedbef588a 100644 --- a/extension/src/popup/helpers/navigate.ts +++ b/extension/src/popup/helpers/navigate.ts @@ -14,3 +14,23 @@ export const navigateTo = ( /* Firefox will not let you use window.open to programatically open a tab. Use this instead */ export const openTab = (url: string) => browser.tabs.create({ url }); + +export const openSidebar = async () => { + try { + if ((browser as any).sidebarAction) { + // Firefox + await (browser as any).sidebarAction.open(); + } else { + // Chrome — must be called in user gesture context before closing popup + const win = await chrome.windows.getCurrent(); + await chrome.sidePanel.setOptions({ + path: "index.html?mode=sidebar", + enabled: true, + }); + await chrome.sidePanel.open({ windowId: win.id! }); + } + } catch (e) { + console.error("Failed to open sidebar:", e); + } + window.close(); +}; diff --git a/extension/src/popup/views/IntegrationTest.tsx b/extension/src/popup/views/IntegrationTest.tsx index 018183880e..9e1c1fa2ab 100644 --- a/extension/src/popup/views/IntegrationTest.tsx +++ b/extension/src/popup/views/IntegrationTest.tsx @@ -347,6 +347,7 @@ export const IntegrationTest = () => { isDataSharingAllowed: true, isMemoValidationEnabled: true, isHideDustEnabled: true, + isOpenSidebarByDefault: false, }); runAsserts("saveSettings", () => { assertEq(res.networkDetails, FUTURENET_NETWORK_DETAILS); diff --git a/extension/src/popup/views/Preferences/index.tsx b/extension/src/popup/views/Preferences/index.tsx index 00d9322086..821e6e6479 100644 --- a/extension/src/popup/views/Preferences/index.tsx +++ b/extension/src/popup/views/Preferences/index.tsx @@ -29,6 +29,7 @@ export const Preferences = () => { isValidatingMemoValue: boolean; isDataSharingAllowedValue: boolean; isHideDustEnabledValue: boolean; + isOpenSidebarByDefaultValue: boolean; } const handleSubmit = async (formValue: SettingValues) => { @@ -36,6 +37,7 @@ export const Preferences = () => { isValidatingMemoValue, isDataSharingAllowedValue, isHideDustEnabledValue, + isOpenSidebarByDefaultValue, } = formValue; await dispatch( @@ -43,6 +45,7 @@ export const Preferences = () => { isMemoValidationEnabled: isValidatingMemoValue, isDataSharingAllowed: isDataSharingAllowedValue, isHideDustEnabled: isHideDustEnabledValue, + isOpenSidebarByDefault: isOpenSidebarByDefaultValue, }), ); }; @@ -95,13 +98,18 @@ export const Preferences = () => { state: state.state, }); - const { isMemoValidationEnabled, isDataSharingAllowed, isHideDustEnabled } = - state.data.settings; + const { + isMemoValidationEnabled, + isDataSharingAllowed, + isHideDustEnabled, + isOpenSidebarByDefault, + } = state.data.settings; const initialValues: SettingValues = { isValidatingMemoValue: isMemoValidationEnabled, isDataSharingAllowedValue: isDataSharingAllowed, isHideDustEnabledValue: isHideDustEnabled, + isOpenSidebarByDefaultValue: isOpenSidebarByDefault ?? false, }; return ( @@ -173,6 +181,27 @@ export const Preferences = () => { {t("Hide payments smaller than 0.1 XLM")}
+ + {typeof globalThis.chrome?.sidePanel?.open === "function" && ( +
+
+ {t("Open sidebar mode by default")} +
+ } + id="isOpenSidebarByDefaultValue" + /> +
+
+ + {t( + "Open Freighter in sidebar instead of popup when clicking the extension icon", + )} + +
+ )} diff --git a/extension/src/popup/views/__tests__/Account.test.tsx b/extension/src/popup/views/__tests__/Account.test.tsx index 8a36244801..65673ee16e 100644 --- a/extension/src/popup/views/__tests__/Account.test.tsx +++ b/extension/src/popup/views/__tests__/Account.test.tsx @@ -228,6 +228,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -873,6 +874,7 @@ describe("Account view", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -926,6 +928,7 @@ describe("Account view", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -997,6 +1000,7 @@ describe("Account view", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1069,6 +1073,7 @@ describe("Account view", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/AccountCreator.test.tsx b/extension/src/popup/views/__tests__/AccountCreator.test.tsx index 272ffd203b..ab514e89cc 100644 --- a/extension/src/popup/views/__tests__/AccountCreator.test.tsx +++ b/extension/src/popup/views/__tests__/AccountCreator.test.tsx @@ -39,6 +39,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/AccountHistory.test.tsx b/extension/src/popup/views/__tests__/AccountHistory.test.tsx index d6c7c3ae66..337a400a51 100644 --- a/extension/src/popup/views/__tests__/AccountHistory.test.tsx +++ b/extension/src/popup/views/__tests__/AccountHistory.test.tsx @@ -90,6 +90,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -214,6 +215,7 @@ describe("AccountHistory", () => { networkDetails: TESTNET_NETWORK_DETAILS, networksList: DEFAULT_NETWORKS, isHideDustEnabled: true, + isOpenSidebarByDefault: false, }, }} > diff --git a/extension/src/popup/views/__tests__/AddFunds.test.tsx b/extension/src/popup/views/__tests__/AddFunds.test.tsx index 262acebf20..5df25b640e 100644 --- a/extension/src/popup/views/__tests__/AddFunds.test.tsx +++ b/extension/src/popup/views/__tests__/AddFunds.test.tsx @@ -47,6 +47,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/GrantAccess.test.tsx b/extension/src/popup/views/__tests__/GrantAccess.test.tsx index 4ffb639acb..9f1af918fa 100644 --- a/extension/src/popup/views/__tests__/GrantAccess.test.tsx +++ b/extension/src/popup/views/__tests__/GrantAccess.test.tsx @@ -40,6 +40,7 @@ const mockLoadSettings = () => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx index 1ba8dc7706..3e7520f863 100644 --- a/extension/src/popup/views/__tests__/ManageAssets.test.tsx +++ b/extension/src/popup/views/__tests__/ManageAssets.test.tsx @@ -317,6 +317,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index 2efef3baa5..2fd30c1fe6 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -82,6 +82,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -330,6 +331,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -445,6 +447,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -583,6 +586,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -696,6 +700,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -817,6 +822,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -938,6 +944,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1053,6 +1060,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1174,6 +1182,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1312,6 +1321,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1485,6 +1495,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, @@ -1650,6 +1661,7 @@ describe("SignTransactions", () => { isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true, diff --git a/extension/src/popup/views/__tests__/Swap.test.tsx b/extension/src/popup/views/__tests__/Swap.test.tsx index 7715604bd2..9389ae3257 100644 --- a/extension/src/popup/views/__tests__/Swap.test.tsx +++ b/extension/src/popup/views/__tests__/Swap.test.tsx @@ -160,6 +160,7 @@ jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => isDataSharingAllowed: false, isMemoValidationEnabled: false, isHideDustEnabled: true, + isOpenSidebarByDefault: false, settingsState: SettingsState.SUCCESS, isSorobanPublicEnabled: false, isRpcHealthy: true,