From 7e78e0efc71a06652f8d83fb11c11b3e7712cb32 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 | 24 +++++++++++--- 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 | 9 +++++- .../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, 85 insertions(+), 30 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 f81f8b27b..1b9a14b3d 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -113,8 +113,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); }; }; @@ -123,12 +123,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 d4b8057e3..8433401f3 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -146,7 +146,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 ca167473d..8a7d96458 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -186,13 +186,22 @@ export class ApprovalsService { return approvedOrigins.includes(interfaceOrigin); } - async approveConnection(interfaceOrigin: string): Promise { + async approveConnection( + interfaceOrigin: string, + chainId?: string + ): Promise { const alreadyApproved = await this.isConnectionApproved(interfaceOrigin); + 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 @@ -202,13 +211,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 6735fa520..d7b31cf0d 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 d53f3f7ee..ca93ebd31 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 3098cd12d..233122339 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 { data: chain } = useAtomValue(chainParametersAtom); + const chainId = chain?.chainId || ""; const { connect } = useExtensionConnect(); return ( <> {extensionAttachStatus === "attached" && !isExtensionConnected && ( - + connect(chainId)} + > Connect Extension )} diff --git a/apps/namadillo/src/hooks/useExtensionConnect.ts b/apps/namadillo/src/hooks/useExtensionConnect.ts index 237eb7712..876036965 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 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..7c1ef0679 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 = @@ -62,19 +63,19 @@ export const useIntegration = ( export const useIntegrationConnection = ( chainKey: K ): [ - 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 { 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;