From f127d4fc7e626c91334c54e826a273029074e9bf 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 | 6 ++- .../src/hooks/useExtensionConnect.ts | 7 ++-- packages/integrations/src/Namada.ts | 4 +- .../integrations/src/hooks/useIntegration.ts | 15 +++---- packages/types/src/namada.ts | 2 +- 12 files changed, 94 insertions(+), 33 deletions(-) diff --git a/apps/extension/src/Approvals/ApproveConnection.tsx b/apps/extension/src/Approvals/ApproveConnection.tsx index 9d396f60f6..9775c94842 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 f2e253eae6..b5c77bc67f 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 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.ts b/apps/extension/src/background/approvals/service.ts index 338e70a148..d6f90bc8d5 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 4c127f86eb..1d48b4ae21 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 8f31f095b3..2e24d5290d 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 0a157cf6f6..afaec06954 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 c6a5e50aa0..344d60093f 100644 --- a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx +++ b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx @@ -13,7 +13,11 @@ export const ConnectExtensionButton = (): JSX.Element => { return ( <> {extensionAttachStatus === "attached" && !isConnected && ( - + connect("todo")} + > Connect Keychain )} diff --git a/apps/namadillo/src/hooks/useExtensionConnect.ts b/apps/namadillo/src/hooks/useExtensionConnect.ts index 1e5b7d781b..9a4c844b0c 100644 --- a/apps/namadillo/src/hooks/useExtensionConnect.ts +++ b/apps/namadillo/src/hooks/useExtensionConnect.ts @@ -7,7 +7,7 @@ import { useEffect } from "react"; type UseConnectOutput = { connectionStatus: ConnectStatus; isConnected: boolean; - connect: () => Promise; + connect: (chainId: string) => Promise; }; export const useExtensionConnect = ( @@ -26,11 +26,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 2beb459ecf..8d94b4dab4 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 d4db23e318..abbdc30912 100644 --- a/packages/integrations/src/hooks/useIntegration.ts +++ b/packages/integrations/src/hooks/useIntegration.ts @@ -16,7 +16,8 @@ import { type ExtensionConnection = ( onSuccess: () => T, - onFail?: () => U + onFail?: () => U, + chainId?: string ) => Promise; export const IntegrationsContext = createContext(integrations); @@ -49,19 +50,19 @@ export const useIntegrationConnection = < >( extensionKey: K ): [ - IntegrationFromExtensionKey, - boolean, - ExtensionConnection, -] => { + IntegrationFromExtensionKey, + boolean, + ExtensionConnection, + ] => { const integration = useIntegration(extensionKey); 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 { diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index 1f36cc4792..181f5abcdf 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;