@thirdweb-dev/wallets
Version:
<p align="center"> <br /> <a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> <br /> </p> <h1 align="center">thirdweb Wallet SDK</h1> <p align="center"> <a h
422 lines (412 loc) • 14.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var defineProperty = require('../../../../dist/defineProperty-9051a5d3.cjs.dev.js');
var errors = require('../../../../dist/errors-a8e8ea7b.cjs.dev.js');
var ethers = require('ethers');
var walletIds = require('../../../../dist/walletIds-a0be5020.cjs.dev.js');
var url = require('../../../../dist/url-4b641c31.cjs.dev.js');
var WagmiConnector = require('../../../../dist/WagmiConnector-6ff7d562.cjs.dev.js');
require('@thirdweb-dev/chains');
require('eventemitter3');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
const chainsToRequest = new Set([1, 137, 10, 42161, 56]);
const NAMESPACE = "eip155";
const REQUESTED_CHAINS_KEY = "wagmi.requestedChains";
const ADD_ETH_CHAIN_METHOD = "wallet_addEthereumChain";
const LAST_USED_CHAIN_ID = "last-used-chain-id";
class WalletConnectConnector extends WagmiConnector.WagmiConnector {
constructor(config) {
super({
...config,
options: {
isNewChainsStale: true,
...config.options
}
});
defineProperty._defineProperty(this, "id", walletIds.walletIds.walletConnect);
defineProperty._defineProperty(this, "name", "WalletConnect");
defineProperty._defineProperty(this, "ready", true);
defineProperty._defineProperty(this, "onAccountsChanged", accounts => {
if (accounts.length === 0) {
this.emit("disconnect");
} else {
if (accounts[0]) {
this.emit("change", {
account: ethers.utils.getAddress(accounts[0])
});
}
}
});
defineProperty._defineProperty(this, "onChainChanged", async chainId => {
const id = Number(chainId);
const unsupported = this.isChainUnsupported(id);
await this._storage.setItem(LAST_USED_CHAIN_ID, String(chainId));
this.emit("change", {
chain: {
id,
unsupported
}
});
});
defineProperty._defineProperty(this, "onDisconnect", async () => {
await this._setRequestedChainsIds([]);
await this._storage.removeItem(LAST_USED_CHAIN_ID);
this.emit("disconnect");
});
defineProperty._defineProperty(this, "onDisplayUri", uri => {
this.emit("message", {
type: "display_uri",
data: uri
});
});
defineProperty._defineProperty(this, "onConnect", () => {
this.emit("connect", {
provider: this._provider
});
});
this._storage = config.options.storage;
this._createProvider();
this.filteredChains = this.chains.length > 50 ? this.chains.filter(c => {
return chainsToRequest.has(c.chainId);
}) : this.chains;
this.showWalletConnectModal = this.options.qrcode !== false;
}
async connect() {
let {
chainId: chainIdP,
pairingTopic
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
try {
let targetChainId = chainIdP;
if (!targetChainId) {
const lastUsedChainIdStr = await this._storage.getItem(LAST_USED_CHAIN_ID);
const lastUsedChainId = lastUsedChainIdStr ? parseInt(lastUsedChainIdStr) : undefined;
if (lastUsedChainId && !this.isChainUnsupported(lastUsedChainId)) {
targetChainId = lastUsedChainId;
} else {
targetChainId = this.filteredChains[0]?.chainId;
}
}
if (!targetChainId) {
throw new Error("No chains found on connector.");
}
const provider = await this.getProvider();
this.setupListeners();
if (provider.session) {
await provider.disconnect();
const optionalChains = this.filteredChains.filter(chain => chain.chainId !== targetChainId).map(optionalChain => optionalChain.chainId);
this.emit("message", {
type: "connecting"
});
await provider.connect({
pairingTopic,
chains: [targetChainId],
optionalChains: optionalChains.length > 0 ? optionalChains : [targetChainId]
});
await this._setRequestedChainsIds(this.filteredChains.map(_ref => {
let {
chainId
} = _ref;
return chainId;
}));
}
// If session exists and chains are authorized, enable provider for required chain
const accounts = await provider.enable();
if (!accounts[0]) {
throw new Error("No accounts found on provider.");
}
const account = ethers.utils.getAddress(accounts[0]);
const id = await this.getChainId();
const unsupported = this.isChainUnsupported(id);
return {
account,
chain: {
id,
unsupported
},
provider: new ethers.providers.Web3Provider(provider)
};
} catch (error) {
if (/user rejected/i.test(error?.message)) {
throw new errors.UserRejectedRequestError(error);
}
throw error;
}
}
async disconnect() {
const cleanup = () => {
if (typeof localStorage === "undefined") {
return;
}
for (const key in localStorage) {
if (key.startsWith("wc@2")) {
localStorage.removeItem(key);
}
}
};
cleanup();
const provider = await this.getProvider();
const disconnectProvider = async () => {
try {
await provider.disconnect();
} catch (error) {
if (!/No matching key/i.test(error.message)) {
throw error;
}
} finally {
this._removeListeners();
await this._setRequestedChainsIds([]);
cleanup();
}
};
disconnectProvider();
}
async getAccount() {
const {
accounts
} = await this.getProvider();
if (!accounts[0]) {
throw new Error("No accounts found on provider.");
}
return ethers.utils.getAddress(accounts[0]);
}
async getChainId() {
const {
chainId
} = await this.getProvider();
return chainId;
}
async getProvider() {
let {
chainId
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (!this._provider) {
await this._createProvider();
}
if (chainId) {
await this.switchChain(chainId);
}
if (!this._provider) {
throw new Error("No provider found.");
}
return this._provider;
}
async getSigner() {
let {
chainId
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const [provider, account] = await Promise.all([this.getProvider({
chainId
}), this.getAccount()]);
return new ethers.providers.Web3Provider(provider, chainId).getSigner(account);
}
async isAuthorized() {
try {
const [account, provider] = await Promise.all([this.getAccount(), this.getProvider()]);
const isChainsStale = await this._isChainsStale();
// If an account does not exist on the session, then the connector is unauthorized.
if (!account) {
return false;
}
// If the chains are stale on the session, then the connector is unauthorized.
if (isChainsStale && provider.session) {
try {
await provider.disconnect();
} catch {} // eslint-disable-line no-empty
return false;
}
return true;
} catch {
return false;
}
}
async switchChain(chainId) {
const chain = this.chains.find(chain_ => chain_.chainId === chainId);
if (!chain) {
throw new errors.SwitchChainError(`Chain with ID: ${chainId}, not found on connector.`);
}
try {
const provider = await this.getProvider();
const namespaceChains = this._getNamespaceChainsIds();
const namespaceMethods = this._getNamespaceMethods();
const isChainApproved = namespaceChains.includes(chainId);
if (!isChainApproved && namespaceMethods.includes(ADD_ETH_CHAIN_METHOD)) {
const firstExplorer = chain.explorers && chain.explorers[0];
const blockExplorerUrls = firstExplorer ? {
blockExplorerUrls: [firstExplorer.url]
} : {};
await provider.request({
method: ADD_ETH_CHAIN_METHOD,
params: [{
chainId: ethers.utils.hexValue(chain.chainId),
chainName: chain.name,
nativeCurrency: chain.nativeCurrency,
rpcUrls: url.getValidPublicRPCUrl(chain),
// no clientId on purpose
...blockExplorerUrls
}]
});
const requestedChains = await this._getRequestedChainsIds();
requestedChains.push(chainId);
await this._setRequestedChainsIds(requestedChains);
}
await provider.request({
method: "wallet_switchEthereumChain",
params: [{
chainId: ethers.utils.hexValue(chainId)
}]
});
return chain;
} catch (error) {
const message = typeof error === "string" ? error : error?.message;
if (/user rejected request/i.test(message)) {
throw new errors.UserRejectedRequestError(error);
}
throw new errors.SwitchChainError(error);
}
}
async _createProvider() {
if (!this._initProviderPromise && typeof window !== "undefined") {
this._initProviderPromise = this.initProvider();
}
return this._initProviderPromise;
}
async initProvider() {
const {
default: EthereumProvider,
OPTIONAL_EVENTS,
OPTIONAL_METHODS
} = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@walletconnect/ethereum-provider')); });
const [defaultChain, ...optionalChains] = this.filteredChains.map(_ref2 => {
let {
chainId
} = _ref2;
return chainId;
});
if (defaultChain) {
// EthereumProvider populates & deduplicates required methods and events internally
this._provider = await EthereumProvider.init({
showQrModal: this.showWalletConnectModal,
projectId: this.options.projectId,
methods: ["eth_sendTransaction", "personal_sign", "eth_signTypedData_v4"],
optionalMethods: OPTIONAL_METHODS,
optionalEvents: OPTIONAL_EVENTS,
chains: [defaultChain],
optionalChains: optionalChains,
metadata: {
name: this.options.dappMetadata.name,
description: this.options.dappMetadata.description || "",
url: this.options.dappMetadata.url,
icons: [this.options.dappMetadata.logoUrl || ""]
},
rpcMap: Object.fromEntries(this.filteredChains.map(chain => [chain.chainId, chain.rpc[0] || "" // TODO: handle chain.rpc being empty array
])),
qrModalOptions: this.options.qrModalOptions
});
}
}
/**
* Checks if the target chains match the chains that were
* initially requested by the connector for the WalletConnect session.
* If there is a mismatch, this means that the chains on the connector
* are considered stale, and need to be revalidated at a later point (via
* connection).
*
* There may be a scenario where a dapp adds a chain to the
* connector later on, however, this chain will not have been approved or rejected
* by the wallet. In this case, the chain is considered stale.
*
* There are exceptions however:
* - If the wallet supports dynamic chain addition via `eth_addEthereumChain`,
* then the chain is not considered stale.
* - If the `isNewChainsStale` flag is falsy on the connector, then the chain is
* not considered stale.
*
* For the above cases, chain validation occurs dynamically when the user
* attempts to switch chain.
*
* Also check that dapp supports at least 1 chain from previously approved session.
*/
async _isChainsStale() {
const namespaceMethods = this._getNamespaceMethods();
if (namespaceMethods.includes(ADD_ETH_CHAIN_METHOD)) {
return false;
}
if (!this.options.isNewChainsStale) {
return false;
}
const requestedChains = await this._getRequestedChainsIds();
const connectorChains = this.filteredChains.map(_ref3 => {
let {
chainId
} = _ref3;
return chainId;
});
const namespaceChains = this._getNamespaceChainsIds();
if (namespaceChains.length && !namespaceChains.some(id => connectorChains.includes(id))) {
return false;
}
return !connectorChains.every(id => requestedChains.includes(id));
}
async setupListeners() {
if (!this._provider) {
return;
}
this._removeListeners();
this._provider.on("accountsChanged", this.onAccountsChanged);
this._provider.on("chainChanged", this.onChainChanged);
this._provider.on("disconnect", this.onDisconnect);
this._provider.on("session_delete", this.onDisconnect);
this._provider.on("display_uri", this.onDisplayUri);
this._provider.on("connect", this.onConnect);
}
_removeListeners() {
if (!this._provider) {
return;
}
this._provider.removeListener("accountsChanged", this.onAccountsChanged);
this._provider.removeListener("chainChanged", this.onChainChanged);
this._provider.removeListener("disconnect", this.onDisconnect);
this._provider.removeListener("session_delete", this.onDisconnect);
this._provider.removeListener("display_uri", this.onDisplayUri);
this._provider.removeListener("connect", this.onConnect);
}
async _setRequestedChainsIds(chains) {
await this._storage.setItem(REQUESTED_CHAINS_KEY, JSON.stringify(chains));
}
async _getRequestedChainsIds() {
const data = await this._storage.getItem(REQUESTED_CHAINS_KEY);
return data ? JSON.parse(data) : [];
}
_getNamespaceChainsIds() {
if (!this._provider) {
return [];
}
const chainIds = this._provider.session?.namespaces[NAMESPACE]?.chains?.map(chain => parseInt(chain.split(":")[1] || ""));
return chainIds ?? [];
}
_getNamespaceMethods() {
if (!this._provider) {
return [];
}
const methods = this._provider.session?.namespaces[NAMESPACE]?.methods;
return methods ?? [];
}
}
exports.WalletConnectConnector = WalletConnectConnector;