Skip to content

Commit

Permalink
feat: begin adding chain ID to connect approval
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Sep 24, 2024
1 parent 6bd1e33 commit eb38b75
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 38 deletions.
32 changes: 31 additions & 1 deletion apps/extension/src/Approvals/ApproveConnection.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
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";

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<Chain>();

const fetchChain = async (): Promise<Chain> => {
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<void> => {
if (interfaceOrigin) {
await requester.sendMessage(
Ports.Background,
new ConnectInterfaceResponseMsg(interfaceOrigin, allowConnection)
new ConnectInterfaceResponseMsg(
interfaceOrigin,
allowConnection,
chainId
)
);
await closeCurrentTab();
}
Expand All @@ -28,6 +55,9 @@ export const ApproveConnection: React.FC = () => {
<Alert type="warning">
Approve connection for <strong>{interfaceOrigin}</strong>?
</Alert>
{chainId && chain && chain.chainId !== chainId && (
<Alert type="warning">Enable signing for {chainId}?</Alert>
)}
<Stack gap={2}>
<ActionButton onClick={() => handleResponse(true)}>
Approve
Expand Down
9 changes: 5 additions & 4 deletions apps/extension/src/background/approvals/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ const handleIsConnectionApprovedMsg: (
const handleApproveConnectInterfaceMsg: (
service: ApprovalsService
) => InternalHandler<ApproveConnectInterfaceMsg> = (service) => {
return async (_, { origin }) => {
return await service.approveConnection(origin);
return async (_, { origin, chainId }) => {
return await service.approveConnection(origin, chainId);
};
};

Expand All @@ -135,12 +135,13 @@ const handleConnectInterfaceResponseMsg: (
) => InternalHandler<ConnectInterfaceResponseMsg> = (service) => {
return async (
{ senderTabId: popupTabId },
{ interfaceOrigin, allowConnection }
{ interfaceOrigin, allowConnection, chainId }
) => {
return await service.approveConnectionResponse(
popupTabId,
interfaceOrigin,
allowConnection
allowConnection,
chainId
);
};
};
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/src/background/approvals/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ export class ConnectInterfaceResponseMsg extends Message<void> {

constructor(
public readonly interfaceOrigin: string,
public readonly allowConnection: boolean
public readonly allowConnection: boolean,
public readonly chainId?: string
) {
super();
}
Expand Down
39 changes: 31 additions & 8 deletions apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,38 @@ export class ApprovalsService {
resolvers.reject(new Error("Sign Tx rejected"));
}

async isConnectionApproved(interfaceOrigin: string): Promise<boolean> {
async isConnectionApproved(
interfaceOrigin: string,
chainId?: string
): Promise<boolean> {
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<void> {
const alreadyApproved = await this.isConnectionApproved(interfaceOrigin);
async approveConnection(
interfaceOrigin: string,
chainId?: string
): Promise<void> {
const alreadyApproved = await this.isConnectionApproved(
interfaceOrigin,
chainId
);

const params: Record<string, string> = {
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
Expand All @@ -207,13 +225,18 @@ export class ApprovalsService {
async approveConnectionResponse(
popupTabId: number,
interfaceOrigin: string,
allowConnection: boolean
allowConnection: boolean,
chainId?: string
): Promise<void> {
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);
}
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/provider/InjectedNamada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { Signer } from "./Signer";
export class InjectedNamada implements INamada {
constructor(private readonly _version: string) {}

public async connect(): Promise<void> {
return await InjectedProxy.requestMethod<string, void>("connect");
public async connect(chainId?: string): Promise<void> {
return await InjectedProxy.requestMethod<string, void>("connect", chainId);
}

public async disconnect(): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/provider/Namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export class Namada implements INamada {
protected readonly requester?: MessageRequester
) {}

public async connect(): Promise<void> {
public async connect(chainId?: string): Promise<void> {
return await this.requester?.sendMessage(
Ports.Background,
new ApproveConnectInterfaceMsg()
new ApproveConnectInterfaceMsg(chainId)
);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/extension/src/provider/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class ApproveConnectInterfaceMsg extends Message<void> {
return MessageType.ApproveConnectInterface;
}

constructor() {
constructor(public readonly chainId?: string) {
super();
}

Expand Down
11 changes: 9 additions & 2 deletions apps/namadillo/src/App/Common/ConnectExtensionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
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";

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 && (
<ActionButton backgroundColor="yellow" size="sm" onClick={connect}>
<ActionButton
backgroundColor="yellow"
size="sm"
onClick={() => connect(chainId)}
>
Connect Keychain
</ActionButton>
)}
Expand Down
12 changes: 7 additions & 5 deletions apps/namadillo/src/hooks/useExtensionConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,32 @@ import { useEffect } from "react";
type UseConnectOutput = {
connectionStatus: ConnectStatus;
isConnected: boolean;
connect: () => Promise<void>;
connect: (chainId: string) => Promise<void>;
};

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) {
setConnectionStatus("connecting");
}
}, [isConnectingToExtension]);

const handleConnectExtension = async (): Promise<void> => {
const handleConnectExtension = async (chainId: string): Promise<void> => {
if (connectionStatus === "connected") return;
withConnection(
() => setConnectionStatus("connected"),
() => setConnectionStatus("error")
() => setConnectionStatus("error"),
chainId
);
};

Expand Down
4 changes: 2 additions & 2 deletions packages/integrations/src/Namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export default class Namada implements Integration<Account, Signer> {
return !!this._namada;
}

public async connect(): Promise<void> {
await this._namada?.connect();
public async connect(chainId?: string): Promise<void> {
await this._namada?.connect(chainId);
}

public async disconnect(): Promise<void> {
Expand Down
20 changes: 11 additions & 9 deletions packages/integrations/src/hooks/useIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { ChainKey, ExtensionKey } from "@namada/types";

type ExtensionConnection<T, U> = (
onSuccess: () => T,
onFail?: () => U
onFail?: () => U,
chainId?: string
) => Promise<void>;

type IntegrationFromExtensionKey<K extends ExtensionKey> =
Expand Down Expand Up @@ -60,21 +61,22 @@ export const useIntegration = <K extends ChainKey>(
* Tuple of integration, connection status and connection function.
*/
export const useIntegrationConnection = <TSuccess, TFail, K extends ChainKey>(
chainKey: K
chainKey: K,
chainId?: string
): [
IntegrationFromChainKey<K>,
boolean,
ExtensionConnection<TSuccess, TFail>,
] => {
IntegrationFromChainKey<K>,
boolean,
ExtensionConnection<TSuccess, TFail>,
] => {
const integration = useIntegration(chainKey);
const [isConnectingToExtension, setIsConnectingToExtension] = useState(false);

const connect: ExtensionConnection<TSuccess, TFail> = useCallback(
async (onSuccess, onFail) => {
async (onSuccess, onFail, chainId) => {
setIsConnectingToExtension(true);
try {
if (integration.detect()) {
await integration.connect();
await integration.connect(chainId);
await onSuccess();
}
} catch {
Expand All @@ -84,7 +86,7 @@ export const useIntegrationConnection = <TSuccess, TFail, K extends ChainKey>(
}
setIsConnectingToExtension(false);
},
[chainKey]
[chainKey, chainId]
);

return [integration, isConnectingToExtension, connect];
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type BalancesProps = {

export interface Namada {
accounts(chainId?: string): Promise<DerivedAccount[] | undefined>;
connect(): Promise<void>;
connect(chainId?: string): Promise<void>;
disconnect(): Promise<void>;
isConnected(): Promise<boolean | undefined>;
defaultAccount(chainId?: string): Promise<DerivedAccount | undefined>;
Expand Down

0 comments on commit eb38b75

Please sign in to comment.