Add metamask support (#173)
This commit is contained in:
parent
f8d24eb264
commit
8a0ac14413
|
@ -2,8 +2,10 @@ import React, { useEffect } from "react";
|
|||
import styled from "styled-components";
|
||||
|
||||
import { ChatState, useChatState } from "../../contexts/chatStateProvider";
|
||||
import { useIdentity } from "../../contexts/identityProvider";
|
||||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||
import { CreateIcon } from "../Icons/CreateIcon";
|
||||
import { UserCreation } from "../UserCreation/UserCreation";
|
||||
|
||||
import { Channel } from "./Channel";
|
||||
|
||||
|
@ -55,24 +57,34 @@ export function Channels({ onCommunityClick }: ChannelsProps) {
|
|||
}
|
||||
}, [notifications, activeChannel]);
|
||||
const setChatState = useChatState()[1];
|
||||
const identity = useIdentity();
|
||||
|
||||
return (
|
||||
<ChannelList>
|
||||
<GenerateChannels type={"channel"} onCommunityClick={onCommunityClick} />
|
||||
|
||||
<Chats>
|
||||
<ChatsBar>
|
||||
<Heading>Chat</Heading>
|
||||
<EditBtn onClick={() => setChatState(ChatState.ChatCreation)}>
|
||||
<CreateIcon />
|
||||
</EditBtn>
|
||||
</ChatsBar>
|
||||
<ChatsList>
|
||||
<GenerateChannels
|
||||
type={"group"}
|
||||
onCommunityClick={onCommunityClick}
|
||||
/>
|
||||
<GenerateChannels type={"dm"} onCommunityClick={onCommunityClick} />
|
||||
</ChatsList>
|
||||
{identity ? (
|
||||
<>
|
||||
<ChatsBar>
|
||||
<Heading>Chat</Heading>
|
||||
<EditBtn onClick={() => setChatState(ChatState.ChatCreation)}>
|
||||
<CreateIcon />
|
||||
</EditBtn>
|
||||
</ChatsBar>
|
||||
<ChatsList>
|
||||
<GenerateChannels
|
||||
type={"group"}
|
||||
onCommunityClick={onCommunityClick}
|
||||
/>
|
||||
<GenerateChannels
|
||||
type={"dm"}
|
||||
onCommunityClick={onCommunityClick}
|
||||
/>
|
||||
</ChatsList>
|
||||
</>
|
||||
) : (
|
||||
<UserCreation permission={true} />
|
||||
)}
|
||||
</Chats>
|
||||
</ChannelList>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { useState } from "react";
|
|||
import styled from "styled-components";
|
||||
|
||||
import { ChatState, useChatState } from "../contexts/chatStateProvider";
|
||||
import { useIdentity } from "../contexts/identityProvider";
|
||||
import { useNarrow } from "../contexts/narrowProvider";
|
||||
|
||||
import { Channels } from "./Channels/Channels";
|
||||
|
@ -20,7 +19,6 @@ import { UserCreationModal } from "./Modals/UserCreationModal";
|
|||
import { WalletConnectModal } from "./Modals/WalletConnectModal";
|
||||
import { WalletModal } from "./Modals/WalletModal";
|
||||
import { ToastMessageList } from "./ToastMessages/ToastMessageList";
|
||||
import { UserCreation } from "./UserCreation/UserCreation";
|
||||
|
||||
function Modals() {
|
||||
return (
|
||||
|
@ -42,13 +40,12 @@ export function Chat() {
|
|||
const [state] = useChatState();
|
||||
const [showMembers, setShowMembers] = useState(false);
|
||||
const narrow = useNarrow();
|
||||
const identity = useIdentity();
|
||||
return (
|
||||
<ChatWrapper>
|
||||
{!narrow && (
|
||||
<ChannelsWrapper>
|
||||
<StyledCommunity />
|
||||
{identity ? <Channels /> : <UserCreation permission={true} />}
|
||||
<Channels />
|
||||
</ChannelsWrapper>
|
||||
)}
|
||||
{state === ChatState.ChatBody && (
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
useIdentity,
|
||||
useNickname,
|
||||
useSetIdentity,
|
||||
useSetNikcname,
|
||||
} from "../../contexts/identityProvider";
|
||||
import { useModal } from "../../contexts/modalProvider";
|
||||
import { ButtonNo, ButtonYes } from "../Buttons/buttonStyle";
|
||||
|
@ -26,6 +27,7 @@ export const LogoutModalName = "LogoutModal";
|
|||
export const LogoutModal = () => {
|
||||
const { setModal } = useModal(LogoutModalName);
|
||||
const logout = useSetIdentity();
|
||||
const setNickname = useSetNikcname();
|
||||
const identity = useIdentity();
|
||||
const nickname = useNickname();
|
||||
|
||||
|
@ -71,6 +73,7 @@ export const LogoutModal = () => {
|
|||
onClick={() => {
|
||||
setModal(false);
|
||||
logout(undefined);
|
||||
setNickname(undefined);
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
useIdentity,
|
||||
useSetIdentity,
|
||||
useSetNikcname,
|
||||
useWalletIdentity,
|
||||
} from "../../contexts/identityProvider";
|
||||
import { useModal } from "../../contexts/modalProvider";
|
||||
import { Contact } from "../../models/Contact";
|
||||
|
@ -36,6 +37,7 @@ import { EmojiKey, UserAddress } from "./ProfileModal";
|
|||
export const UserCreationModalName = "UserCreationModal";
|
||||
|
||||
export function UserCreationModal() {
|
||||
const walletIdentity = useWalletIdentity();
|
||||
const identity = useIdentity();
|
||||
const setIdentity = useSetIdentity();
|
||||
const setNickname = useSetNikcname();
|
||||
|
@ -93,7 +95,7 @@ export function UserCreationModal() {
|
|||
onChange={(e) => setCustomNameInput(e.currentTarget.value)}
|
||||
/>
|
||||
)}
|
||||
{!nextStep && encryptedIdentity && (
|
||||
{!nextStep && encryptedIdentity && !walletIdentity && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
const identity = await decryptIdentity(
|
||||
|
@ -141,10 +143,10 @@ export function UserCreationModal() {
|
|||
if (nextStep) {
|
||||
setModal(false);
|
||||
} else {
|
||||
const identity = Identity.generate();
|
||||
setIdentity(identity);
|
||||
saveIdentity(identity, "noPassword");
|
||||
const identity = walletIdentity || Identity.generate();
|
||||
setNickname(customNameInput);
|
||||
setIdentity(identity);
|
||||
!walletIdentity && saveIdentity(identity, "noPassword");
|
||||
setNextStep(true);
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { Identity } from "status-communities/dist/cjs";
|
||||
import { genPrivateKeyWithEntropy } from "status-communities/dist/cjs/utils";
|
||||
import styled from "styled-components";
|
||||
|
||||
import {
|
||||
useSetIdentity,
|
||||
useSetWalletIdentity,
|
||||
} from "../../contexts/identityProvider";
|
||||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||
import { useModal } from "../../contexts/modalProvider";
|
||||
import { CoinbaseLogo } from "../Icons/CoinbaseLogo";
|
||||
import { MetamaskLogo } from "../Icons/MetamaskLogo";
|
||||
|
@ -9,14 +16,87 @@ import { WalletConnectLogo } from "../Icons/WalletConnectLogo";
|
|||
import { CoinbaseModalName } from "./CoinbaseModal";
|
||||
import { Modal } from "./Modal";
|
||||
import { Heading, MiddleSection, Section, Text } from "./ModalStyle";
|
||||
import { UserCreationModalName } from "./UserCreationModal";
|
||||
import { WalletConnectModalName } from "./WalletConnectModal";
|
||||
|
||||
export const WalletModalName = "WalletModal";
|
||||
|
||||
export function WalletModal() {
|
||||
const { setModal } = useModal(WalletModalName);
|
||||
const setIdentity = useSetIdentity();
|
||||
const setWalletIdentity = useSetWalletIdentity();
|
||||
const userCreationModal = useModal(UserCreationModalName);
|
||||
const { setModal: setWalleConnectModal } = useModal(WalletConnectModalName);
|
||||
const { setModal: setCoinbaseModal } = useModal(CoinbaseModalName);
|
||||
const { messenger } = useMessengerContext();
|
||||
|
||||
const handleMetamaskClick = useCallback(async () => {
|
||||
const ethereum = (window as any)?.ethereum as any | undefined;
|
||||
if (ethereum && messenger) {
|
||||
try {
|
||||
if (ethereum?.isMetaMask) {
|
||||
const [account] = await ethereum.request({
|
||||
method: "eth_requestAccounts",
|
||||
});
|
||||
|
||||
const msgParams = JSON.stringify({
|
||||
domain: {
|
||||
chainId: 1,
|
||||
name: window.location.origin,
|
||||
version: "1",
|
||||
},
|
||||
message: {
|
||||
action: "Status Chat Key",
|
||||
onlySignOn: "https://auth.status.im/",
|
||||
message:
|
||||
"I'm aware that i am signing message that creates a private chat key for status communicator. And I have double checked everything is fine.",
|
||||
},
|
||||
primaryType: "Mail",
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: "name", type: "string" },
|
||||
{ name: "version", type: "string" },
|
||||
{ name: "chainId", type: "uint256" },
|
||||
],
|
||||
Mail: [
|
||||
{ name: "action", type: "string" },
|
||||
{ name: "onlySignOn", type: "string" },
|
||||
{ name: "message", type: "string" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const params = [account, msgParams];
|
||||
const method = "eth_signTypedData_v4";
|
||||
|
||||
const signature = await ethereum.request({
|
||||
method,
|
||||
params,
|
||||
from: account,
|
||||
});
|
||||
const privateKey = genPrivateKeyWithEntropy(signature);
|
||||
|
||||
const loadedIdentity = new Identity(privateKey);
|
||||
|
||||
const userInNetwork = await messenger.checkIfUserInWakuNetwork(
|
||||
loadedIdentity.publicKey
|
||||
);
|
||||
|
||||
if (userInNetwork) {
|
||||
setIdentity(loadedIdentity);
|
||||
} else {
|
||||
setWalletIdentity(loadedIdentity);
|
||||
userCreationModal.setModal(true);
|
||||
}
|
||||
setModal(false);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
alert("Error");
|
||||
}
|
||||
}
|
||||
alert("Metamask not found");
|
||||
}, [messenger]);
|
||||
|
||||
return (
|
||||
<Modal name={WalletModalName}>
|
||||
|
@ -34,7 +114,7 @@ export function WalletModal() {
|
|||
<Heading>Coinbase Wallet</Heading>
|
||||
<CoinbaseLogo />
|
||||
</Wallet>
|
||||
<Wallet>
|
||||
<Wallet onClick={handleMetamaskClick}>
|
||||
<Heading>MetaMask</Heading>
|
||||
<MetamaskLogo />
|
||||
</Wallet>
|
||||
|
|
|
@ -4,11 +4,15 @@ import { Identity } from "status-communities/dist/cjs";
|
|||
const IdentityContext = createContext<{
|
||||
identity: Identity | undefined;
|
||||
setIdentity: React.Dispatch<React.SetStateAction<Identity | undefined>>;
|
||||
walletIdentity: Identity | undefined;
|
||||
setWalletIdentity: React.Dispatch<React.SetStateAction<Identity | undefined>>;
|
||||
nickname: string | undefined;
|
||||
setNickname: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
}>({
|
||||
identity: undefined,
|
||||
setIdentity: () => undefined,
|
||||
walletIdentity: undefined,
|
||||
setWalletIdentity: () => undefined,
|
||||
nickname: undefined,
|
||||
setNickname: () => undefined,
|
||||
});
|
||||
|
@ -21,6 +25,14 @@ export function useSetIdentity() {
|
|||
return useContext(IdentityContext).setIdentity;
|
||||
}
|
||||
|
||||
export function useWalletIdentity() {
|
||||
return useContext(IdentityContext).walletIdentity;
|
||||
}
|
||||
|
||||
export function useSetWalletIdentity() {
|
||||
return useContext(IdentityContext).setWalletIdentity;
|
||||
}
|
||||
|
||||
export function useNickname() {
|
||||
return useContext(IdentityContext).nickname;
|
||||
}
|
||||
|
@ -35,11 +47,21 @@ interface IdentityProviderProps {
|
|||
|
||||
export function IdentityProvider({ children }: IdentityProviderProps) {
|
||||
const [identity, setIdentity] = useState<Identity | undefined>(undefined);
|
||||
const [walletIdentity, setWalletIdentity] = useState<Identity | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [nickname, setNickname] = useState<string | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<IdentityContext.Provider
|
||||
value={{ identity, setIdentity, nickname, setNickname }}
|
||||
value={{
|
||||
identity,
|
||||
setIdentity,
|
||||
nickname,
|
||||
setNickname,
|
||||
walletIdentity,
|
||||
setWalletIdentity,
|
||||
}}
|
||||
children={children}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { PageDirection, Waku, WakuMessage } from "js-waku";
|
|||
import { idToContactCodeTopic } from "./contentTopic";
|
||||
import { Identity } from "./identity";
|
||||
import { StatusUpdate_StatusType } from "./proto/communities/v1/status_update";
|
||||
import { bufToHex } from "./utils";
|
||||
import { bufToHex, getLatestUserNickname } from "./utils";
|
||||
import { ChatIdentity } from "./wire/chat_identity";
|
||||
import { StatusUpdate } from "./wire/status_update";
|
||||
|
||||
|
@ -115,30 +115,11 @@ export class Contacts {
|
|||
|
||||
const handleNickname = async (): Promise<void> => {
|
||||
if (this.identity) {
|
||||
const publicKey = bufToHex(this.identity.publicKey);
|
||||
const now = new Date().getTime();
|
||||
let newNickname = "";
|
||||
let clock = 0;
|
||||
await this.waku.store.queryHistory([idToContactCodeTopic(publicKey)], {
|
||||
callback: (msgs) =>
|
||||
msgs.some((e) => {
|
||||
try {
|
||||
if (e.payload) {
|
||||
const chatIdentity = ChatIdentity.decode(e?.payload);
|
||||
if (chatIdentity) {
|
||||
if (chatIdentity?.displayName) {
|
||||
clock = chatIdentity?.clock ?? 0;
|
||||
newNickname = chatIdentity?.displayName;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
pageDirection: PageDirection.BACKWARD,
|
||||
});
|
||||
const { clock, nickname: newNickname } = await getLatestUserNickname(
|
||||
this.identity.publicKey,
|
||||
this.waku
|
||||
);
|
||||
|
||||
if (this.nickname) {
|
||||
if (this.nickname !== newNickname) {
|
||||
|
@ -150,7 +131,7 @@ export class Contacts {
|
|||
}
|
||||
} else {
|
||||
this.nickname = newNickname;
|
||||
this.callbackNickname(publicKey, newNickname);
|
||||
this.callbackNickname(bufToHex(this.identity.publicKey), newNickname);
|
||||
if (clock < now - NICKNAME_BROADCAST_INTERVAL) {
|
||||
await sendNickname();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ import * as secp256k1 from "secp256k1";
|
|||
import { hexToBuf } from "./utils";
|
||||
|
||||
export class Identity {
|
||||
public constructor(public privateKey: Uint8Array) {}
|
||||
private pubKey: Uint8Array;
|
||||
public constructor(public privateKey: Uint8Array) {
|
||||
this.pubKey = secp256k1.publicKeyCreate(this.privateKey, true);
|
||||
}
|
||||
|
||||
public static generate(): Identity {
|
||||
const privateKey = generatePrivateKey();
|
||||
|
@ -31,6 +34,6 @@ export class Identity {
|
|||
* Returns the compressed public key.
|
||||
*/
|
||||
public get publicKey(): Uint8Array {
|
||||
return secp256k1.publicKeyCreate(this.privateKey, true);
|
||||
return this.pubKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { CreateOptions as WakuCreateOptions } from "js-waku/build/main/lib/waku"
|
|||
import { Chat } from "./chat";
|
||||
import { Identity } from "./identity";
|
||||
import { ApplicationMetadataMessage_Type } from "./proto/status/v1/application_metadata_message";
|
||||
import { getLatestUserNickname } from "./utils";
|
||||
import { ApplicationMetadataMessage } from "./wire/application_metadata_message";
|
||||
import { ChatMessage, Content } from "./wire/chat_message";
|
||||
|
||||
|
@ -249,4 +250,12 @@ export class Messenger {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
async checkIfUserInWakuNetwork(publicKey: Uint8Array): Promise<boolean> {
|
||||
const { clock, nickname } = await getLatestUserNickname(
|
||||
publicKey,
|
||||
this.waku
|
||||
);
|
||||
return clock > 0 && nickname !== "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { ec } from "elliptic";
|
||||
import { utils } from "js-waku";
|
||||
import { PageDirection, utils, Waku } from "js-waku";
|
||||
|
||||
import { idToContactCodeTopic } from "./contentTopic";
|
||||
import { ChatIdentity } from "./proto/communities/v1/chat_identity";
|
||||
|
||||
const EC = new ec("secp256k1");
|
||||
|
||||
|
@ -17,3 +20,38 @@ export function compressPublicKey(key: Uint8Array): string {
|
|||
const PubKey = EC.keyFromPublic(key);
|
||||
return "0x" + PubKey.getPublic(true, "hex");
|
||||
}
|
||||
|
||||
export function genPrivateKeyWithEntropy(key: string): Uint8Array {
|
||||
const pair = EC.genKeyPair({ entropy: key });
|
||||
return hexToBuf("0x" + pair.getPrivate("hex"));
|
||||
}
|
||||
|
||||
export async function getLatestUserNickname(
|
||||
key: Uint8Array,
|
||||
waku: Waku
|
||||
): Promise<{ clock: number; nickname: string }> {
|
||||
const publicKey = bufToHex(key);
|
||||
let nickname = "";
|
||||
let clock = 0;
|
||||
await waku.store.queryHistory([idToContactCodeTopic(publicKey)], {
|
||||
callback: (msgs) =>
|
||||
msgs.some((e) => {
|
||||
try {
|
||||
if (e.payload) {
|
||||
const chatIdentity = ChatIdentity.decode(e?.payload);
|
||||
if (chatIdentity) {
|
||||
if (chatIdentity?.displayName) {
|
||||
clock = chatIdentity?.clock ?? 0;
|
||||
nickname = chatIdentity?.displayName;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
pageDirection: PageDirection.BACKWARD,
|
||||
});
|
||||
return { clock, nickname };
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue