Beta 100 (#27)

* update deps

* revert react dep updates

* chore: update deps to beta.50

* fix: get dapp running again up to Client.connect()

* save progress

* feat(debug): sets up a debug peerClient as responder

* refactor: remove more hardcoded example connect params

* fix: gets `checkPersistedState` working again

* fix: gets client.disconnect + effects working again

* feat: integrates `session_update` handling with namespaces

* fix: remove hardcoded `chains`

* stash progress

* Establish session

* save progress - established session with example dapp

* refactor: rewrite namespace helpers to handle `requiredNamespaces` (#23)

* fix: re-enables restoring persisted session

* refactor: remove debug peerClient code

* fix: re-enables restoring persisted pairings

* Save progress

* fix: re-enables pairing modal, connecting from existing pairing

* Update modals to handle new payloads

* fix(types): fix Metadata typing

* chore(deps): upgrade to beta.53

* refactor: adjusts event handler args for beta.53 `session_update`

* stash

* fix: adds missing keys for `DEFAULT_EIP155_EVENTS` enum

* stash

* chore: update comment for client.request typing FIXME

* feat: integrate beta.54

* feat: integrate beta.55, removes FIXME comments for client.request types

* chore: clean up unused import

* fix: log session_ping event

* fix: log incoming `session_event`

* chore: upgrade client@2.0.0-beta.55 -> sign-client@2.0.0-beta.56

* chore: integrate beta.57

* New beta changes (#24)

* smal lchange

* fix types

* Add package info

* format empty methods / events

* Update deps

* adjust styles

* attempt ios layout fix

* Revert "attempt ios layout fix"

This reverts commit f0176f2ef52f338980ee54e75a767b3d452733f2.

* beta.54

* fix build

* Update to beta 55

* Add todos

* update wallet to beta 56

* Update mumbai rpc add logger

* update to beta 57

* chore: adds note on beta.100 compatibility

* plock

* beta.58

* beta.100;

Co-authored-by: Ilja <idaderko@gmail.com>
Co-authored-by: Ben Kremer <ben@walletconnect.com>
Co-authored-by: Ilja <IljaDaderko@users.noreply.github.com>
This commit is contained in:
Pedro Gomes
2022-05-30 11:46:15 +02:00
committed by GitHub
co-authored by Ilja Ben Kremer Ilja
parent dc76f21c2c
commit 954831538d
44 changed files with 2048 additions and 1561 deletions
+7 -10
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { version } from "@walletconnect/client/package.json";
import { version } from "@walletconnect/sign-client/package.json";
import Banner from "./components/Banner";
import Blockchain from "./components/Blockchain";
@@ -80,11 +80,12 @@ export default function App() {
throw new Error("WalletConnect is not initialized");
}
// Suggest existing pairings (if any).
if (client.pairing.topics.length) {
return openPairingModal();
if (client.pairing.values.length) {
openPairingModal();
} else {
// If no existing pairings are available, trigger `WalletConnectClient.connect`.
connect();
}
// If no existing pairings are available, trigger `WalletConnectClient.connect`.
connect();
};
const onPing = async () => {
@@ -204,9 +205,7 @@ export default function App() {
return !accounts.length && !Object.keys(balances).length ? (
<SLanding center>
<Banner />
<h6>
<span>{`Using v${version || "2.0.0-beta"}`}</span>
</h6>
<h6>{`Using v${version || "2.0.0-beta"}`}</h6>
<SButtonContainer>
<h6>Select chains:</h6>
<SToggleContainer>
@@ -252,8 +251,6 @@ export default function App() {
);
};
console.log(accounts);
return (
<SLayout>
<Column maxWidth={1000} spanHeight>
@@ -50,7 +50,7 @@ const SActiveSession = styled(SActiveAccount as any)`
interface HeaderProps {
ping: () => Promise<void>;
disconnect: () => Promise<void>;
session: SessionTypes.Created | undefined;
session: SessionTypes.Struct | undefined;
}
const Header = (props: HeaderProps) => {
@@ -6,7 +6,7 @@ import { PairingTypes } from "@walletconnect/types";
import Peer from "./Peer";
interface PairingProps {
pairing: PairingTypes.Settled;
pairing: PairingTypes.Struct;
onClick?: any;
}
@@ -16,16 +16,14 @@ const SPairingContainer = styled.div`
`;
const Pairing = (props: PairingProps) => {
const {
state: { metadata },
} = props.pairing;
const { peerMetadata } = props.pairing;
return (
<SPairingContainer onClick={props.onClick}>
<div>
{typeof metadata !== "undefined" ? (
<Peer oneLiner metadata={metadata} />
{typeof peerMetadata !== "undefined" ? (
<Peer oneLiner metadata={peerMetadata} />
) : (
<div>{`Unknown`}</div>
<div>{`Unknown Wallet`}</div>
)}
</div>
</SPairingContainer>
+2 -2
View File
@@ -1,6 +1,6 @@
import { SignClientTypes } from "@walletconnect/types";
import * as React from "react";
import styled from "styled-components";
import { AppMetadata } from "@walletconnect/types";
import { colors, fonts } from "../styles";
const SPeerOneLiner = styled.div`
@@ -53,7 +53,7 @@ const SName = styled(SCenter as any)`
interface PeerProps {
oneLiner?: boolean;
metadata: AppMetadata;
metadata: SignClientTypes.Metadata;
}
const Peer = (props: PeerProps) =>
+36 -18
View File
@@ -26,24 +26,6 @@ export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID;
export const DEFAULT_RELAY_URL = process.env.REACT_APP_RELAY_URL;
export enum DEFAULT_EIP155_METHODS {
ETH_SEND_TRANSACTION = "eth_sendTransaction",
ETH_SIGN_TRANSACTION = "eth_signTransaction",
ETH_SIGN = "eth_sign",
PERSONAL_SIGN = "personal_sign",
ETH_SIGN_TYPED_DATA = "eth_signTypedData",
}
export enum DEFAULT_COSMOS_METHODS {
COSMOS_SIGN_DIRECT = "cosmos_signDirect",
COSMOS_SIGN_AMINO = "cosmos_signAmino",
}
export enum DEFAULT_SOLANA_METHODS {
SOL_SIGN_TRANSACTION = "solana_signTransaction",
SOL_SIGN_MESSAGE = "solana_signMessage",
}
export const DEFAULT_LOGGER = "debug";
export const DEFAULT_APP_METADATA = {
@@ -52,3 +34,39 @@ export const DEFAULT_APP_METADATA = {
url: "https://walletconnect.com/",
icons: ["https://avatars.githubusercontent.com/u/37784886"],
};
/**
* EIP155
*/
export enum DEFAULT_EIP155_METHODS {
ETH_SEND_TRANSACTION = "eth_sendTransaction",
ETH_SIGN_TRANSACTION = "eth_signTransaction",
ETH_SIGN = "eth_sign",
PERSONAL_SIGN = "personal_sign",
ETH_SIGN_TYPED_DATA = "eth_signTypedData",
}
export enum DEFAULT_EIP_155_EVENTS {
ETH_CHAIN_CHANGED = "chainChanged",
ETH_ACCOUNTS_CHANGED = "accountsChanged",
}
/**
* COSMOS
*/
export enum DEFAULT_COSMOS_METHODS {
COSMOS_SIGN_DIRECT = "cosmos_signDirect",
COSMOS_SIGN_AMINO = "cosmos_signAmino",
}
export enum DEFAULT_COSMOS_EVENTS {}
/**
* SOLANA
*/
export enum DEFAULT_SOLANA_METHODS {
SOL_SIGN_TRANSACTION = "solana_signTransaction",
SOL_SIGN_MESSAGE = "solana_signMessage",
}
export enum DEFAULT_SOLANA_EVENTS {}
@@ -1,4 +1,4 @@
import Client, { CLIENT_EVENTS } from "@walletconnect/client";
import Client from "@walletconnect/sign-client";
import { PairingTypes, SessionTypes } from "@walletconnect/types";
import QRCodeModal from "@walletconnect/legacy-modal";
import {
@@ -14,28 +14,26 @@ import { PublicKey } from "@solana/web3.js";
import {
DEFAULT_APP_METADATA,
DEFAULT_COSMOS_METHODS,
DEFAULT_EIP155_METHODS,
DEFAULT_LOGGER,
DEFAULT_PROJECT_ID,
DEFAULT_RELAY_URL,
DEFAULT_SOLANA_METHODS,
} from "../constants";
import { AccountBalances, apiGetAccountBalance } from "../helpers";
import { ERROR, getAppMetadata } from "@walletconnect/utils";
import { getPublicKeysFromAccounts } from "../helpers/solana";
import { getRequiredNamespaces } from "../helpers/namespaces";
/**
* Types
*/
interface IContext {
client: Client | undefined;
session: SessionTypes.Created | undefined;
session: SessionTypes.Struct | undefined;
connect: (pairing?: { topic: string }) => Promise<void>;
disconnect: () => Promise<void>;
isInitializing: boolean;
chains: string[];
pairings: string[];
pairings: PairingTypes.Struct[];
accounts: string[];
solanaPublicKeys?: Record<string, PublicKey>;
balances: AccountBalances;
@@ -53,8 +51,8 @@ export const ClientContext = createContext<IContext>({} as IContext);
*/
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
const [client, setClient] = useState<Client>();
const [pairings, setPairings] = useState<string[]>([]);
const [session, setSession] = useState<SessionTypes.Created>();
const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
const [session, setSession] = useState<SessionTypes.Struct>();
const [isFetchingBalances, setIsFetchingBalances] = useState(false);
const [isInitializing, setIsInitializing] = useState(false);
@@ -96,78 +94,53 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
}
};
const getSupportedNamespaces = useCallback(() => {
const supportedNamespaces: string[] = [];
chains.forEach(chainId => {
const [namespace] = chainId.split(":");
if (!supportedNamespaces.includes(namespace)) {
supportedNamespaces.push(namespace);
}
});
return supportedNamespaces;
}, [chains]);
const getSupportedMethods = (namespaces: string[]) => {
const supportedMethods: string[] = namespaces
.map(namespace => {
switch (namespace) {
case "eip155":
return Object.values(DEFAULT_EIP155_METHODS);
case "cosmos":
return Object.values(DEFAULT_COSMOS_METHODS);
case "solana":
return Object.values(DEFAULT_SOLANA_METHODS);
default:
throw new Error(`No default methods for namespace: ${namespace}`);
}
})
const onSessionConnected = useCallback(async (_session: SessionTypes.Struct) => {
const allNamespaceAccounts = Object.values(_session.namespaces)
.map(namespace => namespace.accounts)
.flat();
const allNamespaceChains = Object.keys(_session.namespaces);
return supportedMethods;
};
const onSessionConnected = useCallback(async (_session: SessionTypes.Settled) => {
setSession(_session);
setChains(_session.permissions.blockchain.chains);
setAccounts(_session.state.accounts);
setSolanaPublicKeys(getPublicKeysFromAccounts(_session.state.accounts));
await getAccountBalances(_session.state.accounts);
setChains(allNamespaceChains);
setAccounts(allNamespaceAccounts);
setSolanaPublicKeys(getPublicKeysFromAccounts(allNamespaceAccounts));
await getAccountBalances(allNamespaceAccounts);
}, []);
const connect = useCallback(
async (pairing?: { topic: string }) => {
async pairing => {
if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
console.log("connect", pairing);
console.log("connect, pairing topic is:", pairing?.topic);
try {
const supportedNamespaces = getSupportedNamespaces();
const methods = getSupportedMethods(supportedNamespaces);
const requiredNamespaces = getRequiredNamespaces(chains);
console.log("requiredNamespaces config for connect:", requiredNamespaces);
const session = await client.connect({
metadata: getAppMetadata() || DEFAULT_APP_METADATA,
pairing,
permissions: {
blockchain: {
chains,
},
jsonrpc: {
methods,
},
},
const { uri, approval } = await client.connect({
pairingTopic: pairing?.topic,
requiredNamespaces,
});
onSessionConnected(session);
// Open QRCode modal if a URI was returned (i.e. we're not connecting an existing pairing).
if (uri) {
QRCodeModal.open(uri, () => {
console.log("EVENT", "QR Code Modal closed");
});
}
const session = await approval();
console.log("Established session:", session);
await onSessionConnected(session);
} catch (e) {
console.error(e);
// ignore rejection
} finally {
// close modal in case it was open
QRCodeModal.close();
}
// close modal in case it was open
QRCodeModal.close();
},
[chains, client, onSessionConnected, getSupportedNamespaces],
[chains, client, onSessionConnected],
);
const disconnect = useCallback(async () => {
@@ -181,6 +154,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
topic: session.topic,
reason: ERROR.USER_DISCONNECTED.format(),
});
// Reset app state after disconnect.
reset();
}, [client, session]);
const _subscribeToEvents = useCallback(
@@ -189,25 +164,24 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
throw new Error("WalletConnect is not initialized");
}
_client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => {
const { uri } = proposal.signal.params;
console.log("EVENT", "QR Code Modal open");
QRCodeModal.open(uri, () => {
console.log("EVENT", "QR Code Modal closed");
});
_client.on("session_ping", args => {
console.log("EVENT", "session_ping", args);
});
_client.on(CLIENT_EVENTS.pairing.created, async () => {
setPairings(_client.pairing.topics);
_client.on("session_event", args => {
console.log("EVENT", "session_event", args);
});
_client.on(CLIENT_EVENTS.session.updated, (updatedSession: SessionTypes.Settled) => {
console.log("EVENT", "session_updated");
_client.on("session_update", ({ topic, params }) => {
console.log("EVENT", "session_update", { topic, params });
const { namespaces } = params;
const _session = _client.session.get(topic);
const updatedSession = { ..._session, namespaces };
onSessionConnected(updatedSession);
});
_client.on(CLIENT_EVENTS.session.deleted, () => {
console.log("EVENT", "session_deleted");
_client.on("session_delete", () => {
console.log("EVENT", "session_delete");
reset();
});
},
@@ -220,12 +194,17 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
throw new Error("WalletConnect is not initialized");
}
// populates existing pairings to state
setPairings(_client.pairing.topics);
setPairings(_client.pairing.values);
console.log("RESTORED PAIRINGS: ", _client.pairing.values);
if (typeof session !== "undefined") return;
// populates existing session to state (assume only the top one)
if (_client.session.topics.length) {
const _session = await _client.session.get(_client.session.topics[0]);
onSessionConnected(_session);
// populates (the last) existing session to state
if (_client.session.length) {
const lastKeyIndex = _client.session.keys.length - 1;
const _session = _client.session.get(_client.session.keys[lastKeyIndex]);
console.log("RESTORED SESSION:", _session);
await onSessionConnected(_session);
return _session;
}
},
[session, onSessionConnected],
@@ -239,8 +218,10 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
logger: DEFAULT_LOGGER,
relayUrl: DEFAULT_RELAY_URL,
projectId: DEFAULT_PROJECT_ID,
metadata: getAppMetadata() || DEFAULT_APP_METADATA,
});
console.log("CREATED CLIENT: ", _client);
setClient(_client);
await _subscribeToEvents(_client);
await _checkPersistedState(_client);
@@ -123,7 +123,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
let valid = false;
try {
await client.session.ping(session.topic);
await client.ping({ topic: session.topic });
valid = true;
} catch (e) {
valid = false;
@@ -163,7 +163,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
};
}
const result = await client!.request({
const result = await client!.request<string>({
topic: session!.topic,
chainId,
request: {
@@ -187,7 +187,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const tx = await formatTestTransaction(account);
const signedTx: string = await client!.request({
const signedTx = await client!.request<string>({
topic: session!.topic,
chainId,
request: {
@@ -217,7 +217,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const params = [hexMsg, address];
// send message
const signature: string = await client!.request({
const signature = await client!.request<string>({
topic: session!.topic,
chainId,
request: {
@@ -255,7 +255,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const params = [address, hexMsg];
// send message
const signature: string = await client!.request({
const signature = await client!.request<string>({
topic: session!.topic,
chainId,
request: {
@@ -290,7 +290,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const params = [address, message];
// send message
const signature = await client!.request({
const signature = await client!.request<string>({
topic: session!.topic,
chainId,
request: {
@@ -357,7 +357,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
};
// send message
const result = await client!.request({
const result = await client!.request<{ signature: string }>({
topic: session!.topic,
chainId,
request: {
@@ -400,7 +400,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const params = { signerAddress: address, signDoc };
// send message
const result = await client!.request({
const result = await client!.request<{ signature: string }>({
topic: session!.topic,
chainId,
request: {
@@ -456,7 +456,8 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
);
try {
const { signature } = await client!.request({
const result = await client!.request<{ signature: string }>({
chainId,
topic: session!.topic,
request: {
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION,
@@ -478,7 +479,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
// We only need `Buffer.from` here to satisfy the `Buffer` param type for `addSignature`.
// The resulting `UInt8Array` is equivalent to just `bs58.decode(...)`.
transaction.addSignature(senderPublicKey, Buffer.from(bs58.decode(signature)));
transaction.addSignature(senderPublicKey, Buffer.from(bs58.decode(result.signature)));
const valid = transaction.verifySignatures();
@@ -486,7 +487,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION,
address,
valid,
result: signature,
result: result.signature,
};
} catch (error: any) {
throw new Error(error);
@@ -507,7 +508,8 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
);
try {
const { signature } = await client!.request({
const result = await client!.request<{ signature: string }>({
chainId,
topic: session!.topic,
request: {
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE,
@@ -518,13 +520,17 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
},
});
const valid = verifyMessageSignature(senderPublicKey.toBase58(), signature, message);
const valid = verifyMessageSignature(
senderPublicKey.toBase58(),
result.signature,
message,
);
return {
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE,
address,
valid,
result: signature,
result: result.signature,
};
} catch (error: any) {
throw new Error(error);
@@ -0,0 +1,63 @@
import { ProposalTypes } from "@walletconnect/types";
import {
DEFAULT_COSMOS_EVENTS,
DEFAULT_COSMOS_METHODS,
DEFAULT_EIP155_METHODS,
DEFAULT_EIP_155_EVENTS,
DEFAULT_SOLANA_EVENTS,
DEFAULT_SOLANA_METHODS,
} from "../constants";
export const getNamespacesFromChains = (chains: string[]) => {
const supportedNamespaces: string[] = [];
chains.forEach(chainId => {
const [namespace] = chainId.split(":");
if (!supportedNamespaces.includes(namespace)) {
supportedNamespaces.push(namespace);
}
});
return supportedNamespaces;
};
export const getSupportedMethodsByNamespace = (namespace: string) => {
switch (namespace) {
case "eip155":
return Object.values(DEFAULT_EIP155_METHODS);
case "cosmos":
return Object.values(DEFAULT_COSMOS_METHODS);
case "solana":
return Object.values(DEFAULT_SOLANA_METHODS);
default:
throw new Error(`No default methods for namespace: ${namespace}`);
}
};
export const getSupportedEventsByNamespace = (namespace: string) => {
switch (namespace) {
case "eip155":
return Object.values(DEFAULT_EIP_155_EVENTS);
case "cosmos":
return Object.values(DEFAULT_COSMOS_EVENTS);
case "solana":
return Object.values(DEFAULT_SOLANA_EVENTS);
default:
throw new Error(`No default events for namespace: ${namespace}`);
}
};
export const getRequiredNamespaces = (chains: string[]): ProposalTypes.RequiredNamespaces => {
const selectedNamespaces = getNamespacesFromChains(chains);
console.log("selected namespaces:", selectedNamespaces);
return Object.fromEntries(
selectedNamespaces.map(namespace => [
namespace,
{
methods: getSupportedMethodsByNamespace(namespace),
chains: chains.filter(chain => chain.startsWith(namespace)),
events: getSupportedEventsByNamespace(namespace) as any[],
},
]),
);
};
@@ -9,7 +9,7 @@ import { STable } from "../components/shared";
import { SModalContainer, SModalTitle } from "./shared";
interface PairingModalProps {
pairings: PairingTypes.Settled[];
pairings: PairingTypes.Struct[];
connect: (pairing?: { topic: string }) => Promise<void>;
}