Skip to content

Commit

Permalink
feat: begin porting Namada Keychain integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Nov 22, 2024
1 parent db40ff0 commit 924304f
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 76 deletions.
14 changes: 5 additions & 9 deletions apps/extension/src/provider/InjectedNamada.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
Account,
Chain,
DerivedAccount,
Namada as INamada,
Signer as ISigner,
SignArbitraryProps,
Expand All @@ -26,16 +26,12 @@ export class InjectedNamada implements INamada {
return await InjectedProxy.requestMethod<string, boolean>("isConnected");
}

public async accounts(): Promise<DerivedAccount[]> {
return await InjectedProxy.requestMethod<string, DerivedAccount[]>(
"accounts"
);
public async accounts(): Promise<Account[]> {
return await InjectedProxy.requestMethod<string, Account[]>("accounts");
}

public async defaultAccount(): Promise<DerivedAccount> {
return await InjectedProxy.requestMethod<string, DerivedAccount>(
"defaultAccount"
);
public async defaultAccount(): Promise<Account> {
return await InjectedProxy.requestMethod<string, Account>("defaultAccount");
}

public async updateDefaultAccount(address: string): Promise<void> {
Expand Down
29 changes: 17 additions & 12 deletions apps/extension/src/provider/Namada.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
Account,
Chain,
DerivedAccount,
Namada as INamada,
SignArbitraryProps,
SignArbitraryResponse,
Expand All @@ -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,
Expand Down Expand Up @@ -55,18 +55,23 @@ export class Namada implements INamada {
);
}

public async accounts(): Promise<DerivedAccount[] | undefined> {
return await this.requester?.sendMessage(
Ports.Background,
new QueryAccountsMsg()
);
public async accounts(): Promise<Account[] | undefined> {
return (
await this.requester?.sendMessage(
Ports.Background,
new QueryAccountsMsg()
)
)?.map(toPublicAccount);
}

public async defaultAccount(): Promise<DerivedAccount | undefined> {
return await this.requester?.sendMessage(
Ports.Background,
new QueryDefaultAccountMsg()
);
public async defaultAccount(): Promise<Account | undefined> {
return await this.requester
?.sendMessage(Ports.Background, new QueryDefaultAccountMsg())
.then((defaultAccount) => {
if (defaultAccount) {
return toPublicAccount(defaultAccount);
}
});
}

public async updateDefaultAccount(address: string): Promise<void> {
Expand Down
36 changes: 0 additions & 36 deletions apps/extension/src/provider/Signer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { chains } from "@namada/chains";
import {
Account,
AccountType,
Signer as ISigner,
Namada,
SignArbitraryResponse,
Expand All @@ -11,39 +8,6 @@ import {
export class Signer implements ISigner {
constructor(private readonly _namada: Namada) {}

public async accounts(): Promise<Account[] | undefined> {
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<Account | undefined> {
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,
Expand Down
31 changes: 30 additions & 1 deletion apps/extension/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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;
};
60 changes: 60 additions & 0 deletions apps/namadillo/src/integrations/NamadaKeychain.ts
Original file line number Diff line number Diff line change
@@ -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<Namada | undefined> {
if ((window as WindowWithNamada).namada) {
return (window as WindowWithNamada).namada;
}

if (document.readyState === "complete") {
return (window as WindowWithNamada).namada;
}

return new Promise<Namada | undefined>((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<Namada> {
const namada = await this._get();
return namada!;
}

async connect(registry: ChainRegistryEntry): Promise<void> {
const namada = await this.get();
await namada.connect(registry.chain.chain_id);
}

async getAddress(): Promise<string> {
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<Signer> {
const namada = await this.get();
return namada.getSigner();
}
}
3 changes: 3 additions & 0 deletions apps/namadillo/src/integrations/types.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
6 changes: 2 additions & 4 deletions packages/integrations/src/Namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,11 @@ export default class Namada implements Integration<Account, Signer> {
public async accounts(
chainId?: string
): Promise<readonly Account[] | undefined> {
const signer = this._namada?.getSigner();
return await signer?.accounts(chainId);
return await this._namada?.accounts(chainId);
}

public async defaultAccount(chainId?: string): Promise<Account | undefined> {
const signer = this._namada?.getSigner();
return await signer?.defaultAccount(chainId);
return await this._namada?.defaultAccount(chainId);
}

public async updateDefaultAccount(address: string): Promise<void> {
Expand Down
6 changes: 1 addition & 5 deletions packages/types/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ChainKey } from "./chain";

export type Bip44Path = {
account: number;
change: number;
Expand Down Expand Up @@ -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;
};
12 changes: 6 additions & 6 deletions packages/types/src/namada.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -26,11 +26,11 @@ export type BalancesProps = {
};

export interface Namada {
accounts(chainId?: string): Promise<DerivedAccount[] | undefined>;
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): Promise<boolean | undefined>;
defaultAccount(chainId?: string): Promise<DerivedAccount | undefined>;
accounts(chainId?: string): Promise<Account[] | undefined>;
connect(chainId?: string): Promise<void>;
disconnect(chainId?: string): Promise<void>;
isConnected(chainId?: string): Promise<boolean | undefined>;
defaultAccount(chainId?: string): Promise<Account | undefined>;
updateDefaultAccount(address: string): Promise<void>;
sign(props: SignProps): Promise<Uint8Array[] | undefined>;
signArbitrary(
Expand Down
3 changes: 0 additions & 3 deletions packages/types/src/signer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Account } from "./account";
import { TxProps } from "./tx";

export type SignArbitraryResponse = {
Expand All @@ -7,8 +6,6 @@ export type SignArbitraryResponse = {
};

export interface Signer {
accounts: (chainId?: string) => Promise<Account[] | undefined>;
defaultAccount: (chainId?: string) => Promise<Account | undefined>;
sign: (
tx: TxProps | TxProps[],
signer: string,
Expand Down

0 comments on commit 924304f

Please sign in to comment.