Add WalletConnect support

Co-authored-by: Njoku Emmanuel <kalunjoku123@gmail.com>
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier
2021-08-03 16:25:16 +02:00
co-authored by Njoku Emmanuel
parent 767355b782
commit 400a2df0a4
15 changed files with 2272 additions and 105 deletions
+1693 -20
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -31,12 +31,20 @@
"@gnosis.pm/safe-service-client": "^0.1.0-alpha.0",
"@stardazed/streams": "^3.1.0",
"@types/marked": "^2.0.3",
"@ethersproject/abstract-provider": "^5.4.0",
"@walletconnect/client": "^1.4.2-rc.2",
"@walletconnect/signer-connection": "^1.4.2-rc.2",
"@walletconnect/types": "^1.4.2-rc.2",
"@walletconnect/utils": "^1.4.2-rc.2",
"buffer": "^6.0.3",
"ethereum-blockies": "^0.1.1",
"eventemitter3": "4.0.7",
"eip1193-provider": "1.0.1",
"ethers": "^5.0.31",
"marked": "^2.0.7",
"multibase": "^4.0.4",
"multihashes": "^4.0.2",
"pure-svg-code": "^1.0.6",
"svelte": "^3.32.3",
"svelte-preprocess": "^4.7.2",
"svelte-routing": "^1.6.0"
+7 -1
View File
@@ -15,9 +15,15 @@
import Loading from '@app/Loading.svelte';
import Modal from '@app/Modal.svelte';
const loadConfig = getConfig().then(cfg => {
const loadConfig = getConfig().then(async cfg => {
if ($state.connection === Connection.Connected) {
state.refreshBalance(cfg);
} else if ($state.connection === Connection.Disconnected) {
// Update the session state if we're already connected to WalletConnect
// from a previous session.
if (cfg.walletConnect.client.connected) {
await state.connectWalletConnect(cfg);
}
}
return cfg;
});
+34 -7
View File
@@ -1,23 +1,42 @@
<script lang="ts">
import { Connection } from "@app/session";
import { state } from '@app/session';
import { get } from "svelte/store";
import { Connection, state } from "@app/session";
import type { Err } from '@app/error';
import Error from '@app/Error.svelte';
import type { Config } from '@app/config';
import ConnectWallet from "@app/components/Modal/ConnectWallet.svelte";
export let config: Config;
export let caption = "Connect";
export let className = "";
export let style = "";
export let config: Config;
let walletUnavailable = !window.ethereum;
let error: Err | null = null;
const onModalClose = () => {
const wcs = get(config.walletConnect.state);
if (wcs.state === "open") {
config.walletConnect.state.set({ state: "close" });
wcs.onClose();
}
};
const onConnect = async () => {
try {
await state.connectWalletConnect(config);
} catch (e) {
walletConnectState.set({ state: "close" });
error = e;
}
};
$: connecting = $state.connection === Connection.Connecting;
$: walletConnectState = config.walletConnect.state;
</script>
<style>
</style>
<button
on:click={() => state.connect(config)}
on:click|stopPropagation={onConnect}
{style}
class="connect {className}"
disabled={connecting || walletUnavailable}
@@ -29,3 +48,11 @@
{caption}
{/if}
</button>
{#if $walletConnectState.state === "open"}
<ConnectWallet {config} uri={$walletConnectState.uri} on:close={onModalClose} />
{:else if error}
<Error floating title="Wallet connection failed" {error}
on:close={() => error = null} />
{/if}
+1 -1
View File
@@ -148,7 +148,7 @@
</span>
<button class="address outline small" bind:this={sessionButton}
on:click={disconnectWallet}
on:click={() => disconnectWallet(config)}
on:mouseover={() => sessionButtonHover = true}
on:mouseout={() => sessionButtonHover = false}
>
+2 -1
View File
@@ -4,6 +4,7 @@
export let subtle = false;
export let small = false;
export let narrow = false;
export let center = false;
</script>
<style>
@@ -96,7 +97,7 @@
<div class="modal-overlay"></div>
{/if}
<div class:modal-floating={floating} class="centered">
<div class:modal-floating={floating} class:centered={!center}>
<div class="modal" class:error
class:modal-subtle={subtle}
class:modal-narrow={narrow}
+131
View File
@@ -0,0 +1,131 @@
import type WalletConnect from "@walletconnect/client";
import * as ethers from "ethers";
import * as ethersBytes from "@ethersproject/bytes";
import type { TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
import { Deferrable, resolveProperties } from "@ethersproject/properties";
import { _TypedDataEncoder } from "ethers/lib/utils";
import type { TypedDataDomain, TypedDataField } from "@ethersproject/abstract-signer";
export class WalletConnectSigner extends ethers.Signer {
public walletConnect: WalletConnect;
public readonly provider: ethers.providers.JsonRpcProvider;
constructor(
walletConnect: WalletConnect,
provider: ethers.providers.JsonRpcProvider,
) {
super();
this.provider = provider;
this.walletConnect = walletConnect;
}
async getAddress(): Promise<string> {
const accountAddress = this.walletConnect.accounts[0];
if (!accountAddress) {
throw new Error(
"The connected wallet has no accounts or there is a connection problem"
);
}
return accountAddress;
}
async _signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
// Populate any ENS names (in-place)
const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => {
return this.provider.resolveName(name);
});
const address = await this.getAddress();
const signature = await this.walletConnect.signTypedData([
address.toLowerCase(),
JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value)),
]);
return signature;
}
async signMessage(message: ethers.Bytes | string): Promise<string> {
const prefix = ethers.utils.toUtf8Bytes(
`\x19Ethereum Signed Message:\n${message.length}`
);
const msg = ethers.utils.concat([prefix, message]);
const address = await this.getAddress();
const keccakMessage = ethers.utils.keccak256(msg);
const signature = await this.walletConnect.signMessage([
address.toLowerCase(),
keccakMessage,
]);
return signature;
}
async sendTransaction(
transaction: Deferrable<TransactionRequest>
): Promise<TransactionResponse> {
const tx = await resolveProperties(transaction);
const from = tx.from || (await this.getAddress());
const txHash = await this.walletConnect.sendTransaction({
from,
to: tx.to,
value: maybeBigNumberToString(tx.value),
data: bytesLikeToString(tx.data),
});
return <TransactionResponse>{
hash: txHash,
nonce: tx.nonce,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
data: bytesLikeToString(tx.data) || "",
value: tx.value,
chainId: tx.chainId,
confirmations: 0,
from: from,
wait: (confirmations?: number) => {
return this.provider?.waitForTransaction(txHash, confirmations);
}
};
}
async signTransaction(
transaction: Deferrable<TransactionRequest>
): Promise<string> {
const tx = await resolveProperties(transaction);
const from = tx.from || (await this.getAddress());
const nonce = await this.provider.getTransactionCount(from);
const signedTx = await this.walletConnect.signTransaction({
from,
to: tx.to,
value: maybeBigNumberToString(tx.value || 0),
gasLimit: maybeBigNumberToString(tx.gasLimit || 200 * 1000),
gasPrice: maybeBigNumberToString(tx.gasPrice || 0),
nonce,
data: bytesLikeToString(tx.data),
});
return signedTx;
}
connect(): ethers.Signer {
throw new Error("WalletConnectSigner.connect should never be called");
}
}
function maybeBigNumberToString(
bn: ethers.BigNumberish | undefined
): string | undefined {
if (bn === undefined) {
return undefined;
} else {
return ethers.BigNumber.from(bn).toString();
}
}
function bytesLikeToString(
bytes: ethersBytes.BytesLike | undefined
): string | undefined {
if (bytes === undefined) {
return undefined;
} else {
return ethersBytes.hexlify(bytes);
}
}
+5 -7
View File
@@ -1,4 +1,3 @@
// TODO: Show "look at your wallet" / "confirm tx" before state change.
import { ethers } from 'ethers';
import { writable } from 'svelte/store';
import type { BigNumber } from 'ethers';
@@ -99,7 +98,7 @@ export async function registrationFee(config: Config): Promise<BigNumber> {
}
export async function registerName(name: string, owner: string, config: Config): Promise<void> {
assert(config.signer);
assert(config.signer, "signer is not available");
if (! name) return;
@@ -144,7 +143,7 @@ async function commitAndRegister(name: string, owner: string, config: Config): P
}
async function commit(commitment: string, fee: BigNumber, minAge: number, config: Config): Promise<void> {
assert(config.signer);
assert(config.signer, "signer is not available");
const owner = config.signer;
const ownerAddr = await owner.getAddress();
@@ -187,8 +186,7 @@ async function permitSignature(
value: ethers.BigNumberish,
deadline: ethers.BigNumberish,
): Promise<ethers.Signature> {
assert(owner.provider);
assert(owner.provider, "provider is not available");
state.set({ connection: State.SigningPermit });
const ownerAddr = await owner.getAddress();
@@ -222,7 +220,7 @@ async function permitSignature(
}
async function register(name: string, owner: string, salt: Uint8Array, config: Config) {
assert(config.signer);
assert(config.signer, "signer is not available");
state.set({ connection: State.SigningRegister });
const tx = await registrar(config).connect(config.signer).register(
@@ -233,7 +231,7 @@ async function register(name: string, owner: string, salt: Uint8Array, config: C
console.log("Sent", tx);
await tx.wait();
window.localStorage.clear();
window.localStorage.removeItem("commitment");
state.set({ connection: State.Registered });
}
+79
View File
@@ -0,0 +1,79 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { qrcode } from "pure-svg-code";
import Modal from "@app/Modal.svelte";
import { state } from "@app/session";
import type { Config } from "@app/config";
export let uri: string;
export let config: Config;
$: svgString = qrcode({
content: uri,
width: 200,
height: 200,
color: "black",
padding: 0,
background: "white",
ecl: "M",
});
const dispatch = createEventDispatcher();
const onClickConnect = () => { state.connectMetamask(config); };
const onClose = () => { dispatch("close"); };
</script>
<style>
.qrcode-wrapper {
width: min-content;
margin: 0 auto;
padding: 0.75rem;
background-color: white;
}
.qrcode {
width: 200px;
height: 200px;
}
.wrapper {
cursor: default;
display: flex;
flex-direction: column;
height: 100%;
}
</style>
<div class="wrapper">
<Modal floating center>
<div slot="title">
<div>👛</div>
<div>Connect your wallet</div>
</div>
<div slot="subtitle">
<div class="text-small">
WalletConnect<br/>
<a href="https://walletconnect.org/wallets" class="link">
View compatible wallets
</a>
</div>
</div>
<div slot="body">
<div class="qrcode-wrapper">
<div class="qrcode">
{@html svgString}
</div>
</div>
</div>
<div slot="actions">
<button class="secondary tiny text-small" on:click={onClickConnect} disabled={!config.metamaskSigner}>
Connect with Metamask
</button>
<button class="text tiny text-small" on:click={onClose}>
Close
</button>
</div>
</Modal>
</div>
+1
View File
@@ -48,6 +48,7 @@
"tokens": []
},
"alchemy": { "key": "cQFlLK8EokIGlJhd_soImwEyUoC7Ec8r" },
"walletConnect": { "bridge": "https://radicle.bridge.walletconnect.org" },
"ceramic": { "api": "https://ceramic-clay.3boxlabs.com" },
"radicle": { "api": "http://0.0.0.0:8888" },
"ipfs": { "gateway": "https://ipfs.io/ipfs/" },
+140 -36
View File
@@ -1,9 +1,13 @@
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";
import { ethers } from "ethers";
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import SafeServiceClient from "@gnosis.pm/safe-service-client";
import CeramicClient from "@ceramicnetwork/http-client";
import { IDX } from "@ceramicstudio/idx";
import WalletConnect from "@walletconnect/client";
import config from "@app/config.json";
import { WalletConnectSigner } from "./WalletConnectSigner";
declare global {
interface Window {
@@ -12,6 +16,15 @@ declare global {
}
}
/// Gas limits for various transactions.
const gasLimits = {
createOrg: 1_200_000,
};
export type WalletConnectState =
{ state: "close" }
| { state: "open"; uri: string; onClose: any };
export class Config {
network: { name: string; chainId: number };
registrar: { address: string; domain: string };
@@ -21,7 +34,14 @@ export class Config {
orgs: { subgraph: string; contractHash: string };
gasLimits: { createOrg: number };
provider: ethers.providers.JsonRpcProvider;
signer: ethers.Signer & TypedDataSigner | null;
signer: ethers.Signer & TypedDataSigner | WalletConnectSigner | null;
walletConnect: {
client: WalletConnect;
bridge: string;
signer: WalletConnectSigner;
state: Writable<WalletConnectState>;
};
metamaskSigner: ethers.Signer & TypedDataSigner | null;
safe: {
api?: string;
client?: SafeServiceClient;
@@ -36,29 +56,31 @@ export class Config {
constructor(
network: { name: string; chainId: number },
provider: ethers.providers.JsonRpcProvider | null,
signer: ethers.Signer & TypedDataSigner | null,
provider: ethers.providers.JsonRpcProvider,
metamaskSigner: ethers.Signer & TypedDataSigner | null,
) {
const cfg = (<Record<string, any>> config)[network.name];
const api = config.radicle.api;
if (! cfg) {
throw `Network ${network.name} is not supported`;
}
// Use Alchemy in production, on mainnet.
provider = network.name === "homestead" && import.meta.env.PROD
? new ethers.providers.AlchemyProvider(network.name, config.alchemy.key)
: provider;
if (! provider) {
throw `No Web3 provider available.`;
}
const cfg = (<Record<string, any>> config)[network.name];
const ceramic = new CeramicClient(config.ceramic.api);
const idx = new IDX({ ceramic });
const walletConnectState = writable<WalletConnectState>(
{ state: "close" }
);
const wc = Config.initializeWalletConnect(
config.walletConnect.bridge,
walletConnectState,
provider
);
this.network = network;
this.metamaskSigner = metamaskSigner;
this.walletConnect = {
bridge: config.walletConnect.bridge,
client: wc.connector,
signer: wc.signer,
state: walletConnectState,
};
this.seed = { api };
this.registrar = cfg.registrar;
this.radToken = cfg.radToken;
@@ -70,7 +92,7 @@ export class Config {
? new SafeServiceClient(this.safe.api)
: undefined;
this.provider = provider;
this.signer = signer;
this.signer = null;
this.gasLimits = gasLimits;
this.abi = config.abi;
this.idx = { client: idx };
@@ -83,6 +105,10 @@ export class Config {
);
}
setSigner(signer: ethers.Signer & TypedDataSigner | WalletConnectSigner): void {
this.signer = signer;
}
// Return the config with an overwritten seed URL.
withSeed(seed: string): Config {
const cfg = {} as Config;
@@ -91,31 +117,109 @@ export class Config {
return cfg;
}
}
/// Gas limits for various transactions.
const gasLimits = {
createOrg: 1_200_000,
};
getWalletConnectSigner(): WalletConnectSigner {
if (this.walletConnect.client.connected) {
this.setSigner(this.walletConnect.signer);
return this.walletConnect.signer;
}
const wc = Config.initializeWalletConnect(
this.walletConnect.bridge,
this.walletConnect.state,
this.provider
);
this.walletConnect.client = wc.connector;
this.walletConnect.signer = wc.signer;
this.setSigner(wc.signer);
return wc.signer;
}
static initializeWalletConnect(
bridge: string,
state: Writable<WalletConnectState>,
provider: ethers.providers.JsonRpcProvider
): {
connector: WalletConnect;
signer: WalletConnectSigner;
} {
const walletConnect = new WalletConnect({
bridge: config.walletConnect.bridge,
qrcodeModal: {
open: (uri: string, onClose) => {
state.set({ state: "open", uri, onClose });
},
close: () => {
state.set({ state: "close" });
}
},
clientMeta: {
name: "Radicle",
description: "Radicle Interface",
url: window.location.host,
icons: [
`${window.location.protocol}//${window.location.host}/favicon.ico`,
`${window.location.protocol}//${window.location.host}/favicon.svg`
],
}
});
walletConnect.on("modal_closed", () => {
state.set({ state: "close" });
});
const walletConnectSigner = new WalletConnectSigner(walletConnect, provider);
return {
connector: walletConnect,
signer: walletConnectSigner,
};
}
}
function isMetamaskInstalled(): boolean {
const { ethereum } = window;
return Boolean(ethereum && ethereum.isMetaMask);
}
export async function getConfig(): Promise<Config> {
let config: Config;
if (isMetamaskInstalled()) {
const metamask = new ethers.providers.Web3Provider(window.ethereum);
const network = await metamask.ready;
config = new Config(network, metamask, metamask.getSigner());
function getProvider(
network: { name: string },
config: Record<string, any>,
metamask: ethers.providers.JsonRpcProvider | null,
): ethers.providers.JsonRpcProvider {
// Use Alchemy in production, on mainnet. Otherwise use Metamask if installed.
if (network.name === "homestead" && import.meta.env.PROD) {
return new ethers.providers.AlchemyProvider(network.name, config.alchemy.key);
} else if (metamask) {
return metamask;
} else {
// If we don't have Metamask, we default to Homestead.
const network = { name: "homestead", chainId: 1 };
config = new Config(network, null, null);
throw new Error("No Web3 provider available.");
}
console.log(config);
}
return config;
export async function getConfig(): Promise<Config> {
const metamask = isMetamaskInstalled()
? new ethers.providers.Web3Provider(window.ethereum)
: null;
const metamaskSigner = metamask?.getSigner() || null;
let network = { name: "homestead", chainId: 1 };
if (metamask) {
// If Metamask is detected, we use the network configured there.
network = await metamask.ready;
}
const networkConfig = (<Record<string, any>> config)[network.name];
if (! networkConfig) {
throw new Error(`Network ${network.name} is not supported`);
}
const provider = getProvider(network, networkConfig, metamask);
const cfg = new Config(
network,
provider,
metamaskSigner,
);
console.log(cfg, "config");
return cfg;
}
+73
View File
@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-var-requires */
"use strict";
var Buffer = require("buffer/").Buffer;
var typedarrayToBuffer = require("typedarray-to-buffer");
const ENC_UTF8 = 'utf8';
const ENC_HEX = 'hex';
function bufferToArray(buf) {
return new Uint8Array(buf);
}
exports.bufferToArray = bufferToArray;
function bufferToUtf8(buf) {
return buf.toString(ENC_UTF8);
}
exports.bufferToUtf8 = bufferToUtf8;
function bufferToHex(buf, prefixed = false) {
const hex = buf.toString(ENC_HEX);
return prefixed ? addHexPrefix(hex) : hex;
}
exports.bufferToHex = bufferToHex;
function utf8ToBuffer(utf8) {
return Buffer.from(utf8, ENC_UTF8);
}
exports.utf8ToBuffer = utf8ToBuffer;
function utf8ToArray(utf8) {
return bufferToArray(utf8ToBuffer(utf8));
}
exports.utf8ToArray = utf8ToArray;
function hexToArray(hex) {
return bufferToArray(hexToBuffer(hex));
}
exports.hexToArray = hexToArray;
function hexToBuffer(hex) {
return Buffer.from(removeHexPrefix(hex), ENC_HEX);
}
exports.hexToBuffer = hexToBuffer;
function removeHexPrefix(hex) {
return hex.replace(/^0x/, '');
}
exports.removeHexPrefix = removeHexPrefix;
function addHexPrefix(hex) {
return hex.startsWith('0x') ? hex : `0x${hex}`;
}
exports.addHexPrefix = addHexPrefix;
function arrayToBuffer(arr) {
return typedarrayToBuffer(arr);
}
exports.arrayToBuffer = arrayToBuffer;
function arrayToHex(arr, prefixed = false) {
return bufferToHex(arrayToBuffer(arr), prefixed);
}
exports.arrayToHex = arrayToHex;
function concatArrays(...args) {
let result = [];
args.forEach(arg => (result = result.concat(Array.from(arg))));
return new Uint8Array([...result]);
}
exports.concatArrays = concatArrays;
+17
View File
@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-var-requires */
var isTypedArray = require('is-typedarray').strict;
var Buffer = require('buffer/').Buffer;
module.exports = function typedarrayToBuffer(arr) {
if (isTypedArray(arr)) {
var buf = Buffer.from(arr.buffer);
if (arr.byteLength !== arr.buffer.byteLength) {
buf = buf.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
}
return buf;
} else {
return Buffer.from(arr);
}
};
+69 -22
View File
@@ -3,6 +3,7 @@ import type { BigNumber } from 'ethers';
import type { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
import { Config, getConfig } from "@app/config";
import { Unreachable, assert, assertEq } from "@app/error";
import * as ethers from "ethers";
export enum Connection {
Disconnected,
@@ -29,10 +30,10 @@ export interface Session {
}
export interface Store extends Readable<State> {
connect(config: Config): Promise<void>;
connectMetamask(config: Config): Promise<void>;
connectWalletConnect(config: Config): Promise<void>;
updateBalance(n: BigNumber): void;
refreshBalance(config: Config): Promise<void>;
setTxSigning(): void;
setTxPending(tx: TransactionResponse): void;
setTxConfirmed(tx: TransactionReceipt): void;
@@ -41,42 +42,80 @@ export interface Store extends Readable<State> {
export const loadState = (initial: State): Store => {
const store = writable<State>(initial);
const session = window.localStorage.getItem("session");
const metamask = window.localStorage.getItem("metamask");
if (session) store.set({ connection: Connection.Connected, session: JSON.parse(session) });
if (metamask) {
store.set({ connection: Connection.Connected, session: JSON.parse(metamask) });
}
return {
subscribe: store.subscribe,
connect: async (config: Config) => {
assert(config.signer);
connectMetamask: async (config: Config) => {
assert(config.metamaskSigner);
const state = get(store);
assertEq(state.connection, Connection.Disconnected);
assertEq(state.connection, Connection.Disconnected || Connection.Connecting);
store.set({ connection: Connection.Connecting });
// TODO: This hangs on Brave, if you have to unlock your wallet..
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (e) {
console.error(e);
}
await window.ethereum.request({ method: 'eth_requestAccounts' });
const signer = config.signer;
const signer = config.metamaskSigner;
const address = await signer.getAddress();
config.setSigner(signer);
try {
config.walletConnect.state.set({ state: "close" });
const tokenBalance: BigNumber = await config.token.balanceOf(address);
const session = { address, tokenBalance, tx: null };
store.set({
connection: Connection.Connected,
session,
});
saveSession({ ...session, tokenBalance: null });
saveSession({ ...session });
} catch (e) {
console.error(e);
}
},
connectWalletConnect: async (config: Config) => {
store.set({ connection: Connection.Connecting });
const signer = config.getWalletConnectSigner();
try {
await config.walletConnect.client.connect();
const address = await signer.getAddress();
const tokenBalance: BigNumber = await config.token.balanceOf(address);
const session = { address, tokenBalance, tx: null };
const network = await ethers.providers.getNetwork(
signer.walletConnect.chainId
);
if (network.chainId !== config.network.chainId) {
config.walletConnect.client.killSession();
throw new Error(
`Network mismatch. Please switch to ${config.network.name}.`
);
}
store.set({ connection: Connection.Connected, session });
} catch (e) {
store.set({ connection: Connection.Disconnected });
// There seems to be no way to detect this "error" caused by the user
// closing the modal dialog, besides matching on the message string.
// Welcome to the wonderful ghetto that is WalletConnect.
//
// Since it's not really an error, we don't throw if this is what happened.
if (e.message !== "User close QRCode Modal") {
throw e;
}
}
},
updateBalance: (n: BigNumber) => {
store.update((s: State) => {
assert(s.connection === Connection.Connected);
@@ -168,7 +207,7 @@ export const loadState = (initial: State): Store => {
// In case of locking Metamask the accountsChanged event returns undefined.
// To prevent out of sync state, the wallet gets disconnected.
if (address === undefined) {
disconnectWallet();
disconnectMetamask();
} else {
s.session.address = address;
saveSession(s.session);
@@ -178,11 +217,12 @@ export const loadState = (initial: State): Store => {
return s;
}
});
}
},
};
};
export const state = loadState({ connection: Connection.Disconnected });
export const session = derived(state, s => {
if (s.connection === Connection.Connected) {
return s.session;
@@ -193,7 +233,7 @@ export const session = derived(state, s => {
window.ethereum?.on('chainChanged', () => {
// We disconnect the wallet to avoid out of sync state
// between the account address and IDX DIDs
disconnectWallet();
disconnectMetamask();
location.reload();
});
@@ -222,13 +262,20 @@ export async function approveSpender(spender: string, amount: BigNumber, config:
}
}
export function disconnectWallet(): void {
window.localStorage.removeItem("session");
location.reload();
export function disconnectMetamask(): void {
window.localStorage.removeItem('metamask');
window.location.reload();
}
export function disconnectWallet(config: Config): void {
if (config.walletConnect.client.connected) {
config.walletConnect.client.killSession();
}
disconnectMetamask();
}
function saveSession(session: Session): void {
window.localStorage.setItem("session", JSON.stringify({
window.localStorage.setItem("metamask", JSON.stringify({
...session, tokenBalance: null
}));
}
+12 -10
View File
@@ -1,26 +1,28 @@
import path from "path";
import { UserConfig } from "vite";
import svelte from "@sveltejs/vite-plugin-svelte";
import rewriteAll from "vite-plugin-rewrite-all";
import path from 'path';
import { UserConfig } from 'vite';
import svelte from '@sveltejs/vite-plugin-svelte';
import rewriteAll from 'vite-plugin-rewrite-all';
const config: UserConfig = {
optimizeDeps: {
exclude: ['svelte-routing']
exclude: ['svelte-routing', '@pedrouid/environment', '@pedrouid/iso-crypto']
},
plugins: [svelte(), rewriteAll()],
resolve: {
alias: {
"@app": path.resolve("./src"),
// Polyfill for Node.js "stream" library.
"stream": path.resolve("./src/polyfills/stream.ts"),
'@app': path.resolve('./src'),
// Polyfill for Node.js 'stream' library.
'stream': path.resolve('./src/polyfills/stream.ts'),
'typedarray-to-buffer': path.resolve('./src/polyfills/typedarray-to-buffer.js'),
'enc-utils': path.resolve('./src/polyfills/enc-utils.js'),
},
},
define: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"process.env": { READABLE_STREAM: "disable" }
'process.env': { READABLE_STREAM: 'disable' }
},
build: {
outDir: "build",
outDir: 'build',
sourcemap: true
}
};