Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add web3modal extension #742

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4df5a5a
add web3modal extension
hcote Apr 22, 2024
0b7d9c7
remove unused vars
hcote Apr 22, 2024
a5864bb
rename
hcote Apr 22, 2024
55cc255
move override methods to thirdpartywallet module
hcote Apr 23, 2024
a746b06
remove provider dependency
hcote Apr 23, 2024
ffbb08d
typing updates
hcote Apr 24, 2024
a92e507
fix logout on disconnect
hcote Apr 24, 2024
a29c027
add returns to fns routing to tpw methods
hcote Apr 24, 2024
c3b302e
Merge branch 'master' into hcote-web3modal
hcote Apr 24, 2024
26604e3
move all logic to thirdpartywallet module
hcote Apr 25, 2024
42a4280
dont route nftCheckout to 3pw
hcote Apr 25, 2024
5a56d1a
update iframe.allow for google login
hcote Apr 30, 2024
3732179
fix capitalization
hcote May 4, 2024
56bb93c
add connectWIthUI options param
hcote May 7, 2024
f262429
make param optional
hcote May 12, 2024
b9b9caa
add event 1193 event listeners after connecting
hcote May 13, 2024
56fde71
update isConnected initialization
hcote May 14, 2024
3390b6f
remove old tests
hcote May 15, 2024
ad6d291
update tests
hcote May 18, 2024
dac3493
fix deepsource lint errors
hcote May 19, 2024
7953979
Merge branch 'master' into hcote-web3modal
hcote May 19, 2024
3a83159
update ls key names
hcote May 20, 2024
d328947
temp: remove reference to ThirdPartyWallets
hcote May 21, 2024
b44497f
update import
hcote May 21, 2024
53124ba
pluralize thirdpartywallets & update to web3modal canary version for …
hcote May 29, 2024
afa46ff
fix tests
hcote May 29, 2024
0386f43
implement nft.checkout with 3rd-party wallets (#744)
octave08 May 31, 2024
e8f9583
nft checkout update
hcote May 31, 2024
d862d23
add intermediary events for nft.checkout
hcote Jun 6, 2024
baa4f36
Merge branch 'master' into hcote-web3modal
hcote Jun 6, 2024
7ebccbf
Merge branch 'master' into hcote-web3modal
hcote Jun 7, 2024
218e104
yarn.lock
hcote Jun 7, 2024
c714eea
update web3modal package
hcote Jun 12, 2024
c05cd91
Merge branch 'master' into hcote-web3modal
hcote Jun 12, 2024
884053f
yarn.lock
hcote Jun 12, 2024
39d6e46
update web3modal package
hcote Jun 14, 2024
ff13a5f
yarn.lock
hcote Jun 14, 2024
17937db
update web3modal sdk
hcote Jun 20, 2024
3fe30b8
Merge branch 'master' into hcote-web3modal
hcote Jun 20, 2024
fb7b0c8
remove unsubscribeFromModalEvents
hcote Jun 21, 2024
911b23e
remove comments
hcote Jun 21, 2024
adddb32
add web3modal polyfill
hcote Jun 22, 2024
f0a0b3a
pass configOptions directly
hcote Jul 2, 2024
bca2cfc
Merge branch 'master' into hcote-web3modal
hcote Jul 2, 2024
9499a53
remove polyfills
hcote Jul 2, 2024
3194efc
update eslint config
hcote Jul 2, 2024
95261f5
update jest coverage
hcote Jul 2, 2024
2b79c30
Merge branch 'master' into hcote-web3modal
hcote Jul 13, 2024
e3ecd35
yarn
hcote Jul 13, 2024
561e4f0
yarn.lock
hcote Jul 13, 2024
8b1ae7f
Merge branch 'master' into hcote-web3modal
hcote Jul 22, 2024
f68fbea
update localstoragekeys to enum
hcote Jul 23, 2024
6cc7d86
fix: types does not have export yet
hcote Jul 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
pluralize thirdpartywallets & update to web3modal canary version for …
…status
  • Loading branch information
hcote committed May 29, 2024
commit 53124ba6b4af665bd7cad0e1798ed2cac5d48a0c
5 changes: 3 additions & 2 deletions packages/@magic-ext/web3modal-ethers5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
]
},
"devDependencies": {
"@magic-sdk/commons": "^24.0.2"
"@magic-sdk/commons": "^24.0.2",
"@magic-sdk/types": "24.0.2-canary.742.9175925480.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this canary version needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because @magic-sdk/types is a separate package, which is getting updated types that this extension is using, we need @magic-sdk/types to be published. So I think yes we need to reference a canary version at least at the first publish, then we can update the extension to use a non-canary

},
"dependencies": {
"@web3modal/ethers5": "^4.1.11",
"@web3modal/ethers5": "4.1.12-ethers-status.0",
"ethers": "5.7.2"
}
}
52 changes: 33 additions & 19 deletions packages/@magic-ext/web3modal-ethers5/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,30 @@ export class Web3ModalExtension extends Extension.Internal<'web3modal'> {
ethersConfig: defaultConfig({ metadata: configOptions }),
});

// Set delay for web3modal to register if user is connected
setTimeout(() => {
if (!this.modal.getIsConnected()) return;
this.setIsConnected();
this.setEip1193EventListeners();
}, 50);
const unsubscribeFromProviderEvents = this.modal.subscribeProvider(({ status }) => {
if (status === 'connected') {
unsubscribeFromProviderEvents();
this.setIsConnected();
this.setEip1193EventListeners();
}
if (status === 'disconnected') {
unsubscribeFromProviderEvents();
}
});
}

public setIsConnected() {
localStorage.setItem('magic_3pw_provider', 'web3modal');
hcote marked this conversation as resolved.
Show resolved Hide resolved
localStorage.setItem('magic_3pw_address', this.modal.getAddress() as string);
localStorage.setItem('magic_3pw_chainId', (this.modal.getChainId() as number).toString());
this.sdk.thirdPartyWallet.isConnected = true;
this.sdk.thirdPartyWallets.isConnected = true;
}

public initialize() {
this.sdk.thirdPartyWallet.enabledWallets.web3modal = true;
this.sdk.thirdPartyWallet.isConnected = Boolean(localStorage.getItem('magic_3pw_address'));
this.sdk.thirdPartyWallet.eventListeners.push({
event: 'web3modal_selected' as ThirdPartyWalletEvents,
this.sdk.thirdPartyWallets.enabledWallets.web3modal = true;
this.sdk.thirdPartyWallets.isConnected = Boolean(localStorage.getItem('magic_3pw_address'));
this.sdk.thirdPartyWallets.eventListeners.push({
event: ThirdPartyWalletEvents.Web3ModalSelected,
callback: async (payloadId) => {
await this.connectToWeb3modal(payloadId);
},
Expand All @@ -52,7 +56,7 @@ export class Web3ModalExtension extends Extension.Internal<'web3modal'> {
this.modal.subscribeProvider(({ address, chainId }) => {
// If user disconnected all accounts from wallet
if (!address && localStorage.getItem('magic_3pw_address')) {
this.sdk.thirdPartyWallet.resetState();
this.sdk.thirdPartyWallets.resetThirdPartyWalletState();
return this.sdk.rpcProvider.emit('accountsChanged', []);
}
if (address && address !== localStorage.getItem('magic_3pw_address')) {
Expand All @@ -67,23 +71,33 @@ export class Web3ModalExtension extends Extension.Internal<'web3modal'> {
});
}

private handleUserConnected(payloadId: string, address: string = this.modal.getAddress() as string) {
this.setIsConnected();
this.createIntermediaryEvent(ThirdPartyWalletEvents.WalletConnected, payloadId)(address);
this.setEip1193EventListeners();
}

private connectToWeb3modal(payloadId: string) {
const { modal } = this;

const promiEvent = this.utils.createPromiEvent<string[]>(() => {
// Listen for wallet connected
const promiEvent = this.utils.createPromiEvent<string[]>(async () => {
// If user is already connected, emit event and return
if (this.modal.getIsConnected()) {
this.handleUserConnected(payloadId);
return;
}

// Listen for wallet connected event
const unsubscribeFromProviderEvents = modal.subscribeProvider(({ address, error }) => {
// User rejected connection request
if (error) {
unsubscribeFromProviderEvents();
this.createIntermediaryEvent('wallet_rejected', payloadId)();
this.createIntermediaryEvent(ThirdPartyWalletEvents.WalletRejected, payloadId)();
}
// If user connected wallet, keep listeners active
if (address) {
this.setIsConnected();
this.createIntermediaryEvent('wallet_connected', payloadId)(address);
this.handleUserConnected(payloadId);
unsubscribeFromProviderEvents();
this.setEip1193EventListeners();
}
});

Expand All @@ -92,7 +106,7 @@ export class Web3ModalExtension extends Extension.Internal<'web3modal'> {
if (event.data.event === 'MODAL_CLOSE') {
unsubscribeFromModalEvents();
unsubscribeFromProviderEvents();
this.createIntermediaryEvent('wallet_rejected', payloadId)();
this.createIntermediaryEvent(ThirdPartyWalletEvents.WalletRejected, payloadId)();
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const web3modalParams = {
test('initialize updates `enabledWallets`, `isConnected`, and `eventListeners`', () => {
const magic = createMagicSDKWithExtension({}, [new Web3ModalExtension(web3modalParams)]);
magic.web3modal.initialize();
expect(magic.thirdPartyWallet.enabledWallets.web3modal).toBeTruthy();
expect(magic.thirdPartyWallet.isConnected).toBeFalsy();
expect(magic.thirdPartyWallet.eventListeners.length).toEqual(1);
expect(magic.thirdPartyWallets.enabledWallets.web3modal).toBeTruthy();
expect(magic.thirdPartyWallets.isConnected).toBeFalsy();
expect(magic.thirdPartyWallets.eventListeners.length).toEqual(1);
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ test('setIsConnected sets localStorage values', () => {
expect(localStorage.getItem('magic_3pw_provider')).toEqual('web3modal');
expect(localStorage.getItem('magic_3pw_address')).toEqual('0x123');
expect(localStorage.getItem('magic_3pw_chainId')).toEqual('1');
expect(magic.thirdPartyWallet.isConnected).toBeTruthy();
expect(magic.thirdPartyWallets.isConnected).toBeTruthy();
});
6 changes: 3 additions & 3 deletions packages/@magic-sdk/provider/src/core/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { AuthModule } from '../modules/auth';
import { UserModule } from '../modules/user';
import { WalletModule } from '../modules/wallet';
import { ThirdPartyWalletModule } from '../modules/third-party-wallet';
import { ThirdPartyWalletsModule } from '../modules/third-party-wallets';
import { RPCProviderModule } from '../modules/rpc-provider';
import { ViewController } from './view-controller';
import { createURL } from '../util/url';
Expand Down Expand Up @@ -156,7 +156,7 @@ export class SDKBase {
/**
* Contains internal methods for third-party wallets.
*/
public thirdPartyWallet: ThirdPartyWalletModule;
public thirdPartyWallets: ThirdPartyWalletsModule;

/**
* Contains a Web3-compliant provider. Pass this module to your Web3/Ethers
Expand Down Expand Up @@ -184,7 +184,7 @@ export class SDKBase {
this.user = new UserModule(this);
this.wallet = new WalletModule(this);
this.nft = new NFTModule(this);
this.thirdPartyWallet = new ThirdPartyWalletModule(this);
this.thirdPartyWallets = new ThirdPartyWalletsModule(this);
this.rpcProvider = new RPCProviderModule(this) as any;

// Prepare extensions
Expand Down
4 changes: 2 additions & 2 deletions packages/@magic-sdk/provider/src/modules/base-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export class BaseModule {
*/
protected request<ResultType = any, Events extends EventsDefinition = void>(payload: Partial<JsonRpcRequestPayload>) {
if (
this.sdk.thirdPartyWallet.isConnected &&
this.sdk.thirdPartyWallets.isConnected &&
hcote marked this conversation as resolved.
Show resolved Hide resolved
payload.method !== MagicPayloadMethod.IntermediaryEvent &&
payload.method !== MagicPayloadMethod.NFTCheckout &&
payload.method !== MagicPayloadMethod.Login
hcote marked this conversation as resolved.
Show resolved Hide resolved
) {
const promiEvent = createPromiEvent<ResultType, Events>((resolve, reject) => {
this.sdk.thirdPartyWallet.requestOverride(payload).then(resolve).catch(reject);
this.sdk.thirdPartyWallets.requestOverride(payload).then(resolve).catch(reject);
});
return promiEvent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { JsonRpcRequestPayload, MagicPayloadMethod, MagicUserMetadata, ThirdPart
import { BaseModule } from './base-module';
import { PromiEvent, createPromiEvent } from '../util';

export class ThirdPartyWalletModule extends BaseModule {
export class ThirdPartyWalletsModule extends BaseModule {
public eventListeners: { event: ThirdPartyWalletEvents; callback: (payloadId: string) => Promise<void> }[] = [];
public enabledWallets: Record<string, boolean> = {};
public isConnected = false;

public resetState() {
public resetThirdPartyWalletState() {
localStorage.removeItem('magic_3pw_provider');
localStorage.removeItem('magic_3pw_address');
localStorage.removeItem('magic_3pw_chainId');
Expand All @@ -17,7 +17,7 @@ export class ThirdPartyWalletModule extends BaseModule {
public requestOverride(payload: Partial<JsonRpcRequestPayload>) {
// Handle method overrides if login/getInfo/isLoggedIn/logout
if (payload.method === MagicPayloadMethod.Login) {
this.resetState();
this.resetThirdPartyWalletState();
return super.request(payload);
}
if (payload.method === MagicPayloadMethod.GetInfo) {
Expand All @@ -35,7 +35,7 @@ export class ThirdPartyWalletModule extends BaseModule {
return this.web3modalRequest(payload);
// Fallback to default request
default:
this.resetState();
this.resetThirdPartyWalletState();
return super.request(payload);
}
}
Expand All @@ -47,7 +47,7 @@ export class ThirdPartyWalletModule extends BaseModule {
case 'web3modal':
return this.web3modalIsLoggedIn();
default:
this.resetState();
this.resetThirdPartyWalletState();
return super.request(payload);
}
}
Expand All @@ -57,14 +57,14 @@ export class ThirdPartyWalletModule extends BaseModule {
case 'web3modal':
return this.web3modalGetInfo();
default:
this.resetState();
this.resetThirdPartyWalletState();
return super.request(payload);
}
}

private logout(payload: Partial<JsonRpcRequestPayload>): PromiEvent<boolean> {
const provider = localStorage.getItem('magic_3pw_provider');
this.resetState();
this.resetThirdPartyWalletState();
switch (provider) {
case 'web3modal': {
return this.web3modalLogout();
Expand All @@ -85,30 +85,75 @@ export class ThirdPartyWalletModule extends BaseModule {

private web3modalIsLoggedIn() {
return createPromiEvent<boolean>((resolve) => {
// Required delay to allow web3modal to register connection
setTimeout(() => {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const walletStatus = this.sdk.web3modal.modal.getStatus();
if (walletStatus === 'connected') {
resolve(true);
}
if (walletStatus === 'disconnected') {
this.resetThirdPartyWalletState();
resolve(false);
}
if (walletStatus === 'reconnecting') {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const isLoggedIn: boolean = this.sdk.web3modal.modal.getIsConnected();
resolve(isLoggedIn);
}, 50);
const unsubscribeFromProviderEvents = this.sdk.web3modal.modal.subscribeProvider(({ status }) => {
if (status === 'connected') {
unsubscribeFromProviderEvents();
resolve(true);
}
if (status === 'disconnected') {
unsubscribeFromProviderEvents();
this.resetThirdPartyWalletState();
resolve(false);
}
});
}
});
}

private formatWeb3modalGetInfoResponse() {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const walletType = this.sdk.web3modal.modal.getWalletInfo()?.name;
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const userAddress = this.sdk.web3modal.modal.getAddress();
return {
publicAddress: userAddress as string,
email: null,
issuer: `$did:ethr:${userAddress}`,
phoneNumber: null,
isMfaEnabled: false,
recoveryFactors: [] as [],
walletType: walletType || 'web3modal',
};
}

private web3modalGetInfo() {
return createPromiEvent<MagicUserMetadata>((resolve) => {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const walletType = this.sdk.web3modal.modal.getWalletInfo()?.name;
return createPromiEvent<MagicUserMetadata>((resolve, reject) => {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const userAddress = this.sdk.web3modal.modal.getAddress();
resolve({
publicAddress: userAddress,
email: null,
issuer: `$did:ethr:${userAddress}`,
phoneNumber: null,
isMfaEnabled: false,
recoveryFactors: [],
walletType,
});
const walletStatus = this.sdk.web3modal.modal.getStatus();
if (walletStatus === 'connected') {
resolve(this.formatWeb3modalGetInfoResponse());
}

if (walletStatus === 'disconnected') {
this.resetThirdPartyWalletState();
reject('Magic RPC Error: [-32603] Internal error: User denied account access.');
}

if (walletStatus === 'reconnecting') {
// @ts-expect-error Property 'web3modal' does not exist on type 'SDKBase'.
const unsubscribeFromProviderEvents = this.sdk.web3modal.modal.subscribeProvider(({ status }) => {
if (status === 'connected') {
unsubscribeFromProviderEvents();
resolve(this.formatWeb3modalGetInfoResponse());
}
if (status === 'disconnected') {
unsubscribeFromProviderEvents();
this.resetThirdPartyWalletState();
reject('Magic RPC Error: [-32603] Internal error: User denied account access.');
}
});
}
});
}

Expand Down
4 changes: 2 additions & 2 deletions packages/@magic-sdk/provider/src/modules/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ export class WalletModule extends BaseModule {
try {
const loginRequestPayload = createJsonRpcRequestPayload(MagicPayloadMethod.Login, [
{
enabledWallets: this.sdk.thirdPartyWallet.enabledWallets,
enabledWallets: this.sdk.thirdPartyWallets.enabledWallets,
...options,
},
]);

const loginRequest = this.request<string[], ConnectWithUiEvents>(loginRequestPayload);

this.sdk.thirdPartyWallet.eventListeners.forEach(({ event, callback }) => {
this.sdk.thirdPartyWallets.eventListeners.forEach(({ event, callback }) => {
loginRequest.on(event, () => callback(loginRequestPayload.id as string));
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,9 @@ test('Calls third party wallet request override if third party wallet is connect
mockLocalStorage();
const response = new JsonRpcResponse(requestPayload).applyResult('hello world');
const { baseModule } = createBaseModule(jest.fn().mockImplementation(() => Promise.resolve(response)));
baseModule.sdk.thirdPartyWallet.isConnected = true;
baseModule.sdk.thirdPartyWallets.isConnected = true;
const spy = jest
.spyOn(baseModule.sdk.thirdPartyWallet, 'requestOverride')
.spyOn(baseModule.sdk.thirdPartyWallets, 'requestOverride')
.mockImplementation(() => Promise.resolve('hello world'));
const result = await baseModule.request(requestPayload);
expect(result).toBe('hello world');
Expand All @@ -328,9 +328,9 @@ test('Does not call thirdPartyWallet requestOverride if method is magic_intermed
mockLocalStorage();
const response = new JsonRpcResponse(requestPayload).applyResult('magic_intermediary_event');
const { baseModule } = createBaseModule(jest.fn().mockImplementation(() => Promise.resolve(response)));
baseModule.sdk.thirdPartyWallet.isConnected = true;
baseModule.sdk.thirdPartyWallets.isConnected = true;
const spy = jest
.spyOn(baseModule.sdk.thirdPartyWallet, 'requestOverride')
.spyOn(baseModule.sdk.thirdPartyWallets, 'requestOverride')
.mockImplementation(() => Promise.resolve({}));
expect(spy).toBeCalledTimes(0);
});
Expand All @@ -339,9 +339,9 @@ test('Does not call thirdPartyWallet requestOverride if method is magic_nft_chec
mockLocalStorage();
const response = new JsonRpcResponse(requestPayload).applyResult('magic_nft_checkout');
const { baseModule } = createBaseModule(jest.fn().mockImplementation(() => Promise.resolve(response)));
baseModule.sdk.thirdPartyWallet.isConnected = true;
baseModule.sdk.thirdPartyWallets.isConnected = true;
const spy = jest
.spyOn(baseModule.sdk.thirdPartyWallet, 'requestOverride')
.spyOn(baseModule.sdk.thirdPartyWallets, 'requestOverride')
.mockImplementation(() => Promise.resolve({}));
expect(spy).toBeCalledTimes(0);
});
Expand All @@ -350,9 +350,9 @@ test('Does not call thirdPartyWallet requestOverride if method is mc_login', ()
mockLocalStorage();
const response = new JsonRpcResponse(requestPayload).applyResult('mc_login');
const { baseModule } = createBaseModule(jest.fn().mockImplementation(() => Promise.resolve(response)));
baseModule.sdk.thirdPartyWallet.isConnected = true;
baseModule.sdk.thirdPartyWallets.isConnected = true;
const spy = jest
.spyOn(baseModule.sdk.thirdPartyWallet, 'requestOverride')
.spyOn(baseModule.sdk.thirdPartyWallets, 'requestOverride')
.mockImplementation(() => Promise.resolve({}));
expect(spy).toBeCalledTimes(0);
});
Loading
Loading