@magiclabs/wagmi-connector
Version:
wagmi connector to connect with Magic SDK
215 lines (214 loc) • 8.95 kB
JavaScript
import { createConnector } from '@wagmi/core';
import { UserRejectedRequestError, getAddress } from 'viem';
import { createModal } from '../modal/view.js';
import { normalizeChainId } from '../utils.js';
import { magicConnector } from './magicConnector.js';
export function dedicatedWalletConnector({ chains, options }) {
let { id, name, type, isModalOpen, getAccount, getMagicSDK, getProvider, onAccountsChanged } = magicConnector({
chains,
options: { ...options, connectorType: 'dedicated' },
});
const magic = getMagicSDK();
const registerProviderEventListeners = (provider, onChainChanged, onDisconnect) => {
if (provider.on) {
provider.on('accountsChanged', onAccountsChanged);
provider.on('chainChanged', chain => onChainChanged(chain));
provider.on('disconnect', onDisconnect);
}
};
const oauthProviders = options.oauthOptions?.providers ?? [];
const oauthCallbackUrl = options.oauthOptions?.callbackUrl;
const enableSMSLogin = options.enableSMSLogin ?? false;
const enableEmailLogin = options.enableEmailLogin ?? true;
/**
* This method is used to get user details from the modal UI
* It first creates the modal UI and then waits for the user to
* fill in the details and submit the form.
*/
const getUserDetailsByForm = async (enableSMSLogin, enableEmailLogin, oauthProviders) => {
const output = (await createModal({
accentColor: options.accentColor,
isDarkMode: options.isDarkMode,
customLogo: options.customLogo,
customHeaderText: options.customHeaderText,
enableSMSLogin: enableSMSLogin,
enableEmailLogin: enableEmailLogin,
oauthProviders,
}));
isModalOpen = false;
return output;
};
return createConnector(config => ({
id,
type,
name,
magic,
getProvider,
getAccount,
onAccountsChanged,
async connect() {
if (!options.apiKey) {
throw new Error('Magic API Key is not provided.');
}
const provider = await getProvider();
if (provider?.on) {
provider.on('accountsChanged', this.onAccountsChanged.bind(this));
provider.on('chainChanged', this.onChainChanged.bind(this));
provider.on('disconnect', this.onDisconnect.bind(this));
}
let chainId;
try {
chainId = await this.getChainId();
}
catch {
chainId = 0;
}
if (await this.isAuthorized()) {
return {
chainId,
accounts: [await getAccount()],
};
}
if (!isModalOpen) {
const modalOutput = await getUserDetailsByForm(enableSMSLogin, enableEmailLogin, oauthProviders);
const magic = getMagicSDK();
// LOGIN WITH MAGIC USING OAUTH PROVIDER
if (modalOutput.oauthProvider)
await magic.oauth.loginWithRedirect({
provider: modalOutput.oauthProvider,
redirectURI: oauthCallbackUrl ?? window.location.href,
});
// LOGIN WITH MAGIC USING EMAIL
if (modalOutput.email)
await magic.auth.loginWithEmailOTP({
email: modalOutput.email,
});
// LOGIN WITH MAGIC USING PHONE NUMBER
if (modalOutput.phoneNumber)
await magic.auth.loginWithSMS({
phoneNumber: modalOutput.phoneNumber,
});
if (await magic.user.isLoggedIn())
return {
accounts: [await getAccount()],
chainId,
};
}
throw new UserRejectedRequestError(Error('User Rejected Request'));
},
async disconnect() {
try {
const magic = getMagicSDK();
await magic?.user.logout();
localStorage.removeItem('magicRedirectResult');
config.emitter.emit('disconnect');
}
catch (error) {
console.error('Error disconnecting from Magic SDK:', error);
}
},
async getAccounts() {
const provider = await getProvider();
const accounts = (await provider?.send('eth_accounts', []));
return accounts.map(x => getAddress(x));
},
getChainId: async () => {
const provider = await getProvider();
if (provider) {
const chainId = await provider.send('eth_chainId', []);
return normalizeChainId(chainId);
}
const networkOptions = options.magicSdkConfiguration?.network;
if (typeof networkOptions === 'object') {
const chainID = networkOptions.chainId;
if (chainID)
return normalizeChainId(chainID);
}
throw new Error('Chain ID is not defined');
},
switchChain: async function ({ chainId }) {
if (!options.networks) {
throw new Error('Switch chain not supported: please provide networks in options');
}
const normalizedChainId = normalizeChainId(chainId);
const chain = chains.find(x => x.id === normalizedChainId);
if (!chain) {
throw new Error(`Unsupported chainId: ${chainId}`);
}
const network = options.networks.find(x => {
if (typeof x === 'object' && x.chainId) {
return normalizeChainId(x.chainId) === normalizedChainId;
}
if (typeof x === 'string') {
const networkMap = {
mainnet: 1,
sepolia: 11155111,
goerli: 5,
};
const networkId = networkMap[x.toLowerCase()] ?? null;
return networkId !== null && normalizeChainId(networkId) === normalizedChainId;
}
return normalizeChainId(x) === normalizedChainId;
});
const provider = (await this.getProvider());
if (provider?.off) {
provider.off('accountsChanged', this.onAccountsChanged);
provider.off('chainChanged', this.onChainChanged);
provider.off('disconnect', this.onDisconnect);
}
const newOptions = {
...options,
connectorType: 'dedicated',
magicSdkConfiguration: {
...options.magicSdkConfiguration,
network,
},
};
const { getAccount, getMagicSDK, getProvider, onAccountsChanged } = magicConnector({
chains,
options: newOptions,
});
this.getAccount = getAccount;
this.magic = getMagicSDK();
this.getProvider = getProvider;
this.onAccountsChanged = onAccountsChanged;
const metadata = await this.magic?.user.getInfo();
const account = metadata?.publicAddress;
registerProviderEventListeners(this.magic.rpcProvider, this.onChainChanged, this.onDisconnect);
this.onChainChanged(chain.id.toString());
config.emitter.emit('change', { accounts: [account] });
this.onAccountsChanged([account]);
return chain;
},
isAuthorized: async () => {
try {
const magic = getMagicSDK();
if (!magic) {
return false;
}
const isLoggedIn = await magic.user.isLoggedIn();
if (isLoggedIn)
return true;
const result = await magic.oauth.getRedirectResult();
if (result) {
localStorage.setItem('magicRedirectResult', JSON.stringify(result));
}
return result !== null;
}
catch { }
return false;
},
onChainChanged(chain) {
const chainId = normalizeChainId(chain);
config.emitter.emit('change', { chainId });
},
async onConnect(connectInfo) {
const chainId = normalizeChainId(connectInfo.chainId);
const accounts = await this.getAccounts();
config.emitter.emit('connect', { accounts, chainId });
},
onDisconnect: () => {
config.emitter.emit('disconnect');
},
}));
}