From 981ab1ffa2d59453f16658550d6f83b3403f288a Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:17 +0100 Subject: [PATCH 01/24] fix(#471): expose window.__queryClient only outside production Never leak the QueryClient in a production bundle; dev-only check keeps pnpm dev e2e (lock-unlock-lifecycle) working without NEXT_PUBLIC_E2E. --- src/context/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context/index.tsx b/src/context/index.tsx index 42d1cb5..cfcadbc 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -21,10 +21,10 @@ function ContextProvider({ children }: { children: ReactNode }) { }); useEffect(() => { - if ( - typeof window !== 'undefined' && - (process.env.NODE_ENV !== 'production' || process.env.NEXT_PUBLIC_E2E === 'true') - ) { + // Dev-only escape hatch for local debugging and Playwright specs that run + // `next dev` (NODE_ENV=development). Never expose the QueryClient in a + // production bundle — not even when NEXT_PUBLIC_E2E is set (issue #471). + if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'production') { window.__queryClient = queryClient; } }, [queryClient]); From 4fa8506e3fa3bf9eccd484d8f86d342a9f178169 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:18 +0100 Subject: [PATCH 02/24] fix(#472): render tvl directly instead of formatCredits double-format getPlatformStats already returns totalValueLocked as a localized currency string (e.g. $3,500); formatCredits turned it into $0.0M XLM. --- src/components/PlatformStats/PlatformStats.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlatformStats/PlatformStats.tsx b/src/components/PlatformStats/PlatformStats.tsx index a817cc2..ab9d933 100644 --- a/src/components/PlatformStats/PlatformStats.tsx +++ b/src/components/PlatformStats/PlatformStats.tsx @@ -35,7 +35,7 @@ export const PlatformStats: React.FC = ({ initialData }) => Total Value Locked - {stats ? formatCredits(stats.tvl) : '0.0M XLM'} + {stats ? stats.tvl : '$0'} From 076c44ce9ee698dfd7c51d6ea4d9158aa9e24ce4 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:18 +0100 Subject: [PATCH 03/24] test(#472): align PlatformStats mock with formatted tvl / credit velocity --- src/components/PlatformStats/PlatformStats.test.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/PlatformStats/PlatformStats.test.tsx b/src/components/PlatformStats/PlatformStats.test.tsx index 2bfa1dc..4a55f81 100644 --- a/src/components/PlatformStats/PlatformStats.test.tsx +++ b/src/components/PlatformStats/PlatformStats.test.tsx @@ -13,10 +13,13 @@ describe(' Layout Component', () => { it('matches baseline style design snapshot tests', () => { vi.spyOn(hooks, 'usePlatformStats').mockReturnValue({ data: { - tvl: '42300000000000', + // Production-shaped values: getPlatformStats formats TVL with + // toLocaleString({ style: 'currency' }), and getCreditVelocity returns + // a raw credit-unit count that formatCredits scales for display. + tvl: '$3,500', activePools: 12, totalFarmers: 15450, - creditVelocity: '8500000000000' + creditVelocity: '8500000000000', }, isLoading: false, isError: false, From 0f02e38c746ccc93750ab2321ce4d0a07775b285 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:18 +0100 Subject: [PATCH 04/24] test(#472): regenerate PlatformStats snapshot ($3,500, 8500.0M XLM) --- .../PlatformStats/__snapshots__/PlatformStats.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlatformStats/__snapshots__/PlatformStats.test.tsx.snap b/src/components/PlatformStats/__snapshots__/PlatformStats.test.tsx.snap index 7932376..50e1703 100644 --- a/src/components/PlatformStats/__snapshots__/PlatformStats.test.tsx.snap +++ b/src/components/PlatformStats/__snapshots__/PlatformStats.test.tsx.snap @@ -23,7 +23,7 @@ exports[` Layout Component > matches baseline style design snap
- 42300000.0M XLM + $3,500
From c1028ba36d73fda6177926da4ee5e8f308974bc0 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:23 +0100 Subject: [PATCH 05/24] fix(#473): stop excluding tests from tsc type-checking Drop **/*.test.{ts,tsx} from tsconfig exclude so test files are type-checked; keep vitest/playwright configs and tests/**, e2e/**. --- tsconfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index e36ffd4..43abc97 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,8 +28,6 @@ "vitest.config.ts", "playwright.config.ts", "tests/**", - "e2e/**", - "**/*.test.ts", - "**/*.test.tsx" + "e2e/**" ] } From 14c79625ded01175d9d10a2c0266104131e2ba43 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:23 +0100 Subject: [PATCH 06/24] fix(#473): widen SkeletonBar w prop to accept responsive object --- src/app/loading.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/loading.tsx b/src/app/loading.tsx index 3883e6c..47d10de 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -3,7 +3,15 @@ import { usePathname } from "next/navigation"; import { Box, Flex, Spinner, Text } from "@chakra-ui/react"; -function SkeletonBar({ w = "100%", h = "16px", delay = 0 }: { w?: string; h?: string; delay?: number }) { +function SkeletonBar({ + w = "100%", + h = "16px", + delay = 0, +}: { + w?: string | { base: string; md: string }; + h?: string; + delay?: number; +}) { return ( Date: Thu, 24 Sep 2026 17:50:23 +0100 Subject: [PATCH 07/24] fix(#473): import useEffect in Navbar (missing from import) --- src/components/Navbar/Navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 6ed4aaf..70d089e 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -22,7 +22,7 @@ import { usePathname } from "next/navigation"; import ThemeToggle from "@/components/ThemeToggle/ThemeToggle"; import { usePlatformStats } from "@/hooks/useSorobanQuery"; import { useToast } from "@/hooks/useToast"; -import { useState } from "react"; +import { useEffect, useState } from "react"; const MORE_LINKS = [ { href: "/prices", label: "Prices" }, From 7471281786e2260e8a0c397f807247b1fd6ecafe Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:23 +0100 Subject: [PATCH 08/24] fix(#473): remove duplicate stepRef declaration in useLockFlow --- src/hooks/useLockFlow.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/useLockFlow.ts b/src/hooks/useLockFlow.ts index d5c99ac..e707897 100644 --- a/src/hooks/useLockFlow.ts +++ b/src/hooks/useLockFlow.ts @@ -48,8 +48,6 @@ export function useLockFlow({ const [error, setError] = useState(null); // Use refs to avoid recreating execute callback when step or walletApi change during execution - const stepRef = useRef(step); - stepRef.current = step; const walletApiRef = useRef(walletApi); walletApiRef.current = walletApi; // step changes throughout the flow (idle -> simulating -> signing -> From ab3e84719d1c9798c86bdf1c2d1aa8f74c22a531 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:23 +0100 Subject: [PATCH 09/24] fix(#473): export FarmStore type from farmStore for tests --- src/store/farmStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/farmStore.ts b/src/store/farmStore.ts index 48ea64d..1f91c9d 100644 --- a/src/store/farmStore.ts +++ b/src/store/farmStore.ts @@ -4,7 +4,7 @@ import type { FarmPosition } from "@/types/farm"; type FarmModal = "none" | "unlock" | "deposit" | "boost"; -type FarmStore = { +export type FarmStore = { selectedPosition: FarmPosition | null; activeModal: FarmModal; pendingTxHash: string | null; From 18f39959ccdc730f9ab4d60cc16111efd6b78549 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 10/24] test(#473): type BoostModal wallet mock (signTransaction, mutation cast) --- src/components/BoostModal/BoostModal.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/BoostModal/BoostModal.test.tsx b/src/components/BoostModal/BoostModal.test.tsx index c59d073..29fdc58 100644 --- a/src/components/BoostModal/BoostModal.test.tsx +++ b/src/components/BoostModal/BoostModal.test.tsx @@ -21,7 +21,7 @@ const useSetBoostMock = vi.mocked(useSetBoost); const defaultWallet = { publicKey: "GA3CD2PYXOQCXW7ZVQW3MOA3JFZCE4F4IG2FD66I55TQASPCNKYYEFRN", - walletApi: {}, + walletApi: { signTransaction: vi.fn() }, networkName: "TESTNET", isNetworkMismatch: false, isConnected: true, @@ -47,7 +47,7 @@ beforeEach(() => { mutate: vi.fn(), isPending: false, reset: vi.fn(), - } as ReturnType extends infer R ? R : never); + } as unknown as ReturnType); useFarmStore.setState({ selectedPosition: null, activeModal: "none", From f75e2719d2fd0ce3e485733a5da28e1b05c4a46a Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 11/24] test(#473): type UnlockModal FarmStore selectors and unlockAssets mocks --- .../UnlockModal/UnlockModal.test.tsx | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/UnlockModal/UnlockModal.test.tsx b/src/components/UnlockModal/UnlockModal.test.tsx index 2013665..d1e6ce1 100644 --- a/src/components/UnlockModal/UnlockModal.test.tsx +++ b/src/components/UnlockModal/UnlockModal.test.tsx @@ -1,10 +1,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import UnlockModal from './UnlockModal'; -import { useFarmStore } from '@/store/farmStore'; +import { useFarmStore, type FarmStore } from '@/store/farmStore'; import { useStellarWallet } from '@/context/StellarWalletContext'; import { useUnlockAssetsFeePreview } from '@/hooks/useSorobanQuery'; -import { unlockAssets } from '@/lib/soroban'; +import { unlockAssets, type UnlockAssetsCallbacks } from '@/lib/soroban'; import { useCountdown } from '@/hooks/useCountdown'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ChakraProvider } from '@chakra-ui/react'; @@ -97,18 +97,19 @@ describe('UnlockModal', () => { vi.clearAllMocks(); // Default mock implementations - vi.mocked(useFarmStore).mockImplementation((selector: (state: Record) => unknown) => { + vi.mocked(useFarmStore).mockImplementation((selector: (state: FarmStore) => unknown) => { const state = { activeModal: 'unlock', selectedPosition: mockPosition, close: vi.fn(), - }; + } as unknown as FarmStore; return selector(state); }); vi.mocked(useStellarWallet).mockReturnValue({ publicKey: 'G_TEST_USER', walletApi: { signTransaction: vi.fn() } as never, + networkName: 'TESTNET', isNetworkMismatch: false, isConnected: true, connect: vi.fn(), @@ -136,8 +137,12 @@ describe('UnlockModal', () => { }); it('does not render modal content when position is null', () => { - vi.mocked(useFarmStore).mockImplementation((selector: (state: Record) => unknown) => { - return selector({ activeModal: 'unlock', selectedPosition: null, close: vi.fn() }); + vi.mocked(useFarmStore).mockImplementation((selector: (state: FarmStore) => unknown) => { + return selector({ + activeModal: 'unlock', + selectedPosition: null, + close: vi.fn(), + } as unknown as FarmStore); }); renderWithProviders(createElement(UnlockModal)); // Modal should not show Unlock header @@ -197,13 +202,14 @@ describe('UnlockModal', () => { }); it('handles successful unlock flow', async () => { - vi.mocked(unlockAssets).mockImplementation(async (args: Record) => { - const onStep = args.onStep as ((s: string) => void) | undefined; - const onHash = args.onHash as ((h: string) => void) | undefined; - onStep?.('simulating'); - onStep?.('signing'); - onStep?.('submitting'); - onHash?.('test-hash-abc'); + vi.mocked(unlockAssets).mockImplementation(async (args: { + onStep?: UnlockAssetsCallbacks['onStep']; + onHash?: UnlockAssetsCallbacks['onHash']; + }) => { + args.onStep?.('simulating'); + args.onStep?.('signing'); + args.onStep?.('submitting'); + args.onHash?.('test-hash-abc'); return { success: true, hash: 'test-hash-abc', status: 'SUCCESS' }; }); @@ -225,9 +231,10 @@ describe('UnlockModal', () => { }); it('handles unlock error flow — result.success === false', async () => { - vi.mocked(unlockAssets).mockImplementation(async (args: Record) => { - const onStep = args.onStep as ((s: string) => void) | undefined; - onStep?.('simulating'); + vi.mocked(unlockAssets).mockImplementation(async (args: { + onStep?: UnlockAssetsCallbacks['onStep']; + }) => { + args.onStep?.('simulating'); return { success: false, status: 'FAILED', From daf3725cc2cd05873cc230e2f08f12033e052223 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 12/24] test(#473): cast process.env in validateEnv tests --- src/config/validateEnv.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/validateEnv.test.ts b/src/config/validateEnv.test.ts index 53247d4..ad236a2 100644 --- a/src/config/validateEnv.test.ts +++ b/src/config/validateEnv.test.ts @@ -11,7 +11,7 @@ const BASE_ENV = { NEXT_PUBLIC_HORIZON_URL: 'https://horizon-testnet.stellar.org', NEXT_PUBLIC_BACKEND_API_URL: 'http://localhost:4000/api/v1', STELLAR_FEE_SPONSOR_SECRET: 'SABC123', -} as NodeJS.ProcessEnv; +} as unknown as NodeJS.ProcessEnv; describe('collectEnvWarnings', () => { it('returns no warnings when every var is present and well-formed', () => { From 7f584be048fc8bb36de48da3a8aa8353ad691c4c Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 13/24] test(#473): type fetchLeaderboardMock as vi.fn in useLeaderboard tests --- src/hooks/useLeaderboard.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLeaderboard.test.ts b/src/hooks/useLeaderboard.test.ts index 8290d1d..e315f8f 100644 --- a/src/hooks/useLeaderboard.test.ts +++ b/src/hooks/useLeaderboard.test.ts @@ -22,7 +22,7 @@ const mockStakeEntries: LeaderboardEntry[] = [ { address: "addr-stake", totalCredits: 10, totalStake: 999, boostUtilization: 0.9 }, ]; -let fetchLeaderboardMock: ReturnType; +let fetchLeaderboardMock: ReturnType unknown>>; vi.mock("./useLeaderboard", async (importOriginal) => { const actual = await importOriginal(); From 8c1191ce893b1585a3a8ecc43150d6b2c696f40b Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 14/24] test(#473): add beforeEach import and typed wallet/toast mocks --- src/hooks/useSorobanEvents.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks/useSorobanEvents.test.ts b/src/hooks/useSorobanEvents.test.ts index 58284a2..632b677 100644 --- a/src/hooks/useSorobanEvents.test.ts +++ b/src/hooks/useSorobanEvents.test.ts @@ -39,6 +39,8 @@ describe("useSorobanEvents", () => { publicKey: TEST_PUBLIC_KEY, isConnected: true, walletApi: null, + networkName: "TESTNET", + isNetworkMismatch: false, connect: vi.fn(), disconnect: vi.fn(), }); @@ -370,6 +372,8 @@ describe("useSorobanEvents", () => { publicKey: null, isConnected: false, walletApi: null, + networkName: null, + isNetworkMismatch: false, connect: vi.fn(), disconnect: vi.fn(), }); From ac88a6960a22a6c4224682cce5a1dc5ae4b23122 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:24 +0100 Subject: [PATCH 15/24] test(#473): add networkName/isNetworkMismatch and toast mock typing --- src/hooks/useSorobanQuery.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hooks/useSorobanQuery.test.ts b/src/hooks/useSorobanQuery.test.ts index ccd5fe1..3004379 100644 --- a/src/hooks/useSorobanQuery.test.ts +++ b/src/hooks/useSorobanQuery.test.ts @@ -1,7 +1,7 @@ import { createElement, type ReactNode } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { waitFor } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { renderHook, act } from "@/test/renderHook"; import { sorobanService } from "@/lib/soroban"; import { @@ -272,13 +272,15 @@ describe("useSetBoost (#92)", () => { publicKey: TEST_PUBLIC_KEY, isConnected: true, walletApi: { signTransaction: vi.fn().mockResolvedValue("signed-xdr") }, + networkName: "TESTNET", + isNetworkMismatch: false, connect: vi.fn(), disconnect: vi.fn(), }); const { useToast } = await import("@chakra-ui/react"); toastMock = vi.fn(); - vi.mocked(useToast).mockReturnValue(toastMock); + vi.mocked(useToast).mockReturnValue(toastMock as never); }); it("calls sorobanService.setBoost with correct arguments", async () => { @@ -372,6 +374,8 @@ describe("useSetBoost (#92)", () => { publicKey: null, isConnected: false, walletApi: null, + networkName: null, + isNetworkMismatch: false, connect: vi.fn(), disconnect: vi.fn(), }); @@ -441,13 +445,15 @@ describe("useUnlockAssets (#138)", () => { publicKey: TEST_PUBLIC_KEY, isConnected: true, walletApi: { signTransaction: vi.fn().mockResolvedValue("signed-xdr") }, + networkName: "TESTNET", + isNetworkMismatch: false, connect: vi.fn(), disconnect: vi.fn(), }); const { useToast } = await import("@chakra-ui/react"); toastMock = vi.fn(); - vi.mocked(useToast).mockReturnValue(toastMock); + vi.mocked(useToast).mockReturnValue(toastMock as never); }); it("invalidates the stellarBalance cache on success, matching useLockAssets", async () => { From 59dbf9ff37ec64cbf25f34309b74feb79e199aa6 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:31 +0100 Subject: [PATCH 16/24] test(#473): use Buffer.alloc for StrKey seeds in feeBumpGuard tests --- src/lib/feeBumpGuard.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/feeBumpGuard.test.ts b/src/lib/feeBumpGuard.test.ts index 0d695ee..d963c30 100644 --- a/src/lib/feeBumpGuard.test.ts +++ b/src/lib/feeBumpGuard.test.ts @@ -29,9 +29,9 @@ function attachDummySignature(tx: { addDecoratedSignature: (sig: xdr.DecoratedSi ); } -const POOL_ID = StrKey.encodeContract(new Uint8Array(32).fill(1)); -const OTHER_CONTRACT_ID = StrKey.encodeContract(new Uint8Array(32).fill(2)); -const SOURCE_PUBKEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(3)); +const POOL_ID = StrKey.encodeContract(Buffer.alloc(32, 1)); +const OTHER_CONTRACT_ID = StrKey.encodeContract(Buffer.alloc(32, 2)); +const SOURCE_PUBKEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 3)); function buildLockAssetsTx(poolId: string) { const account = new Account(SOURCE_PUBKEY, '0'); @@ -65,7 +65,7 @@ describe('assertSponsorableInnerTransaction', () => { const tx = new TransactionBuilder(account, { fee: '100', networkPassphrase: Networks.TESTNET }) .addOperation( Operation.payment({ - destination: StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(4)), + destination: StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 4)), asset: Asset.native(), amount: '1', }), From a71b75d006673649ef0059f511744b01d67faa1b Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:31 +0100 Subject: [PATCH 17/24] test(#473): cast SorobanAuthorizationEntry fixtures in auth tests --- src/lib/soroban.auth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/soroban.auth.test.ts b/src/lib/soroban.auth.test.ts index 9e5f962..3b33b01 100644 --- a/src/lib/soroban.auth.test.ts +++ b/src/lib/soroban.auth.test.ts @@ -49,7 +49,7 @@ describe('validateSimulationAuth', () => { }, ], }), - } as xdr.SorobanAuthorizationEntry; + } as unknown as xdr.SorobanAuthorizationEntry; const simResult = { result: { From fa6e5cf5f16189522d75956d6894c85e77315851 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:31 +0100 Subject: [PATCH 18/24] test(#473): use Buffer.alloc for StrKey seeds in feebump tests --- src/lib/soroban.feebump.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/soroban.feebump.test.ts b/src/lib/soroban.feebump.test.ts index 320e595..7a2a470 100644 --- a/src/lib/soroban.feebump.test.ts +++ b/src/lib/soroban.feebump.test.ts @@ -18,9 +18,9 @@ vi.mock('@/config', () => ({ stellarNetwork: 'TESTNET', })); -const USER_PUBKEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(1)); -const SPONSOR_PUBKEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(2)); -const DEST_PUBKEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(3)); +const USER_PUBKEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 1)); +const SPONSOR_PUBKEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 2)); +const DEST_PUBKEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 3)); describe('buildFeeBumpTransaction', () => { it('wraps an inner transaction in a fee-bump transaction with correct fee and sponsor', () => { From cf850417feedb469acbb790c4baf803903599386 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:32 +0100 Subject: [PATCH 19/24] test(#473): use Buffer.alloc for StrKey seeds in history tests --- src/lib/soroban.history.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/soroban.history.test.ts b/src/lib/soroban.history.test.ts index cbf3eff..187dd70 100644 --- a/src/lib/soroban.history.test.ts +++ b/src/lib/soroban.history.test.ts @@ -2,8 +2,8 @@ import { describe, it, expect, vi } from 'vitest'; import { xdr, nativeToScVal, StrKey, Address } from '@stellar/stellar-sdk'; import { getUserTransactionHistory, getAllEvents } from './soroban'; // Generate valid-format Stellar G-addresses from fixed 32-byte seeds. -const USER_KEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32)); -const OTHER_KEY = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(1)); +const USER_KEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32)); +const OTHER_KEY = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 1)); const POOL_ID = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4'; const TX_HASH_1 = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'; const TX_HASH_2 = 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'; From f0a0f6c8539a46ae7a43bd6b108a91f98344449e Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:32 +0100 Subject: [PATCH 20/24] test(#473): destructure rpcServer from makeService in network-guard tests --- src/lib/soroban.network-guard.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/soroban.network-guard.test.ts b/src/lib/soroban.network-guard.test.ts index d32997f..52c2603 100644 --- a/src/lib/soroban.network-guard.test.ts +++ b/src/lib/soroban.network-guard.test.ts @@ -152,7 +152,7 @@ describe("SorobanService network passphrase guard", () => { }); it("throws FreighterError when wallet network mismatches in unlockAssets", async () => { - const { service } = makeService(); + const { service, rpcServer } = makeService(); rpcServer.simulateTransaction.mockResolvedValue({ result: { auth: [makeAuthEntry("unlock_assets")] }, minResourceFee: "100", @@ -173,7 +173,7 @@ describe("SorobanService network passphrase guard", () => { }); it("throws FreighterError when wallet network mismatches in setBoost", async () => { - const { service } = makeService(); + const { service, rpcServer } = makeService(); rpcServer.simulateTransaction.mockResolvedValue({ result: { auth: [makeAuthEntry("set_boost")] }, minResourceFee: "100", From 630214d3b93dac788c484af520b2dd01c2f79f4a Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:32 +0100 Subject: [PATCH 21/24] test(#473): use Buffer.alloc for StrKey seeds in soroban tests --- src/lib/soroban.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/soroban.test.ts b/src/lib/soroban.test.ts index ef9e098..d3b27f0 100644 --- a/src/lib/soroban.test.ts +++ b/src/lib/soroban.test.ts @@ -34,7 +34,7 @@ type InvokeContractOperation = { describe('buildLockAssetsTransaction', () => { it('builds a lock_assets contract call with a 7-decimal i128 amount', async () => { - const publicKey = StrKey.encodeEd25519PublicKey(new Uint8Array(32).fill(7)); + const publicKey = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 7)); const keypair = Keypair.fromPublicKey(publicKey); expect(keypair.publicKey()).toBe(publicKey); const account = new Account(publicKey, '42'); From 48dd67abbdc39755d3e77b6c99399efcd713d0db Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:32 +0100 Subject: [PATCH 22/24] test(#475): seed factory + simulation account via webServer env Set NEXT_PUBLIC_FACTORY_CONTRACT_ID and NEXT_PUBLIC_SIMULATION_ACCOUNT so getFactoryPools succeeds under the Playwright dev server. --- e2e/playwright.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index fb1223b..5e35d6b 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -34,6 +34,14 @@ export default defineConfig({ 'http://localhost:3000/__mock-leaderboard-api', NEXT_PUBLIC_MIN_LOCK_PERIOD_SECONDS: '604800', NEXT_PUBLIC_E2E: 'true', + // Network-level seeding for farm.spec (issue #475): getFactoryPools / + // getUserPosition short-circuit unless a factory contract and a funded + // simulation account are configured. Both are satisfied by the RPC + // mocks in e2e/farm.spec.ts (POOLS_XDR / POSITION_XDR / ACCOUNT_XDR). + NEXT_PUBLIC_FACTORY_CONTRACT_ID: + 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM', + NEXT_PUBLIC_SIMULATION_ACCOUNT: + 'GA3CD2PYXOQCXW7ZVQW3MOA3JFZCE4F4IG2FD66I55TQASPCNKYYEFRN', }, }, }); From 7cddb267b83310b7deb130841c7bbb72897cc631 Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 17:50:32 +0100 Subject: [PATCH 23/24] test(#475): seed farm e2e at the network layer, drop __queryClient Mock soroban-testnet RPC (get_pools/get_user_position XDR, sendTransaction flips position state) instead of injecting into window.__queryClient; stateful lock/unlock flows with fixed clock for countdown tests. --- e2e/farm.spec.ts | 163 +++++++++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 91 deletions(-) diff --git a/e2e/farm.spec.ts b/e2e/farm.spec.ts index 35462d6..97a3dc1 100644 --- a/e2e/farm.spec.ts +++ b/e2e/farm.spec.ts @@ -1,12 +1,19 @@ import { type Page } from '@playwright/test'; import { Networks, TransactionBuilder } from '@stellar/stellar-sdk'; -import { test, expect, TEST_PUBLIC_KEY, TEST_ADDRESS_DISPLAY } from './mocks/freighter'; +import { test, expect, TEST_ADDRESS_DISPLAY } from './mocks/freighter'; // Pre-computed XDR constants (generated with @stellar/stellar-sdk) // Pools ScVal XDR: scvVec([scvMap({ id: 'pool-xlm', contract_address: '...', asset_code: 'XLM', ... })]) const POOLS_XDR = 'AAAAEAAAAAEAAAABAAAAEQAAAAEAAAAKAAAADwAAAAJpZAAAAAAADgAAAAhwb29sLXhsbQAAAA8AAAAQY29udHJhY3RfYWRkcmVzcwAAAA4AAAA4Q0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQyS00AAAAPAAAACmFzc2V0X2NvZGUAAAAAAA4AAAADWExNAAAAAA8AAAAJaXNfbmF0aXZlAAAAAAAAAAAAAAEAAAAPAAAACmRhaWx5X3JhdGUAAAAAAAoAAAAAAAAAAAAAAAAAAYagAAAADwAAAA9taW5fbG9ja19wZXJpb2QAAAAABQAAAAAACTqAAAAADwAAAAx0b3RhbF9sb2NrZWQAAAAKAAAAAAAAAAAAAAAXSHboAAAAAA8AAAALdG90YWxfdXNlcnMAAAAAAwAAAAUAAAAPAAAACWlzX2FjdGl2ZQAAAAAAAAAAAAABAAAADwAAAApjcmVhdGVkX2F0AAAAAAAFAAAAAAAAAAA='; +// User position ScVal XDRs — locked (10 XLM at FIXED_NOW_MS-60s) and empty. +// Generated with nativeToScVal({ amount, locked_at, credits, is_locked, unlockable_at }). +const POSITION_XDR = + 'AAAAEQAAAAEAAAAFAAAADgAAAAZhbW91bnQAAAAAAAUAAAAABfXhAAAAAA4AAAAHY3JlZGl0cwAAAAAFAAAAAAAAAAAAAAAOAAAACWlzX2xvY2tlZAAAAAAAAAAAAAABAAAADgAAAAlsb2NrZWRfYXQAAAAAAAAFAAAAAGhO4UQAAAAOAAAADXVubG9ja2FibGVfYXQAAAAAAAAFAAAAAGhYG8Q='; +const EMPTY_POSITION_XDR = + 'AAAAEQAAAAEAAAAFAAAADgAAAAZhbW91bnQAAAAAAAUAAAAAAAAAAAAAAA4AAAAHY3JlZGl0cwAAAAAFAAAAAAAAAAAAAAAOAAAACWlzX2xvY2tlZAAAAAAAAAAAAAAAAAAADgAAAAlsb2NrZWRfYXQAAAAAAAAFAAAAAAAAAAAAAAAOAAAADXVubG9ja2FibGVfYXQAAAAAAAAFAAAAAAAAAAA='; + // Account LedgerEntry XDR for getLedgerEntries mock response const ACCOUNT_XDR = 'AAAAAAAAAAA2Ien4u6Ar2/msLbY4G0lyInC8QbRR+8jvZwBJ4mqxggAAABdIdugAAAAAAEmWAtIAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA'; @@ -27,6 +34,8 @@ const SUCCESS_META_XDR = 'AAAAAAAAAAA='; const FIXED_NOW_MS = 1_750_000_000_000; const CONNECT_WALLET_BUTTON_NAME = /connect (freighter|wallet)/i; +type PositionState = 'empty' | 'locked'; + function getSimulatedFunctionName(transactionXdr?: string): string | null { if (!transactionXdr) return null; @@ -49,9 +58,18 @@ function getSimulatedFunctionName(transactionXdr?: string): string | null { } // ── RPC fetch mock ────────────────────────────────────────────────────────── - -async function mockSorobanRpc(page: Page): Promise { +// +// Seeds pool + position data at the network layer (issue #475) so farm.spec +// no longer depends on `window.__queryClient`. get_pools / get_user_position +// return pre-computed XDRs; sendTransaction flips the mock position state so +// the post-mutation QueryClient invalidation refetches the updated value. + +async function mockSorobanRpc( + page: Page, + options: { initialPosition?: PositionState } = {}, +): Promise { let submittedTransactionXdr = ''; + let positionState: PositionState = options.initialPosition ?? 'empty'; await page.route('**/horizon-testnet.stellar.org/accounts/**', async (route) => { await route.fulfill({ @@ -72,6 +90,28 @@ async function mockSorobanRpc(page: Page): Promise { let result: unknown; switch (body.method) { + case 'getHealth': + result = { status: 'OK', latestLedger: 100 }; + break; + + case 'getLatestLedger': + // Intentionally missing headerXdr/metadataXdr: parseRawLatestLedger + // throws, getCreditVelocity / useSorobanEvents catch → "0" / abort + // polling. Avoids a real-network continue() while staying hermetic. + result = { sequence: 100, id: 'b'.repeat(64) }; + break; + + case 'getEvents': + result = { + latestLedger: 100, + oldestLedger: 1, + latestLedgerCloseTime: '0', + oldestLedgerCloseTime: '0', + cursor: '', + events: [], + }; + break; + case 'getLedgerEntries': result = { entries: [ @@ -85,7 +125,7 @@ async function mockSorobanRpc(page: Page): Promise { }; break; - case 'simulateTransaction': + case 'simulateTransaction': { const functionName = getSimulatedFunctionName(body.params?.transaction); const auth = functionName === 'lock_assets' @@ -93,22 +133,33 @@ async function mockSorobanRpc(page: Page): Promise { : functionName === 'unlock_assets' ? [UNLOCK_ASSETS_AUTH_XDR] : []; - // Works for get_pools, get_user_position, and unlock_assets alike. - // get_user_position parsing ignores a Vec retval and returns null (no position), - // so positions come exclusively from the QueryClient seed in tests. + // Function-specific retval: get_pools → POOLS_XDR, + // get_user_position → POSITION_XDR / EMPTY_POSITION_XDR, + // lock/unlock (and anything else) → scvVoid. + let retvalXdr = 'AAAAAQ=='; + if (functionName === 'get_pools') retvalXdr = POOLS_XDR; + else if (functionName === 'get_user_position') { + retvalXdr = positionState === 'locked' ? POSITION_XDR : EMPTY_POSITION_XDR; + } result = { id: String(body.id), transactionData: SOROBAN_DATA_XDR, - results: [{ xdr: 'AAAAAQ==', auth }], // scvVoid + results: [{ xdr: retvalXdr, auth }], minResourceFee: '100', events: [], cost: { cpuInsns: '1000', memBytes: '1000' }, latestLedger: 100, }; break; + } - case 'sendTransaction': + case 'sendTransaction': { submittedTransactionXdr = body.params?.transaction ?? ''; + const sentFn = getSimulatedFunctionName(submittedTransactionXdr); + // Flip mock position state BEFORE getTransaction SUCCESS so the + // mutation onSuccess → invalidateQueries refetch sees the new value. + if (sentFn === 'lock_assets') positionState = 'locked'; + else if (sentFn === 'unlock_assets') positionState = 'empty'; result = { hash: 'a'.repeat(64), status: 'PENDING', @@ -116,6 +167,7 @@ async function mockSorobanRpc(page: Page): Promise { latestLedgerCloseTime: '0', }; break; + } case 'getTransaction': result = { @@ -145,52 +197,6 @@ async function mockSorobanRpc(page: Page): Promise { }); } -// ── QueryClient seed helpers ──────────────────────────────────────────────── - -const MOCK_POOL = { - id: 'pool-xlm', - contractAddress: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM', - asset: { code: 'XLM', isNative: true }, - dailyRate: '0.0000001', - minLockPeriod: 604800, // 7 days in seconds - totalLocked: '10.0000000', - totalUsers: 5, - isActive: true, - createdAt: 0, -}; - -function makeMockPosition(lockedAtMs: number) { - return { - user: TEST_PUBLIC_KEY, - poolId: 'pool-xlm', - amount: '10.0000000', - lockedAt: lockedAtMs, - credits: '0', - isLocked: true, - unlockableAt: lockedAtMs + 604_800_000, - }; -} - -async function seedPools(page: Page): Promise { - await page.evaluate((pool) => { - const qc = (window as any).__queryClient; - if (qc) qc.setQueryData(['pools'], [pool]); - }, MOCK_POOL); -} - -async function seedPosition(page: Page, lockedAtMs: number): Promise { - const pos = makeMockPosition(lockedAtMs); - await page.evaluate( - ({ pool, position, pubKey }) => { - const qc = (window as any).__queryClient; - if (!qc) return; - qc.setQueryData(['pools'], [pool]); - qc.setQueryData(['userPosition', 'all', pubKey], [{ pool, position }]); - }, - { pool: MOCK_POOL, position: pos, pubKey: TEST_PUBLIC_KEY }, - ); -} - async function connectWallet(page: Page): Promise { await page.getByRole('button', { name: CONNECT_WALLET_BUTTON_NAME }).first().click(); await page.waitForFunction( @@ -220,15 +226,14 @@ test.describe('Farm E2E', () => { }); test('2 · deposit — modal accepts 10 XLM and submits', async ({ page }) => { - await mockSorobanRpc(page); + // Start empty; sendTransaction of lock_assets flips the mock position → + // locked so the post-success QueryClient invalidation refetches POSITION_XDR. + await mockSorobanRpc(page, { initialPosition: 'empty' }); await page.goto('/farm'); await page.waitForLoadState('networkidle'); await connectWallet(page); - // Seed pool data so the Farm pools section renders a row - await seedPools(page); - - // Wait for pool row with Deposit button + // Wait for pool row with Deposit button (seeded via get_pools XDR mock) const depositBtn = page.getByRole('button', { name: /^\+ deposit$/i }).first(); await expect(depositBtn).toBeVisible({ timeout: 8_000 }); await depositBtn.click(); @@ -248,16 +253,15 @@ test.describe('Farm E2E', () => { // Chakra renders a spinner; just ensure the button still exists }); - // After the 1.5 s stub delay, seed a position so "My earnings" shows 10 XLM + // Give the lock flow time to submit + flip mock position + invalidate. await page.waitForTimeout(1_800); - await seedPosition(page, FIXED_NOW_MS - 60_000); // My earnings row now shows the staked amount await expect(page.getByText('10.0000000').last()).toBeVisible({ timeout: 5_000 }); }); test('3 · countdown visible — Unlock button is disabled before lock period', async ({ page }) => { - await mockSorobanRpc(page); + await mockSorobanRpc(page, { initialPosition: 'locked' }); // Set the clock to a fixed point so the countdown is always non-zero await page.clock.setFixedTime(new Date(FIXED_NOW_MS)); @@ -266,9 +270,6 @@ test.describe('Farm E2E', () => { await page.waitForLoadState('networkidle'); await connectWallet(page); - // Seed a position locked 1 minute ago (7-day lock period → ~7 days remaining) - await seedPosition(page, FIXED_NOW_MS - 60_000); - // Countdown label is visible and contains time-remaining text (e.g. "6d …"). // Both the pool row (FarmPoolRow) and the earnings row (EarningRow) render // this position's countdown independently, so two elements legitimately @@ -283,14 +284,13 @@ test.describe('Farm E2E', () => { }); test('4 · unlock available — fast-forward 8 days enables Unlock', async ({ page }) => { - await mockSorobanRpc(page); + await mockSorobanRpc(page, { initialPosition: 'locked' }); await page.clock.setFixedTime(new Date(FIXED_NOW_MS)); await page.goto('/farm'); await page.waitForLoadState('networkidle'); await connectWallet(page); - await seedPosition(page, FIXED_NOW_MS - 60_000); // Fast-forward 8 days (past the 7-day lock period) const eightDaysMs = 8 * 24 * 60 * 60 * 1_000; @@ -301,7 +301,6 @@ test.describe('Farm E2E', () => { await page.goto('/farm'); await page.waitForLoadState('networkidle'); await connectWallet(page); - await seedPosition(page, FIXED_NOW_MS - 60_000); // Unlock button should now be enabled const unlockBtn = page.getByRole('button', { name: /^unlock$/i }).first(); @@ -313,7 +312,9 @@ test.describe('Farm E2E', () => { }); test('5 · unlock — fill modal, sign, submit; stake drops to 0', async ({ page }) => { - await mockSorobanRpc(page); + // Start locked; sendTransaction of unlock_assets flips the mock position → + // empty so the post-success QueryClient invalidation refetches EMPTY_POSITION_XDR. + await mockSorobanRpc(page, { initialPosition: 'locked' }); // Start with clock 8 days in the future so Unlock is immediately available const eightDaysMs = 8 * 24 * 60 * 60 * 1_000; @@ -322,7 +323,6 @@ test.describe('Farm E2E', () => { await page.goto('/farm'); await page.waitForLoadState('networkidle'); await connectWallet(page); - await seedPosition(page, FIXED_NOW_MS - 60_000); // Unlock button should be enabled const unlockBtn = page.getByRole('button', { name: /^unlock$/i }).first(); @@ -347,26 +347,7 @@ test.describe('Farm E2E', () => { page.getByText(/unlock (confirmed|submitted)/i).first(), ).toBeVisible({ timeout: 15_000 }); - // After success, update cache to show 0 stake - await page.evaluate( - ({ pool, pubKey }) => { - const qc = (window as any).__queryClient; - if (!qc) return; - const emptyPos = { - user: pubKey, - poolId: 'pool-xlm', - amount: '0.0000000', - lockedAt: 0, - credits: '0', - isLocked: false, - unlockableAt: 0, - }; - qc.setQueryData(['userPosition', 'all', pubKey], [{ pool, position: emptyPos }]); - }, - { pool: MOCK_POOL, pubKey: TEST_PUBLIC_KEY }, - ); - - // "My earnings" now shows 0.0000000 + // "My earnings" now shows 0.0000000 after the position flip + refetch await expect(page.getByText('0.0000000').last()).toBeVisible({ timeout: 5_000 }); }); }); From d7cae34f04e3e9866080332868af5acc4cb833fc Mon Sep 17 00:00:00 2001 From: TheCodingChef-eth Date: Thu, 24 Sep 2026 19:06:46 +0100 Subject: [PATCH 24/24] fix(#475): store locked_at in epoch ms in POSITION_XDR fixture FarmPosition.lockedAt is ms (unlockAvailableAt = lockedAt + lockPeriodSeconds*1000); seconds values rendered 'Unlocked' instantly. --- e2e/farm.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/farm.spec.ts b/e2e/farm.spec.ts index 97a3dc1..0fbdee6 100644 --- a/e2e/farm.spec.ts +++ b/e2e/farm.spec.ts @@ -8,9 +8,10 @@ const POOLS_XDR = 'AAAAEAAAAAEAAAABAAAAEQAAAAEAAAAKAAAADwAAAAJpZAAAAAAADgAAAAhwb29sLXhsbQAAAA8AAAAQY29udHJhY3RfYWRkcmVzcwAAAA4AAAA4Q0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQyS00AAAAPAAAACmFzc2V0X2NvZGUAAAAAAA4AAAADWExNAAAAAA8AAAAJaXNfbmF0aXZlAAAAAAAAAAAAAAEAAAAPAAAACmRhaWx5X3JhdGUAAAAAAAoAAAAAAAAAAAAAAAAAAYagAAAADwAAAA9taW5fbG9ja19wZXJpb2QAAAAABQAAAAAACTqAAAAADwAAAAx0b3RhbF9sb2NrZWQAAAAKAAAAAAAAAAAAAAAXSHboAAAAAA8AAAALdG90YWxfdXNlcnMAAAAAAwAAAAUAAAAPAAAACWlzX2FjdGl2ZQAAAAAAAAAAAAABAAAADwAAAApjcmVhdGVkX2F0AAAAAAAFAAAAAAAAAAA='; // User position ScVal XDRs — locked (10 XLM at FIXED_NOW_MS-60s) and empty. -// Generated with nativeToScVal({ amount, locked_at, credits, is_locked, unlockable_at }). +// locked_at / unlockable_at are epoch MILLISECONDS (FarmPosition contract: +// unlockAvailableAt = lockedAt + lockPeriodSeconds * 1000). const POSITION_XDR = - 'AAAAEQAAAAEAAAAFAAAADgAAAAZhbW91bnQAAAAAAAUAAAAABfXhAAAAAA4AAAAHY3JlZGl0cwAAAAAFAAAAAAAAAAAAAAAOAAAACWlzX2xvY2tlZAAAAAAAAAAAAAABAAAADgAAAAlsb2NrZWRfYXQAAAAAAAAFAAAAAGhO4UQAAAAOAAAADXVubG9ja2FibGVfYXQAAAAAAAAFAAAAAGhYG8Q='; + 'AAAAEQAAAAEAAAAFAAAADgAAAAZhbW91bnQAAAAAAAUAAAAABfXhAAAAAA4AAAAHY3JlZGl0cwAAAAAFAAAAAAAAAAAAAAAOAAAACWlzX2xvY2tlZAAAAAAAAAAAAAABAAAADgAAAAlsb2NrZWRfYXQAAAAAAAAFAAABl3Qf8aAAAAAOAAAADXVubG9ja2FibGVfYXQAAAAAAAAFAAABl5gsdaA='; const EMPTY_POSITION_XDR = 'AAAAEQAAAAEAAAAFAAAADgAAAAZhbW91bnQAAAAAAAUAAAAAAAAAAAAAAA4AAAAHY3JlZGl0cwAAAAAFAAAAAAAAAAAAAAAOAAAACWlzX2xvY2tlZAAAAAAAAAAAAAAAAAAADgAAAAlsb2NrZWRfYXQAAAAAAAAFAAAAAAAAAAAAAAAOAAAADXVubG9ja2FibGVfYXQAAAAAAAAFAAAAAAAAAAA=';