Skip to content

Commit

Permalink
feat: add switching account approval step (#1109)
Browse files Browse the repository at this point in the history
  • Loading branch information
euharrison authored Sep 14, 2024
1 parent fa6ee91 commit 94b6ec6
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 34 deletions.
5 changes: 5 additions & 0 deletions apps/extension/src/Approvals/Approvals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ApproveConnection } from "./ApproveConnection";
import { ApproveDisconnection } from "./ApproveDisconnection";
import { ApproveSignArbitrary } from "./ApproveSignArbitrary";
import { ApproveSignTx } from "./ApproveSignTx";
import { ApproveUpdateDefaultAccount } from "./ApproveUpdateDefaultAccount";
import { ConfirmSignature } from "./ConfirmSignArbitrary";
import { ConfirmSignLedgerTx } from "./ConfirmSignLedgerTx";
import { ConfirmSignTx } from "./ConfirmSignTx";
Expand Down Expand Up @@ -70,6 +71,10 @@ export const Approvals: React.FC = () => {
path={TopLevelRoute.ApproveDisconnection}
element={<ApproveDisconnection />}
/>
<Route
path={TopLevelRoute.ApproveUpdateDefaultAccount}
element={<ApproveUpdateDefaultAccount />}
/>
<Route
path={`${TopLevelRoute.ApproveSignArbitrary}/:signer`}
element={
Expand Down
70 changes: 70 additions & 0 deletions apps/extension/src/Approvals/ApproveUpdateDefaultAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
ActionButton,
Alert,
Checkbox,
GapPatterns,
Stack,
} from "@namada/components";
import { shortenAddress } from "@namada/utils";
import { PageHeader } from "App/Common";
import { SubmitUpdateDefaultAccountMsg } from "background/approvals";
import clsx from "clsx";
import { useQuery } from "hooks";
import { useRequester } from "hooks/useRequester";
import { Ports } from "router";
import { closeCurrentTab } from "utils";

export const ApproveUpdateDefaultAccount: React.FC = () => {
const requester = useRequester();
const params = useQuery();
const address = params.get("address");
const alias = params.get("alias");

const handleResponse = async (approved: boolean): Promise<void> => {
if (address && approved) {
await requester.sendMessage(
Ports.Background,
new SubmitUpdateDefaultAccountMsg(address)
);
}
await closeCurrentTab();
};

return (
<Stack full gap={GapPatterns.TitleContent} className="pt-4 pb-8">
<PageHeader title="Approve Request" />
<Stack full className="justify-between" gap={12}>
<div className="text-yellow">
<Alert type="warning">Approve update default account?</Alert>
<div
className={clsx(
"flex items-center gap-3 mt-4",
"px-4 py-3 bg-neutral-900 rounded-md"
)}
>
<div className="text-yellow">
<Checkbox checked readOnly />
</div>
<div className="leading-[1.2]">
<div className="text-base text-white">{alias}</div>
<p className="text-sm text-neutral-400 font-medium">
{address && shortenAddress(address, 24)}
</p>
</div>
</div>
</div>
<Stack gap={2}>
<ActionButton onClick={() => handleResponse(true)}>
Approve
</ActionButton>
<ActionButton
outlineColor="yellow"
onClick={() => handleResponse(false)}
>
Reject
</ActionButton>
</Stack>
</Stack>
</Stack>
);
};
3 changes: 3 additions & 0 deletions apps/extension/src/Approvals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export enum TopLevelRoute {
ApproveConnection = "/approve-connection",
ApproveDisconnection = "/approve-disconnection",

// Update default account approval
ApproveUpdateDefaultAccount = "/approve-update-default-account",

// Sign Tx approval
ApproveSignTx = "/approve-sign-tx",
ApproveSignTxDetails = "/approve-sign-tx-details",
Expand Down
28 changes: 28 additions & 0 deletions apps/extension/src/background/approvals/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ApproveDisconnectInterfaceMsg,
ApproveSignArbitraryMsg,
ApproveSignTxMsg,
ApproveUpdateDefaultAccountMsg,
IsConnectionApprovedMsg,
} from "provider";
import { Env, Handler, InternalHandler, Message } from "router";
Expand All @@ -18,6 +19,7 @@ import {
SubmitApprovedSignArbitraryMsg,
SubmitApprovedSignLedgerTxMsg,
SubmitApprovedSignTxMsg,
SubmitUpdateDefaultAccountMsg,
} from "./messages";
import { ApprovalsService } from "./service";

Expand Down Expand Up @@ -54,6 +56,16 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => {
env,
msg as RevokeConnectionMsg
);
case ApproveUpdateDefaultAccountMsg:
return handleApproveUpdateDefaultAccountMsg(service)(
env,
msg as ApproveUpdateDefaultAccountMsg
);
case SubmitUpdateDefaultAccountMsg:
return handleSubmitUpdateDefaultAccountMsg(service)(
env,
msg as SubmitUpdateDefaultAccountMsg
);
case ApproveSignTxMsg:
return handleApproveSignTxMsg(service)(env, msg as ApproveSignTxMsg);
case RejectSignTxMsg:
Expand Down Expand Up @@ -164,6 +176,22 @@ const handleRevokeConnectionMsg: (
};
};

const handleApproveUpdateDefaultAccountMsg: (
service: ApprovalsService
) => InternalHandler<ApproveUpdateDefaultAccountMsg> = (service) => {
return async (_, { address }) => {
return await service.approveUpdateDefaultAccount(address);
};
};

const handleSubmitUpdateDefaultAccountMsg: (
service: ApprovalsService
) => InternalHandler<SubmitUpdateDefaultAccountMsg> = (service) => {
return async ({ senderTabId: popupTabId }, { address }) => {
return await service.submitUpdateDefaultAccount(popupTabId, address);
};
};

const handleApproveSignTxMsg: (
service: ApprovalsService
) => InternalHandler<ApproveSignTxMsg> = (service) => {
Expand Down
4 changes: 4 additions & 0 deletions apps/extension/src/background/approvals/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ApproveDisconnectInterfaceMsg,
ApproveSignArbitraryMsg,
ApproveSignTxMsg,
ApproveUpdateDefaultAccountMsg,
IsConnectionApprovedMsg,
} from "provider";
import { Router } from "router";
Expand All @@ -18,6 +19,7 @@ import {
SubmitApprovedSignArbitraryMsg,
SubmitApprovedSignLedgerTxMsg,
SubmitApprovedSignTxMsg,
SubmitUpdateDefaultAccountMsg,
} from "./messages";

import { ROUTE } from "./constants";
Expand All @@ -38,6 +40,8 @@ export function init(router: Router, service: ApprovalsService): void {
router.registerMessage(ApproveDisconnectInterfaceMsg);
router.registerMessage(DisconnectInterfaceResponseMsg);
router.registerMessage(RevokeConnectionMsg);
router.registerMessage(ApproveUpdateDefaultAccountMsg);
router.registerMessage(SubmitUpdateDefaultAccountMsg);
router.registerMessage(QueryTxDetailsMsg);
router.registerMessage(QuerySignArbitraryDataMsg);
router.registerMessage(QueryPendingTxBytesMsg);
Expand Down
23 changes: 23 additions & 0 deletions apps/extension/src/background/approvals/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum MessageType {
ConnectInterfaceResponse = "connect-interface-response",
DisconnectInterfaceResponse = "disconnect-interface-response",
RevokeConnection = "revoke-connection",
SubmitUpdateDefaultAccount = "submit-update-default-account",
QueryTxDetails = "query-tx-details",
QuerySignArbitraryData = "query-sign-arbitrary-data",
QueryPendingTxBytes = "query-pending-tx-bytes",
Expand Down Expand Up @@ -211,6 +212,28 @@ export class RevokeConnectionMsg extends Message<void> {
}
}

export class SubmitUpdateDefaultAccountMsg extends Message<void> {
public static type(): MessageType {
return MessageType.SubmitUpdateDefaultAccount;
}

constructor(public readonly address: string) {
super();
}

validate(): void {
validateProps(this, ["address"]);
}

route(): string {
return ROUTE;
}

type(): string {
return SubmitUpdateDefaultAccountMsg.type();
}
}

export class QueryTxDetailsMsg extends Message<TxDetails[]> {
public static type(): MessageType {
return MessageType.QueryTxDetails;
Expand Down
30 changes: 29 additions & 1 deletion apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export class ApprovalsService {
protected readonly broadcaster: ExtensionBroadcaster
) {
browser.tabs.onRemoved.addListener((tabId) => {
const resolver = this.getResolver(tabId);
let resolver: Resolver | undefined;
try {
resolver = this.getResolver(tabId);
} catch {
// do nothing if not found as it was resolved by the event handler
}
if (resolver) {
resolver.reject(new Error("Window closed"));
this.removeResolver(tabId);
Expand Down Expand Up @@ -255,6 +260,29 @@ export class ApprovalsService {
await this.broadcaster.revokeConnection();
}

async approveUpdateDefaultAccount(address: string): Promise<void> {
const account = await this.keyRingService.queryAccountDetails(address);

return this.launchApprovalPopup(TopLevelRoute.ApproveUpdateDefaultAccount, {
address,
alias: account?.alias ?? "",
});
}

async submitUpdateDefaultAccount(
popupTabId: number,
address: string
): Promise<void> {
const resolvers = this.getResolver(popupTabId);

try {
await this.keyRingService.updateDefaultAccount(address);
} catch (e) {
resolvers.reject(e);
}
resolvers.resolve();
}

async queryTxDetails(msgId: string): Promise<TxDetails[]> {
const pendingTx = await this.txStore.get(msgId);

Expand Down
15 changes: 0 additions & 15 deletions apps/extension/src/background/keyring/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
CheckDurabilityMsg,
QueryAccountsMsg,
QueryDefaultAccountMsg,
UpdateDefaultAccountMsg,
VerifyArbitraryMsg,
} from "provider/messages";
import { Env, Handler, InternalHandler, Message } from "router";
Expand Down Expand Up @@ -64,11 +63,6 @@ export const getHandler: (service: KeyRingService) => Handler = (service) => {
env,
msg as QueryDefaultAccountMsg
);
case UpdateDefaultAccountMsg:
return handleUpdateDefaultAccountMsg(service)(
env,
msg as UpdateDefaultAccountMsg
);
case QueryParentAccountsMsg:
return handleQueryParentAccountsMsg(service)(
env,
Expand Down Expand Up @@ -187,15 +181,6 @@ const handleQueryDefaultAccountMsg: (
};
};

const handleUpdateDefaultAccountMsg: (
service: KeyRingService
) => InternalHandler<UpdateDefaultAccountMsg> = (service) => {
return async (_, msg) => {
const { address } = msg;
return await service.updateDefaultAccount(address);
};
};

const handleQueryParentAccountsMsg: (
service: KeyRingService
) => InternalHandler<QueryParentAccountsMsg> = (service) => {
Expand Down
2 changes: 0 additions & 2 deletions apps/extension/src/background/keyring/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
CheckDurabilityMsg,
QueryAccountsMsg,
QueryDefaultAccountMsg,
UpdateDefaultAccountMsg,
VerifyArbitraryMsg,
} from "provider/messages";
import { Router } from "router";
Expand Down Expand Up @@ -30,7 +29,6 @@ export function init(router: Router, service: KeyRingService): void {
router.registerMessage(GetActiveAccountMsg);
router.registerMessage(QueryAccountsMsg);
router.registerMessage(QueryDefaultAccountMsg);
router.registerMessage(UpdateDefaultAccountMsg);
router.registerMessage(QueryParentAccountsMsg);
router.registerMessage(QueryAccountDetailsMsg);
router.registerMessage(SaveAccountSecretMsg);
Expand Down
3 changes: 1 addition & 2 deletions apps/extension/src/background/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,7 @@ export class KeyRing {
}

public async updateDefaultAccount(address: string): Promise<void> {
const accounts = await this.queryAllAccounts();
const account = accounts.find((acc) => acc.address === address);
const account = await this.queryAccountDetails(address);
if (!account) {
throw new Error(`Account with address ${address} not found.`);
}
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 @@ -15,12 +15,12 @@ import {
ApproveDisconnectInterfaceMsg,
ApproveSignArbitraryMsg,
ApproveSignTxMsg,
ApproveUpdateDefaultAccountMsg,
CheckDurabilityMsg,
GetChainMsg,
IsConnectionApprovedMsg,
QueryAccountsMsg,
QueryDefaultAccountMsg,
UpdateDefaultAccountMsg,
VerifyArbitraryMsg,
} from "./messages";

Expand Down Expand Up @@ -72,7 +72,7 @@ export class Namada implements INamada {
public async updateDefaultAccount(address: string): Promise<void> {
return await this.requester?.sendMessage(
Ports.Background,
new UpdateDefaultAccountMsg(address)
new ApproveUpdateDefaultAccountMsg(address)
);
}

Expand Down
10 changes: 5 additions & 5 deletions apps/extension/src/provider/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum MessageType {
ApproveDisconnectInterface = "approve-disconnect-interface",
QueryAccounts = "query-accounts",
QueryDefaultAccount = "query-default-account",
UpdateDefaultAccount = "update-default-account",
ApproveUpdateDefaultAccount = "approve-update-default-account",
EncodeRevealPublicKey = "encode-reveal-public-key",
GetChain = "get-chain",
GetChains = "get-chains",
Expand Down Expand Up @@ -231,9 +231,9 @@ export class QueryDefaultAccountMsg extends Message<
}
}

export class UpdateDefaultAccountMsg extends Message<void> {
export class ApproveUpdateDefaultAccountMsg extends Message<void> {
public static type(): MessageType {
return MessageType.UpdateDefaultAccount;
return MessageType.ApproveUpdateDefaultAccount;
}

constructor(public readonly address: string) {
Expand All @@ -245,11 +245,11 @@ export class UpdateDefaultAccountMsg extends Message<void> {
}

route(): string {
return Route.KeyRing;
return Route.Approvals;
}

type(): string {
return UpdateDefaultAccountMsg.type();
return ApproveUpdateDefaultAccountMsg.type();
}
}

Expand Down
Loading

1 comment on commit 94b6ec6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.