diff --git a/cmd/cli/admin.go b/cmd/cli/admin.go
index 8d55c7f6a..ff956bb82 100644
--- a/cmd/cli/admin.go
+++ b/cmd/cli/admin.go
@@ -121,7 +121,7 @@ var (
Short: "delete the key associated with the address or nickname from the keystore",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
- writeToConsole(client.KeystoreDelete(argGetAddrOrNickname(args[0])))
+ writeToConsole(client.KeystoreDelete(argGetAddrOrNickname(args[0]), getPassword()))
},
}
diff --git a/cmd/rpc/README.md b/cmd/rpc/README.md
index bb4cc67fc..b199a435a 100644
--- a/cmd/rpc/README.md
+++ b/cmd/rpc/README.md
@@ -3541,13 +3541,14 @@ $ curl -X POST http://localhost:50003/v1/admin/keystore-import-raw \
**Route:** `/v1/admin/keystore-delete`
-**Description**: removes a key from the keystore using either the address or nickname
+**Description**: removes a key from the keystore using either the address or nickname after validating the password
**HTTP Method**: `POST`
**Request**:
- **nickname**: `string` - the nickname associated with the key
- **address**: `string` - the address associated with the key
+- **password**: `string` - **(required)** the plain-text password used to encrypt the key
**Response**: `hex-string` - the 20 byte address of the newly imported key
@@ -3556,7 +3557,8 @@ $ curl -X POST http://localhost:50003/v1/admin/keystore-import-raw \
$ curl -X POST http://localhost:50003/v1/admin/keystore-delete \
-H "Content-Type: application/json" \
-d '{
- "nickname":"my_key_2"
+ "nickname":"my_key_2",
+ "password":"my_password"
}'
> "b0b4a45ca70104ecc943a49e4553f0e7e1135b01"
diff --git a/cmd/rpc/admin.go b/cmd/rpc/admin.go
index 2c4ca5f4d..cd4e6f875 100644
--- a/cmd/rpc/admin.go
+++ b/cmd/rpc/admin.go
@@ -94,10 +94,12 @@ func (s *Server) KeystoreImportRaw(w http.ResponseWriter, r *http.Request, _ htt
func (s *Server) KeystoreDelete(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Call the keystore handler with a callback to perform the deletion
s.keystoreHandler(w, r, func(k *crypto.Keystore, ptr *keystoreRequest) (any, error) {
- k.DeleteKey(crypto.DeleteOpts{
+ if err := k.DeleteKey(ptr.Password, crypto.DeleteOpts{
Address: ptr.Address,
Nickname: ptr.Nickname,
- })
+ }); err != nil {
+ return nil, err
+ }
// Update the keystore on disk and return the account address
return ptr.Address, k.SaveToFile(s.config.DataDirPath)
})
diff --git a/cmd/rpc/client.go b/cmd/rpc/client.go
index 6b8a6de02..9fe579119 100644
--- a/cmd/rpc/client.go
+++ b/cmd/rpc/client.go
@@ -470,7 +470,7 @@ type AddrOrNickname struct {
Nickname string
}
-func (c *Client) KeystoreDelete(addrOrNickname AddrOrNickname) (returned crypto.AddressI, err lib.ErrorI) {
+func (c *Client) KeystoreDelete(addrOrNickname AddrOrNickname, password string) (returned crypto.AddressI, err lib.ErrorI) {
returned = new(crypto.Address)
if addrOrNickname.Address != "" {
@@ -480,7 +480,8 @@ func (c *Client) KeystoreDelete(addrOrNickname AddrOrNickname) (returned crypto.
return
}
err = c.keystoreRequest(KeystoreDeleteRouteName, keystoreRequest{
- addressRequest: addressRequest{bz},
+ addressRequest: addressRequest{bz},
+ passwordRequest: passwordRequest{password},
}, returned)
return
}
@@ -488,6 +489,7 @@ func (c *Client) KeystoreDelete(addrOrNickname AddrOrNickname) (returned crypto.
if addrOrNickname.Nickname != "" {
err = c.keystoreRequest(KeystoreDeleteRouteName, keystoreRequest{
nicknameRequest: nicknameRequest{addrOrNickname.Nickname},
+ passwordRequest: passwordRequest{password},
}, returned)
return
}
diff --git a/cmd/rpc/web/wallet/public/plugin/canopy/chain.json b/cmd/rpc/web/wallet/public/plugin/canopy/chain.json
index 5a9a90a67..23b0c662a 100644
--- a/cmd/rpc/web/wallet/public/plugin/canopy/chain.json
+++ b/cmd/rpc/web/wallet/public/plugin/canopy/chain.json
@@ -138,7 +138,8 @@
"method": "POST"
},
"body": {
- "nickname": "{{nickname}}"
+ "address": "{{address}}",
+ "password": "{{password}}"
}
},
"validator": {
diff --git a/cmd/rpc/web/wallet/public/plugin/canopy/chain.json.template b/cmd/rpc/web/wallet/public/plugin/canopy/chain.json.template
index 025208720..dacc383ba 100644
--- a/cmd/rpc/web/wallet/public/plugin/canopy/chain.json.template
+++ b/cmd/rpc/web/wallet/public/plugin/canopy/chain.json.template
@@ -57,7 +57,7 @@
},
"keystoreDelete": {
"source": { "base": "admin", "path": "/v1/admin/keystore-delete", "method": "POST" },
- "body": { "nickname": "{{nickname}}" }
+ "body": { "address": "{{address}}", "password": "{{password}}" }
},
"validator": {
"source": { "base": "rpc", "path": "/v1/query/validator", "method": "POST" },
diff --git a/cmd/rpc/web/wallet/src/app/pages/KeyManagement.tsx b/cmd/rpc/web/wallet/src/app/pages/KeyManagement.tsx
index 15d53da15..8d3e35ec6 100644
--- a/cmd/rpc/web/wallet/src/app/pages/KeyManagement.tsx
+++ b/cmd/rpc/web/wallet/src/app/pages/KeyManagement.tsx
@@ -144,8 +144,8 @@ export const KeyManagement = (): JSX.Element => {
className="max-h-[90vh] max-w-[min(96vw,56rem)] gap-0 overflow-hidden border-[#272729] bg-[#171717] p-0"
>
- {activeModal === 'import' ? : null}
- {activeModal === 'create' ? : null}
+ {activeModal === 'import' ? setActiveModal(null)} /> : null}
+ {activeModal === 'create' ? setActiveModal(null)} /> : null}
diff --git a/cmd/rpc/web/wallet/src/app/providers/AccountsListProvider.tsx b/cmd/rpc/web/wallet/src/app/providers/AccountsListProvider.tsx
index 0b625c4fa..b6941c297 100644
--- a/cmd/rpc/web/wallet/src/app/providers/AccountsListProvider.tsx
+++ b/cmd/rpc/web/wallet/src/app/providers/AccountsListProvider.tsx
@@ -30,7 +30,7 @@ type AccountsListContextValue = {
isReady: boolean
refetch: () => Promise
createNewAccount: (nickname: string, password: string) => Promise
- deleteAccount: (accountId: string, onDeleted?: (nextAccountId: string | null) => void) => Promise
+ deleteAccount: (accountId: string, password: string, onDeleted?: (nextAccountId: string | null) => void) => Promise
}
const AccountsListContext = createContext(undefined)
@@ -76,6 +76,7 @@ export function AccountsListProvider({ children }: { children: React.ReactNode }
const deleteAccount = useCallback(async (
accountId: string,
+ password: string,
onDeleted?: (nextAccountId: string | null) => void
): Promise => {
try {
@@ -85,7 +86,8 @@ export function AccountsListProvider({ children }: { children: React.ReactNode }
}
await dsFetch('keystoreDelete', {
- nickname: account.nickname
+ address: account.address,
+ password,
})
// Notify caller about which account to switch to
diff --git a/cmd/rpc/web/wallet/src/app/providers/AccountsProvider.tsx b/cmd/rpc/web/wallet/src/app/providers/AccountsProvider.tsx
index 935d0e32d..b22949291 100644
--- a/cmd/rpc/web/wallet/src/app/providers/AccountsProvider.tsx
+++ b/cmd/rpc/web/wallet/src/app/providers/AccountsProvider.tsx
@@ -19,7 +19,7 @@ type AccountsContextValue = {
switchAccount: (id: string | null) => void
createNewAccount: (nickname: string, password: string) => Promise
- deleteAccount: (accountId: string) => Promise
+ deleteAccount: (accountId: string, password: string) => Promise
refetch: () => Promise
}
@@ -48,8 +48,8 @@ export function useAccounts(): AccountsContextValue {
const selected = useSelectedAccount()
// Wrap deleteAccount to integrate with switchAccount
- const deleteAccount = useCallback(async (accountId: string): Promise => {
- await list.deleteAccount(accountId, (nextAccountId) => {
+ const deleteAccount = useCallback(async (accountId: string, password: string): Promise => {
+ await list.deleteAccount(accountId, password, (nextAccountId: string | null) => {
if (selected.selectedId === accountId && nextAccountId) {
selected.switchAccount(nextAccountId)
}
diff --git a/cmd/rpc/web/wallet/src/components/governance/PollDetailsModal.tsx b/cmd/rpc/web/wallet/src/components/governance/PollDetailsModal.tsx
index 7ff8de7f9..44b98cef1 100644
--- a/cmd/rpc/web/wallet/src/components/governance/PollDetailsModal.tsx
+++ b/cmd/rpc/web/wallet/src/components/governance/PollDetailsModal.tsx
@@ -40,6 +40,15 @@ export const PollDetailsModal: React.FC = ({
onClose,
onVote,
}) => {
+ React.useEffect(() => {
+ if (!isOpen) return;
+ const handler = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ window.addEventListener('keydown', handler);
+ return () => window.removeEventListener('keydown', handler);
+ }, [isOpen, onClose]);
+
if (!poll) return null;
const normalizedHash = poll.proposalHash || poll.hash;
diff --git a/cmd/rpc/web/wallet/src/components/governance/ProposalDetailsModal.tsx b/cmd/rpc/web/wallet/src/components/governance/ProposalDetailsModal.tsx
index 8cec8beeb..fce4b27be 100644
--- a/cmd/rpc/web/wallet/src/components/governance/ProposalDetailsModal.tsx
+++ b/cmd/rpc/web/wallet/src/components/governance/ProposalDetailsModal.tsx
@@ -17,6 +17,16 @@ export const ProposalDetailsModal: React.FC = ({
onClose,
}) => {
const { symbol, factor } = useDenom();
+
+ React.useEffect(() => {
+ if (!isOpen) return;
+ const handler = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ window.addEventListener('keydown', handler);
+ return () => window.removeEventListener('keydown', handler);
+ }, [isOpen, onClose]);
+
if (!proposal) return null;
const getCategoryColor = (category: string) => {
diff --git a/cmd/rpc/web/wallet/src/components/key-management/CurrentWallet.tsx b/cmd/rpc/web/wallet/src/components/key-management/CurrentWallet.tsx
index c637dbaed..0d6bce292 100644
--- a/cmd/rpc/web/wallet/src/components/key-management/CurrentWallet.tsx
+++ b/cmd/rpc/web/wallet/src/components/key-management/CurrentWallet.tsx
@@ -36,7 +36,8 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
const [passwordError, setPasswordError] = useState("");
const [isFetchingKey, setIsFetchingKey] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
- const [deleteConfirmation, setDeleteConfirmation] = useState("");
+ const [deletePassword, setDeletePassword] = useState("");
+ const [deletePasswordError, setDeletePasswordError] = useState("");
const [isDeleting, setIsDeleting] = useState(false);
const [isRenameOpen, setIsRenameOpen] = useState(false);
const [renameNickname, setRenameNickname] = useState("");
@@ -217,7 +218,8 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
return;
}
- setDeleteConfirmation("");
+ setDeletePassword("");
+ setDeletePasswordError("");
setShowDeleteModal(true);
};
@@ -271,20 +273,20 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
const handleConfirmDelete = async () => {
if (!selectedAccount) return;
- const nickname = selectedKeyEntry?.keyNickname || selectedAccount.nickname;
- if (deleteConfirmation !== nickname) {
- toast.error({
- title: "Confirmation Failed",
- description: `Please type "${nickname}" to confirm deletion`,
- });
+ if (!deletePassword) {
+ setDeletePasswordError("Password is required.");
return;
}
setIsDeleting(true);
+ setDeletePasswordError("");
try {
+ const nickname = selectedKeyEntry?.keyNickname || selectedAccount.nickname;
+
await dsFetch("keystoreDelete", {
- nickname: nickname,
+ address: selectedKeyEntry?.keyAddress ?? selectedAccount.address,
+ password: deletePassword,
});
await invalidateKeystore();
@@ -295,9 +297,8 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
});
setShowDeleteModal(false);
- setDeleteConfirmation("");
+ setDeletePassword("");
- // Switch to another account
const otherAccounts = accounts.filter((acc) => acc.id !== selectedAccount.id);
if (otherAccounts.length > 0) {
setTimeout(() => {
@@ -307,6 +308,7 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
switchAccount(null);
}
} catch (error) {
+ setDeletePasswordError("Unable to delete with that password.");
toast.error({
title: "Delete Failed",
description: error instanceof Error ? error.message : String(error),
@@ -591,25 +593,29 @@ export const CurrentWallet = ({ embedded = false }: { embedded?: boolean }): JSX
- Type
+ Enter your wallet password to confirm deletion of
{selectedKeyEntry?.keyNickname || selectedAccount?.nickname}
- to confirm deletion:
+ :
setDeleteConfirmation(e.target.value)}
- placeholder="Type wallet name to confirm"
- className="w-full bg-[#0f0f0f] text-foreground border border-[#272729] rounded-lg px-3 py-2.5 mb-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff1845]/25"
+ type="password"
+ value={deletePassword}
+ onChange={(e) => setDeletePassword(e.target.value)}
+ placeholder="Password"
+ className="w-full bg-[#0f0f0f] text-foreground border border-[#272729] rounded-lg px-3 py-2.5 mb-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff1845]/25"
autoFocus
/>
+ {deletePasswordError && (
+ {deletePasswordError}
+ )}
-
+