@web3auth/wallet-connect-v2-adapter
Version:
wallet connect v2 adapter for web3auth
373 lines (367 loc) • 17 kB
JavaScript
'use strict';
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var baseControllers = require('@toruslabs/base-controllers');
var Client = require('@walletconnect/sign-client');
var utils = require('@walletconnect/utils');
var base = require('@web3auth/base');
var base58 = require('bs58');
var deepmerge = require('deepmerge');
var config = require('./config.js');
var WalletConnectV2Provider = require('./WalletConnectV2Provider.js');
class WalletConnectV2Adapter extends base.BaseAdapter {
constructor(options = {}) {
super(options);
_defineProperty(this, "name", base.WALLET_ADAPTERS.WALLET_CONNECT_V2);
_defineProperty(this, "adapterNamespace", base.ADAPTER_NAMESPACES.MULTICHAIN);
_defineProperty(this, "currentChainNamespace", base.CHAIN_NAMESPACES.OTHER);
_defineProperty(this, "type", base.ADAPTER_CATEGORY.EXTERNAL);
_defineProperty(this, "adapterOptions", {});
_defineProperty(this, "status", base.ADAPTER_STATUS.NOT_READY);
_defineProperty(this, "adapterData", {
uri: ""
});
_defineProperty(this, "connector", null);
_defineProperty(this, "activeSession", null);
_defineProperty(this, "wcProvider", null);
this.adapterOptions = _objectSpread({}, options);
this.setAdapterSettings(options);
}
get connected() {
return !!this.activeSession;
}
get provider() {
if (this.status !== base.ADAPTER_STATUS.NOT_READY && this.wcProvider) {
return this.wcProvider;
}
return null;
}
set provider(_) {
throw new Error("Not implemented");
}
async init(options) {
var _this$adapterOptions$, _this$chainConfig, _this$chainConfig2;
super.checkInitializationRequirements();
const projectId = (_this$adapterOptions$ = this.adapterOptions.adapterSettings) === null || _this$adapterOptions$ === void 0 || (_this$adapterOptions$ = _this$adapterOptions$.walletConnectInitOptions) === null || _this$adapterOptions$ === void 0 ? void 0 : _this$adapterOptions$.projectId;
if (!projectId) {
throw base.WalletInitializationError.invalidParams("Wallet connect project id is required in wallet connect v2 adapter");
}
const wc2Settings = await config.getWalletConnectV2Settings((_this$chainConfig = this.chainConfig) === null || _this$chainConfig === void 0 ? void 0 : _this$chainConfig.chainNamespace, [(_this$chainConfig2 = this.chainConfig) === null || _this$chainConfig2 === void 0 ? void 0 : _this$chainConfig2.chainId], projectId);
if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) {
this.adapterOptions.loginSettings = wc2Settings.loginSettings;
}
this.adapterOptions.adapterSettings = deepmerge(wc2Settings.adapterSettings || {}, this.adapterOptions.adapterSettings || {});
const {
adapterSettings
} = this.adapterOptions;
this.connector = await Client.init(adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.walletConnectInitOptions);
this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({
clientId: this.clientId,
config: {
chainConfig: this.chainConfig
},
connector: this.connector
});
this.emit(base.ADAPTER_EVENTS.READY, base.WALLET_ADAPTERS.WALLET_CONNECT_V2);
this.status = base.ADAPTER_STATUS.READY;
base.log.debug("initializing wallet connect v2 adapter");
if (options.autoConnect) {
await this.checkForPersistedSession();
if (this.connected) {
this.rehydrated = true;
try {
await this.onConnectHandler();
} catch (error) {
base.log.error("wallet auto connect", error);
this.emit(base.ADAPTER_EVENTS.ERRORED, error);
}
} else {
this.status = base.ADAPTER_STATUS.NOT_READY;
this.emit(base.ADAPTER_EVENTS.CACHE_CLEAR);
}
}
}
async connect() {
super.checkConnectionRequirements();
if (!this.connector) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
try {
// if already connected
if (this.connected) {
await this.onConnectHandler();
return this.provider;
}
if (this.status !== base.ADAPTER_STATUS.CONNECTING) {
await this.createNewSession();
}
return this.provider;
} catch (error) {
base.log.error("Wallet connect v2 adapter error while connecting", error);
// ready again to be connected
this.status = base.ADAPTER_STATUS.READY;
this.rehydrated = true;
this.emit(base.ADAPTER_EVENTS.ERRORED, error);
const finalError = error instanceof base.Web3AuthError ? error : base.WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`, error);
throw finalError;
}
}
// should be called only before initialization.
setAdapterSettings(adapterSettings) {
var _this$adapterOptions$2, _this$adapterOptions, _this$adapterOptions$3, _this$adapterOptions2, _this$adapterOptions$4;
super.setAdapterSettings(adapterSettings);
const {
qrcodeModal,
walletConnectInitOptions
} = (adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.adapterSettings) || {};
this.adapterOptions = _objectSpread(_objectSpread({}, this.adapterOptions), {}, {
adapterSettings: (_this$adapterOptions$2 = (_this$adapterOptions = this.adapterOptions) === null || _this$adapterOptions === void 0 ? void 0 : _this$adapterOptions.adapterSettings) !== null && _this$adapterOptions$2 !== void 0 ? _this$adapterOptions$2 : {},
loginSettings: (_this$adapterOptions$3 = (_this$adapterOptions2 = this.adapterOptions) === null || _this$adapterOptions2 === void 0 ? void 0 : _this$adapterOptions2.loginSettings) !== null && _this$adapterOptions$3 !== void 0 ? _this$adapterOptions$3 : {}
});
if (qrcodeModal) this.adapterOptions.adapterSettings.qrcodeModal = qrcodeModal;
if (walletConnectInitOptions) this.adapterOptions.adapterSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$adapterOptions$4 = this.adapterOptions.adapterSettings.walletConnectInitOptions) !== null && _this$adapterOptions$4 !== void 0 ? _this$adapterOptions$4 : {}), walletConnectInitOptions);
const {
loginSettings
} = adapterSettings;
if (loginSettings) this.adapterOptions.loginSettings = _objectSpread(_objectSpread({}, this.adapterOptions.loginSettings || {}), loginSettings);
}
async addChain(chainConfig, init = false) {
var _this$wcProvider;
super.checkAddChainRequirements(chainConfig, init);
await ((_this$wcProvider = this.wcProvider) === null || _this$wcProvider === void 0 ? void 0 : _this$wcProvider.addChain(chainConfig));
this.addChainConfig(chainConfig);
}
async switchChain(params, init = false) {
var _this$wcProvider2;
super.checkSwitchChainRequirements(params, init);
await ((_this$wcProvider2 = this.wcProvider) === null || _this$wcProvider2 === void 0 ? void 0 : _this$wcProvider2.switchChain({
chainId: params.chainId
}));
this.setAdapterSettings({
chainConfig: this.getChainConfig(params.chainId)
});
}
async getUserInfo() {
if (!this.connected) throw base.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
return {};
}
async disconnect(options = {
cleanup: false,
sessionRemovedByWallet: false
}) {
var _this$activeSession, _this$activeSession2;
const {
cleanup
} = options;
if (!this.connector || !this.connected || !((_this$activeSession = this.activeSession) !== null && _this$activeSession !== void 0 && _this$activeSession.topic)) throw base.WalletLoginError.notConnectedError("Not connected with wallet");
if (!options.sessionRemovedByWallet) await this.connector.disconnect({
topic: (_this$activeSession2 = this.activeSession) === null || _this$activeSession2 === void 0 ? void 0 : _this$activeSession2.topic,
reason: utils.getSdkError("USER_DISCONNECTED")
});
this.rehydrated = false;
if (cleanup) {
this.connector = null;
this.status = base.ADAPTER_STATUS.NOT_READY;
this.wcProvider = null;
} else {
// ready to connect again
this.status = base.ADAPTER_STATUS.READY;
}
this.activeSession = null;
this.emit(base.ADAPTER_EVENTS.DISCONNECTED);
}
async authenticateUser() {
if (!this.provider || this.status !== base.ADAPTER_STATUS.CONNECTED) throw base.WalletLoginError.notConnectedError();
const {
chainNamespace,
chainId
} = this.chainConfig;
const accounts = await this.provider.request({
method: chainNamespace === base.CHAIN_NAMESPACES.EIP155 ? "eth_accounts" : "getAccounts"
});
if (accounts && accounts.length > 0) {
const existingToken = base.getSavedToken(accounts[0], this.name);
if (existingToken) {
const isExpired = base.checkIfTokenIsExpired(existingToken);
if (!isExpired) {
return {
idToken: existingToken
};
}
}
const payload = {
domain: window.location.origin,
uri: window.location.href,
address: accounts[0],
chainId: parseInt(chainId, 16),
version: "1",
nonce: Math.random().toString(36).slice(2),
issuedAt: new Date().toISOString()
};
const challenge = await baseControllers.signChallenge(payload, chainNamespace);
const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace);
const idToken = await baseControllers.verifySignedChallenge(chainNamespace, signedMessage, challenge, this.name, this.sessionTime, this.clientId, this.web3AuthNetwork);
base.saveToken(accounts[0], this.name, idToken);
return {
idToken
};
}
throw base.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
}
async enableMFA() {
throw new Error("Method Not implemented");
}
async manageMFA() {
throw new Error("Method Not implemented");
}
cleanupPendingPairings() {
if (!this.connector) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
const inactivePairings = this.connector.pairing.getAll({
active: false
});
if (!utils.isValidArray(inactivePairings)) return;
inactivePairings.forEach(pairing => {
if (this.connector) {
this.connector.pairing.delete(pairing.topic, utils.getSdkError("USER_DISCONNECTED"));
}
});
}
async checkForPersistedSession() {
if (!this.connector) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (this.connector.session.length) {
const lastKeyIndex = this.connector.session.keys.length - 1;
this.activeSession = this.connector.session.get(this.connector.session.keys[lastKeyIndex]);
}
return this.activeSession;
}
async createNewSession(opts = {
forceNewSession: false
}) {
try {
var _this$activeSession3, _this$adapterOptions3;
if (!this.connector) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) throw base.WalletInitializationError.notReady("login settings are not set yet");
this.status = base.ADAPTER_STATUS.CONNECTING;
this.emit(base.ADAPTER_EVENTS.CONNECTING, {
adapter: base.WALLET_ADAPTERS.WALLET_CONNECT_V2
});
if (opts.forceNewSession && (_this$activeSession3 = this.activeSession) !== null && _this$activeSession3 !== void 0 && _this$activeSession3.topic) {
var _this$activeSession4;
await this.connector.disconnect({
topic: (_this$activeSession4 = this.activeSession) === null || _this$activeSession4 === void 0 ? void 0 : _this$activeSession4.topic,
reason: utils.getSdkError("USER_DISCONNECTED")
});
}
const {
uri,
approval
} = await this.connector.connect(this.adapterOptions.loginSettings);
const qrcodeModal = (_this$adapterOptions3 = this.adapterOptions) === null || _this$adapterOptions3 === void 0 || (_this$adapterOptions3 = _this$adapterOptions3.adapterSettings) === null || _this$adapterOptions3 === void 0 ? void 0 : _this$adapterOptions3.qrcodeModal;
// Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing).
if (uri) {
if (qrcodeModal) {
try {
await qrcodeModal.openModal({
uri
});
base.log.debug("EVENT", "QR Code Modal closed");
this.status = base.ADAPTER_STATUS.READY;
this.emit(base.ADAPTER_EVENTS.READY, base.WALLET_ADAPTERS.WALLET_CONNECT_V2);
} catch (error) {
base.log.error("unable to open qr code modal");
}
} else {
this.updateAdapterData({
uri
});
}
}
base.log.info("awaiting session approval from wallet");
// Await session approval from the wallet.
const session = await approval();
this.activeSession = session;
// Handle the returned session (e.g. update UI to "connected" state).
await this.onConnectHandler();
if (qrcodeModal) {
qrcodeModal.closeModal();
}
} catch (error) {
var _message;
if ((_message = error.message) !== null && _message !== void 0 && _message.toLowerCase().includes("proposal expired")) {
// Retry if adapter status is still connecting
base.log.info("current adapter status: ", this.status);
if (this.status === base.ADAPTER_STATUS.CONNECTING) {
base.log.info("retrying to create new wallet connect session since proposal expired");
return this.createNewSession({
forceNewSession: true
});
}
if (this.status === base.ADAPTER_STATUS.READY) {
base.log.info("ignoring proposal expired error since some other adapter is connected");
return;
}
}
base.log.error("error while creating new wallet connect session", error);
this.emit(base.ADAPTER_EVENTS.ERRORED, error);
throw error;
}
}
async onConnectHandler() {
var _this$adapterOptions$5;
if (!this.connector || !this.wcProvider) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (!this.chainConfig) throw base.WalletInitializationError.invalidParams("Chain config is not set");
this.subscribeEvents();
if ((_this$adapterOptions$5 = this.adapterOptions.adapterSettings) !== null && _this$adapterOptions$5 !== void 0 && _this$adapterOptions$5.qrcodeModal) {
this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({
clientId: this.clientId,
config: {
chainConfig: this.chainConfig,
skipLookupNetwork: true
},
connector: this.connector
});
}
await this.wcProvider.setupProvider(this.connector);
this.cleanupPendingPairings();
this.status = base.ADAPTER_STATUS.CONNECTED;
this.emit(base.ADAPTER_EVENTS.CONNECTED, {
adapter: base.WALLET_ADAPTERS.WALLET_CONNECT_V2,
reconnected: this.rehydrated,
provider: this.provider
});
}
subscribeEvents() {
if (!this.connector) throw base.WalletInitializationError.notReady("Wallet adapter is not ready yet");
this.connector.events.on("session_update", ({
topic,
params
}) => {
if (!this.connector) return;
const {
namespaces
} = params;
const _session = this.connector.session.get(topic);
// Overwrite the `namespaces` of the existing session with the incoming one.
const updatedSession = _objectSpread(_objectSpread({}, _session), {}, {
namespaces
});
// Integrate the updated session state into your dapp state.
this.activeSession = updatedSession;
});
this.connector.events.on("session_delete", () => {
// Session was deleted -> reset the dapp state, clean up from user session, etc.
this.disconnect({
sessionRemovedByWallet: true
});
});
}
async _getSignedMessage(challenge, accounts, chainNamespace) {
const signedMessage = await this.provider.request({
method: chainNamespace === base.CHAIN_NAMESPACES.EIP155 ? "personal_sign" : "signMessage",
params: chainNamespace === base.CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : {
message: Buffer.from(challenge)
}
});
if (chainNamespace === base.CHAIN_NAMESPACES.SOLANA) return base58.encode(signedMessage);
return signedMessage;
}
}
exports.WalletConnectV2Adapter = WalletConnectV2Adapter;