Skip to content

Commit b09a16e

Browse files
authored
feat/1295 - Update Namada Keychain integration (#1298)
* feat: begin porting Namada Keychain integration * fix: tests and begin hooking up chainId * feat: remove isShielded, update as needed * feat: make specific hook for namada keychain * feat: hooking up new hook for namada keychain * feat: remove dependency on integrations package * feat: provide chainId, allow approval to enable signing on chainId * fix: fix tests * fix: clean-up
1 parent d96fbbc commit b09a16e

File tree

36 files changed

+383
-221
lines changed

36 files changed

+383
-221
lines changed

apps/extension/src/Approvals/ApproveConnection.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ export const ApproveConnection: React.FC = () => {
1010
const requester = useRequester();
1111
const params = useQuery();
1212
const interfaceOrigin = params.get("interfaceOrigin");
13+
const chainId = params.get("chainId");
1314

1415
const handleResponse = async (allowConnection: boolean): Promise<void> => {
1516
if (interfaceOrigin) {
1617
await requester.sendMessage(
1718
Ports.Background,
18-
new ConnectInterfaceResponseMsg(interfaceOrigin, allowConnection)
19+
new ConnectInterfaceResponseMsg(
20+
interfaceOrigin,
21+
allowConnection,
22+
chainId || undefined
23+
)
1924
);
2025
await closeCurrentTab();
2126
}
@@ -26,7 +31,14 @@ export const ApproveConnection: React.FC = () => {
2631
<PageHeader title="Approve Request" />
2732
<Stack full className="justify-between" gap={12}>
2833
<Alert type="warning">
29-
Approve connection for <strong>{interfaceOrigin}</strong>?
34+
Approve connection for <strong>{interfaceOrigin}</strong>
35+
{chainId && (
36+
<>
37+
{" "}
38+
and enable signing for <strong>{chainId}</strong>
39+
</>
40+
)}
41+
?
3042
</Alert>
3143
<Stack gap={2}>
3244
<ActionButton onClick={() => handleResponse(true)}>

apps/extension/src/background/approvals/handler.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,16 @@ describe("approvals handler", () => {
6868
handler(env, approveTxMsg);
6969
expect(service.approveSignTx).toBeCalled();
7070

71+
const chainId = "chain-id";
7172
const rejectTxMsg = new RejectSignTxMsg("msgId");
7273
handler(env, rejectTxMsg);
7374
expect(service.rejectSignTx).toBeCalled();
7475

75-
const isConnectionApprovedMsg = new IsConnectionApprovedMsg();
76+
const isConnectionApprovedMsg = new IsConnectionApprovedMsg(chainId);
7677
handler(env, isConnectionApprovedMsg);
7778
expect(service.isConnectionApproved).toBeCalled();
7879

79-
const approveConnectInterfaceMsg = new ApproveConnectInterfaceMsg();
80+
const approveConnectInterfaceMsg = new ApproveConnectInterfaceMsg(chainId);
8081
handler(env, approveConnectInterfaceMsg);
8182
expect(service.approveConnection).toBeCalled();
8283

apps/extension/src/background/approvals/handler.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,16 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => {
117117
const handleIsConnectionApprovedMsg: (
118118
service: ApprovalsService
119119
) => InternalHandler<IsConnectionApprovedMsg> = (service) => {
120-
return async (_, { origin }) => {
121-
return await service.isConnectionApproved(origin);
120+
return async (_, { origin, chainId }) => {
121+
return await service.isConnectionApproved(origin, chainId);
122122
};
123123
};
124124

125125
const handleApproveConnectInterfaceMsg: (
126126
service: ApprovalsService
127127
) => InternalHandler<ApproveConnectInterfaceMsg> = (service) => {
128-
return async (_, { origin }) => {
129-
return await service.approveConnection(origin);
128+
return async (_, { origin, chainId }) => {
129+
return await service.approveConnection(origin, chainId);
130130
};
131131
};
132132

@@ -135,21 +135,22 @@ const handleConnectInterfaceResponseMsg: (
135135
) => InternalHandler<ConnectInterfaceResponseMsg> = (service) => {
136136
return async (
137137
{ senderTabId: popupTabId },
138-
{ interfaceOrigin, allowConnection }
138+
{ interfaceOrigin, allowConnection, chainId }
139139
) => {
140140
return await service.approveConnectionResponse(
141141
popupTabId,
142142
interfaceOrigin,
143-
allowConnection
143+
allowConnection,
144+
chainId
144145
);
145146
};
146147
};
147148

148149
const handleApproveDisconnectInterfaceMsg: (
149150
service: ApprovalsService
150151
) => InternalHandler<ApproveDisconnectInterfaceMsg> = (service) => {
151-
return async (_, { origin }) => {
152-
return await service.approveDisconnection(origin);
152+
return async (_, { origin, chainId }) => {
153+
return await service.approveDisconnection(origin, chainId);
153154
};
154155
};
155156

apps/extension/src/background/approvals/messages.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ export class ConnectInterfaceResponseMsg extends Message<void> {
147147

148148
constructor(
149149
public readonly interfaceOrigin: string,
150-
public readonly allowConnection: boolean
150+
public readonly allowConnection: boolean,
151+
public readonly chainId?: string
151152
) {
152153
super();
153154
}

apps/extension/src/background/approvals/service.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ describe("approvals service", () => {
281281
{ interfaceOrigin }
282282
);
283283
expect(service.isConnectionApproved).toHaveBeenCalledWith(
284-
interfaceOrigin
284+
interfaceOrigin,
285+
undefined
285286
);
286287
await expect(promise).resolves.toBeDefined();
287288
});
@@ -371,7 +372,8 @@ describe("approvals service", () => {
371372
{ interfaceOrigin }
372373
);
373374
expect(service.isConnectionApproved).toHaveBeenCalledWith(
374-
interfaceOrigin
375+
interfaceOrigin,
376+
undefined
375377
);
376378
await expect(promise).resolves.toBeDefined();
377379
});

apps/extension/src/background/approvals/service.ts

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,48 @@ export class ApprovalsService {
185185
resolvers.reject(new Error("Sign Tx rejected"));
186186
}
187187

188-
async isConnectionApproved(interfaceOrigin: string): Promise<boolean> {
188+
async isConnectionApproved(
189+
interfaceOrigin: string,
190+
chainId?: string
191+
): Promise<boolean> {
189192
const approvedOrigins =
190193
(await this.localStorage.getApprovedOrigins()) || [];
191194

195+
if (chainId) {
196+
const currentChainId = await this.chainService.getChain();
197+
if (chainId !== currentChainId) {
198+
return false;
199+
}
200+
}
201+
192202
return approvedOrigins.includes(interfaceOrigin);
193203
}
194204

195-
async approveConnection(interfaceOrigin: string): Promise<void> {
196-
const alreadyApproved = await this.isConnectionApproved(interfaceOrigin);
205+
async approveConnection(
206+
interfaceOrigin: string,
207+
chainId?: string
208+
): Promise<void> {
209+
const alreadyApproved = await this.isConnectionApproved(
210+
interfaceOrigin,
211+
chainId
212+
);
213+
214+
const approveConnectionPopupProps: {
215+
interfaceOrigin: string;
216+
chainId?: string;
217+
} = {
218+
interfaceOrigin,
219+
};
220+
221+
if (chainId) {
222+
approveConnectionPopupProps["chainId"] = chainId;
223+
}
197224

198225
if (!alreadyApproved) {
199-
return this.launchApprovalPopup(TopLevelRoute.ApproveConnection, {
200-
interfaceOrigin,
201-
});
226+
return this.launchApprovalPopup(
227+
TopLevelRoute.ApproveConnection,
228+
approveConnectionPopupProps
229+
);
202230
}
203231

204232
// A resolved promise is implicitly returned here if the origin had
@@ -208,13 +236,26 @@ export class ApprovalsService {
208236
async approveConnectionResponse(
209237
popupTabId: number,
210238
interfaceOrigin: string,
211-
allowConnection: boolean
239+
allowConnection: boolean,
240+
chainId?: string
212241
): Promise<void> {
213242
const resolvers = this.getResolver(popupTabId);
214243

215244
if (allowConnection) {
216245
try {
217-
await this.localStorage.addApprovedOrigin(interfaceOrigin);
246+
if (
247+
!(await this.localStorage.getApprovedOrigins())?.includes(
248+
interfaceOrigin
249+
)
250+
) {
251+
// Add approved origin if it hasn't been added
252+
await this.localStorage.addApprovedOrigin(interfaceOrigin);
253+
}
254+
255+
if (chainId) {
256+
// Set approved signing chainId
257+
await this.chainService.updateChain(chainId);
258+
}
218259
} catch (e) {
219260
resolvers.reject(e);
220261
}
@@ -224,8 +265,14 @@ export class ApprovalsService {
224265
}
225266
}
226267

227-
async approveDisconnection(interfaceOrigin: string): Promise<void> {
228-
const isConnected = await this.isConnectionApproved(interfaceOrigin);
268+
async approveDisconnection(
269+
interfaceOrigin: string,
270+
chainId?: string
271+
): Promise<void> {
272+
const isConnected = await this.isConnectionApproved(
273+
interfaceOrigin,
274+
chainId
275+
);
229276

230277
if (isConnected) {
231278
return this.launchApprovalPopup(TopLevelRoute.ApproveDisconnection, {

apps/extension/src/provider/InjectedNamada.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
DerivedAccount,
2+
Account,
33
Namada as INamada,
44
Signer as ISigner,
55
SignArbitraryProps,
@@ -11,30 +11,32 @@ import { InjectedProxy } from "./InjectedProxy";
1111
import { Signer } from "./Signer";
1212

1313
export class InjectedNamada implements INamada {
14-
constructor(private readonly _version: string) {}
14+
constructor(private readonly _version: string) { }
1515

16-
public async connect(): Promise<void> {
17-
return await InjectedProxy.requestMethod<string, void>("connect");
16+
public async connect(chainId?: string): Promise<void> {
17+
return await InjectedProxy.requestMethod<string, void>("connect", chainId);
1818
}
1919

20-
public async disconnect(): Promise<void> {
21-
return await InjectedProxy.requestMethod<string, void>("disconnect");
20+
public async disconnect(chainId?: string): Promise<void> {
21+
return await InjectedProxy.requestMethod<string, void>(
22+
"disconnect",
23+
chainId
24+
);
2225
}
2326

24-
public async isConnected(): Promise<boolean> {
25-
return await InjectedProxy.requestMethod<string, boolean>("isConnected");
27+
public async isConnected(chainId?: string): Promise<boolean> {
28+
return await InjectedProxy.requestMethod<string, boolean>(
29+
"isConnected",
30+
chainId
31+
);
2632
}
2733

28-
public async accounts(): Promise<DerivedAccount[]> {
29-
return await InjectedProxy.requestMethod<string, DerivedAccount[]>(
30-
"accounts"
31-
);
34+
public async accounts(): Promise<Account[]> {
35+
return await InjectedProxy.requestMethod<string, Account[]>("accounts");
3236
}
3337

34-
public async defaultAccount(): Promise<DerivedAccount> {
35-
return await InjectedProxy.requestMethod<string, DerivedAccount>(
36-
"defaultAccount"
37-
);
38+
public async defaultAccount(): Promise<Account> {
39+
return await InjectedProxy.requestMethod<string, Account>("defaultAccount");
3840
}
3941

4042
public async updateDefaultAccount(address: string): Promise<void> {

apps/extension/src/provider/Namada.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { VaultService } from "background/vault";
77
import * as utils from "extension/utils";
88
import { VaultStorage } from "storage";
99
import { KVStoreMock, init } from "test/init";
10+
import { toPublicAccount } from "utils";
1011
import { ACTIVE_ACCOUNT, keyStore, password } from "./data.mock";
1112

1213
// Needed for now as utils import webextension-polyfill directly
@@ -60,6 +61,8 @@ describe("Namada", () => {
6061
await utilityStore.set(PARENT_ACCOUNT_ID_KEY, ACTIVE_ACCOUNT);
6162
const storedKeyStore = keyStore.map((store) => store.public);
6263
const storedAccounts = await namada.accounts();
63-
expect(storedAccounts).toEqual(storedKeyStore);
64+
expect(storedAccounts).toEqual(
65+
storedKeyStore.map((derivedAccount) => toPublicAccount(derivedAccount))
66+
);
6467
});
6568
});

apps/extension/src/provider/Namada.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
DerivedAccount,
2+
Account,
33
Namada as INamada,
44
SignArbitraryProps,
55
SignArbitraryResponse,
@@ -8,7 +8,7 @@ import {
88
} from "@namada/types";
99
import { MessageRequester, Ports } from "router";
1010

11-
import { toEncodedTx } from "utils";
11+
import { toEncodedTx, toPublicAccount } from "utils";
1212
import {
1313
ApproveConnectInterfaceMsg,
1414
ApproveDisconnectInterfaceMsg,
@@ -28,43 +28,48 @@ export class Namada implements INamada {
2828
protected readonly requester?: MessageRequester
2929
) {}
3030

31-
public async connect(): Promise<void> {
31+
public async connect(chainId?: string): Promise<void> {
3232
return await this.requester?.sendMessage(
3333
Ports.Background,
34-
new ApproveConnectInterfaceMsg()
34+
new ApproveConnectInterfaceMsg(chainId)
3535
);
3636
}
3737

38-
public async disconnect(): Promise<void> {
38+
public async disconnect(chainId?: string): Promise<void> {
3939
return await this.requester?.sendMessage(
4040
Ports.Background,
41-
new ApproveDisconnectInterfaceMsg(location.origin)
41+
new ApproveDisconnectInterfaceMsg(location.origin, chainId)
4242
);
4343
}
4444

45-
public async isConnected(): Promise<boolean> {
45+
public async isConnected(chainId?: string): Promise<boolean> {
4646
if (!this.requester) {
4747
throw new Error("no requester");
4848
}
4949

5050
return await this.requester.sendMessage(
5151
Ports.Background,
52-
new IsConnectionApprovedMsg()
52+
new IsConnectionApprovedMsg(chainId)
5353
);
5454
}
5555

56-
public async accounts(): Promise<DerivedAccount[] | undefined> {
57-
return await this.requester?.sendMessage(
58-
Ports.Background,
59-
new QueryAccountsMsg()
60-
);
56+
public async accounts(): Promise<Account[] | undefined> {
57+
return (
58+
await this.requester?.sendMessage(
59+
Ports.Background,
60+
new QueryAccountsMsg()
61+
)
62+
)?.map(toPublicAccount);
6163
}
6264

63-
public async defaultAccount(): Promise<DerivedAccount | undefined> {
64-
return await this.requester?.sendMessage(
65-
Ports.Background,
66-
new QueryDefaultAccountMsg()
67-
);
65+
public async defaultAccount(): Promise<Account | undefined> {
66+
return await this.requester
67+
?.sendMessage(Ports.Background, new QueryDefaultAccountMsg())
68+
.then((defaultAccount) => {
69+
if (defaultAccount) {
70+
return toPublicAccount(defaultAccount);
71+
}
72+
});
6873
}
6974

7075
public async updateDefaultAccount(address: string): Promise<void> {

0 commit comments

Comments
 (0)