Skip to content

Commit

Permalink
Merge pull request ChainSafe#135 from ChainSafe/irubido/snapChanges
Browse files Browse the repository at this point in the history
Snap changes
  • Loading branch information
mpetrunic authored Jul 14, 2022
2 parents e8e9ea5 + ffaead3 commit b141728
Show file tree
Hide file tree
Showing 47 changed files with 566 additions and 615 deletions.
13 changes: 0 additions & 13 deletions packages/adapter/src/api.ts

This file was deleted.

68 changes: 44 additions & 24 deletions packages/adapter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
import '@polkadot/types-augment';
import {injectExtension} from '@polkadot/extension-inject';
import {MetamaskPolkadotSnap} from "./snap";
import {SnapConfig} from "@chainsafe/metamask-polkadot-types";
import {hasMetaMask} from "./utils";
import {hasMetaMask, isMetamaskSnapsSupported, isPolkadotSnapInstalled} from "./utils";

const defaultOrigin = "https://bafybeih426v3jpdwnltjfmeefyt4isrogvgzg2wxvryu6itodvb4vzvuma.ipfs.infura-ipfs.io/";
const defaultSnapOrigin = "https://bafybeih426v3jpdwnltjfmeefyt4isrogvgzg2wxvryu6itodvb4vzvuma.ipfs.infura-ipfs.io/";

/**
*
* @param network
* @param config
* @param pluginOrigin url to package.json
*/
export function injectMetamaskPolkadotSnapProvider(
network: "westend"|"kusama",
config?: SnapConfig,
pluginOrigin?: string
): void {
if(!hasMetaMask()) {
return;
export type SnapInstallationParamNames = "version" | string;

export async function enablePolkadotSnap(
config: SnapConfig,
snapOrigin?: string,
snapInstallationParams: Record<SnapInstallationParamNames, unknown> = {}
): Promise<MetamaskPolkadotSnap> {

const snapId = snapOrigin ?? defaultSnapOrigin;

// check all conditions
if (!hasMetaMask()) {
throw new Error("Metamask is not installed");
}
if (!(await isMetamaskSnapsSupported())) {
throw new Error("Current Metamask version doesn't support snaps");
}
const polkadotSnap = new MetamaskPolkadotSnap(
pluginOrigin || defaultOrigin,
config || {networkName: network}
);
injectExtension(
async () => await polkadotSnap.enableSnap(),
{name: 'metamask-polkadot-snap', version: '1.0.0'}
);
if (!config.networkName) {
throw new Error("Configuration must at least define network type");
}

const isInstalled = await isPolkadotSnapInstalled(snapId);
console.log("isInstalled", isInstalled);
if (!isInstalled) {
// // enable snap
await window.ethereum.request({
method: "wallet_enable",
params: [
{
[`wallet_snap_${snapId}`]: {
...snapInstallationParams,
},
},
],
});
}

// create snap describer
const snap = new MetamaskPolkadotSnap(snapOrigin || defaultSnapOrigin, config);
// set initial configuration
await (await snap.getMetamaskSnapApi()).setConfiguration(config);
// return snap object
return snap;
}
2 changes: 1 addition & 1 deletion packages/adapter/src/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {SignerPayloadJSON, SignerPayloadRaw} from '@polkadot/types/types';
import {MetamaskPolkadotSnap} from "./snap";

async function sendSnapMethod(request: MetamaskPolkadotRpcRequest, snapId: string): Promise<unknown> {
return await window.ethereum.send({
return await window.ethereum.request({
method: snapId,
params: [
request
Expand Down
63 changes: 6 additions & 57 deletions packages/adapter/src/snap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import {Injected, InjectedAccount, InjectedAccounts} from "@polkadot/extension-inject/types";
import {Signer as InjectedSigner, SignerResult} from '@polkadot/api/types';
import {SignerPayloadJSON, SignerPayloadRaw} from '@polkadot/types/types';
import {HexString} from '@polkadot/util/types';
import {
exportSeed,
generateTransactionPayload,
Expand All @@ -17,82 +13,35 @@ import {
} from "./methods";
import {SnapConfig} from "@chainsafe/metamask-polkadot-types";
import {MetamaskSnapApi} from "./types";
import {isPolkadotSnapInstalled} from "./utils";
import {getEventApi} from "./api";

export class MetamaskPolkadotSnap implements Injected {

public accounts: InjectedAccounts = {
get: async (): Promise<InjectedAccount[]> => {
const account: InjectedAccount = {
address: await getAddress.bind(this)(),
genesisHash: null,
name: "Metamask account"
};
return [account];
},
subscribe: () => {
return (): void => {
throw "unsupported method";
};
}
};

public signer: InjectedSigner = {
signPayload: async (payload: SignerPayloadJSON): Promise<SignerResult> => {
const signature = (await signPayloadJSON.bind(this)(payload) as HexString);
const id = this.requestCounter;
this.requestCounter += 1;
return {id, signature};
},
signRaw: async (raw: SignerPayloadRaw): Promise<SignerResult> => {
const signature =( await signPayloadRaw.bind(this)(raw) as HexString);
const id = this.requestCounter;
this.requestCounter += 1;
return {id, signature};
},
update: (): null => null
};
export class MetamaskPolkadotSnap {

protected readonly config: SnapConfig;
//url to package.json
protected readonly pluginOrigin: string;
//pluginOrigin prefixed with wallet_plugin_
protected readonly snapId: string;

private requestCounter: number;

public constructor(pluginOrigin: string, config: SnapConfig) {
this.pluginOrigin = pluginOrigin;
this.snapId = "wallet_plugin_" + this.pluginOrigin;
this.snapId = `wallet_snap_${this.pluginOrigin}`;
this.config = config || {networkName: "westend"};
this.requestCounter = 0;
}

public enableSnap = async (): Promise<Injected> => {
if(!(await isPolkadotSnapInstalled(this.snapId))) {
await window.ethereum.send({
method: "wallet_enable",
params: [{
[this.snapId]: {}
}]
});
await setConfiguration.bind(this)(this.config);
}
return this;
};


public getMetamaskSnapApi = async (): Promise<MetamaskSnapApi> => {
return {
exportSeed: exportSeed.bind(this),
generateTransactionPayload: generateTransactionPayload.bind(this),
getAddress: getAddress.bind(this),
getAllTransactions: getAllTransactions.bind(this),
getBalance: getBalance.bind(this),
getEventApi: getEventApi.bind(this),
getLatestBlock: getLatestBlock.bind(this),
getPublicKey: getPublicKey.bind(this),
send: sendSignedData.bind(this),
setConfiguration: setConfiguration.bind(this),
signPayloadJSON: signPayloadJSON.bind(this),
signPayloadRaw: signPayloadRaw.bind(this),
};
};
}
10 changes: 7 additions & 3 deletions packages/adapter/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import {
BlockInfo,
PolkadotApi,
SnapConfig,
SnapRpcMethodRequest, Transaction,
TxPayload
} from "@chainsafe/metamask-polkadot-types";
import {InjectedExtension} from "@polkadot/extension-inject/types";
import {SignerPayloadRaw} from "@polkadot/types/types/extrinsic";
import {SignerPayloadJSON} from "@polkadot/types/types";

export interface MetamaskSnapApi {
getAddress(): Promise<string>;
getPublicKey(): Promise<string>;
getBalance(): Promise<string>;
exportSeed(): Promise<string>;
getLatestBlock(): Promise<BlockInfo>;
setConfiguration(configuration: SnapConfig): Promise<void>;
getAllTransactions(): Promise<Transaction[]>;

signPayloadJSON(payload: SignerPayloadJSON): Promise<string>;
signPayloadRaw(payload: SignerPayloadRaw): Promise<string>;
send(signature: string, txPayload: TxPayload): Promise<Transaction>;
generateTransactionPayload(amount: string | number, to: string): Promise<TxPayload>;
getEventApi(): Promise<PolkadotApi>;
}

export interface InjectedMetamaskExtension extends InjectedExtension {
Expand All @@ -31,7 +34,8 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
send: (request: SnapRpcMethodRequest | {method: string; params?: any[]}) => Promise<unknown>;
on: (eventName: unknown, callback: unknown) => unknown;
requestIndex: () => Promise<{getPluginApi: (origin: string) => Promise<PolkadotApi>}>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
request: <T>(request: SnapRpcMethodRequest | {method: string; params?: any[]}) => Promise<T>;
};
}
}
36 changes: 21 additions & 15 deletions packages/adapter/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,35 @@ export function hasMetaMask(): boolean {
return window.ethereum.isMetaMask;
}

export async function installPolkadotSnap(pluginOrigin: string): Promise<boolean> {
export async function isPolkadotSnapInstalled(snapOrigin: string, version?: string): Promise<boolean> {
try {
await window.ethereum.send({
method: 'wallet_enable',
params: [{
[pluginOrigin]: {}
}]
});
return true;
return !!Object
.values(await getWalletSnaps())
.find((permission) => permission.id === snapOrigin && (!version || permission.version === version));
} catch (e) {
console.log("Failed to install snap", e);
console.log("Failed to obtain installed snaps", e);
return false;
}
}

export async function isPolkadotSnapInstalled(pluginOrigin: string): Promise<boolean> {
export type GetSnapsResponse =
{ [k: string]: {
permissionName?: string,
id?: string,
version?: string,
initialPermissions?: {[k: string]: unknown}
}
};
async function getWalletSnaps(): Promise<GetSnapsResponse> {
return await window.ethereum.request({
method: 'wallet_getSnaps',
}) as GetSnapsResponse;
}
export async function isMetamaskSnapsSupported(): Promise<boolean> {
try {
const result = await window.ethereum.send({
method: 'wallet_getPlugins',
}) as {[k: string]: {permissionName: string}};
return !!Object.values(result).find((permission) => permission.permissionName === pluginOrigin);
await getWalletSnaps();
return true;
} catch (e) {
console.log("Failed to obtain installed plugins", e);
return false;
}
}
11 changes: 0 additions & 11 deletions packages/example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import { useEffect } from 'react';
import { Dashboard } from "./containers/Dashboard/Dashboard";
import { MetaMaskContextProvider } from "./context/metamask";
//eslint-disable-next-line
import { injectMetamaskPolkadotSnapProvider } from "@chainsafe/metamask-polkadot-adapter";

function App() {

useEffect(() => {
injectMetamaskPolkadotSnapProvider(
"westend",
undefined,
"http://localhost:8081/package.json"
);
}, []);

return (
<MetaMaskContextProvider>
<Dashboard />
Expand Down
13 changes: 7 additions & 6 deletions packages/example/src/components/Account/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import React, { useContext } from "react";
import { Box, Button, Card, CardContent, CardHeader, Divider, Grid, Typography } from '@material-ui/core';
import { formatBalance } from "@polkadot/util/format/formatBalance";
import { getInjectedMetamaskExtension } from "../../services/metamask";
import { getCurrency } from "../../services/format";
import { MetaMaskContext } from "../../context/metamask";

export interface AccountProps {
address: string;
Expand All @@ -13,11 +13,12 @@ export interface AccountProps {

export const Account = (props: AccountProps) => {

const [state] = useContext(MetaMaskContext);

const handleExport = async () => {
const provider = await getInjectedMetamaskExtension();
if (!provider) return;
const metamaskSnapApi = await provider.getMetamaskSnapApi();
const privateKey = await metamaskSnapApi.exportSeed();
if (!state.polkadotSnap.snap) return;
const api = await state.polkadotSnap.snap.getMetamaskSnapApi();
const privateKey = await api.exportSeed();
alert(privateKey);
};

Expand Down
24 changes: 14 additions & 10 deletions packages/example/src/components/SignMessage/SignMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, { useState } from "react";
import React, {useContext, useState} from "react";
import { Box, Button, Card, CardContent, CardHeader, Dialog, Grid, TextField } from '@material-ui/core';
import { getInjectedMetamaskExtension } from "../../services/metamask";
import { stringToHex } from "@polkadot/util/string";
import { web3Accounts } from "@polkadot/extension-dapp";
import { DialogActions, DialogContent, DialogContentText, DialogTitle, Typography } from "@material-ui/core";
import {MetaMaskContext} from "../../context/metamask";

interface Props {
address: string;
}

export const SignMessage: React.FC<Props> = ({address}) => {
const [state] = useContext(MetaMaskContext);

export const SignMessage = () => {
const [textFieldValue, setTextFieldValue] = useState<string>("");
const [modalBody, setModalBody] = useState<string>("");
const [modalOpen, setModalOpen] = useState<boolean>(false);
Expand All @@ -15,20 +20,19 @@ export const SignMessage = () => {
};

const onSubmit = async () => {
if (!state.polkadotSnap.snap) return;
if (textFieldValue) {
const extension = await getInjectedMetamaskExtension();
if (extension && extension.signer && extension.signer.signRaw) {

const api = await state.polkadotSnap.snap.getMetamaskSnapApi();
if (api && api.signPayloadRaw) {
const messageAsHex = stringToHex(textFieldValue);
const address = (await web3Accounts())[0].address;

const messageSignResponse = await extension.signer.signRaw({
const messageSignResponse = await api.signPayloadRaw({
address: address,
data: messageAsHex,
type: "bytes"
});
setTextFieldValue("");
setModalBody(messageSignResponse.signature);
setModalBody(messageSignResponse);
setModalOpen(true);
}
}
Expand Down
Loading

0 comments on commit b141728

Please sign in to comment.