@wagmi/connectors
Version:
Collection of connectors for Wagmi
242 lines • 11.2 kB
JavaScript
import { ChainNotConfiguredError, createConnector } from '@wagmi/core';
import { getAddress, numberToHex, ResourceUnavailableRpcError, SwitchChainError, UserRejectedRequestError, withRetry, withTimeout, } from 'viem';
metaMask.type = 'metaMask';
export function metaMask(parameters = {}) {
let metamask;
let metamaskPromise;
return createConnector((config) => ({
id: 'metaMaskSDK',
name: 'MetaMask',
rdns: ['io.metamask', 'io.metamask.mobile'],
type: metaMask.type,
async connect({ chainId, isReconnecting, withCapabilities } = {}) {
const instance = await this.getInstance();
const provider = instance.getProvider();
let accounts = [];
if (isReconnecting)
accounts = await this.getAccounts().catch(() => []);
try {
let signResponse;
let connectWithResponse;
if (!accounts?.length) {
const chainIds = config.chains.map((chain) => numberToHex(chain.id));
if (parameters.connectAndSign || parameters.connectWith) {
if (parameters.connectAndSign)
signResponse = await instance.connectAndSign({
chainIds,
message: parameters.connectAndSign,
});
else if (parameters.connectWith)
connectWithResponse = await instance.connectWith({
chainIds,
method: parameters.connectWith.method,
params: parameters.connectWith.params,
});
accounts = await this.getAccounts();
}
else {
const result = await instance.connect({ chainIds });
accounts = result.accounts.map((x) => getAddress(x));
}
}
// Switch to chain if provided
let currentChainId = await this.getChainId();
if (chainId && currentChainId !== chainId) {
const chain = await this.switchChain({ chainId }).catch((error) => {
if (error.code === UserRejectedRequestError.code)
throw error;
return { id: currentChainId };
});
currentChainId = chain?.id ?? currentChainId;
}
if (signResponse)
provider.emit('connectAndSign', {
accounts,
chainId: numberToHex(currentChainId),
signResponse,
});
else if (connectWithResponse)
provider.emit('connectWith', {
accounts,
chainId: numberToHex(currentChainId),
connectWithResponse,
});
return {
// TODO(v3): Make `withCapabilities: true` default behavior
accounts: (withCapabilities
? accounts.map((address) => ({ address, capabilities: {} }))
: accounts),
chainId: currentChainId,
};
}
catch (err) {
const error = err;
if (error.code === UserRejectedRequestError.code)
throw new UserRejectedRequestError(error);
if (error.code === ResourceUnavailableRpcError.code)
throw new ResourceUnavailableRpcError(error);
throw error;
}
},
async disconnect() {
const instance = await this.getInstance();
return instance.disconnect();
},
async getAccounts() {
const instance = await this.getInstance();
if (instance.accounts.length)
return instance.accounts.map((x) => getAddress(x));
// Fallback to provider if SDK doesn't return accounts
const provider = instance.getProvider();
const accounts = (await provider.request({
method: 'eth_accounts',
}));
return accounts.map((x) => getAddress(x));
},
async getChainId() {
const instance = await this.getInstance();
if (instance.getChainId())
return Number(instance.getChainId());
// Fallback to provider if SDK doesn't return chainId
const provider = instance.getProvider();
const chainId = await provider.request({ method: 'eth_chainId' });
return Number(chainId);
},
async getProvider() {
const instance = await this.getInstance();
return instance.getProvider();
},
async isAuthorized() {
try {
// MetaMask mobile provider sometimes fails to immediately resolve
// JSON-RPC requests on page load
const timeout = 10;
const accounts = await withRetry(async () => withTimeout(async () => {
const accounts = await this.getAccounts();
if (!accounts.length)
throw new Error('try again');
return accounts;
}, { timeout }), { delay: timeout + 1, retryCount: 3 });
return Boolean(accounts.length);
}
catch {
return false;
}
},
async switchChain({ addEthereumChainParameter, chainId }) {
const chain = config.chains.find(({ id }) => id === Number(chainId));
if (!chain)
throw new SwitchChainError(new ChainNotConfiguredError());
const hexChainId = numberToHex(chainId);
try {
const instance = await this.getInstance();
await instance.switchChain({
chainId: hexChainId,
chainConfiguration: {
blockExplorerUrls: addEthereumChainParameter?.blockExplorerUrls
? [...addEthereumChainParameter.blockExplorerUrls]
: chain.blockExplorers?.default.url
? [chain.blockExplorers.default.url]
: undefined,
chainId: hexChainId,
chainName: addEthereumChainParameter?.chainName ?? chain.name,
iconUrls: addEthereumChainParameter?.iconUrls,
nativeCurrency: addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency,
rpcUrls: addEthereumChainParameter?.rpcUrls
? [...addEthereumChainParameter.rpcUrls]
: chain.rpcUrls.default?.http
? [...chain.rpcUrls.default.http]
: undefined,
},
});
return chain;
}
catch (err) {
const error = err;
if (error.code === UserRejectedRequestError.code)
throw new UserRejectedRequestError(error);
throw new SwitchChainError(error);
}
},
async onAccountsChanged(accounts) {
config.emitter.emit('change', {
accounts: accounts.map((x) => getAddress(x)),
});
},
onChainChanged(chain) {
const chainId = Number(chain);
config.emitter.emit('change', { chainId });
},
async onConnect(connectInfo) {
const accounts = await this.getAccounts();
if (accounts.length === 0)
return;
const chainId = Number(connectInfo.chainId);
config.emitter.emit('connect', { accounts, chainId });
},
async onDisconnect(error) {
// If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting
// https://github.com/MetaMask/providers/pull/120
if (error && error.code === 1013) {
const provider = await this.getProvider();
if (provider && Boolean((await this.getAccounts()).length))
return;
}
config.emitter.emit('disconnect');
},
onDisplayUri(uri) {
config.emitter.emit('message', { type: 'display_uri', data: uri });
},
async getInstance() {
if (!metamask) {
if (!metamaskPromise) {
const { createEVMClient } = await (async () => {
try {
return import('@metamask/connect-evm');
}
catch {
throw new Error('dependency "@metamask/connect-evm" not found');
}
})();
const defaultDappParams = typeof window === 'undefined'
? { name: 'wagmi' }
: { name: window.location.hostname, url: window.location.href };
metamaskPromise = createEVMClient({
...parameters,
api: {
supportedNetworks: Object.fromEntries(config.chains.map((chain) => [
numberToHex(chain.id),
chain.rpcUrls.default?.http[0] ?? '',
])),
},
dapp: parameters.dapp ?? {
...defaultDappParams,
...parameters.dappMetadata,
},
debug: parameters.debug ?? parameters.logging?.sdk,
eventHandlers: {
accountsChanged: this.onAccountsChanged.bind(this),
chainChanged: this.onChainChanged.bind(this),
connect: this.onConnect.bind(this),
disconnect: this.onDisconnect.bind(this),
displayUri: this.onDisplayUri.bind(this),
},
analytics: {
integrationType: 'wagmi',
},
ui: {
...parameters.ui,
...(parameters.headless != null && {
headless: parameters.headless,
}),
},
...(parameters.mobile && { mobile: parameters.mobile }),
});
}
metamask = await metamaskPromise;
}
return metamask;
},
}));
}
//# sourceMappingURL=metaMask.js.map