Skip to content

Commit

Permalink
- Added api.ts tests
Browse files Browse the repository at this point in the history
- Added mock files (from cosmos-metamask-snap)
- Removed unused files from test ui
- Improved styles of test ui
- Exported helper function to create SeiWallet for MM Snap
  • Loading branch information
codebycarson committed Jan 5, 2024
1 parent e3c476e commit 83da2c7
Show file tree
Hide file tree
Showing 19 changed files with 954 additions and 266 deletions.
102 changes: 45 additions & 57 deletions packages/core/src/lib/metamask-snap/snapWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { BIP44Node } from '@metamask/key-tree';
import { AccountData, encodeSecp256k1Signature, StdSignDoc } from '@cosmjs/amino';
import { Buffer } from 'buffer';
import { CosmJSOfflineSigner } from './cosmjs';
import { getSnapEthereumProvider, sendReqToSnap } from './utils';
import { compressedPubKeyToAddress, serializeAminoSignDoc, serializeDirectSignDoc } from '../utils';
import { compressedPubKeyToAddress, serializeAminoSignDoc, serializeDirectSignDoc, verifyArbitrary } from '../utils';
import { CosmJSOfflineSigner } from './cosmjs';
import { SeiWallet } from '../wallet';

const MM_SNAP_ORIGIN = 'npm:@sei-js/metamask-snap';

export class SnapWallet {
constructor(private privateKey: Uint8Array, private compressedPubKey: Uint8Array, private address: string) {}

Expand Down Expand Up @@ -88,60 +86,50 @@ export async function getWallet(account_index = 0, snapId: string): Promise<Snap
throw new Error(`Error creating sei wallet!`);
}

// Awaiting audit
export const experimental_SEI_METAMASK_SNAP: SeiWallet = {
getAccounts: async (chainId) => {
const offlineSigner = new CosmJSOfflineSigner(chainId, MM_SNAP_ORIGIN);
return offlineSigner.getAccounts();
},
connect: async (_: string) => {
const provider = await getSnapEthereumProvider();
const installedSnaps: any = await provider.request({ method: 'wallet_getSnaps' });
if (!installedSnaps || !installedSnaps[MM_SNAP_ORIGIN]) {
await provider.request({
method: 'wallet_requestSnaps',
params: {
[MM_SNAP_ORIGIN]: {}
}
});
}
},
disconnect: async (_: string) => {
throw new Error('Not implemented');
},
getOfflineSigner: async (chainId) => {
return new CosmJSOfflineSigner(chainId, MM_SNAP_ORIGIN);
},
getOfflineSignerAmino: async (chainId) => {
return new CosmJSOfflineSigner(chainId, MM_SNAP_ORIGIN);
},
signArbitrary: async (chainId, signer, message) => {
const offlineSigner = new CosmJSOfflineSigner(chainId, MM_SNAP_ORIGIN);
return offlineSigner.signArbitrary(signer, message);
},
verifyArbitrary: async (_: string, signingAddress, data, signature) => {
const provider = await getSnapEthereumProvider();

return (await provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MM_SNAP_ORIGIN,
request: {
method: 'verifyArbitrary',
export const getMetaMaskSnapSeiWallet = (snapId: string): SeiWallet => {
return {
getAccounts: async (chainId) => {
const offlineSigner = new CosmJSOfflineSigner(chainId, snapId);
return offlineSigner.getAccounts();
},
connect: async (_: string) => {
const provider = await getSnapEthereumProvider();
const installedSnaps: any = await provider.request({ method: 'wallet_getSnaps' });
if (!installedSnaps || !installedSnaps[snapId]) {
await provider.request({
method: 'wallet_requestSnaps',
params: {
signer: signingAddress,
message: data,
signature
[snapId]: {}
}
}
});
}
},
disconnect: async (_: string) => {
throw new Error('Not implemented');
},
getOfflineSigner: async (chainId) => {
return new CosmJSOfflineSigner(chainId, snapId);
},
getOfflineSignerAmino: async (chainId) => {
// This signer includes both signDirect and signAmino, so just return it
return new CosmJSOfflineSigner(chainId, snapId);
},
signArbitrary: async (chainId, signer, message) => {
const offlineSigner = new CosmJSOfflineSigner(chainId, snapId);
return offlineSigner.signArbitrary(signer, message);
},
verifyArbitrary: async (_: string, signingAddress, data, signature) => {
if (!signingAddress || !data) {
throw new Error('Invalid params');
}
})) as unknown as boolean;
},
walletInfo: {
windowKey: 'ethereum',
name: 'Sei Metamask Snap',
website: 'https://metamask.io/',
icon: 'https://github.com/MetaMask/brand-resources/raw/master/SVG/SVG_MetaMask_Icon_Color.svg'
},
isMobileSupported: true
return await verifyArbitrary(signingAddress, data, signature);
},
walletInfo: {
windowKey: 'ethereum',
name: 'Sei Metamask Snap',
website: 'https://metamask.io/',
icon: 'https://github.com/MetaMask/brand-resources/raw/master/SVG/SVG_MetaMask_Icon_Color.svg'
},
isMobileSupported: true
};
};
9 changes: 0 additions & 9 deletions packages/metamask-snap/CHANGELOG.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/metamask-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"dependencies": {
"@metamask/snaps-types": "^3.1.0",
"@metamask/snaps-ui": "^3.1.0",
"@sei-js/core": "0.0.0-internal-20240104185656",
"@sei-js/core": "^3.1.2",
"buffer": "^6.0.3"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/metamask-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/template-snap-monorepo.git"
},
"source": {
"shasum": "Va52pfljmjPtKKPndRZs8/7Dpr049tVBA32yYYuES84=",
"shasum": "BL8YslCsPGPRZSF95ze0fYvr9D/ybmtGXZn19/PqDLs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
40 changes: 19 additions & 21 deletions packages/metamask-snap/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { getAminoPanel, getDirectPanel } from './ui';
import { BIP44CoinTypeNode, getBIP44AddressKeyDeriver } from '@metamask/key-tree';
import { SnapRequest } from './types';
import { SnapWallet } from './snapWallet';
import { StdSignDoc } from '@cosmjs/amino';
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';

export const getPrivateKey = async (account_index?: number) => {
export const getPrivateKey = async (account_index: number = 0) => {
const bip44CoinNode = (await snap.request({
method: 'snap_getBip44Entropy',
params: {
Expand All @@ -18,17 +17,20 @@ export const getPrivateKey = async (account_index?: number) => {
})) as unknown as BIP44CoinTypeNode;

const bip44AddressKeyDeriver = await getBIP44AddressKeyDeriver(bip44CoinNode);
return await bip44AddressKeyDeriver(account_index || 0);
return await bip44AddressKeyDeriver(account_index);
};

export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
const { account_index } = request.params as unknown as SnapRequest;

const account = await getPrivateKey(account_index || 0);
if (!account?.privateKey) return;
const wallet = SnapWallet.create(account.privateKey);
switch (request.method) {
case 'signDirect': {
const account = await getPrivateKey(account_index);

if (!account?.privateKey) return;

const wallet = SnapWallet.create(account.privateKey);

const params = request.params as unknown as SignDirectRequest;

const { signerAddress, signDoc } = params;
Expand All @@ -51,34 +53,30 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
}
});

if (!confirmed) {
throw new Error('User denied transaction');
}
if (!confirmed) throw new Error('User denied transaction');

return await wallet.signDirect(signerAddress, sd);
}
case 'signAmino': {
const { signerAddress, signDoc, chainId, enableExtraEntropy, isADR36 } = request.params as unknown as SignAminoRequest;

const sortedSignDoc: StdSignDoc = {
chain_id: chainId || 'pacific-1',
account_number: signDoc.account_number,
sequence: signDoc.sequence,
fee: signDoc.fee,
memo: signDoc.memo,
msgs: signDoc.msgs
};
const { signerAddress, signDoc, enableExtraEntropy, isADR36 } = request.params as unknown as SignAminoRequest;

const account = await getPrivateKey(account_index);

if (!account?.privateKey) return;

const wallet = SnapWallet.create(account.privateKey);

const confirmed = await snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: getAminoPanel(sortedSignDoc, isADR36)
content: getAminoPanel(signDoc, isADR36)
}
});

if (!confirmed) throw new Error('User denied transaction');

return await wallet.signAmino(signerAddress, sortedSignDoc, { extraEntropy: !!enableExtraEntropy });
return await wallet.signAmino(signerAddress, signDoc, { extraEntropy: !!enableExtraEntropy });
}
case 'getPrivateKey': {
return await getPrivateKey(account_index);
Expand Down
3 changes: 2 additions & 1 deletion packages/metamask-snap/src/snapWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { BIP44Node } from '@metamask/key-tree';
import { AccountData, encodeSecp256k1Signature, StdSignDoc } from '@cosmjs/amino';
import { Buffer } from 'buffer';
import { compressedPubKeyToAddress, serializeAminoSignDoc, serializeDirectSignDoc, SeiWallet } from '@sei-js/core';
import { compressedPubKeyToAddress, serializeAminoSignDoc, serializeDirectSignDoc } from '@sei-js/core';

import { sendReqToSnap } from './utils';

Expand Down Expand Up @@ -53,6 +53,7 @@ export class SnapWallet {
async signAmino(signerAddress: string, signDoc: StdSignDoc, options?: { extraEntropy: boolean }) {
const accounts = this.getAccounts();
const account = accounts.find((acc) => acc.address === signerAddress);

if (!account) {
throw new Error('Signer address does not match wallet address');
}
Expand Down
36 changes: 36 additions & 0 deletions packages/metamask-snap/src/tests/api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { onRpcRequest } from '..';
import snapMock from './mocks/snap.mock';
import input from './mocks/input.mock';
import output from './mocks/output.mock';

describe('OnRPCRequest', () => {
it('getPrivateKey', async () => {
(global as any).snap = snapMock.success;

const resp: any = await onRpcRequest(input.success.getPrivateKey);

expect(resp).toMatchObject(output.success.getPrivateKey);
});

it('signDirect', async () => {
(global as any).snap = snapMock.success;

const resp: any = await onRpcRequest(input.success.signDirect);
expect(resp.signed).toMatchObject(output.success.signDirect.signed);
expect(resp.signature).toHaveProperty('signature');
expect(resp.signature.pub_key).toStrictEqual(output.success.signDirect.signature.pub_key);
});

it('signAmino', async () => {
(global as any).snap = snapMock.success;

const resp: any = await onRpcRequest(input.success.signAmino);
expect(resp.signed).toMatchObject(output.success.signAmino.signed);
expect(resp.signature).toHaveProperty('signature');
expect(resp.signature.pub_key).toStrictEqual(output.success.signAmino.signature.pub_key);
});

it('invalid method', async () => {
await expect(onRpcRequest(input.failure.invalidMethod)).rejects.toThrow(output.failure.invalidMethod);
});
});
Loading

0 comments on commit 83da2c7

Please sign in to comment.