From 924304fffe4904dad22ef0f814632aa12a1df106 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Thu, 21 Nov 2024 14:13:39 -0500 Subject: [PATCH] feat: begin porting Namada Keychain integration --- apps/extension/src/provider/InjectedNamada.ts | 14 ++--- apps/extension/src/provider/Namada.ts | 29 +++++---- apps/extension/src/provider/Signer.ts | 36 ----------- apps/extension/src/utils/index.ts | 31 +++++++++- .../src/integrations/NamadaKeychain.ts | 60 +++++++++++++++++++ apps/namadillo/src/integrations/types.ts | 3 + packages/integrations/src/Namada.ts | 6 +- packages/types/src/account.ts | 6 +- packages/types/src/namada.ts | 12 ++-- packages/types/src/signer.ts | 3 - 10 files changed, 124 insertions(+), 76 deletions(-) create mode 100644 apps/namadillo/src/integrations/NamadaKeychain.ts diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index 4c127f86eb..0b0e568f49 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -1,6 +1,6 @@ import { + Account, Chain, - DerivedAccount, Namada as INamada, Signer as ISigner, SignArbitraryProps, @@ -26,16 +26,12 @@ export class InjectedNamada implements INamada { return await InjectedProxy.requestMethod("isConnected"); } - public async accounts(): Promise { - return await InjectedProxy.requestMethod( - "accounts" - ); + public async accounts(): Promise { + return await InjectedProxy.requestMethod("accounts"); } - public async defaultAccount(): Promise { - return await InjectedProxy.requestMethod( - "defaultAccount" - ); + public async defaultAccount(): Promise { + return await InjectedProxy.requestMethod("defaultAccount"); } public async updateDefaultAccount(address: string): Promise { diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 8f31f095b3..76d3da1ad9 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -1,6 +1,6 @@ import { + Account, Chain, - DerivedAccount, Namada as INamada, SignArbitraryProps, SignArbitraryResponse, @@ -9,7 +9,7 @@ import { } from "@namada/types"; import { MessageRequester, Ports } from "router"; -import { toEncodedTx } from "utils"; +import { toEncodedTx, toPublicAccount } from "utils"; import { ApproveConnectInterfaceMsg, ApproveDisconnectInterfaceMsg, @@ -55,18 +55,23 @@ export class Namada implements INamada { ); } - public async accounts(): Promise { - return await this.requester?.sendMessage( - Ports.Background, - new QueryAccountsMsg() - ); + public async accounts(): Promise { + return ( + await this.requester?.sendMessage( + Ports.Background, + new QueryAccountsMsg() + ) + )?.map(toPublicAccount); } - public async defaultAccount(): Promise { - return await this.requester?.sendMessage( - Ports.Background, - new QueryDefaultAccountMsg() - ); + public async defaultAccount(): Promise { + return await this.requester + ?.sendMessage(Ports.Background, new QueryDefaultAccountMsg()) + .then((defaultAccount) => { + if (defaultAccount) { + return toPublicAccount(defaultAccount); + } + }); } public async updateDefaultAccount(address: string): Promise { diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index 0a2a745304..95a74f4fb9 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -1,7 +1,4 @@ -import { chains } from "@namada/chains"; import { - Account, - AccountType, Signer as ISigner, Namada, SignArbitraryResponse, @@ -11,39 +8,6 @@ import { export class Signer implements ISigner { constructor(private readonly _namada: Namada) {} - public async accounts(): Promise { - return (await this._namada.accounts())?.map( - ({ alias, address, type, publicKey, owner }) => ({ - alias, - address, - viewingKey: owner, - chainId: chains.namada.chainId, - type, - publicKey, - isShielded: type === AccountType.ShieldedKeys, - chainKey: "namada", - }) - ); - } - - public async defaultAccount(): Promise { - const account = await this._namada.defaultAccount(); - - if (account) { - const { alias, address, type, publicKey } = account; - - return { - alias, - address, - chainId: chains.namada.chainId, - type, - publicKey, - isShielded: type === AccountType.ShieldedKeys, - chainKey: "namada", - }; - } - } - public async sign( tx: TxProps | TxProps[], signer: string, diff --git a/apps/extension/src/utils/index.ts b/apps/extension/src/utils/index.ts index 9beb6f4167..b691d559ea 100644 --- a/apps/extension/src/utils/index.ts +++ b/apps/extension/src/utils/index.ts @@ -1,5 +1,12 @@ import { fromBase64, toBase64 } from "@cosmjs/encoding"; -import { AccountType, Bip44Path, Path, TxProps } from "@namada/types"; +import { + Account, + AccountType, + Bip44Path, + DerivedAccount, + Path, + TxProps, +} from "@namada/types"; import { v5 as uuid } from "uuid"; import browser from "webextension-polyfill"; @@ -115,3 +122,25 @@ export const isCustomPath = (path: Path): boolean => { } return false; }; + +/** + * Accepts a derived account, returns only values needed for Account + * @param derivedAccount - Derived account type returned from keyring + * @returns Account type for public API + */ +export const toPublicAccount = (derivedAccount: DerivedAccount): Account => { + const { alias, address, type, publicKey, owner } = derivedAccount; + const isShielded = type === AccountType.ShieldedKeys; + const account: Account = { + alias, + address, + type, + isShielded, + }; + if (isShielded) { + account.viewingKey = owner; + } else { + account.publicKey = publicKey; + } + return account; +}; diff --git a/apps/namadillo/src/integrations/NamadaKeychain.ts b/apps/namadillo/src/integrations/NamadaKeychain.ts new file mode 100644 index 0000000000..3c9d0273db --- /dev/null +++ b/apps/namadillo/src/integrations/NamadaKeychain.ts @@ -0,0 +1,60 @@ +import { Signer, WindowWithNamada } from "@namada/types"; +import { ChainRegistryEntry } from "types"; +import { Namada, WalletConnector } from "./types"; + +export class NamadaWalletManager implements WalletConnector { + install(): void { + console.warn( + "Namada is not available. Redirecting to the Namada download page..." + ); + window.open("https://www.namada.net/extension", "_blank"); + } + + private async _get(): Promise { + if ((window as WindowWithNamada).namada) { + return (window as WindowWithNamada).namada; + } + + if (document.readyState === "complete") { + return (window as WindowWithNamada).namada; + } + + return new Promise((resolve) => { + const documentStateChange = (event: Event): void => { + if ( + event.target && + (event.target as Document).readyState === "complete" + ) { + resolve((window as WindowWithNamada).namada); + document.removeEventListener("readystatechange", documentStateChange); + } + }; + + document.addEventListener("readystatechange", documentStateChange); + }); + } + + async get(): Promise { + const namada = await this._get(); + return namada!; + } + + async connect(registry: ChainRegistryEntry): Promise { + const namada = await this.get(); + await namada.connect(registry.chain.chain_id); + } + + async getAddress(): Promise { + const namada = await this.get(); + const defaultAccount = await namada.defaultAccount(); + if (!defaultAccount) { + throw new Error("No accounts found in keychain!"); + } + return defaultAccount.address; + } + + async getSigner(): Promise { + const namada = await this.get(); + return namada.getSigner(); + } +} diff --git a/apps/namadillo/src/integrations/types.ts b/apps/namadillo/src/integrations/types.ts index d0993015fa..275541e4c9 100644 --- a/apps/namadillo/src/integrations/types.ts +++ b/apps/namadillo/src/integrations/types.ts @@ -1,5 +1,8 @@ +import { WindowWithNamada } from "@namada/types"; import { ChainRegistryEntry } from "types"; +export type Namada = WindowWithNamada["namada"]; + export interface WalletConnector { install(): void; get(): unknown; diff --git a/packages/integrations/src/Namada.ts b/packages/integrations/src/Namada.ts index 2beb459ecf..b8374ae702 100644 --- a/packages/integrations/src/Namada.ts +++ b/packages/integrations/src/Namada.ts @@ -47,13 +47,11 @@ export default class Namada implements Integration { public async accounts( chainId?: string ): Promise { - const signer = this._namada?.getSigner(); - return await signer?.accounts(chainId); + return await this._namada?.accounts(chainId); } public async defaultAccount(chainId?: string): Promise { - const signer = this._namada?.getSigner(); - return await signer?.defaultAccount(chainId); + return await this._namada?.defaultAccount(chainId); } public async updateDefaultAccount(address: string): Promise { diff --git a/packages/types/src/account.ts b/packages/types/src/account.ts index 164f442e3e..ecd3a9abd3 100644 --- a/packages/types/src/account.ts +++ b/packages/types/src/account.ts @@ -1,5 +1,3 @@ -import { ChainKey } from "./chain"; - export type Bip44Path = { account: number; change: number; @@ -43,10 +41,8 @@ export type DerivedAccount = { export type Account = Pick< DerivedAccount, - "address" | "alias" | "type" | "publicKey" | "owner" + "address" | "alias" | "type" | "publicKey" > & { - chainId: string; - chainKey: ChainKey; isShielded: boolean; viewingKey?: string; }; diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index 1f36cc4792..1c22fc9aff 100644 --- a/packages/types/src/namada.ts +++ b/packages/types/src/namada.ts @@ -1,4 +1,4 @@ -import { DerivedAccount } from "./account"; +import { Account } from "./account"; import { Chain } from "./chain"; import { SignArbitraryResponse, Signer } from "./signer"; import { TxProps } from "./tx"; @@ -26,11 +26,11 @@ export type BalancesProps = { }; export interface Namada { - accounts(chainId?: string): Promise; - connect(): Promise; - disconnect(): Promise; - isConnected(): Promise; - defaultAccount(chainId?: string): Promise; + accounts(chainId?: string): Promise; + connect(chainId?: string): Promise; + disconnect(chainId?: string): Promise; + isConnected(chainId?: string): Promise; + defaultAccount(chainId?: string): Promise; updateDefaultAccount(address: string): Promise; sign(props: SignProps): Promise; signArbitrary( diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index e2c4d86118..19fa063fa9 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -1,4 +1,3 @@ -import { Account } from "./account"; import { TxProps } from "./tx"; export type SignArbitraryResponse = { @@ -7,8 +6,6 @@ export type SignArbitraryResponse = { }; export interface Signer { - accounts: (chainId?: string) => Promise; - defaultAccount: (chainId?: string) => Promise; sign: ( tx: TxProps | TxProps[], signer: string,