From 9c70acd9eafe0e26db370a271c42c5af4b3423cb Mon Sep 17 00:00:00 2001 From: Adebesin Cell Date: Fri, 8 Mar 2024 21:57:05 +0100 Subject: [PATCH 1/6] adds magic connector and dedicatedwalletconnector --- package.json | 14 +- .../connectors/dedicatedWalletConnector.ts | 341 ++++++++++-------- src/lib/connectors/magicConnector.ts | 207 +++++++---- src/lib/connectors/tempCodeRunnerFile.ts | 129 +++++++ 4 files changed, 465 insertions(+), 226 deletions(-) create mode 100644 src/lib/connectors/tempCodeRunnerFile.ts diff --git a/package.json b/package.json index eb3acd6..f59746b 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,7 @@ "type": "module", "repository": "https://github.com/magiclabs/wagmi-magic-connector", "license": "MIT", - "keywords": [ - "wagmi", - "extension", - "magic" - ], + "keywords": ["wagmi", "extension", "magic"], "scripts": { "build": "tsc -p tsconfig.json && tsc-esm-fix --target='dist' --ext='.js'", "format": "rome format . --write", @@ -27,18 +23,20 @@ "@changesets/cli": "^2.24.0", "@magic-ext/oauth": "^12.3.0", "@magic-sdk/provider": "^18.3.0", - "@wagmi/core": "^1.4.12", + "@wagmi/connectors": "^4.1.14", + "@wagmi/core": "^2.6.5", "magic-sdk": "^18.3.0", "tsc-esm-fix": "^2.20.10" }, "devDependencies": { + "@types/node": "^20.11.24", "changeset": "^0.2.6", "rome": "12.0.0", "typescript": "^5.0.4", - "viem": "^1.2.10" + "viem": "2.x" }, "peerDependencies": { - "viem": "^1.2.10" + "viem": "2.x" }, "files": [ "dist", diff --git a/src/lib/connectors/dedicatedWalletConnector.ts b/src/lib/connectors/dedicatedWalletConnector.ts index a220336..d6bbf22 100644 --- a/src/lib/connectors/dedicatedWalletConnector.ts +++ b/src/lib/connectors/dedicatedWalletConnector.ts @@ -2,14 +2,17 @@ import { OAuthExtension, OAuthProvider } from '@magic-ext/oauth' import type { InstanceWithExtensions, MagicSDKAdditionalConfiguration, - MagicSDKExtensionsOption, SDKBase, } from '@magic-sdk/provider' -import type { Chain } from '@wagmi/core' +import { createConnector, normalizeChainId } from '@wagmi/core' import { Magic } from 'magic-sdk' +import { + MagicConnectorParams, + MagicOptions, + magicConnector, +} from './magicConnector' +import { UserRejectedRequestError, getAddress } from 'viem' import { createModal } from '../modal/view' -import { MagicConnector, MagicOptions } from './magicConnector' -import { UserRejectedRequestError } from 'viem' interface UserDetails { email: string @@ -17,19 +20,6 @@ interface UserDetails { oauthProvider: OAuthProvider } -interface DedicatedWalletOptions extends MagicOptions { - enableEmailLogin?: boolean - enableSMSLogin?: boolean - oauthOptions?: { - providers: OAuthProvider[] - callbackUrl?: string - } - magicSdkConfiguration?: MagicSDKAdditionalConfiguration< - string, - OAuthExtension[] - > -} - /** * Dedicated Wallet Connector class used to connect to wallet using Dedicated Wallet. * It uses modal UI defined in our package which also takes in various styling options @@ -49,162 +39,219 @@ interface DedicatedWalletOptions extends MagicOptions { * @see https://magic.link/docs/dedicated/overview */ -export class DedicatedWalletConnector extends MagicConnector { - magicSDK?: InstanceWithExtensions +interface DedicatedWalletOptions extends MagicOptions { + enableEmailLogin?: boolean + enableSMSLogin?: boolean + oauthOptions?: { + providers: OAuthProvider[] + callbackUrl?: string + } magicSdkConfiguration?: MagicSDKAdditionalConfiguration< string, - MagicSDKExtensionsOption + OAuthExtension[] > - enableSMSLogin: boolean - enableEmailLogin: boolean - oauthProviders: OAuthProvider[] - oauthCallbackUrl?: string - magicOptions: MagicOptions - - constructor(config: { chains?: Chain[]; options: DedicatedWalletOptions }) { - super(config) - this.magicSdkConfiguration = config.options.magicSdkConfiguration - this.oauthProviders = config.options.oauthOptions?.providers || [] - this.oauthCallbackUrl = config.options.oauthOptions?.callbackUrl - this.enableSMSLogin = config.options.enableSMSLogin || false - this.enableEmailLogin = config.options.enableEmailLogin || true - this.magicOptions = config.options - } - - /** - * Get the Magic Instance - * @throws {Error} if Magic API Key is not provided - */ - getMagicSDK(): InstanceWithExtensions { - if (!this.magicSDK) { - this.magicSDK = new Magic(this.magicOptions.apiKey, { - ...this.magicSdkConfiguration, - extensions: [new OAuthExtension()], - }) - } - return this.magicSDK - } - - /** - * Connect method attempts to connects to wallet using Dedicated Wallet modal - * this will open a modal for the user to select their wallet - */ - async connect() { - if (!this.magicOptions.apiKey) - throw new Error('Magic API Key is not provided.') - - const provider = await this.getProvider() - - if (provider?.on) { - provider.on('accountsChanged', this.onAccountsChanged) - provider.on('chainChanged', this.onChainChanged) - provider.on('disconnect', this.onDisconnect) - } - - // Check if we have a chainId, in case of error just assign 0 for legacy - let chainId: number - try { - chainId = await this.getChainId() - } catch { - chainId = 0 - } - - // if there is a user logged in, return the user - if (await this.isAuthorized()) { - return { - provider, - chain: { - id: chainId, - unsupported: false, - }, - account: await this.getAccount(), - } - } - - // open the modal and process the magic login steps - if (!this.isModalOpen) { - const modalOutput = await this.getUserDetailsByForm( - this.enableSMSLogin, - this.enableEmailLogin, - this.oauthProviders, - ) - - const magic = this.getMagicSDK() - - // LOGIN WITH MAGIC USING OAUTH PROVIDER - if (modalOutput.oauthProvider) - await magic.oauth.loginWithRedirect({ - provider: modalOutput.oauthProvider, - redirectURI: this.oauthCallbackUrl || window.location.href, - }) +} - // LOGIN WITH MAGIC USING EMAIL - if (modalOutput.email) - await magic.auth.loginWithEmailOTP({ - email: modalOutput.email, - }) +export interface DedicatedWalletConnectorParams extends MagicConnectorParams { + options: DedicatedWalletOptions +} - // LOGIN WITH MAGIC USING PHONE NUMBER - if (modalOutput.phoneNumber) - await magic.auth.loginWithSMS({ - phoneNumber: modalOutput.phoneNumber, - }) +export function dedicatedWalletConnector({ + chains, + options, +}: DedicatedWalletConnectorParams) { + let magicSDK: InstanceWithExtensions | null = null - if (await magic.user.isLoggedIn()) - return { - account: await this.getAccount(), - chain: { - id: chainId, - unsupported: false, - }, - provider, - } + const getDedicatedMagicSdk = () => { + if (!magicSDK) { + magicSDK = new Magic(options.apiKey, { + ...options.magicSdkConfiguration, + extensions: [new OAuthExtension()], + }) as InstanceWithExtensions } - throw new UserRejectedRequestError(Error('User Rejected Request')) + return magicSDK } - /** - * checks if user is authorized with Magic. - * It also checks for oauth redirect result incase user - * comes from OAuth flow redirect. - * (without this check, user will not be logged in after oauth redirect) - */ - async isAuthorized() { - try { - const magic = this.getMagicSDK() + let { + id, + name, + type, + isModalOpen, + getAccount, + // biome-ignore lint/correctness/noUnusedVariables: + getMagicSDK, + getProvider, + // getWalletClient, + onAccountsChanged, + } = magicConnector({ chains, options }) - const isLoggedIn = await magic.user.isLoggedIn() - if (isLoggedIn) return true + getMagicSDK = getDedicatedMagicSdk - const result = await magic.oauth.getRedirectResult() - return result !== null - } catch {} - return false - } + 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. */ - async getUserDetailsByForm( + const getUserDetailsByForm = async ( enableSMSLogin: boolean, enableEmailLogin: boolean, oauthProviders: OAuthProvider[], - ): Promise { + ): Promise => { const output: UserDetails = (await createModal({ - accentColor: this.magicOptions.accentColor, - isDarkMode: this.magicOptions.isDarkMode, - customLogo: this.magicOptions.customLogo, - customHeaderText: this.magicOptions.customHeaderText, + accentColor: options.accentColor, + isDarkMode: options.isDarkMode, + customLogo: options.customLogo, + customHeaderText: options.customHeaderText, enableSMSLogin: enableSMSLogin, enableEmailLogin: enableEmailLogin || true, oauthProviders, })) as UserDetails - this.isModalOpen = false + isModalOpen = false return output } -} -export class MagicAuthConnector extends DedicatedWalletConnector {} + return createConnector((config) => ({ + id, + type, + name, + getProvider, + connect: async function () { + 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: number + 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 = getDedicatedMagicSdk() + + // 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')) + }, + + disconnect: async () => { + try { + const magic = getDedicatedMagicSdk() + await magic?.wallet.disconnect() + config.emitter.emit('disconnect') + } catch (error) { + console.error('Error disconnecting from Magic SDK:', error) + } + }, + + getAccounts: async () => { + const provider = await getProvider() + const accounts = (await provider?.request({ + method: 'eth_accounts', + })) as string[] + return accounts.map((x) => getAddress(x)) + }, + + getChainId: async (): Promise => { + const provider = await getProvider() + if (provider) { + const chainId = await provider.request({ + method: 'eth_chainId', + params: [], + }) + 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') + }, + + isAuthorized: async () => { + try { + const magic = getDedicatedMagicSdk() + + if (!magic) { + return false + } + + const isLoggedIn = await magic.user.isLoggedIn() + if (isLoggedIn) return true + + const result = await magic.oauth.getRedirectResult() + return result !== null + } catch {} + return false + }, + + onAccountsChanged, + + 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') + }, + })) +} diff --git a/src/lib/connectors/magicConnector.ts b/src/lib/connectors/magicConnector.ts index 5921d63..5bf6646 100644 --- a/src/lib/connectors/magicConnector.ts +++ b/src/lib/connectors/magicConnector.ts @@ -1,12 +1,10 @@ -import type { OAuthExtension } from '@magic-ext/oauth' +import { OAuthExtension } from '@magic-ext/oauth' import type { InstanceWithExtensions, MagicSDKExtensionsOption, SDKBase, } from '@magic-sdk/provider' -import { Chain, Connector } from '@wagmi/core' -import { createWalletClient, custom, getAddress } from 'viem' -import { normalizeChainId } from '../utils' +import { Chain, createWalletClient, custom, getAddress } from 'viem' const IS_SERVER = typeof window === 'undefined' @@ -25,23 +23,123 @@ export interface MagicOptions { * Dedicated Wallet Connector and Universal Wallet Connector are the two connectors provided by this library * And both of them extend this class. */ -export abstract class MagicConnector extends Connector { - ready = !IS_SERVER - readonly id = 'magic' - readonly name = 'Magic' - isModalOpen = false - - protected constructor(config: { chains?: Chain[]; options: MagicOptions }) { - super(config) - if (!config.options.apiKey) { - throw new Error( - 'Magic API Key is required. Get one at https://dashboard.magic.link/', - ) - } + +// export abstract class MagicConnector extends Connector { +// ready = !IS_SERVER +// readonly id = 'magic' +// readonly name = 'Magic' +// isModalOpen = false + +// protected constructor(config: { chains?: Chain[]; options: MagicOptions }) { +// super(config) +// if (!config.options.apiKey) { +// throw new Error( +// 'Magic API Key is required. Get one at https://dashboard.magic.link/', +// ) +// } +// } + +// async getAccount() { +// const provider = await this.getProvider() +// const accounts = await provider?.request({ +// method: 'eth_accounts', +// }) +// const account = getAddress(accounts[0] as string) +// return account +// } + +// async getWalletClient({ chainId }: { chainId?: number } = {}): Promise { +// const provider = await this.getProvider() +// const account = await this.getAccount() +// const chain = this.chains.find((x) => x.id === chainId) || this.chains[0] +// if (!provider) throw new Error('provider is required.') +// return createWalletClient({ +// account, +// chain, +// transport: custom(provider), +// }) +// } + +// async getProvider() { +// const magic = this.getMagicSDK() +// return magic?.rpcProvider +// } + +// protected onAccountsChanged(accounts: string[]): void { +// if (accounts.length === 0 || !accounts[0]) this.emit('disconnect') +// else this.emit('change', { account: getAddress(accounts[0]) }) +// } + +// protected onChainChanged(chainId: string | number): void { +// const id = normalizeChainId(chainId) +// const unsupported = this.isChainUnsupported(id) +// this.emit('change', { chain: { id, unsupported } }) +// } + +// async getChainId(): Promise { +// const provider = await this.getProvider() +// if (provider) { +// const chainId = await provider.request({ +// method: 'eth_chainId', +// params: [], +// }) +// return normalizeChainId(chainId) +// } +// const networkOptions = this.options.magicSdkConfiguration?.network +// if (typeof networkOptions === 'object') { +// const chainID = networkOptions.chainId +// if (chainID) return normalizeChainId(chainID) +// } +// throw new Error('Chain ID is not defined') +// } + +// protected onDisconnect(): void { +// this.emit('disconnect') +// } + +// async disconnect(): Promise { +// try { +// const magic = this.getMagicSDK() +// await magic?.wallet.disconnect() +// this.emit('disconnect') +// } catch (error) { +// console.error('Error disconnecting from Magic SDK:', error) +// } +// } + +// abstract getMagicSDK(): +// | InstanceWithExtensions +// | InstanceWithExtensions> +// | null +// } + +export interface MagicConnectorParams { + chains?: Chain[] + options: MagicOptions +} + +export function magicConnector({ chains = [], options }: MagicConnectorParams) { + if (!options.apiKey) { + throw new Error( + 'Magic API Key is required. Get one at https://dashboard.magic.link/', + ) } - async getAccount() { - const provider = await this.getProvider() + const getMagicSDK = (): + | InstanceWithExtensions + | InstanceWithExtensions> + | null => { + return null + } + + const getProvider = async () => { + const magic = getMagicSDK() + if (!magic) return null + return magic.rpcProvider + } + + const getAccount = async () => { + const provider = await getProvider() const accounts = await provider?.request({ method: 'eth_accounts', }) @@ -49,10 +147,10 @@ export abstract class MagicConnector extends Connector { return account } - async getWalletClient({ chainId }: { chainId?: number } = {}): Promise { - const provider = await this.getProvider() - const account = await this.getAccount() - const chain = this.chains.find((x) => x.id === chainId) || this.chains[0] + const getWalletClient = async ({ chainId }: { chainId?: number } = {}) => { + const provider = await getProvider() + const account = await getAccount() + const chain = chains.find((x) => x.id === chainId) || chains[0] if (!provider) throw new Error('provider is required.') return createWalletClient({ account, @@ -61,55 +159,22 @@ export abstract class MagicConnector extends Connector { }) } - async getProvider() { - const magic = this.getMagicSDK() - return magic?.rpcProvider - } - - protected onAccountsChanged(accounts: string[]): void { - if (accounts.length === 0 || !accounts[0]) this.emit('disconnect') - else this.emit('change', { account: getAddress(accounts[0]) }) - } - - protected onChainChanged(chainId: string | number): void { - const id = normalizeChainId(chainId) - const unsupported = this.isChainUnsupported(id) - this.emit('change', { chain: { id, unsupported } }) + const onAccountsChanged = async (accounts: string[]) => { + const provider = await getProvider() + if (accounts.length === 0 || !accounts[0]) provider?.emit('disconnect') + else provider?.emit('change', { account: getAddress(accounts[0]) }) } - async getChainId(): Promise { - const provider = await this.getProvider() - if (provider) { - const chainId = await provider.request({ - method: 'eth_chainId', - params: [], - }) - return normalizeChainId(chainId) - } - const networkOptions = this.options.magicSdkConfiguration?.network - if (typeof networkOptions === 'object') { - const chainID = networkOptions.chainId - if (chainID) return normalizeChainId(chainID) - } - throw new Error('Chain ID is not defined') + return { + id: 'magic', + name: 'Magic', + type: 'Magic', + isModalOpen: false, + isReady: IS_SERVER, + getProvider, + getMagicSDK, + getAccount, + getWalletClient, + onAccountsChanged, } - - protected onDisconnect(): void { - this.emit('disconnect') - } - - async disconnect(): Promise { - try { - const magic = this.getMagicSDK() - await magic?.wallet.disconnect() - this.emit('disconnect') - } catch (error) { - console.error('Error disconnecting from Magic SDK:', error) - } - } - - abstract getMagicSDK(): - | InstanceWithExtensions - | InstanceWithExtensions> - | null } diff --git a/src/lib/connectors/tempCodeRunnerFile.ts b/src/lib/connectors/tempCodeRunnerFile.ts new file mode 100644 index 0000000..3094b47 --- /dev/null +++ b/src/lib/connectors/tempCodeRunnerFile.ts @@ -0,0 +1,129 @@ +export function dedicatedWalletConnector({ + chains, + options, +}: DedicatedWalletOptions) { + let magicSDK: InstanceWithExtensions | null = null + + const getDedicatedMagicSdk = () => { + if (!magicSDK) { + magicSDK = new Magic(options.apiKey, { + ...options.magicSdkConfiguration, + extensions: [new OAuthExtension()], + }) as InstanceWithExtensions + } + return magicSDK + } + + let { + id, + name, + type + isModalOpen, + isReady, + getAccount, + getMagicSDK, + getProvider, + getWalletClient, + onAccountsChanged, + } = magicConnector({ chains, options }) + + getMagicSDK = getDedicatedMagicSdk + + return createConnector((config) => ({ + id, + type, + name, + getProvider, + connect: async function() { + 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: number + try { + chainId = await this.getChainId() + } catch { + chainId = 0 + } + + if (await this.isAuthorized()) { + return { + chainId, + accounts: [await getAccount()], + } + } + + return { + accounts: ['0x...'], + chainId, + }; + }, + + disconnect: async () => { + console.log('Disconnecting...') + // Implement disconnect logic here + }, + + getAccounts: async () => { + console.log('Getting accounts...') + // Implement logic to retrieve accounts. Example: + return ['0x...'] // Replace with actual accounts + }, + + getChainId: async () => { + console.log('Getting chain ID...') + // Implement logic to retrieve chain ID. Example: + return 1 // Replace with actual chain ID + }, + + isAuthorized: async () => { + try { + const magic = getDedicatedMagicSdk() + + if(!magic) { + return false + } + + const isLoggedIn = await magic.user.isLoggedIn() + if (isLoggedIn) return true + + const result = await magic.oauth.getRedirectResult() + return result !== null + } catch {} + return false + }, + + onAccountsChanged: (accounts) => { + console.log('Accounts changed:', accounts) + // Implement your logic to handle account changes + }, + + onChainChanged: (chainId) => { + console.log('Chain changed:', chainId) + // Implement your logic to handle chain changes + }, + + onConnect: (connectInfo) => { + console.log('Connected:', connectInfo) + // Implement your logic for when a connection is established + }, + + onDisconnect: (error) => { + console.log('Disconnected:', error) + // Implement your logic for handling disconnection + }, + + onMessage: (message) => { + console.log('Message:', message) + // Implement your logic for handling messages + }, + })) +} From 0877581f3fa2674da50a6796428a2f7d69113334 Mon Sep 17 00:00:00 2001 From: Adebesin Cell Date: Fri, 8 Mar 2024 21:57:36 +0100 Subject: [PATCH 2/6] adds magic connector and dedicatedwalletconnector --- src/lib/connectors/tempCodeRunnerFile.ts | 129 ----------------------- 1 file changed, 129 deletions(-) delete mode 100644 src/lib/connectors/tempCodeRunnerFile.ts diff --git a/src/lib/connectors/tempCodeRunnerFile.ts b/src/lib/connectors/tempCodeRunnerFile.ts deleted file mode 100644 index 3094b47..0000000 --- a/src/lib/connectors/tempCodeRunnerFile.ts +++ /dev/null @@ -1,129 +0,0 @@ -export function dedicatedWalletConnector({ - chains, - options, -}: DedicatedWalletOptions) { - let magicSDK: InstanceWithExtensions | null = null - - const getDedicatedMagicSdk = () => { - if (!magicSDK) { - magicSDK = new Magic(options.apiKey, { - ...options.magicSdkConfiguration, - extensions: [new OAuthExtension()], - }) as InstanceWithExtensions - } - return magicSDK - } - - let { - id, - name, - type - isModalOpen, - isReady, - getAccount, - getMagicSDK, - getProvider, - getWalletClient, - onAccountsChanged, - } = magicConnector({ chains, options }) - - getMagicSDK = getDedicatedMagicSdk - - return createConnector((config) => ({ - id, - type, - name, - getProvider, - connect: async function() { - 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: number - try { - chainId = await this.getChainId() - } catch { - chainId = 0 - } - - if (await this.isAuthorized()) { - return { - chainId, - accounts: [await getAccount()], - } - } - - return { - accounts: ['0x...'], - chainId, - }; - }, - - disconnect: async () => { - console.log('Disconnecting...') - // Implement disconnect logic here - }, - - getAccounts: async () => { - console.log('Getting accounts...') - // Implement logic to retrieve accounts. Example: - return ['0x...'] // Replace with actual accounts - }, - - getChainId: async () => { - console.log('Getting chain ID...') - // Implement logic to retrieve chain ID. Example: - return 1 // Replace with actual chain ID - }, - - isAuthorized: async () => { - try { - const magic = getDedicatedMagicSdk() - - if(!magic) { - return false - } - - const isLoggedIn = await magic.user.isLoggedIn() - if (isLoggedIn) return true - - const result = await magic.oauth.getRedirectResult() - return result !== null - } catch {} - return false - }, - - onAccountsChanged: (accounts) => { - console.log('Accounts changed:', accounts) - // Implement your logic to handle account changes - }, - - onChainChanged: (chainId) => { - console.log('Chain changed:', chainId) - // Implement your logic to handle chain changes - }, - - onConnect: (connectInfo) => { - console.log('Connected:', connectInfo) - // Implement your logic for when a connection is established - }, - - onDisconnect: (error) => { - console.log('Disconnected:', error) - // Implement your logic for handling disconnection - }, - - onMessage: (message) => { - console.log('Message:', message) - // Implement your logic for handling messages - }, - })) -} From 0646a6a7a534c1aad53c534c48e4df8af94ee0b8 Mon Sep 17 00:00:00 2001 From: Adebesin Cell Date: Tue, 12 Mar 2024 20:34:29 +0100 Subject: [PATCH 3/6] fixes formatting --- package.json | 6 +- .../connectors/dedicatedWalletConnector.ts | 50 ++-- src/lib/connectors/magicConnector.ts | 111 ++------ .../connectors/universalWalletConnector.ts | 241 +++++++++--------- src/lib/utils.ts | 2 +- 5 files changed, 161 insertions(+), 249 deletions(-) diff --git a/package.json b/package.json index f59746b..30ed117 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "type": "module", "repository": "https://github.com/magiclabs/wagmi-magic-connector", "license": "MIT", - "keywords": ["wagmi", "extension", "magic"], + "keywords": [ + "wagmi", + "extension", + "magic" + ], "scripts": { "build": "tsc -p tsconfig.json && tsc-esm-fix --target='dist' --ext='.js'", "format": "rome format . --write", diff --git a/src/lib/connectors/dedicatedWalletConnector.ts b/src/lib/connectors/dedicatedWalletConnector.ts index d6bbf22..0f1ac6a 100644 --- a/src/lib/connectors/dedicatedWalletConnector.ts +++ b/src/lib/connectors/dedicatedWalletConnector.ts @@ -1,14 +1,13 @@ -import { OAuthExtension, OAuthProvider } from '@magic-ext/oauth' +import type { OAuthExtension, OAuthProvider } from '@magic-ext/oauth' import type { InstanceWithExtensions, MagicSDKAdditionalConfiguration, SDKBase, } from '@magic-sdk/provider' import { createConnector, normalizeChainId } from '@wagmi/core' -import { Magic } from 'magic-sdk' import { - MagicConnectorParams, - MagicOptions, + type MagicConnectorParams, + type MagicOptions, magicConnector, } from './magicConnector' import { UserRejectedRequestError, getAddress } from 'viem' @@ -60,37 +59,24 @@ export function dedicatedWalletConnector({ chains, options, }: DedicatedWalletConnectorParams) { - let magicSDK: InstanceWithExtensions | null = null - - const getDedicatedMagicSdk = () => { - if (!magicSDK) { - magicSDK = new Magic(options.apiKey, { - ...options.magicSdkConfiguration, - extensions: [new OAuthExtension()], - }) as InstanceWithExtensions - } - return magicSDK - } - let { id, name, type, isModalOpen, getAccount, - // biome-ignore lint/correctness/noUnusedVariables: getMagicSDK, getProvider, - // getWalletClient, onAccountsChanged, - } = magicConnector({ chains, options }) - - getMagicSDK = getDedicatedMagicSdk + } = magicConnector({ + chains, + options: { ...options, connectorType: 'dedicated' }, + }) - const oauthProviders = options.oauthOptions?.providers || [] + const oauthProviders = options.oauthOptions?.providers ?? [] const oauthCallbackUrl = options.oauthOptions?.callbackUrl - const enableSMSLogin = options.enableSMSLogin || false - const enableEmailLogin = options.enableEmailLogin || true + const enableSMSLogin = options.enableSMSLogin ?? false + const enableEmailLogin = options.enableEmailLogin ?? true /** * This method is used to get user details from the modal UI @@ -148,20 +134,23 @@ export function dedicatedWalletConnector({ } } - if (isModalOpen) { + if (!isModalOpen) { const modalOutput = await getUserDetailsByForm( enableSMSLogin, enableEmailLogin, oauthProviders, ) - const magic = getDedicatedMagicSdk() + const magic = getMagicSDK() as InstanceWithExtensions< + SDKBase, + OAuthExtension[] + > // LOGIN WITH MAGIC USING OAUTH PROVIDER if (modalOutput.oauthProvider) await magic.oauth.loginWithRedirect({ provider: modalOutput.oauthProvider, - redirectURI: oauthCallbackUrl || window.location.href, + redirectURI: oauthCallbackUrl ?? window.location.href, }) // LOGIN WITH MAGIC USING EMAIL @@ -187,7 +176,7 @@ export function dedicatedWalletConnector({ disconnect: async () => { try { - const magic = getDedicatedMagicSdk() + const magic = getMagicSDK() await magic?.wallet.disconnect() config.emitter.emit('disconnect') } catch (error) { @@ -222,7 +211,10 @@ export function dedicatedWalletConnector({ isAuthorized: async () => { try { - const magic = getDedicatedMagicSdk() + const magic = getMagicSDK() as InstanceWithExtensions< + SDKBase, + OAuthExtension[] + > if (!magic) { return false diff --git a/src/lib/connectors/magicConnector.ts b/src/lib/connectors/magicConnector.ts index 5bf6646..a32e1e5 100644 --- a/src/lib/connectors/magicConnector.ts +++ b/src/lib/connectors/magicConnector.ts @@ -1,10 +1,12 @@ import { OAuthExtension } from '@magic-ext/oauth' import type { InstanceWithExtensions, + MagicSDKAdditionalConfiguration, MagicSDKExtensionsOption, SDKBase, } from '@magic-sdk/provider' -import { Chain, createWalletClient, custom, getAddress } from 'viem' +import { type EthNetworkConfiguration, Magic } from 'magic-sdk' +import { type Chain, createWalletClient, custom, getAddress } from 'viem' const IS_SERVER = typeof window === 'undefined' @@ -14,6 +16,9 @@ export interface MagicOptions { isDarkMode?: boolean customLogo?: string customHeaderText?: string + connectorType?: 'dedicated' | 'universal' + magicSdkConfiguration?: MagicSDKAdditionalConfiguration + networks?: EthNetworkConfiguration[] } /** @@ -24,95 +29,6 @@ export interface MagicOptions { * And both of them extend this class. */ -// export abstract class MagicConnector extends Connector { -// ready = !IS_SERVER -// readonly id = 'magic' -// readonly name = 'Magic' -// isModalOpen = false - -// protected constructor(config: { chains?: Chain[]; options: MagicOptions }) { -// super(config) -// if (!config.options.apiKey) { -// throw new Error( -// 'Magic API Key is required. Get one at https://dashboard.magic.link/', -// ) -// } -// } - -// async getAccount() { -// const provider = await this.getProvider() -// const accounts = await provider?.request({ -// method: 'eth_accounts', -// }) -// const account = getAddress(accounts[0] as string) -// return account -// } - -// async getWalletClient({ chainId }: { chainId?: number } = {}): Promise { -// const provider = await this.getProvider() -// const account = await this.getAccount() -// const chain = this.chains.find((x) => x.id === chainId) || this.chains[0] -// if (!provider) throw new Error('provider is required.') -// return createWalletClient({ -// account, -// chain, -// transport: custom(provider), -// }) -// } - -// async getProvider() { -// const magic = this.getMagicSDK() -// return magic?.rpcProvider -// } - -// protected onAccountsChanged(accounts: string[]): void { -// if (accounts.length === 0 || !accounts[0]) this.emit('disconnect') -// else this.emit('change', { account: getAddress(accounts[0]) }) -// } - -// protected onChainChanged(chainId: string | number): void { -// const id = normalizeChainId(chainId) -// const unsupported = this.isChainUnsupported(id) -// this.emit('change', { chain: { id, unsupported } }) -// } - -// async getChainId(): Promise { -// const provider = await this.getProvider() -// if (provider) { -// const chainId = await provider.request({ -// method: 'eth_chainId', -// params: [], -// }) -// return normalizeChainId(chainId) -// } -// const networkOptions = this.options.magicSdkConfiguration?.network -// if (typeof networkOptions === 'object') { -// const chainID = networkOptions.chainId -// if (chainID) return normalizeChainId(chainID) -// } -// throw new Error('Chain ID is not defined') -// } - -// protected onDisconnect(): void { -// this.emit('disconnect') -// } - -// async disconnect(): Promise { -// try { -// const magic = this.getMagicSDK() -// await magic?.wallet.disconnect() -// this.emit('disconnect') -// } catch (error) { -// console.error('Error disconnecting from Magic SDK:', error) -// } -// } - -// abstract getMagicSDK(): -// | InstanceWithExtensions -// | InstanceWithExtensions> -// | null -// } - export interface MagicConnectorParams { chains?: Chain[] options: MagicOptions @@ -129,6 +45,19 @@ export function magicConnector({ chains = [], options }: MagicConnectorParams) { | InstanceWithExtensions | InstanceWithExtensions> | null => { + if (options.connectorType === 'dedicated') { + return new Magic(options.apiKey, { + ...options.magicSdkConfiguration, + extensions: [new OAuthExtension()], + }) + } + if (options.connectorType === 'universal') { + return new Magic(options.apiKey, { + ...options.magicSdkConfiguration, + network: + options.magicSdkConfiguration?.network ?? options?.networks?.[0], + }) + } return null } @@ -150,7 +79,7 @@ export function magicConnector({ chains = [], options }: MagicConnectorParams) { const getWalletClient = async ({ chainId }: { chainId?: number } = {}) => { const provider = await getProvider() const account = await getAccount() - const chain = chains.find((x) => x.id === chainId) || chains[0] + const chain = chains.find((x) => x.id === chainId) ?? chains[0] if (!provider) throw new Error('provider is required.') return createWalletClient({ account, diff --git a/src/lib/connectors/universalWalletConnector.ts b/src/lib/connectors/universalWalletConnector.ts index a1ec234..522fe43 100644 --- a/src/lib/connectors/universalWalletConnector.ts +++ b/src/lib/connectors/universalWalletConnector.ts @@ -1,15 +1,10 @@ -import type { - InstanceWithExtensions, - MagicSDKAdditionalConfiguration, - MagicSDKExtensionsOption, - SDKBase, -} from '@magic-sdk/provider' +import type { MagicSDKAdditionalConfiguration } from '@magic-sdk/provider' import type { RPCProviderModule } from '@magic-sdk/provider/dist/types/modules/rpc-provider' import type { EthNetworkConfiguration } from '@magic-sdk/types' -import type { Chain } from '@wagmi/core' -import { Magic } from 'magic-sdk' +import { createConnector } from '@wagmi/core' import { normalizeChainId } from '../utils' -import { MagicConnector } from './magicConnector' +import { magicConnector } from './magicConnector' +import { type Chain, getAddress } from 'viem' export interface UniversalWalletOptions { apiKey: string @@ -36,127 +31,119 @@ export interface UniversalWalletOptions { * @see https://magic.link/docs/universal/overview */ -export class UniversalWalletConnector extends MagicConnector { - magic: InstanceWithExtensions< - SDKBase, - MagicSDKExtensionsOption - > | null - - constructor(config: { chains?: Chain[]; options: UniversalWalletOptions }) { - super(config) - this.magic = this.getMagicSDK() - } - - /** - * Get the Magic Instance - * @throws {Error} if Magic API Key is not provided - */ - getMagicSDK() { - const { apiKey, magicSdkConfiguration, networks } = this.options - if (typeof window === 'undefined') { - return null - } - if (this.magic) return this.magic - this.magic = new Magic(apiKey, { - ...magicSdkConfiguration, - network: magicSdkConfiguration?.network || networks?.[0], - }) - return this.magic - } - - /** - * Connect method attempts to connects to wallet using Universal Wallet modal - * this will open a modal for the user to select their wallet - */ - async connect() { - await this.magic?.wallet.connectWithUI() - const provider = await this.getProvider() - const chainId = await this.getChainId() - - provider && this.registerProviderEventListeners(provider) - - const account = await this.getAccount() - - return { - account, - chain: { - id: chainId, - unsupported: false, - }, - provider, - } - } +interface UniversalWalletConnectorParams { + chains?: Chain[] + options: UniversalWalletOptions +} - /** - * Provider events to run methods on various events - * on user session - */ - private registerProviderEventListeners(provider: RPCProviderModule) { +export function universalWalletConnector({ + chains, + options, +}: UniversalWalletConnectorParams) { + const { + id, + name, + type, + getAccount, + getMagicSDK, + getProvider, + onAccountsChanged, + } = magicConnector({ + chains, + options: { ...options, connectorType: 'universal' }, + }) + + const magic = getMagicSDK() + + const registerProviderEventListeners = ( + provider: RPCProviderModule, + onChainChanged: (chain: string) => void, + onDisconnect: () => void, + ) => { if (provider.on) { - provider.on('accountsChanged', this.onAccountsChanged) - provider.on('chainChanged', this.onChainChanged) - provider.on('disconnect', this.onDisconnect) + provider.on('accountsChanged', onAccountsChanged) + provider.on('chainChanged', (chain) => onChainChanged(chain)) + provider.on('disconnect', onDisconnect) } } - /** - * checks if user is authorized with Universal Wallet - */ - async isAuthorized() { - try { - const walletInfo = await this.magic?.wallet.getInfo() - return !!walletInfo - } catch { - return false - } - } - - /** - * method that switches chains given a chainId. - * This only works when user provides multiple networks in options - * @param chainId - * @throws {Error} if chainId is not supported - */ - async switchChain(chainId: number): Promise { - if (!this.options.networks) { - throw new Error( - 'switch chain not supported: please provide networks in options', - ) - } - - const normalizedChainId = normalizeChainId(chainId) - const chain = this.chains.find((x) => x.id === normalizedChainId) - if (!chain) throw new Error(`Unsupported chainId: ${chainId}`) - - const network = this.options.networks.find( - (x: string | { chainId: string }) => - typeof x === 'object' && x.chainId - ? normalizeChainId(x.chainId) === normalizedChainId - : normalizeChainId(x as string) === normalizedChainId, - ) - - if (!network) throw new Error(`Unsupported chainId: ${chainId}`) - - const account = await this.getAccount() - const provider = await this.getProvider() - - if (provider?.off) { - provider.off('accountsChanged', this.onAccountsChanged) - provider.off('chainChanged', this.onChainChanged) - provider.off('disconnect', this.onDisconnect) - } - - this.magic = new Magic(this.options.apiKey, { - ...this.options.magicSdkConfiguration, - network: network, - }) - - this.registerProviderEventListeners(this.magic.rpcProvider) - this.onChainChanged(chain.id) - this.onAccountsChanged([account]) - - return chain - } + return createConnector((config) => ({ + id, + name, + type, + getProvider, + connect: async function () { + await magic?.wallet.connectWithUI() + const provider = await getProvider() + const chainId = await this.getChainId() + provider && + registerProviderEventListeners( + provider, + (chain) => { + const chainId = normalizeChainId(chain) + config.emitter.emit('change', { chainId }) + }, + this.onDisconnect, + ) + const account = await getAccount() + return { + accounts: [account], + chainId, + } + }, + onAccountsChanged, + getAccounts: async () => { + const provider = await getProvider() + const accounts = (await provider?.request({ + method: 'eth_accounts', + })) as string[] + return accounts.map((x) => getAddress(x)) + }, + + 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 }) + }, + disconnect: async () => { + try { + await magic?.wallet.disconnect() + config.emitter.emit('disconnect') + } catch (error) { + console.error('Error disconnecting from Magic SDK:', error) + } + }, + isAuthorized: async () => { + try { + const walletInfo = await magic?.wallet.getInfo() + return !!walletInfo + } catch { + return false + } + }, + getChainId: async (): Promise => { + const provider = await getProvider() + if (provider) { + const chainId = await provider.request({ + method: 'eth_chainId', + params: [], + }) + 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') + }, + + onDisconnect: () => { + config.emitter.emit('disconnect') + }, + })) } - -export class MagicConnectConnector extends UniversalWalletConnector {} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7f625bb..dee6622 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,7 +2,7 @@ export function normalizeChainId(chainId: string | number | bigint) { if (typeof chainId === 'string') return Number.parseInt( chainId, - chainId.trim().substring(0, 2) === '0x' ? 16 : 10, + chainId.trim().startsWith('0x') ? 16 : 10, ) if (typeof chainId === 'bigint') return Number(chainId) return chainId From 6ec1f77155e523e1a33382ef64bb5498f0bf44e8 Mon Sep 17 00:00:00 2001 From: Adebesin Cell Date: Wed, 20 Mar 2024 08:28:31 +0100 Subject: [PATCH 4/6] Adds readonly to ``chains`` type on connector --- package.json | 6 +----- src/lib/connectors/magicConnector.ts | 2 +- src/lib/connectors/universalWalletConnector.ts | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 30ed117..f59746b 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,7 @@ "type": "module", "repository": "https://github.com/magiclabs/wagmi-magic-connector", "license": "MIT", - "keywords": [ - "wagmi", - "extension", - "magic" - ], + "keywords": ["wagmi", "extension", "magic"], "scripts": { "build": "tsc -p tsconfig.json && tsc-esm-fix --target='dist' --ext='.js'", "format": "rome format . --write", diff --git a/src/lib/connectors/magicConnector.ts b/src/lib/connectors/magicConnector.ts index a32e1e5..6fdbdb6 100644 --- a/src/lib/connectors/magicConnector.ts +++ b/src/lib/connectors/magicConnector.ts @@ -30,7 +30,7 @@ export interface MagicOptions { */ export interface MagicConnectorParams { - chains?: Chain[] + chains: readonly Chain[] options: MagicOptions } diff --git a/src/lib/connectors/universalWalletConnector.ts b/src/lib/connectors/universalWalletConnector.ts index 522fe43..f1a88d5 100644 --- a/src/lib/connectors/universalWalletConnector.ts +++ b/src/lib/connectors/universalWalletConnector.ts @@ -32,7 +32,7 @@ export interface UniversalWalletOptions { */ interface UniversalWalletConnectorParams { - chains?: Chain[] + chains: readonly Chain[] options: UniversalWalletOptions } From 11e850931eab3608b257f8b60c1ff8d581b5a26b Mon Sep 17 00:00:00 2001 From: PBillingsby Date: Thu, 21 Mar 2024 18:50:24 -0400 Subject: [PATCH 5/6] update README for v1 and v2 --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5d02641..e4ee6f2 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,27 @@ Special thanks to the [Everipedia](https://github.com/EveripediaNetwork) team fo - [**Example repositories:**](#example-repositories) # ⬇️ Install +Two versions of the `wagmi-magic-connector` are available, each designed to support different WAGMI versions. -```bash -npm install @magiclabs/wagmi-connector -``` +Note: **It is crucial not to mix up these versions to ensure compatibility and functionality.** + +**V1** +This version utilizes WAGMI version 1. To install, use the following command: +`npm install @magiclabs/wagmi-connector@1.1.5` or +`yarn install @magiclabs/wagmi-connector@1.1.5` -```bash -yarn add @magiclabs/wagmi-connector -``` +**V2 (Beta)** +This version utilizes and includes the latest WAGMI v2 features. + +To install, use the following command: + +`npm install @magiclabs/wagmi-connector` +or +`yarn install @magiclabs/wagmi-connector` + +We actively encourage the community to participate in testing the versions of `wagmi-magic-connector` and to report [any issues or suggestions](https://github.com/magiclabs/wagmi-magic-connector/issues/new/choose) for improvement. Your feedback is invaluable in helping us enhance the quality and stability of the connector. # 🔎 Package TL;DR From 6a45c295af32748466edc0cc67785f2c5da59544 Mon Sep 17 00:00:00 2001 From: James Pacheco Date: Fri, 22 Mar 2024 15:28:38 -0500 Subject: [PATCH 6/6] Bump version --- CHANGELOG.md | 6 ++++++ package.json | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3bb2cc..ac083b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @magiclabs/wagmi-magic-connector +## 2.0.0 + +### Major Changes + +- Update to support WAGMI v2 + ## 1.1.5 ### Patch Changes diff --git a/package.json b/package.json index f59746b..9b57303 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "@magiclabs/wagmi-connector", - "version": "1.1.5", + "version": "2.0.0", "description": "wagmi connector to connect with Magic SDK", "main": "dist/index.js", "types": "dist/index.d.ts", "type": "module", "repository": "https://github.com/magiclabs/wagmi-magic-connector", "license": "MIT", - "keywords": ["wagmi", "extension", "magic"], + "keywords": [ + "wagmi", + "extension", + "magic" + ], "scripts": { "build": "tsc -p tsconfig.json && tsc-esm-fix --target='dist' --ext='.js'", "format": "rome format . --write",