From b09a16e12e941e14e794705307bd868d6a9fef4d Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" <330911+jurevans@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:51:06 +0000 Subject: [PATCH] feat/1295 - Update Namada Keychain integration (#1298) * feat: begin porting Namada Keychain integration * fix: tests and begin hooking up chainId * feat: remove isShielded, update as needed * feat: make specific hook for namada keychain * feat: hooking up new hook for namada keychain * feat: remove dependency on integrations package * feat: provide chainId, allow approval to enable signing on chainId * fix: fix tests * fix: clean-up --- .../src/Approvals/ApproveConnection.tsx | 16 +++- .../src/background/approvals/handler.test.ts | 5 +- .../src/background/approvals/handler.ts | 17 ++-- .../src/background/approvals/messages.ts | 3 +- .../src/background/approvals/service.test.ts | 6 +- .../src/background/approvals/service.ts | 67 ++++++++++++-- apps/extension/src/provider/InjectedNamada.ts | 34 +++---- apps/extension/src/provider/Namada.test.ts | 5 +- apps/extension/src/provider/Namada.ts | 41 +++++---- apps/extension/src/provider/Signer.ts | 36 -------- apps/extension/src/provider/messages.ts | 16 ++-- apps/extension/src/utils/index.ts | 23 +++++ apps/faucet/src/App/Faucet.tsx | 8 +- .../src/App/Common/ConnectExtensionButton.tsx | 25 +++-- apps/namadillo/src/App/Ibc/IbcTransfer.tsx | 6 +- apps/namadillo/src/App/Masp/MaspShield.tsx | 5 +- apps/namadillo/src/App/Masp/MaspUnshield.tsx | 5 +- .../src/App/NamadaTransfer/NamadaTransfer.tsx | 5 +- .../src/App/Setup/ExtensionLoader.tsx | 44 +++++++-- .../App/SwitchAccount/SwitchAccountPanel.tsx | 3 +- apps/namadillo/src/App/WorkerTest.tsx | 2 +- apps/namadillo/src/atoms/accounts/atoms.ts | 18 ++-- apps/namadillo/src/atoms/accounts/services.ts | 6 +- apps/namadillo/src/atoms/arbitraryMessages.ts | 12 ++- apps/namadillo/src/atoms/balance/atoms.ts | 37 +------- apps/namadillo/src/atoms/settings/atoms.ts | 1 - apps/namadillo/src/atoms/shield/services.ts | 4 +- .../src/hooks/useExtensionEvents.tsx | 7 +- apps/namadillo/src/hooks/useNamadaKeychain.ts | 91 +++++++++++++++++++ apps/namadillo/src/index.tsx | 4 +- apps/namadillo/src/integrations/types.ts | 8 +- apps/namadillo/src/lib/query.ts | 10 +- packages/integrations/src/Namada.ts | 12 +-- packages/types/src/account.ts | 7 +- packages/types/src/namada.ts | 12 +-- packages/types/src/signer.ts | 3 - 36 files changed, 383 insertions(+), 221 deletions(-) create mode 100644 apps/namadillo/src/hooks/useNamadaKeychain.ts diff --git a/apps/extension/src/Approvals/ApproveConnection.tsx b/apps/extension/src/Approvals/ApproveConnection.tsx index 9d396f60f6..6fa07369dc 100644 --- a/apps/extension/src/Approvals/ApproveConnection.tsx +++ b/apps/extension/src/Approvals/ApproveConnection.tsx @@ -10,12 +10,17 @@ export const ApproveConnection: React.FC = () => { const requester = useRequester(); const params = useQuery(); const interfaceOrigin = params.get("interfaceOrigin"); + const chainId = params.get("chainId"); const handleResponse = async (allowConnection: boolean): Promise => { if (interfaceOrigin) { await requester.sendMessage( Ports.Background, - new ConnectInterfaceResponseMsg(interfaceOrigin, allowConnection) + new ConnectInterfaceResponseMsg( + interfaceOrigin, + allowConnection, + chainId || undefined + ) ); await closeCurrentTab(); } @@ -26,7 +31,14 @@ export const ApproveConnection: React.FC = () => { - Approve connection for {interfaceOrigin}? + Approve connection for {interfaceOrigin} + {chainId && ( + <> + {" "} + and enable signing for {chainId} + + )} + ? handleResponse(true)}> diff --git a/apps/extension/src/background/approvals/handler.test.ts b/apps/extension/src/background/approvals/handler.test.ts index 1b4903efa0..9e862fe63d 100644 --- a/apps/extension/src/background/approvals/handler.test.ts +++ b/apps/extension/src/background/approvals/handler.test.ts @@ -68,15 +68,16 @@ describe("approvals handler", () => { handler(env, approveTxMsg); expect(service.approveSignTx).toBeCalled(); + const chainId = "chain-id"; const rejectTxMsg = new RejectSignTxMsg("msgId"); handler(env, rejectTxMsg); expect(service.rejectSignTx).toBeCalled(); - const isConnectionApprovedMsg = new IsConnectionApprovedMsg(); + const isConnectionApprovedMsg = new IsConnectionApprovedMsg(chainId); handler(env, isConnectionApprovedMsg); expect(service.isConnectionApproved).toBeCalled(); - const approveConnectInterfaceMsg = new ApproveConnectInterfaceMsg(); + const approveConnectInterfaceMsg = new ApproveConnectInterfaceMsg(chainId); handler(env, approveConnectInterfaceMsg); expect(service.approveConnection).toBeCalled(); diff --git a/apps/extension/src/background/approvals/handler.ts b/apps/extension/src/background/approvals/handler.ts index f2e253eae6..88aac032ed 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -117,16 +117,16 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => { const handleIsConnectionApprovedMsg: ( service: ApprovalsService ) => InternalHandler = (service) => { - return async (_, { origin }) => { - return await service.isConnectionApproved(origin); + return async (_, { origin, chainId }) => { + return await service.isConnectionApproved(origin, chainId); }; }; const handleApproveConnectInterfaceMsg: ( service: ApprovalsService ) => InternalHandler = (service) => { - return async (_, { origin }) => { - return await service.approveConnection(origin); + return async (_, { origin, chainId }) => { + return await service.approveConnection(origin, chainId); }; }; @@ -135,12 +135,13 @@ const handleConnectInterfaceResponseMsg: ( ) => InternalHandler = (service) => { return async ( { senderTabId: popupTabId }, - { interfaceOrigin, allowConnection } + { interfaceOrigin, allowConnection, chainId } ) => { return await service.approveConnectionResponse( popupTabId, interfaceOrigin, - allowConnection + allowConnection, + chainId ); }; }; @@ -148,8 +149,8 @@ const handleConnectInterfaceResponseMsg: ( const handleApproveDisconnectInterfaceMsg: ( service: ApprovalsService ) => InternalHandler = (service) => { - return async (_, { origin }) => { - return await service.approveDisconnection(origin); + return async (_, { origin, chainId }) => { + return await service.approveDisconnection(origin, chainId); }; }; diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 9fad70aad4..2821939105 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -147,7 +147,8 @@ export class ConnectInterfaceResponseMsg extends Message { constructor( public readonly interfaceOrigin: string, - public readonly allowConnection: boolean + public readonly allowConnection: boolean, + public readonly chainId?: string ) { super(); } diff --git a/apps/extension/src/background/approvals/service.test.ts b/apps/extension/src/background/approvals/service.test.ts index dfb8417b3c..a3435a05c1 100644 --- a/apps/extension/src/background/approvals/service.test.ts +++ b/apps/extension/src/background/approvals/service.test.ts @@ -281,7 +281,8 @@ describe("approvals service", () => { { interfaceOrigin } ); expect(service.isConnectionApproved).toHaveBeenCalledWith( - interfaceOrigin + interfaceOrigin, + undefined ); await expect(promise).resolves.toBeDefined(); }); @@ -371,7 +372,8 @@ describe("approvals service", () => { { interfaceOrigin } ); expect(service.isConnectionApproved).toHaveBeenCalledWith( - interfaceOrigin + interfaceOrigin, + undefined ); await expect(promise).resolves.toBeDefined(); }); diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index df287f05d4..3383e3e70d 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -185,20 +185,48 @@ export class ApprovalsService { resolvers.reject(new Error("Sign Tx rejected")); } - async isConnectionApproved(interfaceOrigin: string): Promise { + async isConnectionApproved( + interfaceOrigin: string, + chainId?: string + ): Promise { const approvedOrigins = (await this.localStorage.getApprovedOrigins()) || []; + if (chainId) { + const currentChainId = await this.chainService.getChain(); + if (chainId !== currentChainId) { + return false; + } + } + return approvedOrigins.includes(interfaceOrigin); } - async approveConnection(interfaceOrigin: string): Promise { - const alreadyApproved = await this.isConnectionApproved(interfaceOrigin); + async approveConnection( + interfaceOrigin: string, + chainId?: string + ): Promise { + const alreadyApproved = await this.isConnectionApproved( + interfaceOrigin, + chainId + ); + + const approveConnectionPopupProps: { + interfaceOrigin: string; + chainId?: string; + } = { + interfaceOrigin, + }; + + if (chainId) { + approveConnectionPopupProps["chainId"] = chainId; + } if (!alreadyApproved) { - return this.launchApprovalPopup(TopLevelRoute.ApproveConnection, { - interfaceOrigin, - }); + return this.launchApprovalPopup( + TopLevelRoute.ApproveConnection, + approveConnectionPopupProps + ); } // A resolved promise is implicitly returned here if the origin had @@ -208,13 +236,26 @@ export class ApprovalsService { async approveConnectionResponse( popupTabId: number, interfaceOrigin: string, - allowConnection: boolean + allowConnection: boolean, + chainId?: string ): Promise { const resolvers = this.getResolver(popupTabId); if (allowConnection) { try { - await this.localStorage.addApprovedOrigin(interfaceOrigin); + if ( + !(await this.localStorage.getApprovedOrigins())?.includes( + interfaceOrigin + ) + ) { + // Add approved origin if it hasn't been added + await this.localStorage.addApprovedOrigin(interfaceOrigin); + } + + if (chainId) { + // Set approved signing chainId + await this.chainService.updateChain(chainId); + } } catch (e) { resolvers.reject(e); } @@ -224,8 +265,14 @@ export class ApprovalsService { } } - async approveDisconnection(interfaceOrigin: string): Promise { - const isConnected = await this.isConnectionApproved(interfaceOrigin); + async approveDisconnection( + interfaceOrigin: string, + chainId?: string + ): Promise { + const isConnected = await this.isConnectionApproved( + interfaceOrigin, + chainId + ); if (isConnected) { return this.launchApprovalPopup(TopLevelRoute.ApproveDisconnection, { diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index 70b3dee283..01fca91c5d 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -1,5 +1,5 @@ import { - DerivedAccount, + Account, Namada as INamada, Signer as ISigner, SignArbitraryProps, @@ -11,30 +11,32 @@ import { InjectedProxy } from "./InjectedProxy"; import { Signer } from "./Signer"; export class InjectedNamada implements INamada { - constructor(private readonly _version: string) {} + constructor(private readonly _version: string) { } - public async connect(): Promise { - return await InjectedProxy.requestMethod("connect"); + public async connect(chainId?: string): Promise { + return await InjectedProxy.requestMethod("connect", chainId); } - public async disconnect(): Promise { - return await InjectedProxy.requestMethod("disconnect"); + public async disconnect(chainId?: string): Promise { + return await InjectedProxy.requestMethod( + "disconnect", + chainId + ); } - public async isConnected(): Promise { - return await InjectedProxy.requestMethod("isConnected"); + public async isConnected(chainId?: string): Promise { + return await InjectedProxy.requestMethod( + "isConnected", + chainId + ); } - public async accounts(): Promise { - return await InjectedProxy.requestMethod( - "accounts" - ); + public async accounts(): Promise { + return await InjectedProxy.requestMethod("accounts"); } - public async defaultAccount(): Promise { - return await InjectedProxy.requestMethod( - "defaultAccount" - ); + public async defaultAccount(): Promise { + return await InjectedProxy.requestMethod("defaultAccount"); } public async updateDefaultAccount(address: string): Promise { diff --git a/apps/extension/src/provider/Namada.test.ts b/apps/extension/src/provider/Namada.test.ts index 135ccf35df..226bbf9472 100644 --- a/apps/extension/src/provider/Namada.test.ts +++ b/apps/extension/src/provider/Namada.test.ts @@ -7,6 +7,7 @@ import { VaultService } from "background/vault"; import * as utils from "extension/utils"; import { VaultStorage } from "storage"; import { KVStoreMock, init } from "test/init"; +import { toPublicAccount } from "utils"; import { ACTIVE_ACCOUNT, keyStore, password } from "./data.mock"; // Needed for now as utils import webextension-polyfill directly @@ -60,6 +61,8 @@ describe("Namada", () => { await utilityStore.set(PARENT_ACCOUNT_ID_KEY, ACTIVE_ACCOUNT); const storedKeyStore = keyStore.map((store) => store.public); const storedAccounts = await namada.accounts(); - expect(storedAccounts).toEqual(storedKeyStore); + expect(storedAccounts).toEqual( + storedKeyStore.map((derivedAccount) => toPublicAccount(derivedAccount)) + ); }); }); diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 2ac388311d..0ddd299870 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -1,5 +1,5 @@ import { - DerivedAccount, + Account, Namada as INamada, SignArbitraryProps, SignArbitraryResponse, @@ -8,7 +8,7 @@ import { } from "@namada/types"; import { MessageRequester, Ports } from "router"; -import { toEncodedTx } from "utils"; +import { toEncodedTx, toPublicAccount } from "utils"; import { ApproveConnectInterfaceMsg, ApproveDisconnectInterfaceMsg, @@ -28,43 +28,48 @@ export class Namada implements INamada { protected readonly requester?: MessageRequester ) {} - public async connect(): Promise { + public async connect(chainId?: string): Promise { return await this.requester?.sendMessage( Ports.Background, - new ApproveConnectInterfaceMsg() + new ApproveConnectInterfaceMsg(chainId) ); } - public async disconnect(): Promise { + public async disconnect(chainId?: string): Promise { return await this.requester?.sendMessage( Ports.Background, - new ApproveDisconnectInterfaceMsg(location.origin) + new ApproveDisconnectInterfaceMsg(location.origin, chainId) ); } - public async isConnected(): Promise { + public async isConnected(chainId?: string): Promise { if (!this.requester) { throw new Error("no requester"); } return await this.requester.sendMessage( Ports.Background, - new IsConnectionApprovedMsg() + new IsConnectionApprovedMsg(chainId) ); } - public async accounts(): Promise { - return await this.requester?.sendMessage( - Ports.Background, - new QueryAccountsMsg() - ); + public async accounts(): Promise { + return ( + await this.requester?.sendMessage( + Ports.Background, + new QueryAccountsMsg() + ) + )?.map(toPublicAccount); } - public async defaultAccount(): Promise { - return await this.requester?.sendMessage( - Ports.Background, - new QueryDefaultAccountMsg() - ); + public async defaultAccount(): Promise { + return await this.requester + ?.sendMessage(Ports.Background, new QueryDefaultAccountMsg()) + .then((defaultAccount) => { + if (defaultAccount) { + return toPublicAccount(defaultAccount); + } + }); } public async updateDefaultAccount(address: string): Promise { diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index 0a2a745304..95a74f4fb9 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -1,7 +1,4 @@ -import { chains } from "@namada/chains"; import { - Account, - AccountType, Signer as ISigner, Namada, SignArbitraryResponse, @@ -11,39 +8,6 @@ import { export class Signer implements ISigner { constructor(private readonly _namada: Namada) {} - public async accounts(): Promise { - return (await this._namada.accounts())?.map( - ({ alias, address, type, publicKey, owner }) => ({ - alias, - address, - viewingKey: owner, - chainId: chains.namada.chainId, - type, - publicKey, - isShielded: type === AccountType.ShieldedKeys, - chainKey: "namada", - }) - ); - } - - public async defaultAccount(): Promise { - const account = await this._namada.defaultAccount(); - - if (account) { - const { alias, address, type, publicKey } = account; - - return { - alias, - address, - chainId: chains.namada.chainId, - type, - publicKey, - isShielded: type === AccountType.ShieldedKeys, - chainKey: "namada", - }; - } - } - public async sign( tx: TxProps | TxProps[], signer: string, diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index 39541a99fe..ca70416ee5 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -69,12 +69,7 @@ export class ApproveSignArbitraryMsg extends Message { } validate(): void { - if (!this.signer) { - throw new Error("A signer address is required!"); - } - if (!this.data) { - throw new Error("Signing data is required!"); - } + validateProps(this, ["signer", "data"]); } route(): string { @@ -94,7 +89,7 @@ export class IsConnectionApprovedMsg extends Message { return MessageType.IsConnectionApproved; } - constructor() { + constructor(public readonly chainId?: string) { super(); } @@ -116,7 +111,7 @@ export class ApproveConnectInterfaceMsg extends Message { return MessageType.ApproveConnectInterface; } - constructor() { + constructor(public readonly chainId?: string) { super(); } @@ -138,7 +133,10 @@ export class ApproveDisconnectInterfaceMsg extends Message { return MessageType.ApproveDisconnectInterface; } - constructor(public readonly originToRevoke: string) { + constructor( + public readonly originToRevoke: string, + public readonly chainId?: string + ) { super(); } diff --git a/apps/extension/src/utils/index.ts b/apps/extension/src/utils/index.ts index d75c93f51d..63637838c1 100644 --- a/apps/extension/src/utils/index.ts +++ b/apps/extension/src/utils/index.ts @@ -1,7 +1,9 @@ import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { + Account, AccountType, Bip44Path, + DerivedAccount, Path, TransferProps, TxProps, @@ -157,3 +159,24 @@ export const parseTransferType = ( type, }; }; + +/** + * Accepts a derived account, returns only values needed for Account + * @param derivedAccount - Derived account type returned from keyring + * @returns Account type for public API + */ +export const toPublicAccount = (derivedAccount: DerivedAccount): Account => { + const { alias, address, type, publicKey, owner } = derivedAccount; + const isShielded = type === AccountType.ShieldedKeys; + const account: Account = { + alias, + address, + type, + }; + if (isShielded) { + account.viewingKey = owner; + } else { + account.publicKey = publicKey; + } + return account; +}; diff --git a/apps/faucet/src/App/Faucet.tsx b/apps/faucet/src/App/Faucet.tsx index 186ee85a48..0b680039e6 100644 --- a/apps/faucet/src/App/Faucet.tsx +++ b/apps/faucet/src/App/Faucet.tsx @@ -10,7 +10,7 @@ import { Option, Select, } from "@namada/components"; -import { Account } from "@namada/types"; +import { Account, AccountType } from "@namada/types"; import { bech32mValidation, shortenAddress } from "@namada/utils"; import { chains } from "@namada/chains"; @@ -245,7 +245,11 @@ export const FaucetForm: React.FC = ({ isTestnetLive }) => { await integration.connect(); const accounts = await integration.accounts(); if (accounts) { - setAccounts(accounts.filter((account) => !account.isShielded)); + setAccounts( + accounts.filter( + (account) => account.type !== AccountType.ShieldedKeys + ) + ); } setIsExtensionConnected(true); } catch (e) { diff --git a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx index c6a5e50aa0..1369619263 100644 --- a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx +++ b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx @@ -1,22 +1,29 @@ import { ActionButton } from "@namada/components"; -import { namadaExtensionAttachStatus } from "atoms/settings"; -import { useExtensionConnect } from "hooks/useExtensionConnect"; -import { useAtomValue } from "jotai"; +import { + namadaExtensionAttachStatus, + namadaExtensionConnectionStatus, +} from "atoms/settings"; +import { useNamadaKeychain } from "hooks/useNamadaKeychain"; +import { useAtom, useAtomValue } from "jotai"; export const ConnectExtensionButton = (): JSX.Element => { const extensionAttachStatus = useAtomValue(namadaExtensionAttachStatus); - const { connect, isConnected } = useExtensionConnect(); + const { connect } = useNamadaKeychain(); + const [connectStatus] = useAtom(namadaExtensionConnectionStatus); // TODO create an action button when the extension is connected // but the account is missing, like on useConnectText return ( <> - {extensionAttachStatus === "attached" && !isConnected && ( - - Connect Keychain - - )} + {extensionAttachStatus === "attached" && + connectStatus !== "connected" && ( + + {connectStatus === "connecting" ? + "Connecting..." + : "Connect Keychain"} + + )} {extensionAttachStatus === "detached" && ( { const namadaAddress = useMemo(() => { return ( - defaultAccounts.data?.find((account) => account.isShielded === shielded) - ?.address || "" + defaultAccounts.data?.find( + (account) => (account.type === AccountType.ShieldedKeys) === shielded + )?.address || "" ); }, [defaultAccounts, shielded]); diff --git a/apps/namadillo/src/App/Masp/MaspShield.tsx b/apps/namadillo/src/App/Masp/MaspShield.tsx index 73768c01a9..de67d717f1 100644 --- a/apps/namadillo/src/App/Masp/MaspShield.tsx +++ b/apps/namadillo/src/App/Masp/MaspShield.tsx @@ -1,5 +1,6 @@ import { Chain } from "@chain-registry/types"; import { Panel } from "@namada/components"; +import { AccountType } from "@namada/types"; import { Timeline } from "App/Common/Timeline"; import { params } from "App/routes"; import { @@ -56,10 +57,10 @@ export const MaspShield: React.FC = () => { const chainId = chainParameters.data?.chainId; const sourceAddress = defaultAccounts.data?.find( - (account) => !account.isShielded + (account) => account.type !== AccountType.ShieldedKeys )?.address; const destinationAddress = defaultAccounts.data?.find( - (account) => account.isShielded + (account) => account.type === AccountType.ShieldedKeys )?.address; const selectedAssetAddress = searchParams.get(params.asset) || undefined; diff --git a/apps/namadillo/src/App/Masp/MaspUnshield.tsx b/apps/namadillo/src/App/Masp/MaspUnshield.tsx index 353e728371..e117217143 100644 --- a/apps/namadillo/src/App/Masp/MaspUnshield.tsx +++ b/apps/namadillo/src/App/Masp/MaspUnshield.tsx @@ -1,5 +1,6 @@ import { Chain } from "@chain-registry/types"; import { Panel } from "@namada/components"; +import { AccountType } from "@namada/types"; import { Timeline } from "App/Common/Timeline"; import { params } from "App/routes"; import { @@ -56,10 +57,10 @@ export const MaspUnshield: React.FC = () => { const chainId = chainParameters.data?.chainId; const sourceAddress = defaultAccounts.data?.find( - (account) => account.isShielded + (account) => account.type === AccountType.ShieldedKeys )?.address; const destinationAddress = defaultAccounts.data?.find( - (account) => !account.isShielded + (account) => account.type !== AccountType.ShieldedKeys )?.address; const selectedAssetAddress = searchParams.get(params.asset) || undefined; diff --git a/apps/namadillo/src/App/NamadaTransfer/NamadaTransfer.tsx b/apps/namadillo/src/App/NamadaTransfer/NamadaTransfer.tsx index 5901378d5c..ce259ab8dc 100644 --- a/apps/namadillo/src/App/NamadaTransfer/NamadaTransfer.tsx +++ b/apps/namadillo/src/App/NamadaTransfer/NamadaTransfer.tsx @@ -1,5 +1,6 @@ import { Chain } from "@chain-registry/types"; import { Panel } from "@namada/components"; +import { AccountType } from "@namada/types"; import { Timeline } from "App/Common/Timeline"; import { params } from "App/routes"; import { isShieldedAddress } from "App/Transfer/common"; @@ -82,7 +83,9 @@ export const NamadaTransfer: React.FC = () => { const chainId = chainParameters.data?.chainId; const sourceAddress = defaultAccounts.data?.find((account) => - shielded ? account.isShielded : !account.isShielded + shielded ? + account.type === AccountType.ShieldedKeys + : account.type !== AccountType.ShieldedKeys )?.address; const selectedAssetAddress = searchParams.get(params.asset) || undefined; const selectedAsset = diff --git a/apps/namadillo/src/App/Setup/ExtensionLoader.tsx b/apps/namadillo/src/App/Setup/ExtensionLoader.tsx index 2a324a49f4..87218a6e90 100644 --- a/apps/namadillo/src/App/Setup/ExtensionLoader.tsx +++ b/apps/namadillo/src/App/Setup/ExtensionLoader.tsx @@ -1,9 +1,10 @@ -import { useIntegration } from "@namada/integrations"; +import { chainParametersAtom } from "atoms/chain"; import { namadaExtensionAttachStatus, namadaExtensionConnectionStatus, } from "atoms/settings"; -import { useAtom, useSetAtom } from "jotai"; +import { useNamadaKeychain } from "hooks/useNamadaKeychain"; +import { useAtom, useAtomValue } from "jotai"; import { ReactNode, useEffect } from "react"; import { PageLoader } from "../Common/PageLoader"; @@ -12,19 +13,42 @@ export const ExtensionLoader = ({ }: { children: ReactNode; }): JSX.Element => { + const { namadaKeychain } = useNamadaKeychain(); const [attachStatus, setAttachStatus] = useAtom(namadaExtensionAttachStatus); - const setConnectionStatus = useSetAtom(namadaExtensionConnectionStatus); - const integration = useIntegration("namada"); + const { data: chain } = useAtomValue(chainParametersAtom); + const [connectStatus, setConnectionStatus] = useAtom( + namadaExtensionConnectionStatus + ); - useEffect(() => { - setAttachStatus(integration.detect() ? "attached" : "detached"); + const chainId = chain?.chainId; + const keychainPromise = namadaKeychain.get(); - integration.isConnected().then((isConnected) => { - if (isConnected) { - setConnectionStatus("connected"); + useEffect(() => { + keychainPromise.then((injectedNamada) => { + if (injectedNamada) { + return setAttachStatus("attached"); } + setAttachStatus("detached"); }); - }, [integration]); + }, []); + + useEffect(() => { + if (chainId && attachStatus === "attached") { + keychainPromise.then((injectedNamada) => { + injectedNamada.isConnected(chainId).then((isConnected) => { + if (isConnected) { + return setConnectionStatus("connected"); + } + + // If extension is currently connected, but there is a mismatch in chain IDs, + // prompt for re-approval + if (connectStatus === "connected") { + injectedNamada.connect(chainId); + } + }); + }); + } + }, [attachStatus, chainId]); if (attachStatus === "pending") { return ; diff --git a/apps/namadillo/src/App/SwitchAccount/SwitchAccountPanel.tsx b/apps/namadillo/src/App/SwitchAccount/SwitchAccountPanel.tsx index 62107c24b2..b5996616e5 100644 --- a/apps/namadillo/src/App/SwitchAccount/SwitchAccountPanel.tsx +++ b/apps/namadillo/src/App/SwitchAccount/SwitchAccountPanel.tsx @@ -1,4 +1,5 @@ import { Checkbox, Modal } from "@namada/components"; +import { AccountType } from "@namada/types"; import { ModalTransition } from "App/Common/ModalTransition"; import { accountsAtom, @@ -44,7 +45,7 @@ export const SwitchAccountPanel = (): JSX.Element => {
{data - ?.filter((i) => !i.isShielded) + ?.filter((i) => i.type !== AccountType.ShieldedKeys) .map(({ alias, address }) => (