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

prompt switch network only before transaction signing #518

Merged
merged 2 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 52 additions & 3 deletions src/antelope/mocks/AccountStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
// Mocking AccountStore -----------------------------------
// useAccountStore().getAccount(this.label).account as addressString;
import { EVMAuthenticator } from 'src/antelope/wallets';
import { addressString } from 'src/antelope/types';
import { AntelopeError, addressString } from 'src/antelope/types';
import { CURRENT_CONTEXT, EVMChainSettings, createTraceFunction, useChainStore } from 'src/antelope/mocks';
import { BigNumber } from 'ethers'; 'src/antelope/mocks/FeedbackStore';
import { getAntelope } from 'src/antelope/mocks/AntelopeConfig';
import { useFeedbackStore } from 'src/antelope/mocks';
import { EvmABI, EvmFunctionParam, Label, TransactionResponse } from 'src/antelope/types';
import { subscribeForTransactionReceipt } from 'src/antelope/wallets/utils/trx-utils';

export const errorToString = (error: unknown) =>
getAntelope().config.errorToStringHandler(error);

export interface AccountModel {
label: typeof CURRENT_CONTEXT;
isNative: boolean;
Expand All @@ -32,6 +35,7 @@ let currentAccount = null as addressString | null;
interface LoginEVMActionData {
authenticator: EVMAuthenticator
network: string,
autoLogAccount?: string,
}

class AccountStore {
Expand All @@ -51,9 +55,10 @@ class AccountStore {
} as AccountModel;
}

async loginEVM({ authenticator, network }: LoginEVMActionData, trackAnalyticsEvents: boolean): Promise<boolean> {
async loginEVM({ authenticator, network, autoLogAccount }: LoginEVMActionData, trackAnalyticsEvents: boolean): Promise<boolean> {
currentAuthenticator = authenticator;
currentAccount = await authenticator.login(network, trackAnalyticsEvents);
currentAccount = autoLogAccount ? await authenticator.autoLogin(network, autoLogAccount, trackAnalyticsEvents) : await authenticator.login(network, trackAnalyticsEvents);

const account = useAccountStore().getAccount(authenticator.label);
getAntelope().events.onLoggedIn.next(account);
return true;
Expand Down Expand Up @@ -98,6 +103,10 @@ class AccountStore {
const funcname = 'signCustomTransaction';
this.trace(funcname, label, contract, abi, parameters, value?.toString());

if (! await this.assertNetworkConnection(label)) {
throw new AntelopeError('antelope.evm.error_switch_chain_rejected');
}

try {
useFeedbackStore().setLoading(funcname);
const account = this.loggedAccount as EvmAccountModel;
Expand Down Expand Up @@ -130,6 +139,46 @@ class AccountStore {
}
}

async isConnectedToCorrectNetwork(label: string): Promise<boolean> {
this.trace('isConnectedToCorrectNetwork', label);
try {
useFeedbackStore().setLoading('account.isConnectedToCorrectNetwork');
const authenticator = useAccountStore().getAccount(label)?.authenticator as EVMAuthenticator;
return authenticator.isConnectedToCorrectChain();
} catch (error) {
console.error('Error: ', errorToString(error));
return Promise.resolve(false);
} finally {
useFeedbackStore().unsetLoading('account.isConnectedToCorrectNetwork');
}
}

async assertNetworkConnection(label: string): Promise<boolean> {
if (!await useAccountStore().isConnectedToCorrectNetwork(label)) {
// eslint-disable-next-line no-async-promise-executor
return new Promise<boolean>(async (resolve) => {
const ant = getAntelope();
const authenticator = useAccountStore().loggedAccount.authenticator as EVMAuthenticator;
try {
await authenticator.ensureCorrectChain();
if (!await useAccountStore().isConnectedToCorrectNetwork(label)) {
resolve(false);
} else {
resolve(true);
}
} catch (error) {
const message = (error as Error).message;
if (message === 'antelope.evm.error_switch_chain_rejected') {
ant.config.notifyNeutralMessageHandler(message);
}
resolve(false);
}
});
} else {
return true;
}
}

}

const accountStore = new AccountStore();
Expand Down
33 changes: 0 additions & 33 deletions src/antelope/mocks/EVMStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { ethers } from 'ethers';
import { EVMAuthenticator, InjectedProviderAuth } from 'src/antelope/wallets';
import { createTraceFunction } from 'src/antelope/mocks/FeedbackStore';
import { getAntelope } from 'src/antelope/mocks/AntelopeConfig';
import { EVMChainSettings, useChainStore, useFeedbackStore, useAccountStore } from 'src/antelope/mocks';
import { AntelopeError, EthereumProvider, ExceptionError } from 'src/antelope/types';
import { RpcEndpoint } from 'universal-authenticator-library';
Expand All @@ -21,7 +20,6 @@ class EVMStore {
this.trace('initInjectedProvider', authenticator.getName(), [authenticator.getProvider()]);
const provider: EthereumProvider | null = authenticator.getProvider();
const evm = useEVMStore();
const ant = getAntelope();

if (provider && !provider.__initialized) {
this.trace('initInjectedProvider', authenticator.getName(), 'initializing provider');
Expand All @@ -36,37 +34,6 @@ class EVMStore {
}
}

// this handler activates only when the user comes back from switching to the wrong network on the wallet
// It checks if the user is on the correct network and if not, it shows a notification with a button to switch
const checkNetworkHandler = async () => {
window.removeEventListener('focus', checkNetworkHandler);
if (useAccountStore().loggedAccount) {
const authenticator = useAccountStore().loggedAccount.authenticator as EVMAuthenticator;
if (await authenticator.isConnectedToCorrectChain()) {
evm.trace('checkNetworkHandler', 'correct network');
} else {
const networkName = useChainStore().loggedChain.settings.getDisplay();
const errorMessage = ant.config.localizationHandler('evm_wallet.incorrect_network', { networkName });
const label = ant.config.localizationHandler('evm_wallet.switch');
ant.config.notifyFailureWithAction(errorMessage, {
label,
handler: () => {
authenticator.ensureCorrectChain();
},
});
}
}
};

provider.on('chainChanged', (value) => {
const newNetwork = value as string;
evm.trace('provider.chainChanged', newNetwork);
window.removeEventListener('focus', checkNetworkHandler);
if (useAccountStore().loggedAccount) {
window.addEventListener('focus', checkNetworkHandler);
}
});

provider.on('accountsChanged', async (value) => {
const accounts = value as string[];
const network = useChainStore().currentChain.settings.getNetwork();
Expand Down
18 changes: 18 additions & 0 deletions src/antelope/wallets/authenticators/EVMAuthenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ export abstract class EVMAuthenticator {
}
}

async autoLogin(network: string, account: string, trackAnalyticsEvents?: boolean): Promise<addressString> {
this.trace('autoLogin', network, account);
this.trace('AutoLogin analytics enabled =', trackAnalyticsEvents);

const chain = useChainStore();
try {
chain.setChain(CURRENT_CONTEXT, network);
return account as addressString;
} catch (error) {
if ((error as unknown as ExceptionError).code === 4001) {
throw new AntelopeError('antelope.evm.error_connect_rejected');
} else {
console.error('Error:', error);
throw new AntelopeError('antelope.evm.error_login');
}
}
}

async ensureCorrectChain(): Promise<ethers.providers.Web3Provider> {
this.trace('ensureCorrectChain');
if (usePlatformStore().isMobile) {
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icon--warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/boot/errorHandling.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class NotificationAction {
this.label = payload.label;
this.class = payload.class;
this.handler = payload.handler;
this.onDismiss = payload.onDismiss;
this.iconRight = payload.iconRight;
this.color = payload.color;
}
Expand All @@ -68,6 +69,7 @@ const crossIcon = require('src/assets/icon--cross.svg');
const infoIcon = require('src/assets/icon--info.svg');
const checkIcon = require('src/assets/icon--check.svg');
const discoIcon = require('src/assets/icon--disconnected.svg');
const warningIcon = require('src/assets/icon--warning.svg');

const html = `
<div class="c-notify__container c-notify__container--{type} c-notify__container--{random}">
Expand Down Expand Up @@ -142,11 +144,14 @@ const notifyMessage = function(type, icon, title, message, payload, remember = '
class: 'c-notify__action-btn c-notify__action-btn--hide',
};

let onDismiss = null;

// adding buttons
if (typeof payload === 'string' && type === 'success') {
actions.push(link_btn);
} else if (typeof payload === 'object' && payload instanceof NotificationAction) {
actions.push(action_btn);
onDismiss = payload.onDismiss;
} else if (typeof payload === 'object' && type === 'error') {
actions.push(details_btn);
} else {
Expand Down Expand Up @@ -206,6 +211,7 @@ const notifyMessage = function(type, icon, title, message, payload, remember = '
html: true,
classes: 'c-notify',
actions,
onDismiss,
});
};

Expand Down Expand Up @@ -258,6 +264,16 @@ const notifyFailureWithAction = function(message, payload) {
);
};

const notifyWarningWithAction = function(message, payload) {
return notifyMessage.bind(this)(
'error',
warningIcon,
this.$t('notification.warning_title').toUpperCase(),
message,
new NotificationAction(payload),
);
};

const notifyDisconnected = function() {
return notifyMessage.bind(this)(
'error',
Expand Down Expand Up @@ -332,6 +348,7 @@ export default boot(({ app, store }) => {
app.config.globalProperties.$notifySuccessCopy = notifySuccessCopy.bind(store);
app.config.globalProperties.$notifyFailure = notifyFailure.bind(store);
app.config.globalProperties.$notifyFailureWithAction = notifyFailureWithAction.bind(store);
app.config.globalProperties.$notifyWarningWithAction = notifyWarningWithAction.bind(store);
app.config.globalProperties.$notifyDisconnected = notifyDisconnected.bind(store);
app.config.globalProperties.$notifyNeutralMessage = notifyNeutralMessage.bind(store);
app.config.globalProperties.$notifyRememberInfo = notifyRememberInfo.bind(store);
Expand All @@ -340,6 +357,7 @@ export default boot(({ app, store }) => {
store['$notifySuccessCopy'] = app.config.globalProperties.$notifySuccessCopy;
store['$notifyFailure'] = app.config.globalProperties.$notifyFailure;
store['$notifyFailureWithAction'] = app.config.globalProperties.$notifyFailureWithAction;
store['$notifyWarningWithAction'] = app.config.globalProperties.$notifyWarningWithAction;
store['$notifyDisconnected'] = app.config.globalProperties.$notifyDisconnected;
store['$notifyNeutralMessage'] = app.config.globalProperties.$notifyNeutralMessage;
store['$notifyRememberInfo'] = app.config.globalProperties.$notifyRememberInfo;
Expand Down
10 changes: 6 additions & 4 deletions src/components/LoginModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default defineComponent({
localStorage.setItem(LOGIN_DATA_KEY, JSON.stringify({
type: LOGIN_EVM,
provider: pr_name,
account: address,
}));
});

Expand All @@ -80,7 +81,7 @@ export default defineComponent({

const loginObj = JSON.parse(loginData);
if (loginObj.type === LOGIN_EVM) {
this.loginWithAntelope(loginObj.provider);
this.loginWithAntelope(loginObj.provider, loginObj.account);
} else if (loginObj.type === LOGIN_NATIVE) {
const wallet = this.authenticators.find((a: { getName: () => any; }) => a.getName() === loginObj.provider);
if (wallet) {
Expand Down Expand Up @@ -144,7 +145,7 @@ export default defineComponent({
nativeAccount: accountName,
});
this.$providerManager.setProvider(account);
localStorage.setItem(LOGIN_DATA_KEY, JSON.stringify({ type: LOGIN_NATIVE, provider: wallet.getName() }));
localStorage.setItem(LOGIN_DATA_KEY, JSON.stringify({ type: LOGIN_NATIVE, provider: wallet.getName(), account: evmAccount.address }));
}
this.$emit('hide');
},
Expand All @@ -156,7 +157,7 @@ export default defineComponent({

return wallet.getStyle().icon;
},
async loginWithAntelope(name:string) {
async loginWithAntelope(name:string, autoLogAccount?: string) {
const label = CURRENT_CONTEXT;
const auth = getAntelope().wallets.getAuthenticator(name);
if (!auth) {
Expand All @@ -165,12 +166,13 @@ export default defineComponent({
}
const authenticator = auth.newInstance(label);
const network = useChainStore().currentChain.settings.getNetwork();
useAccountStore().loginEVM({ authenticator, network }, true).then(() => {
useAccountStore().loginEVM({ authenticator, network, autoLogAccount }, true).then(() => {
const address = useAccountStore().getAccount(label).account;
this.setLogin({ address });
localStorage.setItem(LOGIN_DATA_KEY, JSON.stringify({
type: LOGIN_EVM,
provider: name,
account: address,
}));
});
this.$emit('hide');
Expand Down
Loading