Skip to content

Commit

Permalink
[Wallets] - magic wallet connector (#457)
Browse files Browse the repository at this point in the history
Co-authored-by: Joaquim Verges <joaquim.verges@gmail.com>
  • Loading branch information
jnsdls and joaquim-verges committed Dec 13, 2022
1 parent 4cdd0bd commit ccb4ce8
Show file tree
Hide file tree
Showing 19 changed files with 1,273 additions and 218 deletions.
6 changes: 6 additions & 0 deletions .changeset/few-ears-prove.md
@@ -0,0 +1,6 @@
---
"@thirdweb-dev/unity-js-bridge": patch
"@thirdweb-dev/wallets": patch
---

Support for new magic link connector
2 changes: 1 addition & 1 deletion packages/react/package.json
Expand Up @@ -109,7 +109,7 @@
"color": "^4.2.3",
"copy-to-clipboard": "^3.3.2",
"detect-browser": "^5.3.0",
"magic-sdk": "^10.0.0",
"magic-sdk": "^10.1.0",
"mime": "^3.0.0",
"react-cool-dimensions": "^2.0.7",
"tiny-invariant": "^1.2.0",
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions packages/unity-js-bridge/src/thirdweb-bridge.ts
Expand Up @@ -7,6 +7,7 @@ import {
MetaMask,
WalletConnect,
InjectedWallet,
MagicAuthWallet,
} from "@thirdweb-dev/wallets";
import type { AbstractWallet } from "@thirdweb-dev/wallets/dist/declarations/src/wallets/base";
import { BigNumber } from "ethers";
Expand Down Expand Up @@ -43,6 +44,7 @@ const WALLETS = [
InjectedWallet,
WalletConnect,
CoinbaseWallet,
MagicAuthWallet,
] as const;

type PossibleWallet = typeof WALLETS[number]["id"];
Expand Down Expand Up @@ -85,18 +87,19 @@ class ThirdwebBridge implements TWBridge {
console.debug("thirdwebSDK initialization:", chain, options);
const sdkOptions = JSON.parse(options);
const storage =
sdkOptions && sdkOptions.ipfsGatewayUrl
sdkOptions?.storage && sdkOptions?.storage?.ipfsGatewayUrl
? new ThirdwebStorage({
gatewayUrls: {
"ipfs://": [sdkOptions.ipfsGatewayUrl],
"ipfs://": [sdkOptions.storage.ipfsGatewayUrl],
},
})
: new ThirdwebStorage();
const rpcUrl = chain.startsWith("http") ? chain : getRpcUrl(chain, API_KEY);
this.activeSDK = new ThirdwebSDK(rpcUrl, sdkOptions, storage);
for (let wallet of WALLETS) {
const walletInstance = new wallet({
appName: sdkOptions.appName || "thirdweb powered dApp",
appName: sdkOptions.wallet?.appName || "thirdweb powered dApp",
...sdkOptions.wallet?.extras,
});
walletInstance.on("connect", async () =>
this.updateSDKSigner(await walletInstance.getSigner()),
Expand Down
4 changes: 4 additions & 0 deletions packages/wallets/connectors/magic/package.json
@@ -0,0 +1,4 @@
{
"main": "dist/thirdweb-dev-wallets-connectors-magic.cjs.js",
"module": "dist/thirdweb-dev-wallets-connectors-magic.esm.js"
}
18 changes: 15 additions & 3 deletions packages/wallets/package.json
Expand Up @@ -20,6 +20,14 @@
"module": "./wallets/metamask/dist/thirdweb-dev-wallets-wallets-metamask.esm.js",
"default": "./wallets/metamask/dist/thirdweb-dev-wallets-wallets-metamask.cjs.js"
},
"./wallets/magic-auth": {
"module": "./wallets/magic-auth/dist/thirdweb-dev-wallets-wallets-magic-auth.esm.js",
"default": "./wallets/magic-auth/dist/thirdweb-dev-wallets-wallets-magic-auth.cjs.js"
},
"./connectors/magic": {
"module": "./connectors/magic/dist/thirdweb-dev-wallets-connectors-magic.esm.js",
"default": "./connectors/magic/dist/thirdweb-dev-wallets-connectors-magic.cjs.js"
},
"./wallets/wallet-connect": {
"module": "./wallets/wallet-connect/dist/thirdweb-dev-wallets-wallets-wallet-connect.esm.js",
"default": "./wallets/wallet-connect/dist/thirdweb-dev-wallets-wallets-wallet-connect.cjs.js"
Expand Down Expand Up @@ -60,7 +68,7 @@
"preconstruct": {
"entrypoints": [
"index.ts",
"connectors/**",
"connectors/*/index.ts",
"wallets/**"
],
"___experimentalFlags_WILL_CHANGE_IN_PATCH": {
Expand All @@ -71,13 +79,17 @@
"sideEffects": false,
"dependencies": {
"@coinbase/wallet-sdk": "^3.6.0",
"@magic-ext/connect": "^3.1.0",
"@magic-ext/oauth": "^4.1.0",
"@magic-sdk/provider": "^10.1.0",
"@wagmi/chains": "^0.1.0",
"@wagmi/core": "^0.8.0",
"@wagmi/core": "^0.8.2",
"@walletconnect/ethereum-provider": "^1.7.8",
"abitype": "^0.2.5",
"ethers": "^5.7.2",
"eventemitter3": "^5.0.0",
"localforage": "^1.10.0"
"localforage": "^1.10.0",
"magic-sdk": "^10.1.0"
},
"devDependencies": {
"@preconstruct/cli": "^2.2.1",
Expand Down
181 changes: 181 additions & 0 deletions packages/wallets/src/connectors/magic/connectors/magicAuthConnector.ts
@@ -0,0 +1,181 @@
import { MagicConnector, MagicOptions } from "./magicConnector";
import { OAuthExtension, OAuthProvider } from "@magic-ext/oauth";
import {
InstanceWithExtensions,
MagicSDKAdditionalConfiguration,
SDKBase,
} from "@magic-sdk/provider";
import {
Chain,
ChainNotConfiguredError,
normalizeChainId,
UserRejectedRequestError,
} from "@wagmi/core";
import { getAddress } from "ethers/lib/utils.js";
import { Magic } from "magic-sdk";

export interface MagicAuthOptions extends MagicOptions {
enableEmailLogin?: boolean;
enableSMSLogin?: boolean;
oauthOptions?: {
providers: OAuthProvider[];
callbackUrl?: string;
};
magicSdkConfiguration?: MagicSDKAdditionalConfiguration<
string,
OAuthExtension[]
>;
}

export class MagicAuthConnector extends MagicConnector {
magicSDK?: InstanceWithExtensions<SDKBase, OAuthExtension[]>;

magicSdkConfiguration: MagicSDKAdditionalConfiguration<
string,
OAuthExtension[]
>;

enableSMSLogin: boolean;

enableEmailLogin: boolean;

oauthProviders: OAuthProvider[];

oauthCallbackUrl?: string;

constructor(config: { chains?: Chain[]; options: MagicAuthOptions }) {
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 || false;
}

async connect({ chainId }: { chainId?: number }) {
// for a specific chainId we will overwrite the magicSDKConfiguration
if (chainId) {
const chain = this.chains.find((c) => c.id === chainId);
if (chain) {
this.magicSdkConfiguration = {
...this.magicSdkConfiguration,
network: {
chainId: chain.id,
rpcUrl: chain.rpcUrls.default.http[0],
},
};
}
}
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();

// Check if we have a chainId, in case of error just assign 0 for legacy
let chainId_: number;
try {
chainId_ = await this.getChainId();
} catch (e) {
chainId_ = 0;
}

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

// open the modal and process the magic login steps
if (!this.isModalOpen) {
const output = await this.getUserDetailsByForm(
this.enableSMSLogin,
this.enableEmailLogin,
this.oauthProviders,
);
const magic = this.getMagicSDK();

// LOGIN WITH MAGIC LINK WITH OAUTH PROVIDER
if (output.oauthProvider) {
await magic.oauth.loginWithRedirect({
provider: output.oauthProvider,
redirectURI: this.oauthCallbackUrl || window.location.href,
});
}

// LOGIN WITH MAGIC LINK WITH EMAIL
if (output.email) {
await magic.auth.loginWithMagicLink({
email: output.email,
});
}

// LOGIN WITH MAGIC LINK WITH PHONE NUMBER
if (output.phoneNumber) {
await magic.auth.loginWithSMS({
phoneNumber: output.phoneNumber,
});
}

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

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

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

getMagicSDK(): InstanceWithExtensions<SDKBase, OAuthExtension[]> {
if (!this.magicSDK) {
this.magicSDK = new Magic(this.magicOptions.apiKey, {
...this.magicSdkConfiguration,
extensions: [new OAuthExtension()],
});
return this.magicSDK;
}
return this.magicSDK;
}

async switchChain(chainId: number): Promise<Chain> {
// check if the chain is supported
const chain = this.chains.find((c) => c.id === chainId);
if (!chain) {
throw new ChainNotConfiguredError({ chainId, connectorId: this.id });
}
await this.connect({ chainId });
return chain;
}
}

0 comments on commit ccb4ce8

Please sign in to comment.