You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
useMainnetConfirmation()'s requestDeployment(params, onConfirm) is the only code
path that opens MainnetConfirmationModal: it checks isMainnet() and shows the modal,
or calls onConfirm() straight through on testnet. A full-tree search shows requestDeployment is referenced only inside useMainnetConfirmation.ts itself, and MainnetConfirmationModal is referenced only in its own file, a barrel re-export
(components/UI/index.ts), and its Storybook story — never from any form.
CreateToken.tsx → TokenForm.tsx (the actual create-token submit path) calls onSubmit(formData) directly, with no mainnet gate of any kind. MintForm.tsx, BurnForm.tsx, SetMetadataForm.tsx, and AdminPanel.tsx all use the generic,
network-agnostic ConfirmModal — the same "are you sure?" dialog shown on testnet and
mainnet alike. useNetworkGuard only blocks a Freighter-vs-app network mismatch; it
does nothing to add friction to a mainnet-destructive action when Freighter and the app
already agree they're on mainnet.
The README states, as a named feature: "Network Mismatch Protection... with an explicit
confirmation modal before mainnet-destructive actions." That control does not exist in
the shipped app.
Failure Scenario
A user connected to mainnet (Freighter and the app in agreement — no mismatch) deploys
a token, mints extra supply, burns tokens, or updates metadata. They see only the
generic ConfirmModal, identical to what testnet users see — no mainnet-specific
warning, no "type the token symbol to confirm," no extra friction of any kind before a
real-money-destructive transaction is built and sent to Freighter for signing.
Why Critical
It is the one safety control the README calls out by name, specifically for mainnet,
and it is entirely unreachable from any real user flow. Anyone relying on the README's
description of the app's safety posture — an auditor, a new team member, a user
deciding whether to trust the app with mainnet funds — is being told about a control
that isn't wired in.
Tasks
Wire useMainnetConfirmation().requestDeployment (or an equivalent shared hook)
into every mutating submit path that can run on mainnet: TokenForm.tsx
(create), MintForm.tsx, BurnForm.tsx, SetMetadataForm.tsx, AdminPanel.tsx (fee/pause/whitelist/admin-rotation/upgrade actions).
Decide whether MainnetConfirmationModal should replace ConfirmModal on
mainnet across the board, or ConfirmModal should grow a "mainnet mode" that
adds the same friction (e.g. type-to-confirm) — pick one and apply it
consistently rather than leaving two divergent confirmation UIs.
Add a lint rule, code-review checklist item, or a test asserting every contract.call write-path component imports and invokes the mainnet
confirmation gate, so a new form can't reintroduce this gap silently.
Add component/integration tests: on mainnet, submitting each form opens the
mainnet-specific confirmation before any transaction is built; on testnet, the
generic flow is used.
Acceptance Criteria
Every mutating action reachable on mainnet (create, mint, burn, set metadata,
admin actions) is provably gated by a mainnet-specific confirmation step in an
automated test, not just by manual inspection.
No mutating Soroban transaction can be built and handed to Freighter for
mainnet signing without the user passing through that gate first.
The README's claim about the confirmation modal is either now true (preferred)
or corrected to describe the actual behavior.
Filed from the August 2026 critical-issues audit — see CRITICAL_ISSUES.md for the full summary table.
Area: Frontend —
hooks/useMainnetConfirmation.ts,components/UI/MainnetConfirmationModal.tsxSeverity: Critical
Component: Mainnet-destructive-action safety gate
Description
useMainnetConfirmation()'srequestDeployment(params, onConfirm)is the only codepath that opens
MainnetConfirmationModal: it checksisMainnet()and shows the modal,or calls
onConfirm()straight through on testnet. A full-tree search showsrequestDeploymentis referenced only insideuseMainnetConfirmation.tsitself, andMainnetConfirmationModalis referenced only in its own file, a barrel re-export(
components/UI/index.ts), and its Storybook story — never from any form.CreateToken.tsx→TokenForm.tsx(the actual create-token submit path) callsonSubmit(formData)directly, with no mainnet gate of any kind.MintForm.tsx,BurnForm.tsx,SetMetadataForm.tsx, andAdminPanel.tsxall use the generic,network-agnostic
ConfirmModal— the same "are you sure?" dialog shown on testnet andmainnet alike.
useNetworkGuardonly blocks a Freighter-vs-app network mismatch; itdoes nothing to add friction to a mainnet-destructive action when Freighter and the app
already agree they're on mainnet.
The README states, as a named feature: "Network Mismatch Protection... with an explicit
confirmation modal before mainnet-destructive actions." That control does not exist in
the shipped app.
Failure Scenario
A user connected to mainnet (Freighter and the app in agreement — no mismatch) deploys
a token, mints extra supply, burns tokens, or updates metadata. They see only the
generic
ConfirmModal, identical to what testnet users see — no mainnet-specificwarning, no "type the token symbol to confirm," no extra friction of any kind before a
real-money-destructive transaction is built and sent to Freighter for signing.
Why Critical
It is the one safety control the README calls out by name, specifically for mainnet,
and it is entirely unreachable from any real user flow. Anyone relying on the README's
description of the app's safety posture — an auditor, a new team member, a user
deciding whether to trust the app with mainnet funds — is being told about a control
that isn't wired in.
Tasks
useMainnetConfirmation().requestDeployment(or an equivalent shared hook)into every mutating submit path that can run on mainnet:
TokenForm.tsx(create),
MintForm.tsx,BurnForm.tsx,SetMetadataForm.tsx,AdminPanel.tsx(fee/pause/whitelist/admin-rotation/upgrade actions).MainnetConfirmationModalshould replaceConfirmModalonmainnet across the board, or
ConfirmModalshould grow a "mainnet mode" thatadds the same friction (e.g. type-to-confirm) — pick one and apply it
consistently rather than leaving two divergent confirmation UIs.
contract.callwrite-path component imports and invokes the mainnetconfirmation gate, so a new form can't reintroduce this gap silently.
mainnet-specific confirmation before any transaction is built; on testnet, the
generic flow is used.
Acceptance Criteria
admin actions) is provably gated by a mainnet-specific confirmation step in an
automated test, not just by manual inspection.
mainnet signing without the user passing through that gate first.
or corrected to describe the actual behavior.
Filed from the August 2026 critical-issues audit — see
CRITICAL_ISSUES.mdfor the full summary table.