EveripediaNetwork/wagmi-magic-connector

Support for Magic Connect

thomashug opened this issue · 6 comments

  • I'm submitting a ...
    [ ] bug report
    [X] feature request
    [ ] question about the decisions made in the repository
    [ ] question about how to use this project

  • Summary

While still quite early, Magic Connect looks very interesting. It would be nice to have a mechanism allowing to set the connect extension instead of the OAuth extension (as described in the quick start of the @magic-ext/connect
package).

Was pretty easy to integrate, the disconnect function just doesnt seem to work, will let you know how it goes

Hey fren, would you like to provide any example? I was able to add the custom wallet to the wallet list, and it does show up. However, when click the "Magic" wallet, it doesn't pop any magic auth/magic connect

Is there any compatibility issue when changing OAuth extension into Connect extension? I think the benefit outweighs the downside of removing OAuth compatibility since it is what Magic recommends, I'll try to make a PR for demonstration.

Was pretty easy to integrate, the disconnect function just doesnt seem to work, will let you know how it goes

I manage to make it work by changing the method into

  async disconnect(): Promise<void> {
    const magic = this.getMagicSDK()
    await magic.connect.disconnect()
  }

Hey fren, would you like to provide any example? I was able to add the custom wallet to the wallet list, and it does show up. However, when click the "Magic" wallet, it doesn't pop any magic auth/magic connect

I found that changing the getProvider method works, but make sure to replace the other reference for the provider.

  async getProvider() {
    if (this.provider) {
      return this.provider
    }
    const magic = this.getMagicSDK()
    this.provider = new ethers.providers.Web3Provider(magic.rpcProvider as any)
    return this.provider
  }

@neethanwu

This is my code:
import { ConnectExtension } from '@magic-ext/connect';
import {
  InstanceWithExtensions,
  MagicSDKAdditionalConfiguration,
  SDKBase,
} from '@magic-sdk/provider';
import {
  Chain,
  Connector,
  normalizeChainId,
  UserRejectedRequestError,
} from '@wagmi/core';
import { ethers, Signer } from 'ethers';
import { getAddress } from 'ethers/lib/utils';
import { Magic } from 'magic-sdk';


const IS_SERVER = typeof window === 'undefined';

interface Options {
  apiKey: string;
  accentColor?: string;
  isDarkMode?: boolean;
  customLogo?: string;
  customHeaderText?: string;
  enableEmailLogin?: boolean;
  enableSMSlogin?: boolean;
  oauthOptions?: {
    callbackUrl?: string;
  };
  additionalMagicOptions?: MagicSDKAdditionalConfiguration<
    string,
    ConnectExtension[]
  >;
}

interface UserDetails {
  email?: string;
}

export class MagicConnector extends Connector {
  ready = !IS_SERVER;

  readonly id = 'magic';

  readonly name = 'Magic';

  provider: any;

  magicSDK?: InstanceWithExtensions<SDKBase, ConnectExtension[]>;

  isModalOpen = false;

  magicOptions: Options;

  oauthCallbackUrl?: string;

  constructor(config: { chains?: Chain[]; options: Options }) {
    super(config);
    this.magicOptions = config.options;
    this.oauthCallbackUrl = config.options.oauthOptions?.callbackUrl;
  }

  async connect() {
    try {
      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 there is a user logged in
      const isAuthenticated = await this.isAuthorized();

      // if there is a user logged in, return the user
      if (isAuthenticated) {
        return {
          provider,
          chain: {
            id: 0,
            unsupported: false,
          },
          account: await this.getAccount(),
        };
      }

      // open the modal and process the magic login steps
      if (!this.isModalOpen) {
        this.getMagicSDK();

        const signer = await this.getSigner();
        const account = await signer.getAddress();

        return {
          account,
          chain: {
            id: 0,
            unsupported: false,
          },
          provider,
        };
      }
      throw new UserRejectedRequestError('User rejected request');
    } catch (error) {
      throw new UserRejectedRequestError('Something went wrong');
    }
  }

  async getAccount(): Promise<string> {
    const provider = new ethers.providers.Web3Provider(
      await this.getProvider()
    );
    const signer = provider.getSigner();
    const account = await signer.getAddress();
    return account;
  }

  async getProvider() {
    if (this.provider) {
      return this.provider;
    }
    const magic = this.getMagicSDK();
    this.provider = magic.rpcProvider;
    return this.provider;
  }

  async getSigner(): Promise<Signer> {
    const provider = new ethers.providers.Web3Provider(
      await this.getProvider()
    );
    const signer = await provider.getSigner();
    return signer;
  }

  async isAuthorized() {
    try {
      const account = await this.getAccount();
      return !!account;
    } catch {
      return false;
    }
  }

  getMagicSDK(): InstanceWithExtensions<SDKBase, ConnectExtension[]> {

    const customNodeOptions = {
      rpcUrl: 'https://rpc-mainnet.maticvigil.com/',
      chainId: 137,
    }
    if (!this.magicSDK) {
      this.magicSDK = new Magic(this.magicOptions.apiKey, {
        extensions: [new ConnectExtension()],
        network: customNodeOptions
      });
      return this.magicSDK;
    }
    return this.magicSDK;
  }

  async getChainId(): Promise<number> {
    const networkOptions = this.magicOptions.additionalMagicOptions?.network;
    if (typeof networkOptions === 'object') {
      const chainID = networkOptions.chainId;
      if (chainID) {
        return normalizeChainId(chainID);
      }
    }
    throw new Error('Chain ID is not defined');
  }

  protected onAccountsChanged(accounts: string[]): void {
    if (accounts.length === 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 } });
  }

  protected onDisconnect(): void {
    this.emit('disconnect');
  }

  async disconnect(): Promise<void> {
    const magic = this.getMagicSDK();
    await magic.connect.disconnect().catch((e) => {
      console.log(e);
    });
  }
}

I don't actually use the code yet, as when autoconnect is on in Rainbowkit, the Magic popup automatically pops up.

Hey everyone ! Did a PR,#33, that tries to propose a version of the package with Magic connect integration and compatibility with the latest Rainbow Kit package. Please feel free to give out comments !

Added by #33. Now we can use MagicConnectConnector in v0.7.0