@reown/appkit-wallet
Version:
The full stack toolkit to build onchain app UX.
668 lines • 23.7 kB
JavaScript
import { ParseUtil } from '@reown/appkit-common';
import { W3mFrame } from './W3mFrame.js';
import { W3mFrameConstants, W3mFrameRpcConstants } from './W3mFrameConstants.js';
import { W3mFrameHelpers } from './W3mFrameHelpers.js';
import { W3mFrameLogger } from './W3mFrameLogger.js';
import { W3mFrameStorage } from './W3mFrameStorage.js';
function serializeBigInts(value) {
if (typeof value === 'bigint') {
return `0x${value.toString(16)}`;
}
if (Array.isArray(value)) {
return value.map(serializeBigInts);
}
if (value !== null && typeof value === 'object') {
const result = {};
for (const [k, v] of Object.entries(value)) {
result[k] = serializeBigInts(v);
}
return result;
}
return value;
}
export class W3mFrameProvider {
constructor({ projectId, chainId, enableLogger = true, onTimeout, abortController, getActiveCaipNetwork, getCaipNetworks, enableCloudAuthAccount, metadata, sdkVersion, sdkType }) {
this.openRpcRequests = new Map();
this.isInitialized = false;
if (enableLogger) {
this.w3mLogger = new W3mFrameLogger(projectId);
}
this.abortController = abortController;
this.getActiveCaipNetwork = getActiveCaipNetwork;
this.getCaipNetworks = getCaipNetworks;
const rpcUrl = this.getRpcUrl(chainId);
this.projectId = projectId;
this.sdkVersion = sdkVersion;
this.sdkType = sdkType;
this.metadata = metadata;
this.w3mFrame = new W3mFrame({
projectId,
isAppClient: true,
chainId,
enableLogger,
rpcUrl,
enableCloudAuthAccount
});
this.onTimeout = onTimeout;
if (this.getLoginEmailUsed()) {
this.createFrame();
}
}
async createFrame() {
this.w3mFrame.initFrame();
this.initPromise = new Promise(resolve => {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_READY) {
setTimeout(() => {
resolve();
}, 500);
}
});
});
await this.initPromise;
await this.syncDappData({
metadata: this.metadata,
projectId: this.projectId,
sdkVersion: this.sdkVersion,
sdkType: this.sdkType
});
await this.getSmartAccountEnabledNetworks();
this.isInitialized = true;
this.initPromise = undefined;
}
async init() {
if (this.isInitialized) {
return;
}
if (this.initPromise) {
await this.initPromise;
return;
}
await this.createFrame();
}
getLoginEmailUsed() {
return Boolean(W3mFrameStorage.get(W3mFrameConstants.EMAIL_LOGIN_USED_KEY));
}
getEmail() {
return W3mFrameStorage.get(W3mFrameConstants.EMAIL);
}
getUsername() {
return W3mFrameStorage.get(W3mFrameConstants.SOCIAL_USERNAME);
}
async reload() {
try {
await this.appEvent({
type: W3mFrameConstants.APP_RELOAD
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error reloading iframe');
throw error;
}
}
async connectEmail(payload) {
try {
W3mFrameHelpers.checkIfAllowedToTriggerEmail();
await this.init();
const response = await this.appEvent({
type: W3mFrameConstants.APP_CONNECT_EMAIL,
payload
});
this.setNewLastEmailLoginTime();
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting email');
throw error;
}
}
async connectDevice() {
try {
return this.appEvent({
type: W3mFrameConstants.APP_CONNECT_DEVICE
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting device');
throw error;
}
}
async connectOtp(payload) {
try {
return this.appEvent({
type: W3mFrameConstants.APP_CONNECT_OTP,
payload
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting otp');
throw error;
}
}
async isConnected() {
try {
if (!this.getLoginEmailUsed()) {
return { isConnected: false };
}
const response = await this.appEvent({
type: W3mFrameConstants.APP_IS_CONNECTED
});
if (!response?.isConnected) {
this.deleteAuthLoginCache();
}
return response;
}
catch (error) {
this.deleteAuthLoginCache();
this.w3mLogger?.logger.error({ error }, 'Error checking connection');
throw error;
}
}
async getChainId() {
try {
const response = await this.appEvent({
type: W3mFrameConstants.APP_GET_CHAIN_ID
});
this.setLastUsedChainId(response.chainId);
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error getting chain id');
throw error;
}
}
async getSocialRedirectUri(payload) {
try {
await this.init();
return this.appEvent({
type: W3mFrameConstants.APP_GET_SOCIAL_REDIRECT_URI,
payload
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error getting social redirect uri');
throw error;
}
}
async updateEmail(payload) {
try {
const response = await this.appEvent({
type: W3mFrameConstants.APP_UPDATE_EMAIL,
payload
});
this.setNewLastEmailLoginTime();
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error updating email');
throw error;
}
}
async updateEmailPrimaryOtp(payload) {
try {
return this.appEvent({
type: W3mFrameConstants.APP_UPDATE_EMAIL_PRIMARY_OTP,
payload
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error updating email primary otp');
throw error;
}
}
async updateEmailSecondaryOtp(payload) {
try {
const response = await this.appEvent({
type: W3mFrameConstants.APP_UPDATE_EMAIL_SECONDARY_OTP,
payload
});
this.setLoginSuccess(response.newEmail);
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error updating email secondary otp');
throw error;
}
}
async syncTheme(payload) {
try {
return this.appEvent({
type: W3mFrameConstants.APP_SYNC_THEME,
payload
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error syncing theme');
throw error;
}
}
async syncDappData(payload) {
try {
return this.appEvent({
type: W3mFrameConstants.APP_SYNC_DAPP_DATA,
payload
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error syncing dapp data');
throw error;
}
}
async getSmartAccountEnabledNetworks() {
try {
const response = await this.appEvent({
type: W3mFrameConstants.APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS
});
this.persistSmartAccountEnabledNetworks(response.smartAccountEnabledNetworks);
return response;
}
catch (error) {
this.persistSmartAccountEnabledNetworks([]);
this.w3mLogger?.logger.error({ error }, 'Error getting smart account enabled networks');
throw error;
}
}
async setPreferredAccount(type) {
try {
return this.appEvent({
type: W3mFrameConstants.APP_SET_PREFERRED_ACCOUNT,
payload: { type }
});
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error setting preferred account');
throw error;
}
}
async connect(payload) {
if (payload?.socialUri) {
try {
await this.init();
const rpcUrl = this.getRpcUrl(payload.chainId);
const response = await this.appEvent({
type: W3mFrameConstants.APP_CONNECT_SOCIAL,
payload: {
uri: payload.socialUri,
preferredAccountType: payload.preferredAccountType,
chainId: payload.chainId,
siwxMessage: payload.siwxMessage,
rpcUrl
}
});
if (response.userName) {
this.setSocialLoginSuccess(response.userName);
}
this.setLoginSuccess(response.email);
this.setLastUsedChainId(response.chainId);
this.user = response;
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting social');
throw error;
}
}
else {
try {
const chainId = payload?.chainId || this.getLastUsedChainId() || 1;
const response = await this.getUser({
chainId,
preferredAccountType: payload?.preferredAccountType,
siwxMessage: payload?.siwxMessage,
rpcUrl: this.getRpcUrl(chainId)
});
this.setLoginSuccess(response.email);
this.setLastUsedChainId(response.chainId);
this.user = response;
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting');
throw error;
}
}
}
async getUser(payload) {
try {
await this.init();
const chainId = payload?.chainId || this.getLastUsedChainId() || 1;
const response = await this.appEvent({
type: W3mFrameConstants.APP_GET_USER,
payload: { ...payload, chainId, rpcUrl: this.getRpcUrl(chainId) }
});
this.user = response;
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting');
throw error;
}
}
async connectSocial({ uri, chainId, preferredAccountType }) {
try {
await this.init();
const rpcUrl = this.getRpcUrl(chainId);
const response = await this.appEvent({
type: W3mFrameConstants.APP_CONNECT_SOCIAL,
payload: { uri, chainId, rpcUrl, preferredAccountType }
});
if (response.userName) {
this.setSocialLoginSuccess(response.userName);
}
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting social');
throw error;
}
}
async getFarcasterUri() {
try {
await this.init();
const response = await this.appEvent({
type: W3mFrameConstants.APP_GET_FARCASTER_URI
});
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error getting farcaster uri');
throw error;
}
}
async connectFarcaster() {
try {
const response = await this.appEvent({
type: W3mFrameConstants.APP_CONNECT_FARCASTER
});
if (response.userName) {
this.setSocialLoginSuccess(response.userName);
}
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error connecting farcaster');
throw error;
}
}
async switchNetwork({ chainId }) {
try {
const rpcUrl = this.getRpcUrl(chainId);
const response = await this.appEvent({
type: W3mFrameConstants.APP_SWITCH_NETWORK,
payload: { chainId, rpcUrl }
});
this.setLastUsedChainId(response.chainId);
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error switching network');
throw error;
}
}
async disconnect() {
try {
this.deleteAuthLoginCache();
const response = await new Promise(async (resolve) => {
const timeout = setTimeout(() => {
resolve();
}, 3_000);
await this.appEvent({
type: W3mFrameConstants.APP_SIGN_OUT
});
clearTimeout(timeout);
resolve();
});
return response;
}
catch (error) {
this.w3mLogger?.logger.error({ error }, 'Error disconnecting');
throw error;
}
}
async request(req) {
const request = req;
try {
if (W3mFrameRpcConstants.GET_CHAIN_ID === req.method) {
return this.getLastUsedChainId();
}
const namespace = req.chainNamespace || 'eip155';
const chainId = this.getActiveCaipNetwork(namespace)?.id;
request.chainNamespace = namespace;
request.chainId = chainId;
request.rpcUrl = this.getRpcUrl(chainId);
this.rpcRequestHandler?.(req);
const response = await this.appEvent({
type: W3mFrameConstants.APP_RPC_REQUEST,
payload: serializeBigInts(request)
});
this.rpcSuccessHandler?.(response, request);
return response;
}
catch (error) {
this.rpcErrorHandler?.(error, request);
this.w3mLogger?.logger.error({ error }, 'Error requesting');
throw error;
}
}
onRpcRequest(callback) {
this.rpcRequestHandler = callback;
}
onRpcSuccess(callback) {
this.rpcSuccessHandler = callback;
}
onRpcError(callback) {
this.rpcErrorHandler = callback;
}
onIsConnected(callback) {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_IS_CONNECTED_SUCCESS &&
event.payload.isConnected) {
callback();
}
});
}
onNotConnected(callback) {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_IS_CONNECTED_ERROR) {
callback();
}
if (event.type === W3mFrameConstants.FRAME_IS_CONNECTED_SUCCESS &&
!event.payload.isConnected) {
callback();
}
});
}
onConnect(callback) {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_GET_USER_SUCCESS) {
callback(event.payload);
}
});
}
onSocialConnected(callback) {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_CONNECT_SOCIAL_SUCCESS) {
callback(event.payload);
}
});
}
async getCapabilities() {
try {
const capabilities = await this.request({
method: 'wallet_getCapabilities'
});
return capabilities || {};
}
catch {
return {};
}
}
onSetPreferredAccount(callback) {
this.w3mFrame.events.onFrameEvent(event => {
if (event.type === W3mFrameConstants.FRAME_SET_PREFERRED_ACCOUNT_SUCCESS) {
callback(event.payload);
}
else if (event.type === W3mFrameConstants.FRAME_SET_PREFERRED_ACCOUNT_ERROR) {
callback({ type: W3mFrameRpcConstants.ACCOUNT_TYPES.EOA });
}
});
}
getAvailableChainIds() {
return Object.keys(this.w3mFrame.networks);
}
async rejectRpcRequests() {
try {
await Promise.all(Array.from(this.openRpcRequests.values()).map(async ({ abortController, method }) => {
if (!W3mFrameRpcConstants.SAFE_RPC_METHODS.includes(method)) {
abortController.abort();
}
await this.appEvent({
type: W3mFrameConstants.APP_RPC_ABORT
});
}));
this.openRpcRequests.clear();
}
catch (e) {
this.w3mLogger?.logger.error({ error: e }, 'Error aborting RPC request');
}
}
async appEvent(event) {
let requestTimeout = undefined;
let iframeReadyTimeout = undefined;
function replaceEventType(type) {
return type.replace('@w3m-app/', '');
}
const safeEventTypes = [
W3mFrameConstants.APP_SYNC_DAPP_DATA,
W3mFrameConstants.APP_SYNC_THEME,
W3mFrameConstants.APP_SET_PREFERRED_ACCOUNT
];
const type = replaceEventType(event.type);
if (!this.w3mFrame.iframeIsReady &&
!safeEventTypes.includes(event.type)) {
iframeReadyTimeout = setTimeout(() => {
this.onTimeout?.('iframe_load_failed');
this.abortController.abort();
}, 20_000);
}
await this.w3mFrame.frameLoadPromise;
clearTimeout(iframeReadyTimeout);
const shouldCheckForTimeout = [
W3mFrameConstants.APP_CONNECT_EMAIL,
W3mFrameConstants.APP_CONNECT_DEVICE,
W3mFrameConstants.APP_CONNECT_OTP,
W3mFrameConstants.APP_CONNECT_SOCIAL,
W3mFrameConstants.APP_GET_SOCIAL_REDIRECT_URI
]
.map(replaceEventType)
.includes(type);
if (shouldCheckForTimeout) {
requestTimeout = setTimeout(() => {
this.onTimeout?.('iframe_request_timeout');
this.abortController.abort();
}, 120_000);
}
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
this.w3mLogger?.logger.info?.({ event, id }, 'Sending app event');
this.w3mFrame.events.postAppEvent({ ...event, id });
const abortController = new AbortController();
if (type === 'RPC_REQUEST') {
const rpcEvent = event;
this.openRpcRequests.set(id, { ...rpcEvent.payload, abortController });
}
abortController.signal.addEventListener('abort', () => {
if (type === 'RPC_REQUEST') {
reject(new Error('Request was aborted'));
}
else if (type !== 'GET_FARCASTER_URI') {
reject(new Error('Something went wrong'));
}
});
const handler = (framEvent, logger) => {
if (framEvent.id !== id) {
return;
}
logger?.logger.info?.({ framEvent, id }, 'Received frame response');
this.openRpcRequests.delete(framEvent.id);
if (framEvent.type === `-frame/${type}_SUCCESS`) {
if (requestTimeout) {
clearTimeout(requestTimeout);
}
if (iframeReadyTimeout) {
clearTimeout(iframeReadyTimeout);
}
if ('payload' in framEvent) {
resolve(framEvent.payload);
}
resolve(undefined);
}
else if (framEvent.type === `-frame/${type}_ERROR`) {
if (requestTimeout) {
clearTimeout(requestTimeout);
}
if (iframeReadyTimeout) {
clearTimeout(iframeReadyTimeout);
}
if ('payload' in framEvent) {
reject(new Error(framEvent.payload?.message || 'An error occurred'));
}
reject(new Error('An error occurred'));
}
};
this.w3mFrame.events.registerFrameEventHandler(id, frameEvent => handler(frameEvent, this.w3mLogger), this.abortController.signal);
});
}
setNewLastEmailLoginTime() {
W3mFrameStorage.set(W3mFrameConstants.LAST_EMAIL_LOGIN_TIME, Date.now().toString());
}
setSocialLoginSuccess(username) {
W3mFrameStorage.set(W3mFrameConstants.SOCIAL_USERNAME, username);
}
setLoginSuccess(email) {
if (email) {
W3mFrameStorage.set(W3mFrameConstants.EMAIL, email);
}
W3mFrameStorage.set(W3mFrameConstants.EMAIL_LOGIN_USED_KEY, 'true');
W3mFrameStorage.delete(W3mFrameConstants.LAST_EMAIL_LOGIN_TIME);
}
deleteAuthLoginCache() {
W3mFrameStorage.delete(W3mFrameConstants.EMAIL_LOGIN_USED_KEY);
W3mFrameStorage.delete(W3mFrameConstants.EMAIL);
W3mFrameStorage.delete(W3mFrameConstants.LAST_USED_CHAIN_KEY);
W3mFrameStorage.delete(W3mFrameConstants.SOCIAL_USERNAME);
}
setLastUsedChainId(chainId) {
if (chainId) {
W3mFrameStorage.set(W3mFrameConstants.LAST_USED_CHAIN_KEY, String(chainId));
}
}
getLastUsedChainId() {
const chainId = W3mFrameStorage.get(W3mFrameConstants.LAST_USED_CHAIN_KEY) ?? undefined;
const numberChainId = Number(chainId);
return isNaN(numberChainId) ? chainId : numberChainId;
}
persistSmartAccountEnabledNetworks(networks) {
W3mFrameStorage.set(W3mFrameConstants.SMART_ACCOUNT_ENABLED_NETWORKS, networks.join(','));
}
getRpcUrl(chainId) {
let namespace = chainId === undefined ? undefined : 'eip155';
if (typeof chainId === 'string') {
if (chainId.includes(':')) {
namespace = ParseUtil.parseCaipNetworkId(chainId)?.chainNamespace;
}
else if (Number.isInteger(Number(chainId))) {
namespace = 'eip155';
}
else {
namespace = 'solana';
}
}
const caipNetworks = this.getCaipNetworks(namespace);
const activeNetwork = chainId
? caipNetworks.find(network => String(network.id) === String(chainId) || network.caipNetworkId === chainId)
: caipNetworks[0];
return activeNetwork?.rpcUrls.default.http?.[0];
}
}
//# sourceMappingURL=W3mFrameProvider.js.map