From ad493ddf509116afe6d48a093b196c07288cae38 Mon Sep 17 00:00:00 2001 From: Jeesun Kim Date: Tue, 7 Apr 2026 15:56:56 -0700 Subject: [PATCH 1/5] fix simulate step's authMode --- .../build/components/SimulateStepContent.tsx | 11 ++++++----- src/components/FormElements/AuthModePicker.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx b/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx index 8c27e1577..aaa027088 100644 --- a/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx +++ b/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx @@ -68,6 +68,7 @@ export const SimulateStepContent = ({ build, simulate, setSimulateInstructionLeeway, + setSimulateAuthMode, setSimulationResult, setSimulationReadOnly, setAuthEntriesXdr, @@ -80,7 +81,6 @@ export const SimulateStepContent = ({ const [assemblyWarning, setAssemblyWarning] = useState(""); const [isResourcesExpanded, setIsResourcesExpanded] = useState(false); const [xdrFormat, setXdrFormat] = useState("json"); - const [authMode, selectAuthMode] = useState(""); const [simulationDisplay, setSimulationDisplayResult] = useState(""); const [validUntilLedgerSeq, setValidUntilLedgerSeq] = useState(0); @@ -339,15 +339,16 @@ export const SimulateStepContent = ({ {isInvokeContract ? ( { resetSimulateTx(); - selectAuthMode(mode); + setSimulateAuthMode(mode as AuthModeType); }} note={ <> - This simulation shows which signatures are required. It - doesn’t validate signatures or calculate final fees.{" "} + "Record" is recommended for simulation. + "Record" discovers which authorization entries are + required. Learn more diff --git a/src/components/FormElements/AuthModePicker.tsx b/src/components/FormElements/AuthModePicker.tsx index 443059793..5c8d1a2fa 100644 --- a/src/components/FormElements/AuthModePicker.tsx +++ b/src/components/FormElements/AuthModePicker.tsx @@ -5,7 +5,7 @@ import { AuthModeType } from "@/types/types"; const AUTH_MODE_OPTIONS: { id: string; label: string }[] = [ { id: "record", label: "Record" }, { id: "enforce", label: "Enforce" }, - { id: "record-allow-nonroot", label: "Record (allow non-root)" }, + { id: "record_allow_nonroot", label: "Record (allow non-root)" }, ]; /** From 9cfff975ac0115850ed03aedef56983f2755cf45 Mon Sep 17 00:00:00 2001 From: Jeesun Kim Date: Tue, 7 Apr 2026 16:24:47 -0700 Subject: [PATCH 2/5] address copilot's suggestions --- .../build/components/ValidateStepContent.tsx | 4 ++++ src/app/(sidebar)/transaction/build/page.tsx | 9 +++++---- src/components/NewValidationResponseCard/index.tsx | 10 ++++++++-- src/components/SorobanAuthSigning/AuthEntryItem.tsx | 2 +- src/components/SorobanAuthSigning/index.tsx | 6 ++++-- src/components/Tabs/index.tsx | 3 +-- src/components/TransactionStepper/index.tsx | 1 + src/helpers/sorobanUtils.ts | 2 +- src/store/createTransactionFlowStore.ts | 4 ++-- 9 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/app/(sidebar)/transaction/build/components/ValidateStepContent.tsx b/src/app/(sidebar)/transaction/build/components/ValidateStepContent.tsx index a41086e92..f7ca6bd17 100644 --- a/src/app/(sidebar)/transaction/build/components/ValidateStepContent.tsx +++ b/src/app/(sidebar)/transaction/build/components/ValidateStepContent.tsx @@ -72,6 +72,10 @@ export const ValidateStepContent = () => { const onValidate = async () => { if (!network.rpcUrl || !signedXdr) return; + // Clear previous validated result so a failed re-validation can't leave + // stale validatedXdr enabling the next step. + setValidatedXdr(undefined); + try { const response = await simulateTx({ rpcUrl: network.rpcUrl, diff --git a/src/app/(sidebar)/transaction/build/page.tsx b/src/app/(sidebar)/transaction/build/page.tsx index 4cd955e40..40a54686f 100644 --- a/src/app/(sidebar)/transaction/build/page.tsx +++ b/src/app/(sidebar)/transaction/build/page.tsx @@ -73,10 +73,11 @@ export default function BuildTransaction() { return !currentXdr || !(build.isValid.params && build.isValid.operations); } if (activeStep === "simulate") { - // Simulation must be complete. If auth entries exist, they must be signed - // (assembledXdr is set after auth signing + assembly, or after auto-assembly - // when no auth entries are present). - return !simulate.simulationResultJson; + // Simulation must be complete and assembledXdr must exist (set after auth + // signing + assembly, or after auto-assembly when no auth entries are + // present) so the sign step receives a transaction with + // simulation-derived resources/fees. + return !simulate.simulationResultJson || !simulate.assembledXdr; } if (activeStep === "sign") { return !sign.signedXdr; diff --git a/src/components/NewValidationResponseCard/index.tsx b/src/components/NewValidationResponseCard/index.tsx index 9cf2b16a3..7768cfb8b 100644 --- a/src/components/NewValidationResponseCard/index.tsx +++ b/src/components/NewValidationResponseCard/index.tsx @@ -42,7 +42,7 @@ export const NewValidationResponseCard = ({ {/* @TODO to update the SDS design system */} } > @@ -83,7 +83,13 @@ export const NewValidationResponseCard = ({ gap="sm" direction="row" align="center" - justify={footerLeftEl && footerRightEl ? "space-between" : footerRightEl ? "end" : "left"} + justify={ + footerLeftEl && footerRightEl + ? "space-between" + : footerRightEl + ? "end" + : "left" + } addlClassName="ValidationResponseCard__footer" wrap="wrap" > diff --git a/src/components/SorobanAuthSigning/AuthEntryItem.tsx b/src/components/SorobanAuthSigning/AuthEntryItem.tsx index d07edcb32..1510c0263 100644 --- a/src/components/SorobanAuthSigning/AuthEntryItem.tsx +++ b/src/components/SorobanAuthSigning/AuthEntryItem.tsx @@ -114,7 +114,7 @@ export const AuthEntryItem = ({ } catch (e) { const msg = e instanceof Error ? e.message : String(e); trackEvent(TrackingEvent.SOROBAN_AUTH_SIGN_ENTRY_ERROR, { - entryIndex: index, + entryIndex: index + 1, }); return { successMessage: "", errorMessage: msg }; } diff --git a/src/components/SorobanAuthSigning/index.tsx b/src/components/SorobanAuthSigning/index.tsx index 02436a3c9..2767d5c06 100644 --- a/src/components/SorobanAuthSigning/index.tsx +++ b/src/components/SorobanAuthSigning/index.tsx @@ -102,9 +102,11 @@ export const SorobanAuthSigningCard = ({ const signedCount = signedAuthEntriesXdr.filter(Boolean).length; const allSigned = signedCount === entryCount && entryCount > 0; - // Trigger onAllEntriesSigned when individual signing completes all entries + // Trigger onAllEntriesSigned when individual signing completes all entries. + // Only needed in individual mode — "Sign all" calls onAllEntriesSigned + // directly in handleCustomSignAll. useEffect(() => { - if (allSigned) { + if (allSigned && signMode === "individual") { onAllEntriesSigned(signedAuthEntriesXdr); } // Only trigger when allSigned changes to true diff --git a/src/components/Tabs/index.tsx b/src/components/Tabs/index.tsx index e90709130..34dad9517 100644 --- a/src/components/Tabs/index.tsx +++ b/src/components/Tabs/index.tsx @@ -29,7 +29,7 @@ export const Tabs = ({ const isActive = t.href ? pathname === t.href : t.id === activeTabId; const className = `Tab ${addlClassName ?? ""}`; - if (t.href) { + if (t.href && !t.isDisabled) { return ( {t.label} diff --git a/src/components/TransactionStepper/index.tsx b/src/components/TransactionStepper/index.tsx index 637e495eb..cf69c5b95 100644 --- a/src/components/TransactionStepper/index.tsx +++ b/src/components/TransactionStepper/index.tsx @@ -73,6 +73,7 @@ export const TransactionStepper = ({ return (
v?.type && v.value !== undefined; /** * Determines if the simulation result indicates a read-only transaction - * (no auth entries and no write footprint). + * (no write footprint). */ export const checkIsReadOnly = (responseData: Record): boolean => { try { diff --git a/src/store/createTransactionFlowStore.ts b/src/store/createTransactionFlowStore.ts index a521074e6..fbed7a576 100644 --- a/src/store/createTransactionFlowStore.ts +++ b/src/store/createTransactionFlowStore.ts @@ -171,8 +171,8 @@ interface TransactionFlowActions { /** Set the auth mode for the validate step. */ setValidateAuthMode: (mode: AuthModeType) => void; - /** Store the validated transaction XDR. */ - setValidatedXdr: (xdr: string) => void; + /** Store (or clear) the validated transaction XDR. */ + setValidatedXdr: (xdr: string | undefined) => void; /** Update fee bump params. */ setFeeBumpParams: (params: FeeBumpParamsObj) => void; From a6b5effc9522b526dfdfdc98dee363fd2b119f8c Mon Sep 17 00:00:00 2001 From: Jeesun Kim Date: Tue, 7 Apr 2026 20:04:03 -0700 Subject: [PATCH 3/5] fix CI failing --- tests/e2e/sorobanAuthEntry.test.ts | 8 +++++++- tests/e2e/validateStepContent.test.ts | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/e2e/sorobanAuthEntry.test.ts b/tests/e2e/sorobanAuthEntry.test.ts index cc8eda411..a36a185e1 100644 --- a/tests/e2e/sorobanAuthEntry.test.ts +++ b/tests/e2e/sorobanAuthEntry.test.ts @@ -61,11 +61,17 @@ const buildStoreState = () => ({ version: 0, }); +// Testnet network params for the URL querystring so the main store +// initializes with rpcUrl immediately (avoids waiting for NetworkSelector's +// useEffect, which races on slower CI machines). +const TESTNET_QUERY = + "$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;;"; + const seedSessionStorageAndNavigate = async (page: Page) => { const storeState = buildStoreState(); // Navigate first so sessionStorage is on the correct origin - await page.goto(`${baseURL}/transaction/build`); + await page.goto(`${baseURL}/transaction/build?${TESTNET_QUERY}`); await page.evaluate( (stateJson) => { diff --git a/tests/e2e/validateStepContent.test.ts b/tests/e2e/validateStepContent.test.ts index 138836586..529c41b37 100644 --- a/tests/e2e/validateStepContent.test.ts +++ b/tests/e2e/validateStepContent.test.ts @@ -54,11 +54,17 @@ test.describe("Validate Step in Build Flow", () => { version: 0, }); + // Testnet network params for the URL querystring so the main store + // initializes with rpcUrl immediately (avoids waiting for NetworkSelector's + // useEffect, which races on slower CI machines). + const TESTNET_QUERY = + "$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;;"; + const seedSessionStorageAndNavigate = async (page: Page) => { const storeState = buildStoreState(MOCK_SIGNED_XDR, MOCK_AUTH_ENTRY_XDR); // Navigate to the build page first so sessionStorage is on the right origin - await page.goto(`${baseURL}/transaction/build`); + await page.goto(`${baseURL}/transaction/build?${TESTNET_QUERY}`); // Seed sessionStorage with flow store state at the validate step await page.evaluate((stateJson) => { From 4376b1026a8ff4e5557892b50719f2bfcd8837fb Mon Sep 17 00:00:00 2001 From: Jeesun Kim Date: Tue, 7 Apr 2026 20:38:51 -0700 Subject: [PATCH 4/5] Update tests to reflect the latest role=button and network change --- tests/e2e/sorobanAuthEntry.test.ts | 31 ++++++++++++++++----------- tests/e2e/validateStepContent.test.ts | 31 ++++++++++++++++----------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/tests/e2e/sorobanAuthEntry.test.ts b/tests/e2e/sorobanAuthEntry.test.ts index a36a185e1..35a63650e 100644 --- a/tests/e2e/sorobanAuthEntry.test.ts +++ b/tests/e2e/sorobanAuthEntry.test.ts @@ -61,21 +61,28 @@ const buildStoreState = () => ({ version: 0, }); -// Testnet network params for the URL querystring so the main store -// initializes with rpcUrl immediately (avoids waiting for NetworkSelector's -// useEffect, which races on slower CI machines). -const TESTNET_QUERY = - "$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;;"; - const seedSessionStorageAndNavigate = async (page: Page) => { const storeState = buildStoreState(); // Navigate first so sessionStorage is on the correct origin - await page.goto(`${baseURL}/transaction/build?${TESTNET_QUERY}`); + await page.goto(`${baseURL}/transaction/build`); await page.evaluate( (stateJson) => { sessionStorage.setItem("stellar_lab_tx_flow_build", stateJson); + + // Seed localStorage with testnet so the NetworkSelector doesn't open + // a "Load Network" modal on reload (CI has no saved network). + localStorage.setItem( + "stellar_lab_network", + JSON.stringify({ + id: "testnet", + label: "Testnet", + horizonUrl: "https://horizon-testnet.stellar.org", + rpcUrl: "https://soroban-testnet.stellar.org", + passphrase: "Test SDF Network ; September 2015", + }), + ); }, JSON.stringify(storeState), ); @@ -164,7 +171,7 @@ test.describe("Soroban Auth Entry Detection and Signing", () => { await mockSimulateWithAuthEntries(page); await seedSessionStorageAndNavigate(page); - const simulateButton = page.getByRole("button", { name: "Simulate" }); + const simulateButton = page.getByRole("button", { name: "Simulate", exact: true }); await simulateButton.click(); // Wait for simulation result @@ -190,7 +197,7 @@ test.describe("Soroban Auth Entry Detection and Signing", () => { await mockSimulateWithoutAuthEntries(page); await seedSessionStorageAndNavigate(page); - const simulateButton = page.getByRole("button", { name: "Simulate" }); + const simulateButton = page.getByRole("button", { name: "Simulate", exact: true }); await simulateButton.click(); // Wait for simulation result @@ -214,7 +221,7 @@ test.describe("Soroban Auth Entry Detection and Signing", () => { await mockSimulateWithAuthEntries(page); await seedSessionStorageAndNavigate(page); - const simulateButton = page.getByRole("button", { name: "Simulate" }); + const simulateButton = page.getByRole("button", { name: "Simulate", exact: true }); await simulateButton.click(); await expect( @@ -241,7 +248,7 @@ test.describe("Soroban Auth Entry Detection and Signing", () => { await mockSimulateWithAuthEntries(page); await seedSessionStorageAndNavigate(page); - const simulateButton = page.getByRole("button", { name: "Simulate" }); + const simulateButton = page.getByRole("button", { name: "Simulate", exact: true }); await simulateButton.click(); await expect( @@ -264,7 +271,7 @@ test.describe("Soroban Auth Entry Detection and Signing", () => { await mockSimulateWithAuthEntries(page); await seedSessionStorageAndNavigate(page); - const simulateButton = page.getByRole("button", { name: "Simulate" }); + const simulateButton = page.getByRole("button", { name: "Simulate", exact: true }); await simulateButton.click(); await expect( diff --git a/tests/e2e/validateStepContent.test.ts b/tests/e2e/validateStepContent.test.ts index 529c41b37..0a498154e 100644 --- a/tests/e2e/validateStepContent.test.ts +++ b/tests/e2e/validateStepContent.test.ts @@ -54,21 +54,28 @@ test.describe("Validate Step in Build Flow", () => { version: 0, }); - // Testnet network params for the URL querystring so the main store - // initializes with rpcUrl immediately (avoids waiting for NetworkSelector's - // useEffect, which races on slower CI machines). - const TESTNET_QUERY = - "$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;;"; - const seedSessionStorageAndNavigate = async (page: Page) => { const storeState = buildStoreState(MOCK_SIGNED_XDR, MOCK_AUTH_ENTRY_XDR); // Navigate to the build page first so sessionStorage is on the right origin - await page.goto(`${baseURL}/transaction/build?${TESTNET_QUERY}`); + await page.goto(`${baseURL}/transaction/build`); // Seed sessionStorage with flow store state at the validate step await page.evaluate((stateJson) => { sessionStorage.setItem("stellar_lab_tx_flow_build", stateJson); + + // Seed localStorage with testnet so the NetworkSelector doesn't open + // a "Load Network" modal on reload (CI has no saved network). + localStorage.setItem( + "stellar_lab_network", + JSON.stringify({ + id: "testnet", + label: "Testnet", + horizonUrl: "https://horizon-testnet.stellar.org", + rpcUrl: "https://soroban-testnet.stellar.org", + passphrase: "Test SDF Network ; September 2015", + }), + ); }, JSON.stringify(storeState)); // Reload to pick up the seeded sessionStorage @@ -155,7 +162,7 @@ test.describe("Validate Step in Build Flow", () => { }) => { await seedSessionStorageAndNavigate(page); - const validateButton = page.getByRole("button", { name: "Validate" }); + const validateButton = page.getByRole("button", { name: "Validate", exact: true }); await expect(validateButton).toBeEnabled(); }); @@ -165,7 +172,7 @@ test.describe("Validate Step in Build Flow", () => { await mockEnforceSimulationSuccess(page); await seedSessionStorageAndNavigate(page); - const validateButton = page.getByRole("button", { name: "Validate" }); + const validateButton = page.getByRole("button", { name: "Validate", exact: true }); await validateButton.click(); // Wait for success alert @@ -185,7 +192,7 @@ test.describe("Validate Step in Build Flow", () => { await mockEnforceSimulationFailure(page); await seedSessionStorageAndNavigate(page); - const validateButton = page.getByRole("button", { name: "Validate" }); + const validateButton = page.getByRole("button", { name: "Validate", exact: true }); await validateButton.click(); await expect(page.getByText("Validation failed")).toBeVisible(); @@ -197,7 +204,7 @@ test.describe("Validate Step in Build Flow", () => { await mockEnforceSimulationSuccess(page); await seedSessionStorageAndNavigate(page); - const validateButton = page.getByRole("button", { name: "Validate" }); + const validateButton = page.getByRole("button", { name: "Validate", exact: true }); await validateButton.click(); await expect( @@ -240,7 +247,7 @@ test.describe("Validate Step in Build Flow", () => { await mockEnforceSimulationSuccess(page); await seedSessionStorageAndNavigate(page); - const validateButton = page.getByRole("button", { name: "Validate" }); + const validateButton = page.getByRole("button", { name: "Validate", exact: true }); await validateButton.click(); // Wait for success From a5f10ff2e68a817a8c1083781d0edbca9f285758 Mon Sep 17 00:00:00 2001 From: Jeesun Kim Date: Wed, 8 Apr 2026 09:55:12 -0700 Subject: [PATCH 5/5] Apply suggestions --- .../build/components/SimulateStepContent.tsx | 5 +- .../FormElements/AuthModePicker.tsx | 1 + src/constants/networkLimits.ts | 120 +++++++++--------- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx b/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx index aaa027088..017a77f5e 100644 --- a/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx +++ b/src/app/(sidebar)/transaction/build/components/SimulateStepContent.tsx @@ -346,9 +346,8 @@ export const SimulateStepContent = ({ }} note={ <> - "Record" is recommended for simulation. - "Record" discovers which authorization entries are - required. + “Record” is recommended for simulation. “Record” discovers + which authorization entries are required. Learn more diff --git a/src/components/FormElements/AuthModePicker.tsx b/src/components/FormElements/AuthModePicker.tsx index 5c8d1a2fa..efe12f26d 100644 --- a/src/components/FormElements/AuthModePicker.tsx +++ b/src/components/FormElements/AuthModePicker.tsx @@ -5,6 +5,7 @@ import { AuthModeType } from "@/types/types"; const AUTH_MODE_OPTIONS: { id: string; label: string }[] = [ { id: "record", label: "Record" }, { id: "enforce", label: "Enforce" }, + // 'record_allow_nonroot' is what simulation expects for non-root auth entries { id: "record_allow_nonroot", label: "Record (allow non-root)" }, ]; diff --git a/src/constants/networkLimits.ts b/src/constants/networkLimits.ts index 2d5637452..9d8ac963e 100644 --- a/src/constants/networkLimits.ts +++ b/src/constants/networkLimits.ts @@ -83,36 +83,36 @@ export const MAINNET_LIMITS: NetworkLimits = { "persistent_rent_rate_denominator": "1215", "temp_rent_rate_denominator": "2430", "live_soroban_state_size_window": [ - "938265499", - "939339463", - "939620039", - "939989975", - "940203715", - "938755875", - "939712715", - "939907007", - "940579887", - "940945059", - "941822275", - "942656827", - "942622639", - "943022471", - "943232103", - "943916923", - "944769331", - "940348335", - "935887571", - "931281103", - "931780859", - "932683607", - "932729355", - "933125051", - "933226479", - "930603199", - "927038995", - "921998927", - "917665309", - "912971773" + "872934569", + "868347485", + "854299397", + "839916137", + "824718057", + "809814601", + "796699385", + "794302449", + "795206113", + "795306429", + "795657637", + "795985473", + "796327773", + "797330495", + "797845195", + "798436144", + "798713468", + "799009840", + "799799404", + "796528556", + "792463924", + "788384256", + "784365864", + "780847876", + "777323142", + "773260203", + "768864762", + "763580993", + "756757561", + "750240309" ], "state_target_size_bytes": "3000000000", "rent_fee_1kb_state_size_low": "-17000", @@ -151,36 +151,36 @@ export const TESTNET_LIMITS: NetworkLimits = { "persistent_rent_rate_denominator": "1215", "temp_rent_rate_denominator": "2430", "live_soroban_state_size_window": [ - "1041423372", - "1042859116", - "1043438784", - "1044595372", - "1045800392", - "1047826818", - "1048009338", - "1048343892", - "1048835785", - "1048853697", - "1049067283", - "1049128832", - "1050233921", - "1050248337", - "1050391653", - "1050402641", - "1051245103", - "1051272807", - "1051278915", - "1050723183", - "1048825716", - "1049037025", - "1049776357", - "1049796497", - "1050190589", - "1050553803", - "1050557903", - "1050738310", - "1050758258", - "1050945383" + "1053285232", + "1054188318", + "1055474321", + "1055491993", + "1055493361", + "1055726257", + "1055761145", + "1055787709", + "1055800553", + "1056643488", + "1056653740", + "1056672656", + "1056680992", + "1057832885", + "1058067074", + "1058087022", + "1058098858", + "1058039466", + "1058266089", + "1058307509", + "1058035781", + "1056127568", + "1056142308", + "1056153672", + "1056173028", + "1056183752", + "1056194404", + "1056440554", + "1056450958", + "1056826870" ], "state_target_size_bytes": "3000000000", "rent_fee_1kb_state_size_low": "-17000",