From eb38b754268663f3a37f575a5b3cfe15da776505 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Wed, 11 Sep 2024 11:54:56 +0200 Subject: [PATCH] feat: begin adding chain ID to connect approval --- .../src/Approvals/ApproveConnection.tsx | 32 ++++++++++++++- .../src/background/approvals/handler.ts | 9 +++-- .../src/background/approvals/messages.ts | 3 +- .../src/background/approvals/service.ts | 39 +++++++++++++++---- apps/extension/src/provider/InjectedNamada.ts | 4 +- apps/extension/src/provider/Namada.ts | 4 +- apps/extension/src/provider/messages.ts | 2 +- .../src/App/Common/ConnectExtensionButton.tsx | 11 +++++- .../src/hooks/useExtensionConnect.ts | 12 +++--- packages/integrations/src/Namada.ts | 4 +- .../integrations/src/hooks/useIntegration.ts | 20 +++++----- packages/types/src/namada.ts | 2 +- 12 files changed, 104 insertions(+), 38 deletions(-) diff --git a/apps/extension/src/Approvals/ApproveConnection.tsx b/apps/extension/src/Approvals/ApproveConnection.tsx index 9d396f60f..9775c9484 100644 --- a/apps/extension/src/Approvals/ApproveConnection.tsx +++ b/apps/extension/src/Approvals/ApproveConnection.tsx @@ -1,8 +1,11 @@ import { ActionButton, Alert, GapPatterns, Stack } from "@namada/components"; +import { Chain } from "@namada/types"; import { PageHeader } from "App/Common"; import { ConnectInterfaceResponseMsg } from "background/approvals"; import { useQuery } from "hooks"; import { useRequester } from "hooks/useRequester"; +import { GetChainMsg } from "provider"; +import { useEffect, useState } from "react"; import { Ports } from "router"; import { closeCurrentTab } from "utils"; @@ -10,12 +13,36 @@ export const ApproveConnection: React.FC = () => { const requester = useRequester(); const params = useQuery(); const interfaceOrigin = params.get("interfaceOrigin"); + const chainId = params.get("chainId") || undefined; + const [chain, setChain] = useState(); + + const fetchChain = async (): Promise => { + const chainResponse = await requester.sendMessage( + Ports.Background, + new GetChainMsg() + ); + return chainResponse; + }; + + useEffect(() => { + if (chainId) { + fetchChain() + .then((chain) => { + setChain(chain); + }) + .catch((e) => console.error(e)); + } + }, [chainId]); const handleResponse = async (allowConnection: boolean): Promise => { if (interfaceOrigin) { await requester.sendMessage( Ports.Background, - new ConnectInterfaceResponseMsg(interfaceOrigin, allowConnection) + new ConnectInterfaceResponseMsg( + interfaceOrigin, + allowConnection, + chainId + ) ); await closeCurrentTab(); } @@ -28,6 +55,9 @@ export const ApproveConnection: React.FC = () => { Approve connection for {interfaceOrigin}? + {chainId && chain && chain.chainId !== chainId && ( + Enable signing for {chainId}? + )} handleResponse(true)}> Approve diff --git a/apps/extension/src/background/approvals/handler.ts b/apps/extension/src/background/approvals/handler.ts index f2e253eae..b5c77bc67 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -125,8 +125,8 @@ const handleIsConnectionApprovedMsg: ( 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 ); }; }; diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 9fad70aad..282193910 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.ts b/apps/extension/src/background/approvals/service.ts index 338e70a14..d6f90bc8d 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -184,20 +184,38 @@ 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()) || []; - return approvedOrigins.includes(interfaceOrigin); + const { chainId: currentChainId } = await this.chainService.getChain(); + const isChainIdMatched = chainId ? chainId === currentChainId : true; + + return approvedOrigins.includes(interfaceOrigin) && isChainIdMatched; } - 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 params: Record = { + interfaceOrigin, + }; + + if (chainId) { + params.chainId = chainId; + } if (!alreadyApproved) { - return this.launchApprovalPopup(TopLevelRoute.ApproveConnection, { - interfaceOrigin, - }); + return this.launchApprovalPopup(TopLevelRoute.ApproveConnection, params); } // A resolved promise is implicitly returned here if the origin had @@ -207,13 +225,18 @@ 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 (chainId) { + await this.chainService.updateChain(chainId); + } } catch (e) { resolvers.reject(e); } diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index 4c127f86e..1d48b4ae2 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -14,8 +14,8 @@ import { Signer } from "./Signer"; export class InjectedNamada implements INamada { 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 { diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 8f31f095b..2e24d5290 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -30,10 +30,10 @@ 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) ); } diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index 0a157cf6f..afaec0695 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -118,7 +118,7 @@ export class ApproveConnectInterfaceMsg extends Message { return MessageType.ApproveConnectInterface; } - constructor() { + constructor(public readonly chainId?: string) { super(); } diff --git a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx index 9934178cd..ecccb00f8 100644 --- a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx +++ b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx @@ -1,5 +1,6 @@ import { ActionButton } from "@namada/components"; import { useUntilIntegrationAttached } from "@namada/integrations"; +import { chainParametersAtom } from "atoms/chain"; import { namadaExtensionConnectedAtom } from "atoms/settings"; import { useExtensionConnect } from "hooks/useExtensionConnect"; import { useAtomValue } from "jotai"; @@ -7,12 +8,18 @@ import { useAtomValue } from "jotai"; export const ConnectExtensionButton = (): JSX.Element => { const extensionAttachStatus = useUntilIntegrationAttached(); const isExtensionConnected = useAtomValue(namadaExtensionConnectedAtom); - const { connect } = useExtensionConnect(); + const { data: chain } = useAtomValue(chainParametersAtom); + const chainId = chain?.chainId || ""; + const { connect } = useExtensionConnect("namada", chainId); return ( <> {extensionAttachStatus === "attached" && !isExtensionConnected && ( - + connect(chainId)} + > Connect Keychain )} diff --git a/apps/namadillo/src/hooks/useExtensionConnect.ts b/apps/namadillo/src/hooks/useExtensionConnect.ts index 237eb7712..33169a9e0 100644 --- a/apps/namadillo/src/hooks/useExtensionConnect.ts +++ b/apps/namadillo/src/hooks/useExtensionConnect.ts @@ -7,18 +7,19 @@ import { useEffect } from "react"; type UseConnectOutput = { connectionStatus: ConnectStatus; isConnected: boolean; - connect: () => Promise; + connect: (chainId: string) => Promise; }; export const useExtensionConnect = ( - chainKey: ChainKey = "namada" + chainKey: ChainKey = "namada", + chainId?: string ): UseConnectOutput => { const [connectionStatus, setConnectionStatus] = useAtom( namadaExtensionConnectionStatus ); const [_integration, isConnectingToExtension, withConnection] = - useIntegrationConnection(chainKey); + useIntegrationConnection(chainKey, chainId); useEffect(() => { if (isConnectingToExtension) { @@ -26,11 +27,12 @@ export const useExtensionConnect = ( } }, [isConnectingToExtension]); - const handleConnectExtension = async (): Promise => { + const handleConnectExtension = async (chainId: string): Promise => { if (connectionStatus === "connected") return; withConnection( () => setConnectionStatus("connected"), - () => setConnectionStatus("error") + () => setConnectionStatus("error"), + chainId ); }; diff --git a/packages/integrations/src/Namada.ts b/packages/integrations/src/Namada.ts index 2beb459ec..8d94b4dab 100644 --- a/packages/integrations/src/Namada.ts +++ b/packages/integrations/src/Namada.ts @@ -28,8 +28,8 @@ export default class Namada implements Integration { return !!this._namada; } - public async connect(): Promise { - await this._namada?.connect(); + public async connect(chainId?: string): Promise { + await this._namada?.connect(chainId); } public async disconnect(): Promise { diff --git a/packages/integrations/src/hooks/useIntegration.ts b/packages/integrations/src/hooks/useIntegration.ts index d3c178e20..2674b5c44 100644 --- a/packages/integrations/src/hooks/useIntegration.ts +++ b/packages/integrations/src/hooks/useIntegration.ts @@ -13,7 +13,8 @@ import { ChainKey, ExtensionKey } from "@namada/types"; type ExtensionConnection = ( onSuccess: () => T, - onFail?: () => U + onFail?: () => U, + chainId?: string ) => Promise; type IntegrationFromExtensionKey = @@ -60,21 +61,22 @@ export const useIntegration = ( * Tuple of integration, connection status and connection function. */ export const useIntegrationConnection = ( - chainKey: K + chainKey: K, + chainId?: string ): [ - IntegrationFromChainKey, - boolean, - ExtensionConnection, -] => { + IntegrationFromChainKey, + boolean, + ExtensionConnection, + ] => { const integration = useIntegration(chainKey); const [isConnectingToExtension, setIsConnectingToExtension] = useState(false); const connect: ExtensionConnection = useCallback( - async (onSuccess, onFail) => { + async (onSuccess, onFail, chainId) => { setIsConnectingToExtension(true); try { if (integration.detect()) { - await integration.connect(); + await integration.connect(chainId); await onSuccess(); } } catch { @@ -84,7 +86,7 @@ export const useIntegrationConnection = ( } setIsConnectingToExtension(false); }, - [chainKey] + [chainKey, chainId] ); return [integration, isConnectingToExtension, connect]; diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index 1f36cc479..181f5abcd 100644 --- a/packages/types/src/namada.ts +++ b/packages/types/src/namada.ts @@ -27,7 +27,7 @@ export type BalancesProps = { export interface Namada { accounts(chainId?: string): Promise; - connect(): Promise; + connect(chainId?: string): Promise; disconnect(): Promise; isConnected(): Promise; defaultAccount(chainId?: string): Promise;