UNPKG

@0xsequence/connect

Version:
407 lines â€Ē 20.6 kB
import { allNetworks } from '@0xsequence/network'; import { SequenceWaaS, WebrpcEndpointError } from '@0xsequence/waas'; import { ethers } from 'ethers'; import { v4 as uuidv4 } from 'uuid'; import { getAddress, InternalRpcError, ProviderDisconnectedError, toHex, TransactionRejectedRpcError, UserRejectedRequestError, zeroAddress } from 'viem'; import { createConnector } from 'wagmi'; import { LocalStorageKey } from '../../constants/localStorage.js'; import { normalizeChainId } from '../../utils/helpers.js'; import { getPkcePair, getXOauthUrl } from '../X/XAuth.js'; sequenceWaasWallet.type = 'sequence-waas'; export function sequenceWaasWallet(params) { const nodesUrl = params.nodesUrl ?? 'https://nodes.sequence.app'; const showConfirmationModal = params.enableConfirmationModal ?? false; const sequenceWaas = new SequenceWaaS({ waasConfigKey: params.waasConfigKey, projectAccessKey: params.projectAccessKey, network: params.network ?? 137 }); const sequenceWaasProvider = new SequenceWaasProvider(sequenceWaas, showConfirmationModal, nodesUrl); return createConnector(config => ({ id: `sequence-waas`, name: 'Sequence WaaS', type: sequenceWaasWallet.type, sequenceWaas, sequenceWaasProvider, params, async setup() { if (typeof window !== 'object') { // (for SSR) only run in browser client return; } if (params.googleClientId) { await config.storage?.setItem(LocalStorageKey.WaasGoogleClientID, params.googleClientId); } if (params.appleClientId) { await config.storage?.setItem(LocalStorageKey.WaasAppleClientID, params.appleClientId); } if (params.appleRedirectURI) { await config.storage?.setItem(LocalStorageKey.WaasAppleRedirectURI, params.appleRedirectURI); } if (params.epicAuthUrl) { await config.storage?.setItem(LocalStorageKey.WaasEpicAuthUrl, params.epicAuthUrl); } if (params.XClientId && params.XRedirectURI) { const { code_challenge, code_verifier } = await getPkcePair(); const authUrl = await getXOauthUrl(params.XClientId, params.XRedirectURI, code_challenge); await config.storage?.setItem(LocalStorageKey.WaasXAuthUrl, authUrl); await config.storage?.setItem(LocalStorageKey.WaasXClientID, params.XClientId); await config.storage?.setItem(LocalStorageKey.WaasXRedirectURI, params.XRedirectURI); await config.storage?.setItem(LocalStorageKey.WaasXCodeVerifier, code_verifier); } sequenceWaasProvider.on('error', error => { if (isSessionInvalidOrNotFoundError(error)) { this.disconnect(); } }); }, async connect(_connectInfo) { const provider = await this.getProvider(); const isSignedIn = await provider.sequenceWaas.isSignedIn(); if (!isSignedIn) { const googleIdToken = await config.storage?.getItem(LocalStorageKey.WaasGoogleIdToken); const emailIdToken = await config.storage?.getItem(LocalStorageKey.WaasEmailIdToken); const appleIdToken = await config.storage?.getItem(LocalStorageKey.WaasAppleIdToken); const epicIdToken = await config.storage?.getItem(LocalStorageKey.WaasEpicIdToken); const xIdToken = await config.storage?.getItem(LocalStorageKey.WaasXIdToken); let idToken; if (params.loginType === 'google' && googleIdToken) { idToken = googleIdToken; } else if (params.loginType === 'email' && emailIdToken) { idToken = emailIdToken; } else if (params.loginType === 'apple' && appleIdToken) { idToken = appleIdToken; } else if (params.loginType === 'epic' && epicIdToken) { idToken = epicIdToken; } else if (params.loginType === 'X' && xIdToken) { idToken = xIdToken; } await config.storage?.removeItem(LocalStorageKey.WaasGoogleIdToken); await config.storage?.removeItem(LocalStorageKey.WaasEmailIdToken); await config.storage?.removeItem(LocalStorageKey.WaasAppleIdToken); await config.storage?.removeItem(LocalStorageKey.WaasEpicIdToken); await config.storage?.removeItem(LocalStorageKey.WaasXIdToken); if (idToken) { try { let signInResponse; if (params.loginType === 'X') { signInResponse = await provider.sequenceWaas.signIn({ xAccessToken: idToken }, randomName()); } else { signInResponse = await provider.sequenceWaas.signIn({ idToken }, randomName()); } if (signInResponse?.email) { await config.storage?.setItem(LocalStorageKey.WaasSignInEmail, signInResponse.email); } } catch (e) { console.log(e); await this.disconnect(); throw e; } } } const accounts = await this.getAccounts(); if (accounts.length) { await config.storage?.setItem(LocalStorageKey.WaasActiveLoginType, params.loginType); } else { throw new Error('No accounts found'); } return { accounts, chainId: await this.getChainId() }; }, async disconnect() { const provider = await this.getProvider(); try { await provider.sequenceWaas.dropSession({ sessionId: await provider.sequenceWaas.getSessionId(), strict: false }); } catch (e) { console.log(e); } await config.storage?.removeItem(LocalStorageKey.WaasActiveLoginType); await config.storage?.removeItem(LocalStorageKey.WaasSignInEmail); config.emitter.emit('disconnect'); }, async getAccounts() { const provider = await this.getProvider(); try { const isSignedIn = await provider.sequenceWaas.isSignedIn(); if (isSignedIn) { const address = await provider.sequenceWaas.getAddress(); return [getAddress(address)]; } } catch (err) { return []; } return []; }, async getProvider() { return sequenceWaasProvider; }, async isAuthorized() { const provider = await this.getProvider(); const activeWaasOption = await config.storage?.getItem(LocalStorageKey.WaasActiveLoginType); if (params.loginType !== activeWaasOption) { return false; } try { return await provider.sequenceWaas.isSignedIn(); } catch (e) { return false; } }, async switchChain({ chainId }) { const provider = await this.getProvider(); const chain = config.chains.find(c => c.id === chainId) || config.chains[0]; await provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: toHex(chainId) }] }); config.emitter.emit('change', { chainId }); return chain; }, async getChainId() { const provider = await this.getProvider(); return Number(provider.getChainId()); }, async onAccountsChanged(accounts) { return { account: accounts[0] }; }, async onChainChanged(chain) { config.emitter.emit('change', { chainId: normalizeChainId(chain) }); }, async onConnect(_connectInfo) { }, async onDisconnect() { await this.disconnect(); } })); } export class SequenceWaasProvider extends ethers.AbstractProvider { sequenceWaas; showConfirmation; nodesUrl; jsonRpcProvider; requestConfirmationHandler; feeConfirmationHandler; currentNetwork; constructor(sequenceWaas, showConfirmation, nodesUrl) { super(sequenceWaas.config.network); this.sequenceWaas = sequenceWaas; this.showConfirmation = showConfirmation; this.nodesUrl = nodesUrl; const initialChain = sequenceWaas.config.network; const initialChainName = allNetworks.find(n => n.chainId === initialChain || n.name === initialChain)?.name; const initialJsonRpcProvider = new ethers.JsonRpcProvider(`${nodesUrl}/${initialChainName}/${sequenceWaas.config.projectAccessKey}`); this.jsonRpcProvider = initialJsonRpcProvider; this.currentNetwork = ethers.Network.from(sequenceWaas.config.network); } async request({ method, params }) { if (method === 'wallet_switchEthereumChain') { const chainId = normalizeChainId(params?.[0].chainId); const networkName = allNetworks.find(n => n.chainId === chainId)?.name; const jsonRpcProvider = new ethers.JsonRpcProvider(`${this.nodesUrl}/${networkName}/${this.sequenceWaas.config.projectAccessKey}`); this.jsonRpcProvider = jsonRpcProvider; this.currentNetwork = ethers.Network.from(chainId); return null; } if (method === 'eth_chainId') { return toHex(this.currentNetwork.chainId); } if (method === 'eth_accounts') { const address = await this.sequenceWaas.getAddress(); const account = getAddress(address); return [account]; } if (method === 'eth_sendTransaction') { const txns = await ethers.resolveProperties(params?.[0]); const chainId = this.getChainId(); let feeOptionsResponse; try { feeOptionsResponse = await this.checkTransactionFeeOptions({ transactions: [txns], chainId }); } catch (error) { if (isSessionInvalidOrNotFoundError(error)) { await this.emit('error', error); throw new ProviderDisconnectedError(new Error('Provider is not connected')); } else { const message = typeof error === 'object' && error !== null && 'cause' in error ? String(error.cause) || 'Failed to check transaction fee options' : 'Failed to check transaction fee options'; throw new InternalRpcError(new Error(message)); } } const feeOptions = feeOptionsResponse?.feeOptions; let selectedFeeOption; if (!feeOptionsResponse?.isSponsored && feeOptions && feeOptions.length > 0) { if (!this.feeConfirmationHandler) { throw new TransactionRejectedRpcError(new Error('Unable to send transaction: please use useWaasFeeOptions hook and pick a fee option')); } const id = uuidv4(); const confirmation = await this.feeConfirmationHandler.confirmFeeOption(id, feeOptions, txns, chainId); if (!confirmation.confirmed) { throw new UserRejectedRequestError(new Error('User rejected send transaction request')); } if (id !== confirmation.id) { throw new UserRejectedRequestError(new Error('User confirmation ids do not match')); } selectedFeeOption = feeOptions.find(feeOption => { // Handle the case where feeTokenAddress is ZeroAddress and contractAddress is null if (confirmation.feeTokenAddress === zeroAddress && feeOption.token.contractAddress === null) { return true; } return feeOption.token.contractAddress === confirmation.feeTokenAddress; }); } if (this.requestConfirmationHandler && this.showConfirmation) { const id = uuidv4(); const confirmation = await this.requestConfirmationHandler.confirmSignTransactionRequest(id, txns, chainId); if (!confirmation.confirmed) { throw new UserRejectedRequestError(new Error('User rejected send transaction request')); } if (id !== confirmation.id) { throw new UserRejectedRequestError(new Error('User confirmation ids do not match')); } } let response; try { response = await this.sequenceWaas.sendTransaction({ transactions: [await ethers.resolveProperties(params?.[0])], network: chainId, transactionsFeeOption: selectedFeeOption, transactionsFeeQuote: feeOptionsResponse?.feeQuote }); } catch (error) { if (isSessionInvalidOrNotFoundError(error)) { await this.emit('error', error); throw new ProviderDisconnectedError(new Error('Provider is not connected')); } else { const message = typeof error === 'object' && error !== null && 'cause' in error ? String(error.cause) || 'Failed to send transaction' : 'Failed to send transaction'; throw new InternalRpcError(new Error(message)); } } if (response.code === 'transactionFailed') { // Failed throw new TransactionRejectedRpcError(new Error(`Unable to send transaction: ${response.data.error}`)); } if (response.code === 'transactionReceipt') { // Success const { txHash } = response.data; return txHash; } } if (method === 'eth_sign' || method === 'personal_sign') { if (this.requestConfirmationHandler && this.showConfirmation) { const id = uuidv4(); const confirmation = await this.requestConfirmationHandler.confirmSignMessageRequest(id, params?.[0], Number(this.currentNetwork.chainId)); if (!confirmation.confirmed) { throw new UserRejectedRequestError(new Error('User rejected sign message request')); } if (id !== confirmation.id) { throw new UserRejectedRequestError(new Error('User confirmation ids do not match')); } } let sig; try { sig = await this.sequenceWaas.signMessage({ message: params?.[0], network: Number(this.currentNetwork.chainId) }); } catch (error) { if (isSessionInvalidOrNotFoundError(error)) { await this.emit('error', error); throw new ProviderDisconnectedError(new Error('Provider is not connected')); } else { const message = typeof error === 'object' && error !== null && 'cause' in error ? String(error.cause) || 'Failed to sign message' : 'Failed to sign message'; throw new InternalRpcError(new Error(message)); } } return sig.data.signature; } if (method === 'eth_signTypedData' || method === 'eth_signTypedData_v4') { if (this.requestConfirmationHandler && this.showConfirmation) { const id = uuidv4(); const confirmation = await this.requestConfirmationHandler.confirmSignMessageRequest(id, JSON.stringify(JSON.parse(params?.[1]), null, 2), // Pretty print the typed data for confirmation Number(this.currentNetwork.chainId)); if (!confirmation.confirmed) { throw new UserRejectedRequestError(new Error('User rejected sign typed data request')); } if (id !== confirmation.id) { throw new UserRejectedRequestError(new Error('User confirmation ids do not match')); } } let sig; try { sig = await this.sequenceWaas.signTypedData({ typedData: JSON.parse(params?.[1]), network: Number(this.currentNetwork.chainId) }); } catch (error) { if (isSessionInvalidOrNotFoundError(error)) { await this.emit('error', error); throw new ProviderDisconnectedError(new Error('Provider is not connected')); } else { const message = typeof error === 'object' && error !== null && 'cause' in error ? String(error.cause) || 'Failed to sign typed data' : 'Failed to sign typed data'; throw new InternalRpcError(new Error(message)); } } return sig.data.signature; } return await this.jsonRpcProvider.send(method, params ?? []); } async getTransaction(txHash) { return await this.jsonRpcProvider.getTransaction(txHash); } detectNetwork() { return Promise.resolve(this.currentNetwork); } getChainId() { return Number(this.currentNetwork.chainId); } async checkTransactionFeeOptions({ transactions, chainId }) { const resp = await this.sequenceWaas.feeOptions({ transactions: transactions, network: chainId }); if (resp.data.feeQuote && resp.data.feeOptions) { return { feeQuote: resp.data.feeQuote, feeOptions: resp.data.feeOptions, isSponsored: false }; } return { feeQuote: resp.data.feeQuote, feeOptions: resp.data.feeOptions, isSponsored: true }; } } const DEVICE_EMOJIS = [ // 256 emojis for unsigned byte range 0 - 255 ...'ðŸķðŸąðŸ­ðŸđ🐰ðŸĶŠðŸŧ🐞ðŸĻðŸŊðŸĶðŸŪðŸ·ðŸ―ðŸļðŸĩ🙈🙉🙊🐒🐔🐧ðŸĶðŸĪðŸĢðŸĨðŸĶ†ðŸĶ…ðŸĶ‰ðŸĶ‡ðŸšðŸ—ðŸīðŸĶ„🐝🐛ðŸĶ‹ðŸŒðŸžðŸœðŸĶŸðŸĶ—🕷ðŸ•ļðŸĶ‚ðŸĒ🐍ðŸĶŽðŸĶ–ðŸĶ•🐙ðŸĶ‘ðŸĶðŸĶžðŸĶ€ðŸĄðŸ ðŸŸðŸŽðŸģ🐋ðŸĶˆðŸŠðŸ…🐆ðŸĶ“ðŸĶðŸĶ§ðŸ˜ðŸĶ›ðŸĶðŸŠðŸŦðŸĶ’ðŸĶ˜ðŸƒðŸ‚🐄🐎🐖🐏🐑ðŸĶ™ðŸðŸĶŒðŸ•ðŸĐðŸĶŪ🐈🐓ðŸĶƒðŸĶšðŸĶœðŸĶĒðŸĶĐ🕊🐇ðŸĶðŸĶĻðŸĶĄðŸĶĶðŸĶĨ🐁🐀ðŸŋðŸĶ”ðŸū🐉ðŸēðŸŒĩ🎄ðŸŒēðŸŒģðŸŒīðŸŒąðŸŒŋ🍀🎍🎋🍃ðŸ‘Ģ🍂🍁🍄🐚ðŸŒū💐🌷ðŸŒđðŸĨ€ðŸŒšðŸŒļ🌞ðŸŒŧ🌞🌝🍏🍎🍐🍊🍋🍌🍉🍇🍓🍈ðŸĨ­ðŸðŸĨĨðŸĨðŸ…ðŸĨ‘ðŸĨĶðŸĨŽðŸĨ’ðŸŒķðŸŒ―ðŸĨ•🧄🧅ðŸĨ”🍠ðŸĨðŸĨŊ🍞ðŸĨ–ðŸĨĻ🧀ðŸĨšðŸģ🧈ðŸĨžðŸ§‡ðŸĨ“ðŸĨĐ🍗🍖ðŸĶī🌭🍔🍟🍕ðŸĨŠðŸĨ™ðŸ§†ðŸŒŪðŸŒŊðŸĨ—ðŸĨ˜ðŸĨŦ🍝🍜ðŸē🍛ðŸĢðŸąðŸĨŸðŸĶŠðŸĪ🍙🍚🍘ðŸĨðŸĨ ðŸĨŪðŸĒðŸĄðŸ§ðŸĻðŸĶðŸĨ§ðŸ§ðŸ°ðŸŽ‚ðŸŪ🍭🍎ðŸŦðŸŋðŸĐ🍊🌰ðŸĨœðŸ‘€ðŸ‘‚👃👄👅👆👇👈👉👊👋👌👍👎👏👐👑👒👓ðŸŽŊðŸŽ°ðŸŽąðŸŽēðŸŽģðŸ‘ūðŸ‘Ŋ👚ðŸ‘ŧðŸ‘―ðŸ‚ðŸƒðŸ„' ]; // Generate a random name for the session, using a single random emoji and 2 random words // from the list of words of ethers export function randomName() { const wordlistSize = 2048; const words = ethers.wordlists.en; const randomEmoji = DEVICE_EMOJIS[Math.floor(Math.random() * DEVICE_EMOJIS.length)]; const randomWord1 = words.getWord(Math.floor(Math.random() * wordlistSize)); const randomWord2 = words.getWord(Math.floor(Math.random() * wordlistSize)); return `${randomEmoji} ${randomWord1} ${randomWord2}`; } function isSessionInvalidOrNotFoundError(error) { return error instanceof WebrpcEndpointError && error.cause === 'session invalid or not found'; } //# sourceMappingURL=sequenceWaasConnector.js.map