@thirdweb-dev/wallets
Version:
<p align="center"> <br /> <a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> <br /> </p> <h1 align="center">thirdweb Wallet SDK</h1> <p align="center"> <a href="ht
1,000 lines (967 loc) • 32.2 kB
JavaScript
'use strict';
var sdkCommonUtilities = require('@paperxyz/sdk-common-utilities');
var defineProperty = require('./defineProperty-21d22449.cjs.dev.js');
var ethers = require('ethers');
var utils = require('ethers/lib/utils');
const EMBEDDED_WALLET_PATH = "/sdk/2022-08-12/embedded-wallet";
const GET_IFRAME_BASE_URL = () => `${sdkCommonUtilities.getPaperOriginUrl().replace("withpaper.com", "ews.thirdweb.com")}`;
const WALLET_USER_DETAILS_LOCAL_STORAGE_NAME = clientId => `thirdwebEwsWalletUserDetails-${clientId}`;
const WALLET_USER_ID_LOCAL_STORAGE_NAME = clientId => `thirdwebEwsWalletUserId-${clientId}`;
const AUTH_TOKEN_LOCAL_STORAGE_PREFIX = "walletToken";
const AUTH_TOKEN_LOCAL_STORAGE_NAME = clientId => {
return `${AUTH_TOKEN_LOCAL_STORAGE_PREFIX}-${clientId}`;
};
const DEVICE_SHARE_LOCAL_STORAGE_PREFIX = "a";
const DEVICE_SHARE_LOCAL_STORAGE_NAME = (clientId, userId) => `${DEVICE_SHARE_LOCAL_STORAGE_PREFIX}-${clientId}-${userId}`;
const DEVICE_SHARE_LOCAL_STORAGE_NAME_DEPRECATED = clientId => `${DEVICE_SHARE_LOCAL_STORAGE_PREFIX}-${clientId}`;
// Class constructor types
// types for class constructors still a little messy right now.
// Open to PRs from whoever sees this and knows of a cleaner way to handle things
// Auth Types
// Embedded Wallet Types
let UserStatus = /*#__PURE__*/function (UserStatus) {
UserStatus["LOGGED_OUT"] = "Logged Out";
UserStatus["LOGGED_IN_WALLET_INITIALIZED"] = "Logged In, Wallet Initialized";
return UserStatus;
}({});
let UserWalletStatus = /*#__PURE__*/function (UserWalletStatus) {
UserWalletStatus["LOGGED_OUT"] = "Logged Out";
UserWalletStatus["LOGGED_IN_WALLET_UNINITIALIZED"] = "Logged In, Wallet Uninitialized";
UserWalletStatus["LOGGED_IN_NEW_DEVICE"] = "Logged In, New Device";
UserWalletStatus["LOGGED_IN_WALLET_INITIALIZED"] = "Logged In, Wallet Initialized";
return UserWalletStatus;
}({});
// ! Types seem repetitive, but the name should identify which goes where
// this is the return type from the EmbeddedWallet Class getUserWalletStatus method iframe call
// this is the return type from the EmbeddedWallet Class getUserWalletStatus method
// This is returned from the getUser method in PaperEmbeddedWalletSdk
const data = new Map();
class LocalStorage {
constructor(_ref) {
let {
clientId
} = _ref;
this.isSupported = typeof window !== "undefined" && !!window.localStorage;
this.clientId = clientId;
}
async getItem(key) {
if (this.isSupported) {
return window.localStorage.getItem(key);
} else {
return data.get(key) ?? null;
}
}
async setItem(key, value) {
if (this.isSupported) {
return window.localStorage.setItem(key, value);
} else {
data.set(key, value);
}
}
async removeItem(key) {
const item = await this.getItem(key);
if (this.isSupported && item) {
window.localStorage.removeItem(key);
return true;
}
return false;
}
async saveAuthCookie(cookie) {
await this.setItem(AUTH_TOKEN_LOCAL_STORAGE_NAME(this.clientId), cookie);
}
async getAuthCookie() {
return this.getItem(AUTH_TOKEN_LOCAL_STORAGE_NAME(this.clientId));
}
async removeAuthCookie() {
return this.removeItem(AUTH_TOKEN_LOCAL_STORAGE_NAME(this.clientId));
}
async saveDeviceShare(share, userId) {
await this.saveWalletUserId(userId);
await this.setItem(DEVICE_SHARE_LOCAL_STORAGE_NAME(this.clientId, userId), share);
}
async getDeviceShare() {
const userId = await this.getWalletUserId();
if (userId) {
return this.getItem(DEVICE_SHARE_LOCAL_STORAGE_NAME(this.clientId, userId));
}
return null;
}
async removeDeviceShare() {
const userId = await this.getWalletUserId();
if (userId) {
return this.removeItem(DEVICE_SHARE_LOCAL_STORAGE_NAME(this.clientId, userId));
}
return false;
}
async getWalletUserId() {
return this.getItem(WALLET_USER_ID_LOCAL_STORAGE_NAME(this.clientId));
}
async saveWalletUserId(userId) {
await this.setItem(WALLET_USER_ID_LOCAL_STORAGE_NAME(this.clientId), userId);
}
async removeWalletUserId() {
return this.removeItem(WALLET_USER_ID_LOCAL_STORAGE_NAME(this.clientId));
}
}
function sleep(seconds) {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
const iframeBaseStyle = {
height: "100%",
width: "100%",
border: "none",
backgroundColor: "transparent",
colorScheme: "light",
position: "fixed",
top: "0px",
right: "0px",
zIndex: "2147483646",
display: "none"
};
// Global var to help track iframe state
const isIframeLoaded = new Map();
class IframeCommunicator {
constructor(_ref) {
let {
link,
iframeId,
container = document.body,
iframeStyles,
onIframeInitialize
} = _ref;
defineProperty._defineProperty(this, "POLLING_INTERVAL_SECONDS", 1.4);
this.iframeBaseUrl = GET_IFRAME_BASE_URL();
// Creating the IFrame element for communication
let iframe = document.getElementById(iframeId);
const hrefLink = new URL(link);
const sdkVersion = "2.0.7";
hrefLink.searchParams.set("sdkVersion", sdkVersion);
if (!iframe || iframe.src !== hrefLink.href) {
// ! Do not update the hrefLink here or it'll cause multiple re-renders
if (!iframe) {
iframe = document.createElement("iframe");
const mergedIframeStyles = {
...iframeBaseStyle,
...iframeStyles
};
Object.assign(iframe.style, mergedIframeStyles);
iframe.setAttribute("id", iframeId);
iframe.setAttribute("fetchpriority", "high");
container.appendChild(iframe);
}
iframe.src = hrefLink.href;
iframe.setAttribute("data-version", sdkVersion);
const onIframeLoaded = event => {
if (event.data.eventType === "ewsIframeLoaded") {
window.removeEventListener("message", onIframeLoaded);
if (!iframe) {
console.warn("thirdweb Iframe not found");
return;
}
this.onIframeLoadHandler(iframe, onIframeInitialize)();
}
};
window.addEventListener("message", onIframeLoaded);
}
this.iframe = iframe;
}
async onIframeLoadedInitVariables() {
return {};
}
onIframeLoadHandler(iframe, onIframeInitialize) {
return async () => {
const promise = new Promise(async (res, rej) => {
const channel = new MessageChannel();
channel.port1.onmessage = event => {
const {
data
} = event;
channel.port1.close();
if (!data.success) {
return rej(new Error(data.error));
}
isIframeLoaded.set(iframe.src, true);
if (onIframeInitialize) {
onIframeInitialize();
}
return res(true);
};
const INIT_IFRAME_EVENT = "initIframe";
iframe?.contentWindow?.postMessage(
// ? We initialise the iframe with a bunch
// of useful information so that we don't have to pass it
// through in each of the future call. This would be where we do it.
{
eventType: INIT_IFRAME_EVENT,
data: await this.onIframeLoadedInitVariables()
}, this.iframeBaseUrl, [channel.port2]);
});
await promise;
};
}
async call(_ref2) {
let {
procedureName,
params,
showIframe = false
} = _ref2;
while (!isIframeLoaded.get(this.iframe.src)) {
await sleep(this.POLLING_INTERVAL_SECONDS);
}
if (showIframe) {
this.iframe.style.display = "block";
// magic number to let the display render before performing the animation of the modal in
await sleep(0.005);
}
const promise = new Promise((res, rej) => {
const channel = new MessageChannel();
channel.port1.onmessage = async event => {
const {
data
} = event;
channel.port1.close();
if (showIframe) {
// magic number to let modal fade out before hiding it
await sleep(0.1);
this.iframe.style.display = "none";
}
if (!data.success) {
rej(new Error(data.error));
} else {
res(data.data);
}
};
this.iframe.contentWindow?.postMessage({
eventType: procedureName,
data: params
}, this.iframeBaseUrl, [channel.port2]);
});
return promise;
}
/**
* This has to be called by any iframe that will be removed from the DOM.
* Use to make sure that we reset the global loaded state of the particular iframe.src
*/
destroy() {
isIframeLoaded.delete(this.iframe.src);
}
}
class EmbeddedWalletIframeCommunicator extends IframeCommunicator {
constructor(_ref) {
let {
clientId,
customizationOptions
} = _ref;
super({
iframeId: EMBEDDED_WALLET_IFRAME_ID,
link: createEmbeddedWalletIframeLink({
clientId,
path: EMBEDDED_WALLET_PATH,
queryParams: customizationOptions
}).href,
container: document.body
});
this.clientId = clientId;
}
async onIframeLoadedInitVariables() {
const localStorage = new LocalStorage({
clientId: this.clientId
});
return {
authCookie: await localStorage.getAuthCookie(),
deviceShareStored: await localStorage.getDeviceShare(),
walletUserId: await localStorage.getWalletUserId(),
clientId: this.clientId
};
}
}
// This is the URL and ID tag of the iFrame that we communicate with
function createEmbeddedWalletIframeLink(_ref2) {
let {
clientId,
path,
queryParams
} = _ref2;
const embeddedWalletUrl = new URL(`${path}`, GET_IFRAME_BASE_URL());
if (queryParams) {
for (const queryKey of Object.keys(queryParams)) {
embeddedWalletUrl.searchParams.set(queryKey, queryParams[queryKey]?.toString() || "");
}
}
embeddedWalletUrl.searchParams.set("clientId", clientId);
return embeddedWalletUrl;
}
const EMBEDDED_WALLET_IFRAME_ID = "thirdweb-embedded-wallet-iframe";
class AbstractLogin {
/**
* Used to manage the user's auth states. This should not be instantiated directly.
* Call {@link EmbeddedWalletSdk.auth} instead.
*
*/
constructor(_ref) {
let {
querier,
preLogin,
postLogin,
clientId
} = _ref;
this.LoginQuerier = querier;
this.preLogin = preLogin;
this.postLogin = postLogin;
this.clientId = clientId;
}
async sendEmailLoginOtp(_ref2) {
let {
email
} = _ref2;
await this.preLogin();
const result = await this.LoginQuerier.call({
procedureName: "sendThirdwebEmailLoginOtp",
params: {
email
}
});
return result;
}
}
class BaseLogin extends AbstractLogin {
constructor() {
super(...arguments);
defineProperty._defineProperty(this, "closeWindow", _ref => {
let {
isWindowOpenedByFn,
win,
closeOpenedWindow
} = _ref;
if (isWindowOpenedByFn) {
win?.close();
} else {
if (win && closeOpenedWindow) {
closeOpenedWindow(win);
} else if (win) {
win.close();
}
}
});
}
async getGoogleLoginUrl() {
const result = await this.LoginQuerier.call({
procedureName: "getHeadlessGoogleLoginLink",
params: undefined
});
return result;
}
async loginWithModal() {
await this.preLogin();
const result = await this.LoginQuerier.call({
procedureName: "loginWithThirdwebModal",
params: undefined,
showIframe: true
});
return this.postLogin(result);
}
async loginWithEmailOtp(_ref2) {
let {
email
} = _ref2;
await this.preLogin();
const result = await this.LoginQuerier.call({
procedureName: "loginWithThirdwebModal",
params: {
email
},
showIframe: true
});
return this.postLogin(result);
}
async loginWithGoogle(args) {
await this.preLogin();
let win = args?.openedWindow;
let isWindowOpenedByFn = false;
if (!win) {
win = window.open("", "Login", "width=350, height=500");
isWindowOpenedByFn = true;
}
if (!win) {
throw new Error("Something went wrong opening pop-up");
}
await this.preLogin();
// fetch the url to open the login window from iframe
const {
loginLink
} = await this.getGoogleLoginUrl();
win.location.href = loginLink;
// listen to result from the login window
const result = await new Promise((resolve, reject) => {
// detect when the user closes the login window
const pollTimer = window.setInterval(async () => {
if (!win) {
return;
}
if (win.closed) {
clearInterval(pollTimer);
window.removeEventListener("message", messageListener);
reject(new Error("User closed login window"));
}
}, 1000);
const messageListener = async event => {
if (event.origin !== GET_IFRAME_BASE_URL()) {
return;
}
if (typeof event.data !== "object") {
reject(new Error("Invalid event data"));
return;
}
switch (event.data.eventType) {
case "userLoginSuccess":
{
window.removeEventListener("message", messageListener);
clearInterval(pollTimer);
this.closeWindow({
isWindowOpenedByFn,
win,
closeOpenedWindow: args?.closeOpenedWindow
});
if (event.data.authResult) {
resolve(event.data.authResult);
}
break;
}
case "userLoginFailed":
{
window.removeEventListener("message", messageListener);
clearInterval(pollTimer);
this.closeWindow({
isWindowOpenedByFn,
win,
closeOpenedWindow: args?.closeOpenedWindow
});
reject(new Error(event.data.error));
break;
}
case "injectDeveloperClientId":
{
win?.postMessage({
eventType: "injectDeveloperClientIdResult",
developerClientId: this.clientId
}, GET_IFRAME_BASE_URL());
break;
}
}
};
window.addEventListener("message", messageListener);
});
return this.postLogin({
storedToken: {
...result.storedToken,
shouldStoreCookieString: true
},
walletDetails: {
...result.walletDetails,
isIframeStorageEnabled: false
}
});
}
async verifyEmailLoginOtp(_ref3) {
let {
email,
otp
} = _ref3;
const result = await this.LoginQuerier.call({
procedureName: "verifyThirdwebEmailLoginOtp",
params: {
email,
otp
}
});
return this.postLogin(result);
}
}
class Auth {
/**
* Used to manage the user's auth states. This should not be instantiated directly.
* Call {@link EmbeddedWalletSdk.auth} instead.
*
* @param {string} params.clientId the clientId from your thirdweb dashboard
*/
constructor(_ref) {
let {
clientId,
querier,
onAuthSuccess
} = _ref;
this.clientId = clientId;
this.AuthQuerier = querier;
this.localStorage = new LocalStorage({
clientId
});
this.onAuthSuccess = onAuthSuccess;
this.BaseLogin = new BaseLogin({
postLogin: async result => {
return this.postLogin(result);
},
preLogin: async () => {
await this.preLogin();
},
querier: querier,
clientId
});
}
async preLogin() {
await this.logout();
}
async postLogin(_ref2) {
let {
storedToken,
walletDetails
} = _ref2;
if (storedToken.shouldStoreCookieString) {
await this.localStorage.saveAuthCookie(storedToken.cookieString);
}
const initializedUser = await this.onAuthSuccess({
storedToken,
walletDetails
});
return initializedUser;
}
/**
* @description
* Used to log the user into their thirdweb wallet on your platform via a myriad of auth providers
*
* @example
* const thirdwebEmbeddedWallet = new EmbeddedWalletSdk({clientId: "YOUR_CLIENT_ID", chain: "Polygon"})
* try {
* const user = await thirdwebEmbeddedWallet.auth.loginWithModal();
* // user is now logged in
* } catch (e) {
* // User closed modal or something else went wrong during the authentication process
* console.error(e)
* }
*
* @param {(userWalletId: string) => Promise<string | undefined>} args.getRecoveryCode Only present when using RecoveryShareManagement.USER_MANAGED recovery share management. A function that returns the recovery code for a given userWalletId.
*
* @returns {{user: InitializedUser}} An InitializedUser object. See {@link EmbeddedWalletSdk.getUser} for more
*/
async loginWithModal() {
await this.preLogin();
return this.BaseLogin.loginWithModal();
}
/**
* @description
* Used to log the user into their thirdweb wallet using email OTP
*
* @example
* // Basic Flow
* const thirdwebEmbeddedWallet = new EmbeddedWalletSdk({clientId: "", chain: "Polygon"});
* try {
* // prompts user to enter the code they received
* const user = await thirdwebEmbeddedWallet.auth.loginWithThirdwebEmailOtp({ email : "you@example.com" });
* // user is now logged in
* } catch (e) {
* // User closed the OTP modal or something else went wrong during the authentication process
* console.error(e)
* }
*
* @param {string} props.email We will send the email an OTP that needs to be entered in order for them to be logged in.
* @returns {{user: InitializedUser}} An InitializedUser object. See {@link EmbeddedWalletSdk.getUser} for more
*/
async loginWithEmailOtp(args) {
return this.BaseLogin.loginWithEmailOtp(args);
}
async loginWithGoogle(args) {
return this.BaseLogin.loginWithGoogle(args);
}
/**
* A headless way to initiate login with google.
* @returns {{user: InitializedUser}} An InitializedUser object. See {@link EmbeddedWalletSdk.getUser} for more
*/
/**
* @description
* A headless way to send the users at {email} an OTP code.
* You need to then call {@link Auth.verifyEmailLoginOtp} in order to complete the login process
*
* @example
* const thirdwebEmbeddedWallet = new EmbeddedWalletSdk({clientId: "", chain: "Polygon"});
* // sends user an OTP code
* try {
* await thirdwebEmbeddedWallet.auth.sendEmailLoginOtp({ email : "you@example.com" });
* } catch(e) {
* // Error Sending user's email an OTP code
* console.error(e);
* }
*
* // Then when your user is ready to verify their OTP
* try {
* const user = await thirdwebEmbeddedWallet.auth.verifyEmailLoginOtp({ email: "you@example.com", otp: "6-DIGIT_CODE_HERE" });
* } catch(e) {
* // Error verifying the OTP code
* console.error(e)
* }
*
* @param {string} props.email We will send the email an OTP that needs to be entered in order for them to be logged in.
* @returns {{ isNewUser: boolean }} IsNewUser indicates if the user is a new user to your platform
*/
async sendEmailLoginOtp(_ref3) {
let {
email
} = _ref3;
return this.BaseLogin.sendEmailLoginOtp({
email
});
}
/**
* @description
* Used to verify the otp that the user receives from thirdweb
*
* See {@link Auth.sendEmailLoginOtp} for how the headless call flow looks like. Simply swap out the calls to `loginWithThirdwebEmailOtp` with `verifyThirdwebEmailLoginOtp`
*
* @param {string} props.email We will send the email an OTP that needs to be entered in order for them to be logged in.
* @param {string} props.otp The code that the user received in their email
* @returns {{user: InitializedUser}} An InitializedUser object containing the user's status, wallet, authDetails, and more
*/
async verifyEmailLoginOtp(args) {
return this.BaseLogin.verifyEmailLoginOtp(args);
}
/**
* @description
* Logs any existing user out of their wallet.
* @returns {{success: boolean}} true if a user is successfully logged out. false if there's no user currently logged in.
*/
async logout() {
const {
success
} = await this.AuthQuerier.call({
procedureName: "logout",
params: undefined
});
const isRemoveAuthCookie = await this.localStorage.removeAuthCookie();
const isRemoveUserId = await this.localStorage.removeWalletUserId();
return {
success: success || isRemoveAuthCookie || isRemoveUserId
};
}
}
class EthersSigner extends ethers.Signer {
constructor(_ref) {
let {
provider,
clientId,
querier
} = _ref;
super();
defineProperty._defineProperty(this, "DEFAULT_ETHEREUM_CHAIN_ID", 5);
this.clientId = clientId;
this.querier = querier;
// we try to extract a url if possible
this.endpoint = provider.connection?.url;
utils.defineReadOnly(this, "provider", provider);
}
async getAddress() {
const {
address
} = await this.querier.call({
procedureName: "getAddress",
params: undefined
});
return address;
}
async signMessage(message) {
const {
signedMessage
} = await this.querier.call({
procedureName: "signMessage",
params: {
message,
chainId: (await this.provider?.getNetwork())?.chainId ?? this.DEFAULT_ETHEREUM_CHAIN_ID,
rpcEndpoint: this.endpoint
}
});
return signedMessage;
}
async signTransaction(transaction) {
const {
signedTransaction
} = await this.querier.call({
procedureName: "signTransaction",
params: {
transaction,
chainId: (await this.provider?.getNetwork())?.chainId ?? this.DEFAULT_ETHEREUM_CHAIN_ID,
rpcEndpoint: this.endpoint
}
});
return signedTransaction;
}
async _signTypedData(domain, types, message) {
const {
signedTypedData
} = await this.querier.call({
procedureName: "signTypedDataV4",
params: {
domain,
types,
message,
chainId: (await this.provider?.getNetwork())?.chainId ?? this.DEFAULT_ETHEREUM_CHAIN_ID,
rpcEndpoint: this.endpoint
}
});
return signedTypedData;
}
connect(provider) {
return new EthersSigner({
clientId: this.clientId,
provider,
querier: this.querier
});
}
}
class EmbeddedWallet {
/**
* Not meant to be initialized directly. Call {@link .initializeUser} to get an instance
* @param param0
*/
constructor(_ref) {
let {
clientId,
chain,
querier
} = _ref;
this.clientId = clientId;
this.chain = chain;
this.walletManagerQuerier = querier;
this.localStorage = new LocalStorage({
clientId
});
}
/**
* @internal
* Used to set-up the user device in the case that they are using incognito
* @param {string} param.deviceShareStored the value that is saved for the user's device share.
* We save this into the localStorage on the site itself if we could not save it within the iframe's localStorage.
* This happens in incognito mostly
* @param {string} param.walletAddress User's wallet address
* @param {boolean} param.isIframeStorageEnabled Tells us if we were able to store values in the localStorage in our iframe.
* We need to store it under the dev's domain localStorage if we weren't able to store things in the iframe
* @returns {{ walletAddress : string }} The user's wallet details
*/
async postWalletSetUp(_ref2) {
let {
deviceShareStored,
walletAddress,
isIframeStorageEnabled,
walletUserId
} = _ref2;
if (!isIframeStorageEnabled) {
await this.localStorage.saveDeviceShare(deviceShareStored, walletUserId);
}
return {
walletAddress
};
}
/**
* @internal
* Gets the various status states of the user
* @example
* const userStatus = await Paper.getUserWalletStatus();
* switch (userStatus.status) {
* case UserWalletStatus.LOGGED_OUT: {
* // User is logged out, call one of the auth methods on Paper.auth to authenticate the user
* break;
* }
* case UserWalletStatus.LOGGED_IN_WALLET_UNINITIALIZED: {
* // User is logged in, but does not have a wallet associated with it
* // you also have access to the user's details
* userStatus.user.authDetails;
* break;
* }
* case UserWalletStatus.LOGGED_IN_NEW_DEVICE: {
* // User is logged in and created a wallet already, but is missing the device shard
* // You have access to:
* userStatus.user.authDetails;
* userStatus.user.walletAddress;
* break;
* }
* case UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED: {
* // user is logged in and wallet is all set up.
* // You have access to:
* userStatus.user.authDetails;
* userStatus.user.walletAddress;
* userStatus.user.wallet;
* break;
* }
*}
* @returns {GetUserWalletStatusFnReturnType} an object to containing various information on the user statuses
*/
async getUserWalletStatus() {
const userStatus = await this.walletManagerQuerier.call({
procedureName: "getUserStatus",
params: undefined
});
if (userStatus.status === UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED) {
return {
status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED,
user: {
...userStatus.user,
wallet: this
}
};
}
return userStatus;
}
/**
* @description
* Switches the chain that the user wallet is currently on.
* @example
* // user wallet will be set to Polygon
* const Paper = new ThirdwebEmbeddedWalletSdk({clientId: "", chain: "Polygon"});
* const user = await Paper.initializeUser();
* // Switch the user wallet to Mumbai
* await user.wallet.setChain({ chain: "Mumbai" });
* @param {Chain} params.chain The chain that we are changing the user wallet too
*/
async setChain(_ref3) {
let {
chain
} = _ref3;
this.chain = chain;
}
/**
* Returns an Ethers.Js compatible signer that you can use in conjunction with the rest of dApp
* @example
* const Paper = new ThirdwebEmbeddedWalletSdk({clientId: "", chain: "Polygon"});
* const user = await Paper.getUser();
* if (user.status === UserStatus.LOGGED_IN_WALLET_INITIALIZED) {
* // returns a signer on the Polygon mainnet
* const signer = await user.getEthersJsSigner();
* // returns a signer on the specified RPC endpoints
* const signer = await user.getEthersJsSigner({rpcEndpoint: "https://eth-rpc.gateway.pokt.network"});
* }
* @param {Networkish} network.rpcEndpoint the rpc url where calls will be routed through
* @throws If attempting to call the function without the user wallet initialize on their current device. This should never happen if call {@link ThirdwebEmbeddedWalletSdk.initializeUser} before accessing this function
* @returns A signer that is compatible with Ether.js. Defaults to the public rpc on the chain specified when initializing the {@link ThirdwebEmbeddedWalletSdk} instance
*/
async getEthersJsSigner(network) {
const signer = new EthersSigner({
clientId: this.clientId,
provider: ethers.getDefaultProvider(network?.rpcEndpoint ?? sdkCommonUtilities.ChainToPublicRpc[this.chain]),
querier: this.walletManagerQuerier
});
return signer;
}
}
class EmbeddedWalletSdk {
/**
* Used to manage the Auth state of the user.
*/
isClientIdLegacyPaper(clientId) {
if (clientId.indexOf("-") > 0 && clientId.length === 36) {
return true;
} else {
return false;
}
}
/**
* @example
* const thirdwebEmbeddedWallet = new EmbeddedWalletSdk({ clientId: "", chain: "Goerli" });
* @param {string} initParams.clientId the clientId found on the {@link https://thirdweb.com/dashboard/settings dashboard settings}
* @param {Chain} initParams.chain sets the default chain that the EmbeddedWallet will live on.
* @param {CustomizationOptionsType} initParams.styles sets the default style override for any modal that pops up asking for user's details when creating wallet or logging in.
*/
constructor(_ref) {
let {
clientId,
chain,
styles
} = _ref;
if (this.isClientIdLegacyPaper(clientId)) {
throw new Error("You are using a legacy clientId. Please use the clientId found on the thirdweb dashboard settings page");
}
this.clientId = clientId;
this.querier = new EmbeddedWalletIframeCommunicator({
clientId,
customizationOptions: styles
});
this.wallet = new EmbeddedWallet({
clientId,
chain,
querier: this.querier
});
this.auth = new Auth({
clientId,
querier: this.querier,
onAuthSuccess: async authResult => {
await this.wallet.postWalletSetUp({
...authResult.walletDetails,
walletUserId: authResult.storedToken.authDetails.userWalletId
});
await this.querier.call({
procedureName: "initIframe",
params: {
deviceShareStored: authResult.walletDetails.deviceShareStored,
clientId: this.clientId,
walletUserId: authResult.storedToken.authDetails.userWalletId,
authCookie: authResult.storedToken.cookieString
}
});
return {
user: {
status: UserStatus.LOGGED_IN_WALLET_INITIALIZED,
authDetails: authResult.storedToken.authDetails,
wallet: this.wallet,
walletAddress: authResult.walletDetails.walletAddress
}
};
}
});
}
/**
* Gets the usr if they are logged in
* @example
* const user = await thirdwebEmbeddedWallet.getUser();
* switch (user.status) {
* case UserStatus.LOGGED_OUT: {
* // User is logged out, call one of the auth methods on thirdwebEmbeddedWallet.auth to authenticate the user
* break;
* }
* case UserStatus.LOGGED_IN_WALLET_INITIALIZED: {
* // user is logged in and wallet is all set up.
* // You have access to:
* user.status;
* user.authDetails;
* user.walletAddress;
* user.wallet;
* break;
* }
*}
* @returns {GetUser} an object to containing various information on the user statuses
*/
async getUser() {
const userStatus = await this.wallet.getUserWalletStatus();
switch (userStatus.status) {
// user gets {UserWalletStatus.LOGGED_IN_NEW_DEVICE} when they log in but never complete the recovery flow and exits (close modal, refresh etc)
case UserWalletStatus.LOGGED_IN_NEW_DEVICE:
// User gets {UserWalletStatus.LOGGED_IN_WALLET_UNINITIALIZED} when they log in but manage to exit the client in the small window between auth completion and sending them their wallet recovery details
case UserWalletStatus.LOGGED_IN_WALLET_UNINITIALIZED:
// in both case, we simply log them out to reset their state
await this.auth.logout();
return this.getUser();
case UserWalletStatus.LOGGED_OUT:
return {
status: UserStatus.LOGGED_OUT
};
case UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED:
return {
status: UserStatus.LOGGED_IN_WALLET_INITIALIZED,
...userStatus.user
};
}
}
}
exports.AUTH_TOKEN_LOCAL_STORAGE_NAME = AUTH_TOKEN_LOCAL_STORAGE_NAME;
exports.DEVICE_SHARE_LOCAL_STORAGE_NAME = DEVICE_SHARE_LOCAL_STORAGE_NAME;
exports.DEVICE_SHARE_LOCAL_STORAGE_NAME_DEPRECATED = DEVICE_SHARE_LOCAL_STORAGE_NAME_DEPRECATED;
exports.EmbeddedWalletSdk = EmbeddedWalletSdk;
exports.UserStatus = UserStatus;
exports.WALLET_USER_DETAILS_LOCAL_STORAGE_NAME = WALLET_USER_DETAILS_LOCAL_STORAGE_NAME;
exports.WALLET_USER_ID_LOCAL_STORAGE_NAME = WALLET_USER_ID_LOCAL_STORAGE_NAME;