diff --git a/src/antelope/config/AntelopeConfig.ts b/src/antelope/config/AntelopeConfig.ts new file mode 100644 index 000000000..1ad035979 --- /dev/null +++ b/src/antelope/config/AntelopeConfig.ts @@ -0,0 +1,291 @@ +import { Authenticator } from 'universal-authenticator-library'; +import { App } from 'vue'; +import { getAntelope } from 'src/antelope'; +import { AntelopeError, AntelopeErrorPayload } from 'src/antelope/types'; +import { AntelopeDebug } from 'src/antelope/config/AntelopeDebug'; + +export interface ComplexMessage { + tag: string, + class: string, + text: string, +} + +export const chainNetworkNames: Record = { + telos: 'telos-evm', + 'telos-testnet': 'telos-evm-testnet', +}; + +export const errorToString = (error: unknown) => + getAntelope().config.errorToStringHandler(error); + + +export class AntelopeConfig { + transactionError(description: string, error: unknown): AntelopeError { + if (error instanceof AntelopeError) { + return error as AntelopeError; + } + const str = this.errorToStringHandler(error); + // if it matches antelope.*.error_* + if (str.match(/^antelope\.[a-z0-9_]+\.error_/)) { + return new AntelopeError(str, { error }); + } else { + return new AntelopeError(description, { error: str }); + } + } + + // indexer health threshold -- + private __indexer_health_threshold = 10; // 10 seconds + + // indexer health check interval -- + private __indexer_health_check_interval = 1000 * 60 * 5; // 5 minutes expressed in milliseconds + + // notifucation handlers -- + private __notify_error_handler: (message: string) => void = m => alert(`Error: ${m}`); + private __notify_success_handler: (message: string) => void = alert; + private __notify_warning_handler: (message: string) => void = alert; + + // notification handlers -- + private __notify_successful_trx_handler: (link: string) => void = alert; + private __notify_success_message_handler: (message: string, payload?: never) => void = alert; + private __notify_success_copy_handler: () => void = alert; + private __notify_failure_message_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; + private __notify_failure_action_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; + private __notify_warning_action_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; + private __notify_disconnected_handler: () => void = alert; + private __notify_neutral_message_handler: (message: string) => (() => void) = () => (() => void 0); + private __notify_remember_info_handler: (title: string, message: string | ComplexMessage[], payload: string, key: string) => (() => void) = () => (() => void 0); + + // ual authenticators list getter -- + private __authenticators_getter: () => Authenticator[] = () => []; + + // localization handler -- + private __localization_handler: (key: string, payload?: Record) => string = (key: string) => key; + + // transaction error handler -- + private __transaction_error_handler: (err: AntelopeError, trxFailed: string) => void = () => void 0; + + // error to string handler -- + private __error_to_string_handler: (error: unknown) => string = (error: unknown) => { + try { + + type EVMError = {code:string}; + const evmErr = error as EVMError; + + switch (evmErr.code) { + case 'CALL_EXCEPTION': return 'antelope.evm.error_call_exception'; + case 'INSUFFICIENT_FUNDS': return 'antelope.evm.error_insufficient_funds'; + case 'MISSING_NEW': return 'antelope.evm.error_missing_new'; + case 'NONCE_EXPIRED': return 'antelope.evm.error_nonce_expired'; + case 'NUMERIC_FAULT': return 'antelope.evm.error_numeric_fault'; + case 'REPLACEMENT_UNDERPRICED': return 'antelope.evm.error_replacement_underpriced'; + case 'TRANSACTION_REPLACED': return 'antelope.evm.error_transaction_replaced'; + case 'UNPREDICTABLE_GAS_LIMIT': return 'antelope.evm.error_unpredictable_gas_limit'; + case 'USER_REJECTED': return 'antelope.evm.error_user_rejected'; + case 'ACTION_REJECTED': return 'antelope.evm.error_transaction_canceled'; + } + + if (typeof error === 'string') { + return error; + } + if (typeof error === 'number') { + return error.toString(); + } + if (typeof error === 'boolean') { + return error.toString(); + } + if (error instanceof Error) { + return error.message; + } + if (typeof error === 'undefined') { + return 'undefined'; + } + if (typeof error === 'object') { + if (error === null) { + return 'null'; + } + if (Array.isArray(error)) { + return error.map(a => this.__error_to_string_handler(a)).join(', '); + } + return JSON.stringify(error); + } + return 'unknown'; + } catch (er) { + return 'error'; + } + } + + // Vue.App holder -- + private __app: App | null = null; + + constructor( + public debug: AntelopeDebug, + ) { + // + } + + init(app: App) { + this.__app = app; + this.debug.init(); + } + + get app() { + return this.__app; + } + + get indexerHealthThresholdSeconds() { + return this.__indexer_health_threshold; + } + + get indexerHealthCheckInterval() { + return this.__indexer_health_check_interval; + } + + get notifyErrorHandler() { + return this.__notify_error_handler; + } + + get notifySuccessHandler() { + return this.__notify_success_handler; + } + + get notifyWarningHandler() { + return this.__notify_warning_handler; + } + + get notifySuccessfulTrxHandler() { + return this.__notify_successful_trx_handler; + } + + get notifySuccessMessageHandler() { + return this.__notify_success_message_handler; + } + + get notifySuccessCopyHandler() { + return this.__notify_success_copy_handler; + } + + get notifyFailureMessage() { + return this.__notify_failure_message_handler; + } + + get notifyFailureWithAction() { + return this.__notify_failure_action_handler; + } + + get notifyWarningWithAction() { + return this.__notify_warning_action_handler; + } + + get notifyDisconnectedHandler() { + return this.__notify_disconnected_handler; + } + + get notifyNeutralMessageHandler() { + return this.__notify_neutral_message_handler; + } + + get notifyRememberInfoHandler() { + return this.__notify_remember_info_handler; + } + + get authenticatorsGetter() { + return this.__authenticators_getter; + } + + get localizationHandler() { + return this.__localization_handler; + } + + get transactionErrorHandler() { + return this.__transaction_error_handler; + } + + get errorToStringHandler() { + return this.__error_to_string_handler; + } + + // setting indexer constants -- + public setIndexerHealthThresholdSeconds(threshold: number) { + this.__indexer_health_threshold = threshold; + } + + public setIndexerHealthCheckInterval(interval: number) { + this.__indexer_health_check_interval = interval; + } + + // setting notification handlers -- + public setNotifyErrorHandler(handler: (message: string) => void) { + this.__notify_error_handler = handler; + } + + public setNotifySuccessHandler(handler: (message: string) => void) { + this.__notify_success_handler = handler; + } + + public setNotifyWarningHandler(handler: (message: string) => void) { + this.__notify_warning_handler = handler; + } + + public setNotifySuccessfulTrxHandler(handler: (link: string) => void) { + this.__notify_successful_trx_handler = handler; + } + + public setNotifySuccessMessageHandler(handler: (message: string, payload?: never) => void) { + this.__notify_success_message_handler = handler; + } + + public setNotifySuccessCopyHandler(handler: () => void) { + this.__notify_success_copy_handler = handler; + } + + public setNotifyFailureMessage(handler: (message: string, payload?: AntelopeErrorPayload) => void) { + this.__notify_failure_message_handler = handler; + } + + public setNotifyFailureWithAction(handler: (message: string, payload?: AntelopeErrorPayload) => void) { + this.__notify_failure_action_handler = handler; + } + + public setNotifyWarningWithAction(handler: (message: string, payload?: AntelopeErrorPayload) => void) { + this.__notify_warning_action_handler = handler; + } + + public setNotifyDisconnectedHandler(handler: () => void) { + this.__notify_disconnected_handler = handler; + } + + public setNotifyNeutralMessageHandler(handler: (message: string) => (() => void)) { + this.__notify_neutral_message_handler = handler; + } + + public setNotifyRememberInfoHandler(handler: ( + title: string, + message: string | ComplexMessage[], + payload: string, + key: string, + ) => (() => void)) { + this.__notify_remember_info_handler = handler; + } + + // setting authenticators getter -- + public setAuthenticatorsGetter(getter: () => Authenticator[]) { + this.__authenticators_getter = getter; + } + + // setting translation handler -- + public setLocalizationHandler(handler: (key: string, payload?: Record) => string) { + this.__localization_handler = handler; + } + + // setting transaction error handler -- + public setTransactionErrorHandler(handler: (err: AntelopeError, trxFailed: string) => void) { + this.__transaction_error_handler = handler; + } + + // setting error to string handler -- + public setErrorToStringHandler(handler: (catched: unknown) => string) { + this.__error_to_string_handler = handler; + } + +} + diff --git a/src/antelope/config/AntelopeDebug.ts b/src/antelope/config/AntelopeDebug.ts new file mode 100644 index 000000000..ae64269b8 --- /dev/null +++ b/src/antelope/config/AntelopeDebug.ts @@ -0,0 +1,190 @@ +import { ethers } from 'ethers'; +import { getAntelope } from 'src/antelope'; + +export const localStorageKey = 'antelope.debug'; +export const localStorageKeyTurnedOff = 'antelope.debug.turnedOff'; + +export class AntelopeDebug { + private __debugModeAllowed = false; // this is set to false only on production and sensitive environments + private __debugMode = false; // this represents the current state of the debug mode, can be set to true only if __debugModeAllowed is true + private __filteringMode = false; // filtering mode is atemporal state where all call to trace get printed but filtered + private __filtered = new Map(); // this is a map of stores, functions of both that have been turned off for debugging purposes + constructor() { + // + } + init() { + // if we are in localhost, we can allow debug mode + if (document.location.hostname === 'localhost') { + this.allowDebugMode(true); + } + // try to recover the last state of debugMode from the localStorage + this.recoverDebugMode(); + + // if we find the trace flag in the url, we set the debug mode to true + if (document.location.search.includes('trace=true')) { + this.setDebugMode(true); + } + // but if we find the flag trace=false, we set the debug mode to false + if (document.location.search.includes('trace=false')) { + this.setDebugMode(false); + } + } + allowDebugMode(allow: boolean) { + this.__debugModeAllowed = allow; + if (this.__debugMode) { + this.setDebugMode(true); + } + } + isDebugging() { + return this.__debugMode && this.__debugModeAllowed; + } + setDebugMode(debug: boolean) { + this.__debugMode = debug; + if (!this.__debugModeAllowed) { + return 'debug mode not allowed'; + } + this.saveDebugConfig(); + if (this.isDebugging()) { + this.publishAntelopeAPItoWindow(); + return 'debug mode enabled'; + } else { + this.removeAntelopeAPIfromWindow(); + return 'debug mode disabled'; + } + } + turnOn() { + return this.setDebugMode(true); + } + turnOff() { + return this.setDebugMode(false); + } + + /** + * this function publishes the antelope API to the window object + */ + publishAntelopeAPItoWindow() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const _window = window as any; + const ant = getAntelope(); + _window.ant = ant; + _window.antelope = ant; + } + + /** + * this function removes the antelope API from the window object + */ + removeAntelopeAPIfromWindow() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const _window = window as any; + delete _window.ant; + delete _window.antelope; + } + + /** + * In case of debugging, this function allows to turn off a specific store, action or both + * @param key the key to turn off, can be a store name, an action name or both separated by a dot + * @param value the value to set, true to turn off, false to turn on + */ + filterKey(key: string, value: boolean) { + if (value) { + this.__filtered.set(key.toLowerCase(), value); + } else { + this.__filtered.delete(key.toLowerCase()); + } + this.saveDebugConfig(); + return `key "${key}" turned ${value ? 'off' : 'on'}`; + } + + /** + * This function put the debugger in a mode where every call to the trace function will be printed in the console + * but also put as a turned off key in the localStorage. This helps to auto filter all update processes functions + */ + filterStart() { + this.__filteringMode = true; + return 'filtering mode started'; + } + + filterEnd() { + this.__filteringMode = false; + this.saveDebugConfig(); + return 'filtering mode finished'; + } + + /** + * this function checks if a specific store, action or both are allowed to be traced + * @param store the store name + * @param action the action name + * @returns true if the store, action or both are allowed to be traced + */ + isAllowedToTrace(store: string, action: string) { + if (!this.isDebugging()) { + return false; + } + const keyAction = `${action.toLowerCase()}`; + const keyStore = `${store.toLowerCase()}`; + const keyBoth = `${keyStore}.${keyAction}`; + if (this.__filtered.has(keyBoth)) { + return false; + } + if (this.__filtered.has(keyAction)) { + return false; + } + if (this.__filtered.has(keyStore)) { + return false; + } + return true; + } + + /** + * this functions saves the current state of debugMode and the turned off keys in the localStorage + */ + saveDebugConfig() { + localStorage.setItem(localStorageKey, this.__debugMode.toString()); + if (this.__filtered.size === 0) { + localStorage.removeItem(localStorageKeyTurnedOff); + return; + } else { + localStorage.setItem(localStorageKeyTurnedOff, JSON.stringify(Array.from(this.__filtered.entries()))); + } + } + /** + * this function recovers the last state of debugMode and the turned off keys from the localStorage + */ + recoverDebugMode() { + const turnedOff = localStorage.getItem(localStorageKeyTurnedOff); + if (turnedOff) { + const turnedOffMap = new Map(JSON.parse(turnedOff)); + this.__filtered = turnedOffMap as Map; + } + const debugMode = localStorage.getItem(localStorageKey); + if (debugMode) { + this.setDebugMode(debugMode === 'true'); + } + } + + // auxiliar useful functions for debugging + getBigNumberFrom(value: string | number) { + return ethers.BigNumber.from(value); + } + + trace(store_name:string, action: string, args: unknown[]) { + if (this.isAllowedToTrace(store_name, action)) { + const titlecase = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); + const eventName = `${titlecase(store_name)}.${action}()`; + console.debug(eventName, [...args]); + } + if (this.__filteringMode) { + this.filterKey(`${store_name}.${action}`, true); + } + } +} + + + +export type AntelopeDebugTraceType = (message: string, ...args: unknown[]) => void; + +// auxiliary tracing functions +export const createTraceFunction = (store_name: string) => function(action: string, ...args: unknown[]) { + getAntelope().config.debug.trace(store_name, action, args); +} as AntelopeDebugTraceType; + diff --git a/src/antelope/config/index.ts b/src/antelope/config/index.ts index ed88bd523..965d94484 100644 --- a/src/antelope/config/index.ts +++ b/src/antelope/config/index.ts @@ -1,285 +1,3 @@ -import { Authenticator } from 'universal-authenticator-library'; -import { App } from 'vue'; -import { getAntelope } from 'src/antelope'; -import { AntelopeError, AntelopeErrorPayload } from 'src/antelope/types'; +export * from 'src/antelope/config/AntelopeDebug'; +export * from 'src/antelope/config/AntelopeConfig'; -export interface ComplexMessage { - tag: string, - class: string, - text: string, -} - -export class AntelopeConfig { - transactionError(description: string, error: unknown): AntelopeError { - if (error instanceof AntelopeError) { - return error as AntelopeError; - } - const str = this.errorToStringHandler(error); - // if it matches antelope.*.error_* - if (str.match(/^antelope\.[a-z0-9_]+\.error_/)) { - return new AntelopeError(str, { error }); - } else { - return new AntelopeError(description, { error: str }); - } - } - - // indexer health threshold -- - private __indexer_health_threshold = 10; // 10 seconds - - // indexer health check interval -- - private __indexer_health_check_interval = 1000 * 60 * 5; // 5 minutes expressed in milliseconds - - // notifucation handlers -- - private __notify_error_handler: (message: string) => void = m => alert(`Error: ${m}`); - private __notify_success_handler: (message: string) => void = alert; - private __notify_warning_handler: (message: string) => void = alert; - - // notification handlers -- - private __notify_successful_trx_handler: (link: string) => void = alert; - private __notify_success_message_handler: (message: string, payload?: never) => void = alert; - private __notify_success_copy_handler: () => void = alert; - private __notify_failure_message_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; - private __notify_failure_action_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; - private __notify_warning_action_handler: (message: string, payload?: AntelopeErrorPayload) => void = alert; - private __notify_disconnected_handler: () => void = alert; - private __notify_neutral_message_handler: (message: string) => (() => void) = () => (() => void 0); - private __notify_remember_info_handler: (title: string, message: string | ComplexMessage[], payload: string, key: string) => (() => void) = () => (() => void 0); - - // ual authenticators list getter -- - private __authenticators_getter: () => Authenticator[] = () => []; - - // localization handler -- - private __localization_handler: (key: string, payload?: Record) => string = (key: string) => key; - - // transaction error handler -- - private __transaction_error_handler: (err: AntelopeError, trxFailed: string) => void = () => void 0; - - // error to string handler -- - private __error_to_string_handler: (error: unknown) => string = (error: unknown) => { - try { - - type EVMError = {code:string}; - const evmErr = error as EVMError; - - switch (evmErr.code) { - case 'CALL_EXCEPTION': return 'antelope.evm.error_call_exception'; - case 'INSUFFICIENT_FUNDS': return 'antelope.evm.error_insufficient_funds'; - case 'MISSING_NEW': return 'antelope.evm.error_missing_new'; - case 'NONCE_EXPIRED': return 'antelope.evm.error_nonce_expired'; - case 'NUMERIC_FAULT': return 'antelope.evm.error_numeric_fault'; - case 'REPLACEMENT_UNDERPRICED': return 'antelope.evm.error_replacement_underpriced'; - case 'TRANSACTION_REPLACED': return 'antelope.evm.error_transaction_replaced'; - case 'UNPREDICTABLE_GAS_LIMIT': return 'antelope.evm.error_unpredictable_gas_limit'; - case 'USER_REJECTED': return 'antelope.evm.error_user_rejected'; - case 'ACTION_REJECTED': return 'antelope.evm.error_transaction_canceled'; - } - - if (typeof error === 'string') { - return error; - } - if (typeof error === 'number') { - return error.toString(); - } - if (typeof error === 'boolean') { - return error.toString(); - } - if (error instanceof Error) { - return error.message; - } - if (typeof error === 'undefined') { - return 'undefined'; - } - if (typeof error === 'object') { - if (error === null) { - return 'null'; - } - if (Array.isArray(error)) { - return error.map(a => this.__error_to_string_handler(a)).join(', '); - } - return JSON.stringify(error); - } - return 'unknown'; - } catch (er) { - return 'error'; - } - } - - // Vue.App holder -- - private __app: App | null = null; - - constructor() { - // - } - - init(app: App) { - this.__app = app; - } - - get app() { - return this.__app; - } - - get indexerHealthThresholdSeconds() { - return this.__indexer_health_threshold; - } - - get indexerHealthCheckInterval() { - return this.__indexer_health_check_interval; - } - - get notifyErrorHandler() { - return this.__notify_error_handler; - } - - get notifySuccessHandler() { - return this.__notify_success_handler; - } - - get notifyWarningHandler() { - return this.__notify_warning_handler; - } - - get notifySuccessfulTrxHandler() { - return this.__notify_successful_trx_handler; - } - - get notifySuccessMessageHandler() { - return this.__notify_success_message_handler; - } - - get notifySuccessCopyHandler() { - return this.__notify_success_copy_handler; - } - - get notifyFailureMessage() { - return this.__notify_failure_message_handler; - } - - get notifyFailureWithAction() { - return this.__notify_failure_action_handler; - } - - get notifyWarningWithAction() { - return this.__notify_warning_action_handler; - } - - get notifyDisconnectedHandler() { - return this.__notify_disconnected_handler; - } - - get notifyNeutralMessageHandler() { - return this.__notify_neutral_message_handler; - } - - get notifyRememberInfoHandler() { - return this.__notify_remember_info_handler; - } - - get authenticatorsGetter() { - return this.__authenticators_getter; - } - - get localizationHandler() { - return this.__localization_handler; - } - - get transactionErrorHandler() { - return this.__transaction_error_handler; - } - - get errorToStringHandler() { - return this.__error_to_string_handler; - } - - // setting indexer constants -- - public setIndexerHealthThresholdSeconds(threshold: number) { - this.__indexer_health_threshold = threshold; - } - - public setIndexerHealthCheckInterval(interval: number) { - this.__indexer_health_check_interval = interval; - } - - // setting notification handlers -- - public setNotifyErrorHandler(handler: (message: string) => void) { - this.__notify_error_handler = handler; - } - - public setNotifySuccessHandler(handler: (message: string) => void) { - this.__notify_success_handler = handler; - } - - public setNotifyWarningHandler(handler: (message: string) => void) { - this.__notify_warning_handler = handler; - } - - public setNotifySuccessfulTrxHandler(handler: (link: string) => void) { - this.__notify_successful_trx_handler = handler; - } - - public setNotifySuccessMessageHandler(handler: (message: string, payload?: never) => void) { - this.__notify_success_message_handler = handler; - } - - public setNotifySuccessCopyHandler(handler: () => void) { - this.__notify_success_copy_handler = handler; - } - - public setNotifyFailureMessage(handler: (message: string, payload?: AntelopeErrorPayload) => void) { - this.__notify_failure_message_handler = handler; - } - - public setNotifyFailureWithAction(handler: (message: string, payload?: AntelopeErrorPayload) => void) { - this.__notify_failure_action_handler = handler; - } - - public setNotifyWarningWithAction(handler: (message: string, payload?: AntelopeErrorPayload) => void) { - this.__notify_warning_action_handler = handler; - } - - public setNotifyDisconnectedHandler(handler: () => void) { - this.__notify_disconnected_handler = handler; - } - - public setNotifyNeutralMessageHandler(handler: (message: string) => (() => void)) { - this.__notify_neutral_message_handler = handler; - } - - public setNotifyRememberInfoHandler(handler: ( - title: string, - message: string | ComplexMessage[], - payload: string, - key: string, - ) => (() => void)) { - this.__notify_remember_info_handler = handler; - } - - // setting authenticators getter -- - public setAuthenticatorsGetter(getter: () => Authenticator[]) { - this.__authenticators_getter = getter; - } - - // setting translation handler -- - public setLocalizationHandler(handler: (key: string, payload?: Record) => string) { - this.__localization_handler = handler; - } - - // setting transaction error handler -- - public setTransactionErrorHandler(handler: (err: AntelopeError, trxFailed: string) => void) { - this.__transaction_error_handler = handler; - } - - // setting error to string handler -- - public setErrorToStringHandler(handler: (catched: unknown) => string) { - this.__error_to_string_handler = handler; - } - -} - -export const errorToString = (error: unknown) => - getAntelope().config.errorToStringHandler(error); - -export const chainNetworkNames: Record = { - telos: 'telos-evm', - 'telos-testnet': 'telos-evm-testnet', -}; diff --git a/src/antelope/index.ts b/src/antelope/index.ts index 5252cdd2a..8c399e3f0 100644 --- a/src/antelope/index.ts +++ b/src/antelope/index.ts @@ -1,31 +1,31 @@ -import { App } from 'vue'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { App, toRaw } from 'vue'; import { BehaviorSubject, Subject } from 'rxjs'; import { Store } from 'pinia'; -import { AntelopeConfig, chainNetworkNames } from 'src/antelope/config/'; +import { AntelopeConfig, AntelopeDebug, chainNetworkNames } from 'src/antelope/config'; import installPinia from 'src/antelope/stores'; -import { AccountModel } from 'src/antelope/stores/account'; import { ChainModel } from 'src/antelope/stores/chain'; -import { - useAccountStore, - useAllowancesStore, - useBalancesStore, - useChainStore, - useContractStore, - useEVMStore, - useFeedbackStore, - useHistoryStore, - useNftsStore, - usePlatformStore, - useProfileStore, - useResourcesStore, - useRexStore, - useTokensStore, - useUserStore, -} from 'src/antelope'; import { AntelopeWallets } from 'src/antelope/wallets'; +import { AccountModel } from 'src/antelope/stores/account'; + +import { useFeedbackStore } from 'src/antelope/stores/feedback'; +import { usePlatformStore } from 'src/antelope/stores/platform'; +import { useProfileStore } from 'src/antelope/stores/profile'; +import { useResourcesStore } from 'src/antelope/stores/resources'; +import { useUserStore } from 'src/antelope/stores/user'; +import { useChainStore } from 'src/antelope/stores/chain'; +import { useContractStore } from 'src/antelope/stores/contract'; +import { useEVMStore } from 'src/antelope/stores/evm'; +import { useTokensStore } from 'src/antelope/stores/tokens'; +import { useNftsStore } from 'src/antelope/stores/nfts'; +import { useAccountStore } from 'src/antelope/stores/account'; +import { useAllowancesStore } from 'src/antelope/stores/allowances'; +import { useBalancesStore } from 'src/antelope/stores/balances'; +import { useHistoryStore } from 'src/antelope/stores/history'; +import { useRexStore } from 'src/antelope/stores/rex'; // provide typings for `this.$store` declare module '@vue/runtime-core' { @@ -35,6 +35,7 @@ declare module '@vue/runtime-core' { } const events = { + onClear: new Subject<{label:string}>(), onLoggedIn: new Subject(), onLoggedOut: new Subject(), onNetworkChanged: new Subject<{label:string, chain:ChainModel}>(), @@ -113,9 +114,44 @@ export class Antelope { get events() { return events; } + + // shortcut to get debug config + get debug() { + return this.config.debug; + } + + extractStoreState(store: Store) { + const state = store.$state; + const result: Record = {}; + Object.keys(state).forEach((key) => { + const value = toRaw((state as any)[key] as never); + if (key.substring(0, 2) === '__') { + result[key] = value; + } + }); + return result; + } + + /** + * This function prints the state of the store in the console + */ + print() { + if (this.config.debug.isDebugging()) { + console.log('--- Antelope lib ---'); + console.log('Config: ', [this.config]); + console.log('Wallets:', [this.wallets]); + console.log(' --- Stores --- '); + const stores = this.stores; + Object.keys(stores).forEach((key) => { + const titlecase = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); + const eventName = `${titlecase(key)}:`; + console.log(eventName.padEnd(10, ' '), [this.extractStoreState((stores as any)[key])]); + }); + } + } } -const antelope = new Antelope(new AntelopeConfig(), new AntelopeWallets()); +const antelope = new Antelope(new AntelopeConfig(new AntelopeDebug()), new AntelopeWallets()); export const getAntelope = () => antelope; export const installAntelope = (app: App) => { if (app.config.globalProperties.$antelope) { @@ -129,21 +165,28 @@ export const installAntelope = (app: App) => { return antelope; }; -export { useAccountStore } from 'src/antelope/stores/account'; -export { useAllowancesStore } from 'src/antelope/stores/allowances'; -export { useBalancesStore } from 'src/antelope/stores/balances'; -export { useChainStore } from 'src/antelope/stores/chain'; -export { useContractStore } from 'src/antelope/stores/contract'; -export { useEVMStore } from 'src/antelope/stores/evm'; + + + + + + export { useFeedbackStore } from 'src/antelope/stores/feedback'; -export { useHistoryStore } from 'src/antelope/stores/history'; -export { useNftsStore } from 'src/antelope/stores/nfts'; export { usePlatformStore } from 'src/antelope/stores/platform'; export { useProfileStore } from 'src/antelope/stores/profile'; export { useResourcesStore } from 'src/antelope/stores/resources'; -export { useRexStore } from 'src/antelope/stores/rex'; -export { useTokensStore } from 'src/antelope/stores/tokens'; export { useUserStore } from 'src/antelope/stores/user'; +export { useChainStore } from 'src/antelope/stores/chain'; +export { useContractStore } from 'src/antelope/stores/contract'; +export { useEVMStore } from 'src/antelope/stores/evm'; +export { useTokensStore } from 'src/antelope/stores/tokens'; +export { useNftsStore } from 'src/antelope/stores/nfts'; +export { useAccountStore } from 'src/antelope/stores/account'; +export { useAllowancesStore } from 'src/antelope/stores/allowances'; +export { useBalancesStore } from 'src/antelope/stores/balances'; +export { useHistoryStore } from 'src/antelope/stores/history'; +export { useRexStore } from 'src/antelope/stores/rex'; + // this constant is used for a temporal workaround for the multi-context issue // https://github.com/telosnetwork/telos-wallet/issues/582 diff --git a/src/antelope/stores/account.ts b/src/antelope/stores/account.ts index ab2824ab1..d34c9bf10 100644 --- a/src/antelope/stores/account.ts +++ b/src/antelope/stores/account.ts @@ -16,18 +16,8 @@ import { Authenticator, User } from 'universal-authenticator-library'; import { defineStore } from 'pinia'; import { API } from '@greymass/eosio'; -import { createInitFunction, createTraceFunction } from 'src/antelope/stores/feedback'; import { initFuelUserWrapper } from 'src/api/fuel'; -import { - CURRENT_CONTEXT, - useAllowancesStore, - useBalancesStore, - useFeedbackStore, - useHistoryStore, - useNftsStore, -} from 'src/antelope'; -import { getAntelope, useChainStore } from 'src/antelope'; -import { errorToString } from 'src/antelope/config'; +import { createTraceFunction, errorToString } from 'src/antelope/config'; import NativeChainSettings from 'src/antelope/chains/NativeChainSettings'; import { Action, @@ -41,6 +31,14 @@ import { toRaw } from 'vue'; import { getAddress } from 'ethers/lib/utils'; import { OreIdAuthenticator } from 'ual-oreid'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useChainStore, + useFeedbackStore, +} from 'src/antelope'; + export interface LoginNativeActionData { authenticator: Authenticator, @@ -84,7 +82,6 @@ export interface EvmAccountModel extends AccountModel { authenticator: EVMAuthenticator; } - export interface AccountState { // accounts mapped by label __accounts: { [label: Label]: AccountModel }; @@ -115,7 +112,6 @@ export const useAccountStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), async loginNative({ authenticator, network }: LoginNativeActionData): Promise { this.trace('loginNative', authenticator, network); let success = false; @@ -159,10 +155,8 @@ export const useAccountStore = defineStore(store_name, { async loginEVM({ authenticator, network }: LoginEVMActionData): Promise { this.trace('loginEVM', network); - useHistoryStore().clearEvmTransactions(); - useHistoryStore().clearEvmNftTransfers(); - useBalancesStore().clearBalances(); - useNftsStore().clearNFTs(); + const label = authenticator.label; + getAntelope().events.onClear.next({ label }); let success = false; try { @@ -210,20 +204,22 @@ export const useAccountStore = defineStore(store_name, { async logout() { this.trace('logout'); - useHistoryStore().clearEvmTransactions(); - useHistoryStore().clearEvmNftTransfers(); - useBalancesStore().clearBalances(); - useNftsStore().clearNFTs(); - useAllowancesStore().clearAllowances(); try { + + const logged = this.__accounts[CURRENT_CONTEXT]; + const { authenticator } = logged; + + if (authenticator instanceof EVMAuthenticator) { + const label = authenticator.label; + getAntelope().events.onClear.next({ label }); + } + localStorage.removeItem('network'); localStorage.removeItem('account'); localStorage.removeItem('isNative'); localStorage.removeItem('autoLogin'); - const logged = this.__accounts[CURRENT_CONTEXT]; - const { authenticator } = logged; try { authenticator && (await authenticator.logout()); } catch (error) { diff --git a/src/antelope/stores/allowances.ts b/src/antelope/stores/allowances.ts index 91dbcd122..0f53bce9c 100644 --- a/src/antelope/stores/allowances.ts +++ b/src/antelope/stores/allowances.ts @@ -3,16 +3,6 @@ import { filter } from 'rxjs'; import { formatUnits } from 'ethers/lib/utils'; import { BigNumber } from 'ethers'; -import { - CURRENT_CONTEXT, - getAntelope, - useAccountStore, - useChainStore, - useContractStore, - useFeedbackStore, - useNftsStore, - useTokensStore, -} from 'src/antelope'; import { AntelopeError, IndexerAllowanceResponse, @@ -34,13 +24,25 @@ import { isErc721SingleAllowanceRow, isNftCollectionAllowanceRow, } from 'src/antelope/types'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; +import { createTraceFunction } from 'src/antelope/config'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { ZERO_ADDRESS } from 'src/antelope/chains/chain-constants'; import { WriteContractResult } from '@wagmi/core'; import { AccountModel, EvmAccountModel } from 'src/antelope/stores/account'; import { subscribeForTransactionReceipt } from 'src/antelope/stores/utils/trx-utils'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useAccountStore, + useChainStore, + useContractStore, + useFeedbackStore, + useNftsStore, + useTokensStore, +} from 'src/antelope'; + const store_name = 'allowances'; const ALLOWANCES_LIMIT = 10000; @@ -214,9 +216,8 @@ export const useAllowancesStore = defineStore(store_name, { trace: createTraceFunction(store_name), init: () => { const allowancesStore = useAllowancesStore(); - useFeedbackStore().setDebug(store_name, isTracingAll()); - - getAntelope().events.onAccountChanged.pipe( + const ant = getAntelope(); + ant.events.onAccountChanged.pipe( filter(({ label, account }) => !!label && !!account), ).subscribe({ next: ({ label, account }) => { @@ -225,6 +226,10 @@ export const useAllowancesStore = defineStore(store_name, { } }, }); + + ant.events.onClear.subscribe(({ label }) => { + allowancesStore.clearAllowances(label); + }); }, // actions @@ -536,13 +541,12 @@ export const useAllowancesStore = defineStore(store_name, { this.trace('setErc1155Allowances', allowances); this.__erc_1155_allowances[label] = allowances; }, - // utils - clearAllowances() { - this.trace('clearAllowances'); - this.__erc_20_allowances = {}; - this.__erc_721_allowances = {}; - this.__erc_1155_allowances = {}; + clearAllowances(label: Label) { + this.trace('clearAllowances', label); + this.__erc_20_allowances[label] = []; + this.__erc_721_allowances[label] = []; + this.__erc_1155_allowances[label] = []; }, async shapeErc20AllowanceRow(data: IndexerErc20AllowanceResult): Promise { try { diff --git a/src/antelope/stores/balances.ts b/src/antelope/stores/balances.ts index 907c79d63..b1dc65389 100644 --- a/src/antelope/stores/balances.ts +++ b/src/antelope/stores/balances.ts @@ -25,17 +25,9 @@ import { addressString, AntelopeError, } from 'src/antelope/types'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; +import { createTraceFunction } from 'src/antelope/config'; import NativeChainSettings from 'src/antelope/chains/NativeChainSettings'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; -import { - getAntelope, - useAccountStore, - useFeedbackStore, - useChainStore, - CURRENT_CONTEXT, - useContractStore, -} from 'src/antelope'; import { formatWei } from 'src/antelope/stores/utils'; import { BigNumber, ethers } from 'ethers'; import { toRaw } from 'vue'; @@ -52,6 +44,16 @@ import { filter } from 'rxjs'; import { convertCurrency } from 'src/antelope/stores/utils/currency-utils'; import { subscribeForTransactionReceipt } from 'src/antelope/stores/utils/trx-utils'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useAccountStore, + useFeedbackStore, + useChainStore, + useContractStore, +} from 'src/antelope'; + export interface BalancesState { __balances: { [label: Label]: TokenBalance[] }; __wagmiSystemTokenTransferConfig: { [label: Label]: PrepareSendTransactionResult | null }; @@ -71,8 +73,8 @@ export const useBalancesStore = defineStore(store_name, { trace: createTraceFunction(store_name), init: () => { const balanceStore = useBalancesStore(); - useFeedbackStore().setDebug(store_name, isTracingAll()); - getAntelope().events.onAccountChanged.pipe( + const ant = getAntelope(); + ant.events.onAccountChanged.pipe( filter(({ label, account }) => !!label && !!account), ).subscribe({ next: async ({ label, account }) => { @@ -88,6 +90,8 @@ export const useBalancesStore = defineStore(store_name, { await balanceStore.updateBalancesForAccount(CURRENT_CONTEXT, useAccountStore().loggedAccount); } }, 10000); + + ant.events.onClear.subscribe(({ label }) => balanceStore.clearBalances(label)); }, async updateBalances(label: string) { this.trace('updateBalances', label); @@ -477,7 +481,7 @@ export const useBalancesStore = defineStore(store_name, { } }, updateBalance(label: string, balance: TokenBalance): void { - this.trace('updateBalance', label, balance); + this.trace('updateBalance', label, balance.str, balance.token.symbol); const index = this.__balances[label].findIndex(b => b.token.id === balance.token.id); if (index >= 0) { if ( @@ -511,9 +515,9 @@ export const useBalancesStore = defineStore(store_name, { this.__wagmiTokenTransferConfig[label] = config; }, - clearBalances() { - this.trace('clearBalances'); - this.__balances = {}; + clearBalances(label: Label) { + this.trace('clearBalances', label); + this.__balances[label] = []; }, }, }); diff --git a/src/antelope/stores/chain.ts b/src/antelope/stores/chain.ts index 7c54e86d8..177d18121 100644 --- a/src/antelope/stores/chain.ts +++ b/src/antelope/stores/chain.ts @@ -16,12 +16,6 @@ import { defineStore } from 'pinia'; -import { - CURRENT_CONTEXT, - useAccountStore, - useContractStore, - useFeedbackStore, -} from 'src/antelope'; // main native chains import EOS from 'src/antelope/chains/native/eos'; @@ -41,15 +35,22 @@ import TelosEVMTestnet from 'src/antelope/chains/evm/telos-evm-testnet'; import { getAntelope } from 'src/antelope'; import NativeChainSettings from 'src/antelope/chains/NativeChainSettings'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; +import { createTraceFunction } from 'src/antelope/config'; import { AntelopeError, ChainSettings, Label, TokenClass, + stlosAbiPreviewDeposit, + stlosAbiPreviewRedeem, } from 'src/antelope/types'; import { ethers } from 'ethers'; -import { createInitFunction, createTraceFunction } from 'src/antelope/stores/feedback'; +// dependencies -- +import { + CURRENT_CONTEXT, + useFeedbackStore, +} from 'src/antelope'; export const settings: { [key: string]: ChainSettings } = { @@ -103,6 +104,8 @@ const newChainModel = (network: string, isNative: boolean): ChainModel => { export interface ChainState { // chains mapped by label __chains: { [label: Label]: ChainModel }; + // network settings + __networks: { [network: string]: ChainSettings }; } const store_name = 'chain'; @@ -128,7 +131,6 @@ export const useChainStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), // Updates ---- async updateChainData(label: string): Promise { this.trace('updateChainData'); @@ -176,62 +178,26 @@ export const useChainStore = defineStore(store_name, { useFeedbackStore().unsetLoading('updateApy'); } }, - async actualUpdateStakedRatio(label: string): Promise { + async updateStakedRatio(label: string): Promise { // first we need the contract instance to be able to execute queries this.trace('actualUpdateStakedRatio', label); useFeedbackStore().setLoading('actualUpdateStakedRatio'); const chain_settings = useChainStore().getChain(label).settings as EVMChainSettings; const sysToken = chain_settings.getSystemToken(); const stkToken = chain_settings.getStakedSystemToken(); - const authenticator = useAccountStore().getEVMAuthenticator(label); - if (!authenticator) { - useFeedbackStore().unsetLoading('actualUpdateStakedRatio'); - this.trace('actualUpdateStakedRatio', label, '-> no authenticator'); - throw new AntelopeError('antelope.chain.error_no_default_authenticator'); - } - const contract = await useContractStore().getContract(label, stkToken.address, stkToken.type); - if (!contract) { - useFeedbackStore().unsetLoading('actualUpdateStakedRatio'); - this.trace('actualUpdateStakedRatio', label, '-> no contract'); - return; - } - const contractInstance = await contract.getContractInstance(); + + const abi = [stlosAbiPreviewDeposit[0], stlosAbiPreviewRedeem[0]]; + const provider = await getAntelope().wallets.getWeb3Provider(); + const contractInstance = new ethers.Contract(stkToken.address, abi, provider); // Now we preview a deposit of 1 SYS to get the ratio const oneSys = ethers.utils.parseUnits('1.0', sysToken.decimals); - const stakedRatio = await contractInstance.previewDeposit(oneSys); + const stakedRatio = await contractInstance.previewDeposit(oneSys.toString()); const unstakedRatio:ethers.BigNumber = await contractInstance.previewRedeem(oneSys); // Finally we update the store this.setStakedRatio(label, stakedRatio); this.setUnstakedRatio(label, unstakedRatio); useFeedbackStore().unsetLoading('actualUpdateStakedRatio'); }, - async updateStakedRatio(label: string): Promise { - this.trace('updateStakedRatio', label); - const accountModel = useAccountStore().getAccount(label); - try { - if (accountModel && accountModel.account) { - // if the account is already logged, we can update the staked ratio - return this.actualUpdateStakedRatio(label); - } else { - // if the account is not logged, we need to wait for the login and then update the staked ratio - return new Promise((resolve) => { - const sub = getAntelope().events.onAccountChanged.subscribe((result) => { - if (result.label === label) { - sub.unsubscribe(); - if (result.account) { - // we need the user to be logged because the way of getting the staked ratio is by - // executing an action from contract and that internally attempts retrieve the account from the provided signer - resolve(this.actualUpdateStakedRatio(label)); - } - } - }); - }); - } - } catch (error) { - console.error(error); - throw new Error('antelope.chain.error_staked_ratio'); - } - }, async updateGasPrice(label: string): Promise { useFeedbackStore().setLoading('updateGasPrice'); this.trace('updateGasPrice'); @@ -298,4 +264,5 @@ export const useChainStore = defineStore(store_name, { const chainInitialState: ChainState = { __chains: {}, + __networks: settings, }; diff --git a/src/antelope/stores/contract.ts b/src/antelope/stores/contract.ts index 01d969482..f587a2c72 100644 --- a/src/antelope/stores/contract.ts +++ b/src/antelope/stores/contract.ts @@ -13,13 +13,6 @@ import { defineStore } from 'pinia'; -import { - useAccountStore, - useFeedbackStore, - useChainStore, - useEVMStore, - getAntelope, -} from 'src/antelope'; import { AntelopeError, erc1155Abi, @@ -35,26 +28,25 @@ import { EvmContractFactoryData, } from 'src/antelope/types'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; +import { createTraceFunction } from 'src/antelope/config'; import EvmContract, { Erc20Transfer } from 'src/antelope/stores/utils/contracts/EvmContract'; import EvmContractFactory from 'src/antelope/stores/utils/contracts/EvmContractFactory'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { getTopicHash, toChecksumAddress, TRANSFER_SIGNATURES } from 'src/antelope/stores/utils'; -import { EVMAuthenticator } from 'src/antelope/wallets'; import { ethers } from 'ethers'; import { toRaw } from 'vue'; +// dependencies -- +import { + getAntelope, + useChainStore, + useEVMStore, +} from 'src/antelope'; + const LOCAL_SORAGE_CONTRACTS_KEY = 'antelope.contracts'; -const createManager = (authenticator?: EVMAuthenticator):EvmContractManagerI => ({ - getSigner: async () => { - if (!authenticator) { - return null; - } - const provider = await authenticator.web3Provider(); - const account = useAccountStore().getAccount(authenticator.label).account; - return provider.getSigner(account); - }, +const createManager = (signer?: ethers.Signer):EvmContractManagerI => ({ + getSigner: async () => signer ?? null, getWeb3Provider: () => getAntelope().wallets.getWeb3Provider(), getFunctionIface: (hash:string) => toRaw(useEVMStore().getFunctionIface(hash)), getEventIface: (hash:string) => toRaw(useEVMStore().getEventIface(hash)), @@ -84,7 +76,6 @@ export const useContractStore = defineStore(store_name, { actions: { trace: createTraceFunction(store_name), init() { - useFeedbackStore().setDebug(store_name, isTracingAll()); this.loadCache(); }, @@ -157,9 +148,10 @@ export const useContractStore = defineStore(store_name, { * @param label identifier for the chain * @param address address of the contract * @param suspectedToken if you know the contract is a token, you can pass the type here to speed up the process + * @param signer if you want to use a specific signer, you can pass it here. Otherwise the contract will only query read-only functions * @returns the contract or null if it doesn't exist */ - async getContract(label: string, address:string, suspectedToken = ''): Promise { + async getContract(label: string, address:string, suspectedToken = '', signer?: ethers.Signer): Promise { this.trace('getContract', label, address, suspectedToken); const chainSettings = useChainStore().getChain(label).settings as EVMChainSettings; const network = chainSettings.getNetwork(); @@ -204,7 +196,7 @@ export const useContractStore = defineStore(store_name, { // we ensure the contract hast the proper list of supported interfaces metadata.supportedInterfaces = metadata.supportedInterfaces || (suspectedToken ? [suspectedToken] : undefined); this.trace('getContract', 'returning cached metadata', address, [metadata]); - return this.createAndStoreContract(label, addressLower, metadata); + return this.createAndStoreContract(label, addressLower, metadata, signer); } // maybe we already starting processing it, return the promise @@ -219,18 +211,18 @@ export const useContractStore = defineStore(store_name, { if (chainSettings.isIndexerHealthy()) { try { // we have a healthy indexer, let's get it from there first - return await this.fetchContractUsingIndexer(label, address, suspectedToken); + return await this.fetchContractUsingIndexer(label, address, suspectedToken, signer); } catch (e) { console.warn('Indexer did not worked, falling back to hyperion'); - return await this.fetchContractUsingHyperion(label, address, suspectedToken); + return await this.fetchContractUsingHyperion(label, address, suspectedToken, signer); } } else { // we don't have a healthy indexer, let's get it from hyperion - return await this.fetchContractUsingHyperion(label, address, suspectedToken); + return await this.fetchContractUsingHyperion(label, address, suspectedToken, signer); } }, - async fetchContractUsingIndexer(label: string, address:string, suspectedToken = ''): Promise { + async fetchContractUsingIndexer(label: string, address:string, suspectedToken = '', signer?: ethers.Signer): Promise { this.trace('fetchContractUsingIndexer', label, address, suspectedToken); const network = useChainStore().getChain(label).settings.getNetwork(); const addressLower = address.toLowerCase(); @@ -249,7 +241,7 @@ export const useContractStore = defineStore(store_name, { console.warn(`Could not retrieve contract ${address}: ${e}`); throw new AntelopeError('antelope.contracts.error_retrieving_contract', { address }); } - const contract = this.createAndStoreContract(label, address, metadata); + const contract = this.createAndStoreContract(label, address, metadata, signer); resolve(contract); }); @@ -257,7 +249,7 @@ export const useContractStore = defineStore(store_name, { return this.__contracts[network].processing[addressLower]; }, - async fetchContractUsingHyperion(label: string, address:string, suspectedToken = ''): Promise { + async fetchContractUsingHyperion(label: string, address:string, suspectedToken = '', signer?: ethers.Signer): Promise { this.trace('fetchContractUsingHyperion', label, address, suspectedToken); const addressLower = address.toLowerCase(); const network = useChainStore().getChain(label).settings.getNetwork(); @@ -269,16 +261,16 @@ export const useContractStore = defineStore(store_name, { if (metadata && creationInfo) { this.trace('fetchContractUsingHyperion', 'returning verified contract', address, metadata, creationInfo); - return resolve(await this.createAndStoreVerifiedContract(label, addressLower, metadata, creationInfo, suspectedToken)); + return resolve(await this.createAndStoreVerifiedContract(label, addressLower, metadata, creationInfo, suspectedToken, signer)); } - const tokenContract = await this.createAndStoreContractFromTokenList(label, address, suspectedToken, creationInfo); + const tokenContract = await this.createAndStoreContractFromTokenList(label, address, suspectedToken, creationInfo, signer); if (tokenContract) { this.trace('fetchContractUsingHyperion', 'returning contract from token list', address, tokenContract); return resolve(tokenContract); } - const suspectedTokenContract = await this.createAndStoreContractFromSuspectedType(label, address, suspectedToken, creationInfo); + const suspectedTokenContract = await this.createAndStoreContractFromSuspectedType(label, address, suspectedToken, creationInfo, signer); if (suspectedTokenContract) { this.trace('fetchContractUsingHyperion', 'returning contract from suspected type', address, suspectedTokenContract); return resolve(suspectedTokenContract); @@ -286,7 +278,7 @@ export const useContractStore = defineStore(store_name, { if (creationInfo) { this.trace('fetchContractUsingHyperion', 'returning empty contract', address, creationInfo); - return resolve(await this.createAndStoreEmptyContract(label, addressLower, creationInfo)); + return resolve(await this.createAndStoreEmptyContract(label, addressLower, creationInfo, signer)); } else { // We mark this address as not existing so we don't query it again this.trace('fetchContractUsingHyperion', 'returning null', address); @@ -420,6 +412,7 @@ export const useContractStore = defineStore(store_name, { * @param metadata verified metadata of the contract * @param creationInfo creation info of the contract * @param suspectedType type of the contract. It can be 'erc20', 'erc721' or 'erc1155' + * @param signer signer to use for the contract if any * @returns the contract */ async createAndStoreVerifiedContract( @@ -428,6 +421,7 @@ export const useContractStore = defineStore(store_name, { metadata: EvmContractMetadata, creationInfo: EvmContractCreationInfo, suspectedType: string, + signer?: ethers.Signer, ): Promise { this.trace('createAndStoreVerifiedContract', label, address, [metadata], [creationInfo], suspectedType); const token = await this.getToken(label, address, suspectedType) ?? undefined; @@ -439,7 +433,8 @@ export const useContractStore = defineStore(store_name, { creationInfo, verified: true, supportedInterfaces: [token?.type ?? 'none'], - } as EvmContractFactoryData); + } as EvmContractFactoryData, + signer); }, /** @@ -447,12 +442,14 @@ export const useContractStore = defineStore(store_name, { * @param label identifies the chain * @param address address of the contract * @param creationInfo creation info of the contract + * @param signer signer to use for the contract if any * @returns the contract */ async createAndStoreEmptyContract( label: string, address:string, creationInfo: EvmContractCreationInfo | null, + signer?: ethers.Signer, ): Promise { this.trace('createAndStoreEmptyContract', label, address, [creationInfo]); return await this.createAndStoreContract(label, address, { @@ -460,7 +457,8 @@ export const useContractStore = defineStore(store_name, { address, creationInfo, supportedInterfaces: undefined, - } as EvmContractFactoryData); + } as EvmContractFactoryData, + signer); }, /** @@ -470,6 +468,7 @@ export const useContractStore = defineStore(store_name, { * @param address address of the contract * @param suspectedType type of the contract. It can be 'erc20', 'erc721' or 'erc1155' * @param creationInfo creation info of the contract + * @param signer signer to use for the contract if any * @returns the contract or null if the address is not in the token list */ async createAndStoreContractFromTokenList( @@ -477,6 +476,7 @@ export const useContractStore = defineStore(store_name, { address:string, suspectedType:string, creationInfo:EvmContractCreationInfo | null, + signer?: ethers.Signer, ): Promise { const token = await this.getToken(label, address, suspectedType); if (token) { @@ -488,7 +488,8 @@ export const useContractStore = defineStore(store_name, { abi, token, supportedInterfaces: [token.type], - } as EvmContractFactoryData); + } as EvmContractFactoryData, + signer); } else { return null; } @@ -501,6 +502,7 @@ export const useContractStore = defineStore(store_name, { * @param address address of the contract * @param suspectedType type of the contract. It can be 'erc20', 'erc721' or 'erc1155' * @param creationInfo creation info of the contract + * @param signer signer to use for the contract if any * @returns the contract or null if the type is not supported */ async createAndStoreContractFromSuspectedType( @@ -508,6 +510,7 @@ export const useContractStore = defineStore(store_name, { address:string, suspectedType:string, creationInfo:EvmContractCreationInfo | null, + signer?: ethers.Signer, ): Promise { const abi = this.getTokenABI(suspectedType); if (abi) { @@ -517,14 +520,15 @@ export const useContractStore = defineStore(store_name, { creationInfo, abi, supportedInterfaces: [suspectedType], - } as EvmContractFactoryData); + } as EvmContractFactoryData, + signer); } else { return null; } }, // commits ----- - async createAndStoreContract(label: string, address: string, metadata: EvmContractFactoryData): Promise { + async createAndStoreContract(label: string, address: string, metadata: EvmContractFactoryData, signer?: ethers.Signer): Promise { const network = useChainStore().getChain(label).settings.getNetwork(); this.trace('createAndStoreContract', label, network, address, [metadata]); if (!address) { @@ -553,7 +557,7 @@ export const useContractStore = defineStore(store_name, { || (metadata.abi ?? []).length > 0 && (metadata.abi ?? []).length > (this.__contracts[network].cached[index]?.abi?.length ?? 0) ) { // This manager provides the signer and the web3 provider - metadata.manager = createManager(useAccountStore().getAuthenticator(label) as EVMAuthenticator); + metadata.manager = createManager(signer); // we create the contract using the factory const contract = this.__factory.buildContract(metadata); diff --git a/src/antelope/stores/evm.ts b/src/antelope/stores/evm.ts index a1a1ee39f..f8239a228 100644 --- a/src/antelope/stores/evm.ts +++ b/src/antelope/stores/evm.ts @@ -13,7 +13,6 @@ import { ethers } from 'ethers'; import { defineStore } from 'pinia'; import { RpcEndpoint } from 'universal-authenticator-library'; import { BehaviorSubject, filter } from 'rxjs'; -import { createInitFunction, createTraceFunction } from 'src/antelope/stores/feedback'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { events_signatures, functions_overrides } from 'src/antelope/stores/utils'; @@ -23,16 +22,16 @@ import { supportsInterfaceAbi, ERC721_TYPE, Collectible, - EthereumProvider, } from 'src/antelope/types'; import { toRaw } from 'vue'; +import { EVMAuthenticator, InjectedProviderAuth } from 'src/antelope/wallets'; +import { createTraceFunction } from 'src/antelope/config'; + +// dependencies -- import { - getAntelope, - useAccountStore, useChainStore, useFeedbackStore, } from 'src/antelope'; -import { EVMAuthenticator, InjectedProviderAuth } from 'src/antelope/wallets'; const onEvmReady = new BehaviorSubject(false); @@ -60,95 +59,8 @@ export const useEVMStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), // actions --- - async initInjectedProvider(authenticator: InjectedProviderAuth): Promise { - 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'); - // ensure this provider actually has the correct methods - // Check consistency of the provider - const methods = ['request', 'on']; - const candidate = provider as unknown as Record; - for (const method of methods) { - if (typeof candidate[method] !== 'function') { - console.warn(`MetamaskAuth.getProvider: method ${method} not found`); - throw new AntelopeError('antelope.evm.error_invalid_provider'); - } - } - - // 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 }); - ant.config.notifyFailureWithAction(errorMessage, { - label: ant.config.localizationHandler('evm_wallet.switch'), - 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(); - evm.trace('provider.accountsChanged', ...accounts); - - if (accounts.length > 0) { - // If we are here one of two possible things had happened: - // 1. The user has just logged in to the wallet - // 2. The user has switched the account in the wallet - - // if we are in case 1, then we are in the middle of the login process and we don't need to do anything - // We can tell because the account store has no logged account - - // But if we are in case 2 and have a logged account, we need to re-login the account using the same authenticator - // overwriting the previous logged account, which in turn will trigger all account data to be reloaded - if (useAccountStore().loggedAccount) { - // if the user is already authenticated we try to re login the account using the same authenticator - const authenticator = useAccountStore().loggedAccount.authenticator as EVMAuthenticator; - if (!authenticator) { - console.error('Inconsistency: logged account authenticator is null', authenticator); - } else { - useAccountStore().loginEVM({ authenticator, network }); - } - } - } else { - // the user has disconnected the all the accounts from the wallet so we logout - useAccountStore().logout(); - } - }); - - // This initialized property is not part of the standard provider, it's just a flag to know if we already initialized the provider - provider.__initialized = true; - evm.addInjectedProvider(authenticator); - } - authenticator.onReady.next(true); - }, - async isProviderOnTheCorrectChain(provider: ethers.providers.Web3Provider, correctChainId: string): Promise { const { chainId } = await provider.getNetwork(); const response = Number(chainId).toString() === correctChainId; diff --git a/src/antelope/stores/feedback.ts b/src/antelope/stores/feedback.ts index f038b1a48..eb06d2788 100644 --- a/src/antelope/stores/feedback.ts +++ b/src/antelope/stores/feedback.ts @@ -25,29 +25,7 @@ import { defineStore } from 'pinia'; import { getAntelope } from 'src/antelope'; import { toRaw } from 'vue'; - - -// auxiliary tracing functions -export const createTraceFunction = (store_name: string) => function(action: string, ...args: unknown[]) { - if (useFeedbackStore().isDebugging(store_name)) { - const titlecase = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); - const eventName = `${titlecase(store_name)}.${action}()`; - console.debug(eventName, [...args]); - } -}; - - -// only if we are NOT in production mode search in the url for the trace flag -// to turn on the Antelope trace mode -let trace = false; -if (document.location.hostname !== 'wallet.telos.net') { - const urlParams = new URLSearchParams(window.location.search); - trace = urlParams.get('trace') === 'true'; -} -export const isTracingAll = () => trace; -export const createInitFunction = (store_name: string, debug?: boolean) => function() { - useFeedbackStore().setDebug(store_name, debug ?? isTracingAll()); -}; +import { createTraceFunction } from 'src/antelope/config'; // ------------------------------------------- @@ -64,8 +42,6 @@ export interface FeedbackState { __errors: Map; // progress is a map of processes name and progress value __processes: Map; - // debug flags for each store - __debug: Map; } const store_name = 'feedback'; @@ -82,7 +58,6 @@ export const useFeedbackStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), // Loading ---- setLoading(name: string) { if (!this.__loading.includes(name)) { @@ -112,13 +87,6 @@ export const useFeedbackStore = defineStore(store_name, { unsetProgress(name: string) { this.__processes.delete(name); }, - // Debug mode ---- - setDebug(store_name: string, value: boolean) { - this.__debug.set(store_name, value); - }, - isDebugging(store_name: string) { - return this.__debug.get(store_name) || false; - }, }, }); @@ -126,7 +94,6 @@ const feedbackiInitialState: FeedbackState = { __loading: [], __errors: new Map(), __processes: new Map(), - __debug: new Map(), }; diff --git a/src/antelope/stores/history.ts b/src/antelope/stores/history.ts index 5778cc886..8abb115f5 100644 --- a/src/antelope/stores/history.ts +++ b/src/antelope/stores/history.ts @@ -14,11 +14,6 @@ import { defineStore } from 'pinia'; -import { - createTraceFunction, - isTracingAll, - useFeedbackStore, -} from 'src/antelope/stores/feedback'; import { Label, EvmTransaction, @@ -37,18 +32,22 @@ import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { useChainStore } from 'src/antelope/stores/chain'; import { toRaw } from 'vue'; import { BigNumber } from 'ethers'; +import { formatUnits } from 'ethers/lib/utils'; +import { getGasInTlos, WEI_PRECISION } from 'src/antelope/stores/utils'; +import { convertCurrency } from 'src/antelope/stores/utils/currency-utils'; +import { dateIsWithinXMinutes } from 'src/antelope/stores/utils/date-utils'; +import { createTraceFunction } from 'src/antelope/config'; + +// dependencies -- import { CURRENT_CONTEXT, getAntelope, useContractStore, useNftsStore, + useFeedbackStore, useTokensStore, useUserStore, -} from '..'; -import { formatUnits } from 'ethers/lib/utils'; -import { getGasInTlos, WEI_PRECISION } from 'src/antelope/stores/utils'; -import { convertCurrency } from 'src/antelope/stores/utils/currency-utils'; -import { dateIsWithinXMinutes } from 'src/antelope/stores/utils/date-utils'; +} from 'src/antelope'; export const transfers_filter_limit = 10000; @@ -99,16 +98,20 @@ export const useHistoryStore = defineStore(store_name, { trace: createTraceFunction(store_name), init: () => { const self = useHistoryStore(); - self.clearEvmNftTransfers(); - self.clearEvmTransactions(); - useFeedbackStore().setDebug(store_name, isTracingAll()); - getAntelope().events.onAccountChanged.subscribe({ + const ant = getAntelope(); + self.clearEvmNftTransfers(CURRENT_CONTEXT); + self.clearEvmTransactions(CURRENT_CONTEXT); + ant.events.onAccountChanged.subscribe({ next: ({ account }) => { if (account) { self.setEVMTransactionsFilter({ address: account.account }); } }, }); + ant.events.onClear.subscribe(({ label }) => { + self.clearEvmTransactions(label); + self.clearEvmNftTransfers(label); + }); }, // actions --- @@ -242,7 +245,7 @@ export const useHistoryStore = defineStore(store_name, { this.setEvmNftTransfers(label, transfers); } catch (error) { - this.clearEvmNftTransfers(); + this.clearEvmNftTransfers(label); throw new AntelopeError('antelope.history.error_fetching_nft_transfers'); } finally { nftTransfersUpdated = (new Date()).getTime(); @@ -450,29 +453,29 @@ export const useHistoryStore = defineStore(store_name, { this.trace('setEvmTransactionsRowCount', count); this.__total_evm_transaction_count[label] = count; }, - clearEvmTransactions() { - this.trace('clearEvmTransactions'); + clearEvmTransactions(label: Label) { + this.trace('clearEvmTransactions', label); this.setEVMTransactionsFilter({ address: '' }); this.__evm_transactions = { - [CURRENT_CONTEXT]: { + [label]: { transactions: [], }, }; this.__shaped_evm_transaction_rows = { - [CURRENT_CONTEXT]: [], + [label]: [], }; this.__total_evm_transaction_count = { - [CURRENT_CONTEXT]: 0, + [label]: 0, }; }, setEvmNftTransfers(label: Label, transfers: Map): void { this.trace('setEvmNftTransfers', transfers); this.__evm_nft_transfers[label] = transfers; }, - clearEvmNftTransfers(): void { - this.trace('clearEvmNftTransfers'); - this.__evm_nft_transfers = { [CURRENT_CONTEXT]: new Map() }; + clearEvmNftTransfers(label: Label): void { + this.trace('clearEvmNftTransfers', label); + this.__evm_nft_transfers = { [label]: new Map() }; }, }, }); diff --git a/src/antelope/stores/nfts.ts b/src/antelope/stores/nfts.ts index 402b7175d..91b3e45a7 100644 --- a/src/antelope/stores/nfts.ts +++ b/src/antelope/stores/nfts.ts @@ -16,16 +16,22 @@ import { addressString, AntelopeError, } from 'src/antelope/types'; - -import { useFeedbackStore, getAntelope, useChainStore, useEVMStore, CURRENT_CONTEXT } from 'src/antelope'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; import { toRaw } from 'vue'; import { EvmAccountModel, useAccountStore } from 'src/antelope/stores/account'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; -import { errorToString } from 'src/antelope/config'; +import { createTraceFunction, errorToString } from 'src/antelope/config'; import { truncateAddress } from 'src/antelope/stores/utils/text-utils'; import { subscribeForTransactionReceipt } from 'src/antelope/stores/utils/trx-utils'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useFeedbackStore, + useChainStore, + useEVMStore, +} from 'src/antelope'; + export interface NFTsInventory { owner: Address; list: Collectible[]; @@ -127,8 +133,8 @@ export const useNftsStore = defineStore(store_name, { trace: createTraceFunction(store_name), init: () => { const self = useNftsStore(); - useFeedbackStore().setDebug(store_name, isTracingAll()); - getAntelope().events.onAccountChanged.subscribe({ + const ant = getAntelope(); + ant.events.onAccountChanged.subscribe({ next: async ({ label, account }) => { if (label) { self.__inventory[label] = { @@ -139,6 +145,9 @@ export const useNftsStore = defineStore(store_name, { } }, }); + ant.events.onClear.subscribe(({ label }) => { + self.clearNFTs(label); + }); }, async updateNFTsForAccount(label: string, account: string) { this.trace('updateNFTsForAccount', label, account); @@ -404,9 +413,13 @@ export const useNftsStore = defineStore(store_name, { setUserFilter(filter: UserNftFilter) { this.__user_filter = filter; }, - clearNFTs() { + clearNFTs(label: Label) { this.trace('clearNFTs'); - this.__inventory = {}; + this.__inventory[label] = { + owner: '', + list: [], + loading: false, + }; this.setUserFilter({}); this.setPaginationFilter({ offset: 0, diff --git a/src/antelope/stores/platform.ts b/src/antelope/stores/platform.ts index 2a3355808..b28bf09d3 100644 --- a/src/antelope/stores/platform.ts +++ b/src/antelope/stores/platform.ts @@ -15,11 +15,7 @@ import { defineStore } from 'pinia'; -import { - useFeedbackStore, -} from 'src/antelope'; -import { errorToString } from 'src/antelope/config'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; +import { createTraceFunction, errorToString } from 'src/antelope/config'; export interface PlatformState { __is_browser: boolean; @@ -42,7 +38,6 @@ export const usePlatformStore = defineStore(store_name, { actions: { trace: createTraceFunction(store_name), init: () => { - useFeedbackStore().setDebug(store_name, isTracingAll()); const platform = usePlatformStore(); // detect brave browser diff --git a/src/antelope/stores/profile.ts b/src/antelope/stores/profile.ts index 198bf304d..7ed437ee9 100644 --- a/src/antelope/stores/profile.ts +++ b/src/antelope/stores/profile.ts @@ -6,7 +6,7 @@ import { defineStore } from 'pinia'; -import { createInitFunction, createTraceFunction } from 'src/antelope/stores/feedback'; +import { createTraceFunction } from 'src/antelope/config'; export interface ProfileState { @@ -21,7 +21,6 @@ export const useProfileStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), }, }); diff --git a/src/antelope/stores/resources.ts b/src/antelope/stores/resources.ts index f992a3c8c..01ecd530e 100644 --- a/src/antelope/stores/resources.ts +++ b/src/antelope/stores/resources.ts @@ -11,7 +11,7 @@ import { defineStore } from 'pinia'; -import { createInitFunction, createTraceFunction } from 'src/antelope/stores/feedback'; +import { createTraceFunction } from 'src/antelope/config'; export interface ResourcesState { __: string; @@ -25,7 +25,6 @@ export const useResourcesStore = defineStore(store_name, { }, actions: { trace: createTraceFunction(store_name), - init: createInitFunction(store_name), }, }); diff --git a/src/antelope/stores/rex.ts b/src/antelope/stores/rex.ts index fe93dd8f5..e79ce986a 100644 --- a/src/antelope/stores/rex.ts +++ b/src/antelope/stores/rex.ts @@ -8,20 +8,24 @@ import { ethers } from 'ethers'; import { defineStore } from 'pinia'; import { filter } from 'rxjs'; -import { - isTracingAll, - createTraceFunction, - useFeedbackStore, -} from 'src/antelope/stores/feedback'; import { AntelopeError, EvmRexDeposit, Label, TransactionResponse } from 'src/antelope/types'; import { toRaw } from 'vue'; import { AccountModel, useAccountStore } from 'src/antelope/stores/account'; -import { CURRENT_CONTEXT, getAntelope, useBalancesStore, useChainStore, useContractStore } from 'src/antelope'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { WEI_PRECISION } from 'src/antelope/stores/utils'; import { subscribeForTransactionReceipt } from 'src/antelope/stores/utils/trx-utils'; +import { createTraceFunction } from 'src/antelope/config'; import { prettyTimePeriod } from 'src/antelope/stores/utils/date-utils'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useFeedbackStore, + useBalancesStore, + useChainStore, + useContractStore, +} from 'src/antelope'; export interface RexModel { withdrawable: ethers.BigNumber; @@ -66,7 +70,6 @@ export const useRexStore = defineStore(store_name, { actions: { trace: createTraceFunction(store_name), init: () => { - useFeedbackStore().setDebug(store_name, isTracingAll()); getAntelope().events.onAccountChanged.pipe( filter(({ label, account }) => !!label && !!account), ).subscribe({ diff --git a/src/antelope/stores/tokens.ts b/src/antelope/stores/tokens.ts index 88f17adb4..1e1984c3b 100644 --- a/src/antelope/stores/tokens.ts +++ b/src/antelope/stores/tokens.ts @@ -9,16 +9,23 @@ import { TokenClass, TokenPrice, } from 'src/antelope/types'; -import { getAntelope, useFeedbackStore, useChainStore, CURRENT_CONTEXT } from 'src/antelope'; import { toRaw } from 'vue'; -import { errorToString } from 'src/antelope/config'; +import { createTraceFunction, errorToString } from 'src/antelope/config'; import { filter } from 'rxjs'; -import { createTraceFunction, isTracingAll } from 'src/antelope/stores/feedback'; import { ChainModel } from 'src/antelope/stores/chain'; import { dateIsWithinXMinutes } from 'src/antelope/stores/utils/date-utils'; import { getTokenPriceDataFromIndexer } from 'src/api/price'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; +// dependencies -- +import { + CURRENT_CONTEXT, + getAntelope, + useFeedbackStore, + useChainStore, +} from 'src/antelope'; + + export interface TokensState { __tokens: { [label: Label]: TokenClass[] }; __prices: { @@ -50,7 +57,6 @@ export const useTokensStore = defineStore(store_name, { actions: { trace: createTraceFunction(store_name), init: () => { - useFeedbackStore().setDebug(store_name, isTracingAll()); getAntelope().events.onNetworkChanged.pipe( filter(e => e.label === CURRENT_CONTEXT), ).subscribe({ diff --git a/src/antelope/stores/user.ts b/src/antelope/stores/user.ts index d0cb971c6..ee211d834 100644 --- a/src/antelope/stores/user.ts +++ b/src/antelope/stores/user.ts @@ -13,13 +13,14 @@ */ import { defineStore } from 'pinia'; -import { errorToString } from 'src/antelope/config'; +import { createTraceFunction, errorToString } from 'src/antelope/config'; import { AccountModel } from 'src/antelope/stores/account'; + +// dependencies -- import { - createTraceFunction, - isTracingAll, -} from 'src/antelope/stores/feedback'; -import { getAntelope, useFeedbackStore } from 'src/antelope'; + getAntelope, + useFeedbackStore, +} from 'src/antelope'; export type AccountList = Array; @@ -84,7 +85,6 @@ export const useUserStore = defineStore(store_name, { actions: { trace: createTraceFunction(store_name), init: () => { - useFeedbackStore().setDebug(store_name, isTracingAll()); const ant = getAntelope(); // we want to react when the logged account changes ant.events.onLoggedIn.subscribe((acc: AccountModel) => useUserStore().handleAccountLoggedIn(acc)); diff --git a/src/antelope/stores/utils/abi/stlosAbi.ts b/src/antelope/stores/utils/abi/stlosAbi.ts index 092d44eda..9a6bb33b0 100644 --- a/src/antelope/stores/utils/abi/stlosAbi.ts +++ b/src/antelope/stores/utils/abi/stlosAbi.ts @@ -47,3 +47,52 @@ export const stlosAbiWithdraw: EvmABI = [ type: 'function', }, ]; + + + +export const stlosAbiPreviewRedeem: EvmABI = [ + { + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'shares', + type: 'uint256', + }, + ], + name: 'previewRedeem', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +export const stlosAbiPreviewDeposit: EvmABI = [ + { + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'assets', + type: 'uint256', + }, + ], + name: 'previewDeposit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + diff --git a/src/antelope/wallets/AntelopeWallets.ts b/src/antelope/wallets/AntelopeWallets.ts index 69bf429d8..26fca5477 100644 --- a/src/antelope/wallets/AntelopeWallets.ts +++ b/src/antelope/wallets/AntelopeWallets.ts @@ -1,6 +1,3 @@ -import { isTracingAll } from 'src/antelope/stores/feedback'; -import { useFeedbackStore } from 'src/antelope/stores/feedback'; -import { createTraceFunction } from 'src/antelope/stores/feedback'; import { EVMAuthenticator } from 'src/antelope/wallets/authenticators/EVMAuthenticator'; import { useAccountStore } from 'src/antelope/stores/account'; import { CURRENT_CONTEXT, useChainStore } from 'src/antelope'; @@ -8,19 +5,20 @@ import { RpcEndpoint } from 'universal-authenticator-library'; import { ethers } from 'ethers'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; import { AntelopeError } from 'src/antelope/types'; +import { AntelopeDebugTraceType, createTraceFunction } from 'src/antelope/config'; const name = 'AntelopeWallets'; export class AntelopeWallets { - private trace: (message: string, ...args: unknown[]) => void; + private trace: AntelopeDebugTraceType; private authenticators: Map = new Map(); constructor() { this.trace = createTraceFunction(name); } init() { - useFeedbackStore().setDebug(name, isTracingAll()); + this.trace('init'); } addEVMAuthenticator(authenticator: EVMAuthenticator) { diff --git a/src/antelope/wallets/authenticators/EVMAuthenticator.ts b/src/antelope/wallets/authenticators/EVMAuthenticator.ts index f76e9c43a..a208ae070 100644 --- a/src/antelope/wallets/authenticators/EVMAuthenticator.ts +++ b/src/antelope/wallets/authenticators/EVMAuthenticator.ts @@ -4,9 +4,9 @@ import { SendTransactionResult, WriteContractResult } from '@wagmi/core'; import { BigNumber, ethers } from 'ethers'; import { CURRENT_CONTEXT, getAntelope, useAccountStore, useContractStore } from 'src/antelope'; import EVMChainSettings from 'src/antelope/chains/EVMChainSettings'; +import { AntelopeDebugTraceType, createTraceFunction } from 'src/antelope/config'; import { useChainStore } from 'src/antelope/stores/chain'; import { useEVMStore } from 'src/antelope/stores/evm'; -import { createTraceFunction, isTracingAll, useFeedbackStore } from 'src/antelope/stores/feedback'; import { usePlatformStore } from 'src/antelope/stores/platform'; import { setApprovalForAllAbi } from 'src/antelope/stores/utils/abi/setApprovalForAllAbi'; import { AntelopeError, NftTokenInterface, ERC1155_TYPE, ERC721_TYPE, EvmABI, EvmABIEntry, EvmFunctionParam, EvmTransactionResponse, ExceptionError, TokenClass, addressString, erc20Abi, erc721Abi, escrowAbiWithdraw, stlosAbiDeposit, stlosAbiWithdraw, wtlosAbiDeposit, wtlosAbiWithdraw, erc1155Abi, erc20AbiApprove, erc721ApproveAbi } from 'src/antelope/types'; @@ -14,13 +14,12 @@ import { AntelopeError, NftTokenInterface, ERC1155_TYPE, ERC721_TYPE, EvmABI, Ev export abstract class EVMAuthenticator { readonly label: string; - readonly trace: (message: string, ...args: unknown[]) => void; + readonly trace: AntelopeDebugTraceType; constructor(label: string) { this.label = label; const name = `${this.getName()}(${label})`; this.trace = createTraceFunction(name); - useFeedbackStore().setDebug(name, isTracingAll()); } /** diff --git a/src/antelope/wallets/authenticators/InjectedProviderAuth.ts b/src/antelope/wallets/authenticators/InjectedProviderAuth.ts index 94026a185..a893f27ae 100644 --- a/src/antelope/wallets/authenticators/InjectedProviderAuth.ts +++ b/src/antelope/wallets/authenticators/InjectedProviderAuth.ts @@ -2,7 +2,7 @@ import { BigNumber, ethers } from 'ethers'; import { BehaviorSubject, filter, map } from 'rxjs'; -import { useEVMStore, useFeedbackStore } from 'src/antelope'; +import { getAntelope, useAccountStore, useChainStore, useEVMStore, useFeedbackStore } from 'src/antelope'; import { AntelopeError, EthereumProvider, @@ -21,9 +21,95 @@ export abstract class InjectedProviderAuth extends EVMAuthenticator { // this is just a dummy label to identify the authenticator base class constructor(label: string) { super(label); - useEVMStore().initInjectedProvider(this); + this.initInjectedProvider(this); } + async initInjectedProvider(authenticator: InjectedProviderAuth): Promise { + this.trace('initInjectedProvider', authenticator.getName(), [authenticator.getProvider()]); + const provider: EthereumProvider | null = authenticator.getProvider(); + const ant = getAntelope(); + + if (provider && !provider.__initialized) { + this.trace('initInjectedProvider', authenticator.getName(), 'initializing provider'); + // ensure this provider actually has the correct methods + // Check consistency of the provider + const methods = ['request', 'on']; + const candidate = provider as unknown as Record; + for (const method of methods) { + if (typeof candidate[method] !== 'function') { + console.warn(`MetamaskAuth.getProvider: method ${method} not found`); + throw new AntelopeError('antelope.evm.error_invalid_provider'); + } + } + + // 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()) { + this.trace('checkNetworkHandler', 'correct network'); + } else { + const networkName = useChainStore().loggedChain.settings.getDisplay(); + const errorMessage = ant.config.localizationHandler('evm_wallet.incorrect_network', { networkName }); + ant.config.notifyFailureWithAction(errorMessage, { + label: ant.config.localizationHandler('evm_wallet.switch'), + handler: () => { + authenticator.ensureCorrectChain(); + }, + }); + } + } + }; + + provider.on('chainChanged', (value) => { + const newNetwork = value as string; + this.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(); + this.trace('provider.accountsChanged', ...accounts); + + if (accounts.length > 0) { + // If we are here one of two possible things had happened: + // 1. The user has just logged in to the wallet + // 2. The user has switched the account in the wallet + + // if we are in case 1, then we are in the middle of the login process and we don't need to do anything + // We can tell because the account store has no logged account + + // But if we are in case 2 and have a logged account, we need to re-login the account using the same authenticator + // overwriting the previous logged account, which in turn will trigger all account data to be reloaded + if (useAccountStore().loggedAccount) { + // if the user is already authenticated we try to re login the account using the same authenticator + const authenticator = useAccountStore().loggedAccount.authenticator as EVMAuthenticator; + if (!authenticator) { + console.error('Inconsistency: logged account authenticator is null', authenticator); + } else { + useAccountStore().loginEVM({ authenticator, network }); + } + } + } else { + // the user has disconnected the all the accounts from the wallet so we logout + useAccountStore().logout(); + } + }); + + // This initialized property is not part of the standard provider, it's just a flag to know if we already initialized the provider + provider.__initialized = true; + useEVMStore().addInjectedProvider(authenticator); + } + authenticator.onReady.next(true); + } + + async login(network: string): Promise { const chainSettings = this.getChainSettings(); const authName = this.getName(); diff --git a/src/antelope/wallets/authenticators/OreIdAuth.ts b/src/antelope/wallets/authenticators/OreIdAuth.ts index 3dc365a9f..d09552d52 100644 --- a/src/antelope/wallets/authenticators/OreIdAuth.ts +++ b/src/antelope/wallets/authenticators/OreIdAuth.ts @@ -11,7 +11,7 @@ import { addressString, EvmTransactionResponse, } from 'src/antelope/types'; -import { useFeedbackStore } from 'src/antelope/stores/feedback'; +import { useFeedbackStore } from 'src/antelope'; import { useChainStore } from 'src/antelope/stores/chain'; import { RpcEndpoint } from 'universal-authenticator-library'; import { TELOS_ANALYTICS_EVENT_NAMES } from 'src/antelope/chains/chain-constants'; diff --git a/src/antelope/wallets/authenticators/WalletConnectAuth.ts b/src/antelope/wallets/authenticators/WalletConnectAuth.ts index 5bdb6de7b..a346afced 100644 --- a/src/antelope/wallets/authenticators/WalletConnectAuth.ts +++ b/src/antelope/wallets/authenticators/WalletConnectAuth.ts @@ -19,9 +19,11 @@ import { Web3Modal, Web3ModalConfig } from '@web3modal/html'; import { BigNumber, ethers } from 'ethers'; import { TELOS_ANALYTICS_EVENT_NAMES } from 'src/antelope/chains/chain-constants'; import { useChainStore } from 'src/antelope/stores/chain'; -import { useContractStore } from 'src/antelope/stores/contract'; -import { useFeedbackStore } from 'src/antelope/stores/feedback'; -import { usePlatformStore } from 'src/antelope/stores/platform'; +import { + useContractStore, + useFeedbackStore, + usePlatformStore, +} from 'src/antelope'; import { AntelopeError, EvmABI, diff --git a/src/boot/antelope.ts b/src/boot/antelope.ts index a42b2106c..aa968eff0 100644 --- a/src/boot/antelope.ts +++ b/src/boot/antelope.ts @@ -97,6 +97,12 @@ export default boot(({ app }) => { ant.config.setIndexerHealthThresholdSeconds(10); ant.config.setIndexerHealthCheckInterval(5000); + // We only allow debug mode if we are not in production or in a sensitive environment + const weAreNotInProduction = process.env.NODE_ENV !== 'production'; + const weAreInLocalhost = document.location.hostname === 'localhost'; + const weAreInNetlify = document.location.hostname.includes('netlify'); + ant.config.debug.allowDebugMode(weAreNotInProduction || weAreInLocalhost || weAreInNetlify); + // Finally, we check if the url has the network parameter and if so, we connect to that network // Otherwise we just let the store decide which network to connect to const network = new URLSearchParams(window.location.search).get('network'); diff --git a/src/pages/home/HomePage.vue b/src/pages/home/HomePage.vue index e8c5a2899..74ae1528f 100644 --- a/src/pages/home/HomePage.vue +++ b/src/pages/home/HomePage.vue @@ -158,6 +158,14 @@ onMounted(() => { margin-bottom: 48px; } + &__logo-container { + flex-grow: 1; + align-self: center; + display: flex; + flex-direction: column; + justify-content: space-evenly; + } + &__logo { width: 180px; } diff --git a/test/jest/__tests__/antelope/stores/balances.spec.ts b/test/jest/__tests__/antelope/stores/balances.spec.ts index 202e7d16c..ac54caaba 100644 --- a/test/jest/__tests__/antelope/stores/balances.spec.ts +++ b/test/jest/__tests__/antelope/stores/balances.spec.ts @@ -3,7 +3,6 @@ import { setActivePinia, createPinia } from 'pinia'; import { useFeedbackStore, - FeedbackActions, MockData, useChainStore, getAntelope, @@ -60,14 +59,12 @@ describe('Antelope Balance Store', () => { }); describe('Initializing the store', () => { - test('should initialize the store', () => { - expect(useFeedbackStore).not.toHaveBeenCalled(); - expect(FeedbackActions.setDebug).not.toHaveBeenCalled(); - + test('should subscribe to the onAccountChanged event', () => { + expect(getAntelope().events.onAccountChanged.pipe).not.toHaveBeenCalled(); + expect(getAntelope().events.onAccountChanged.pipe().subscribe).not.toHaveBeenCalled(); store.init(); - - expect(useFeedbackStore).toHaveBeenCalled(); - expect(FeedbackActions.setDebug).toHaveBeenCalled(); + expect(getAntelope().events.onAccountChanged.pipe).toHaveBeenCalled(); + expect(getAntelope().events.onAccountChanged.pipe().subscribe).toHaveBeenCalled(); }); }); diff --git a/test/jest/__tests__/antelope/stores/profile.spec.ts b/test/jest/__tests__/antelope/stores/profile.spec.ts index 163bd25c5..ef9712f65 100644 --- a/test/jest/__tests__/antelope/stores/profile.spec.ts +++ b/test/jest/__tests__/antelope/stores/profile.spec.ts @@ -1,8 +1,7 @@ import { setActivePinia, createPinia, Store } from 'pinia'; // Mockups -jest.mock('src/antelope/stores/feedback', () => ({ - createInitFunction: jest.fn(), +jest.mock('src/antelope/config', () => ({ createTraceFunction: jest.fn(), })); diff --git a/test/jest/__tests__/antelope/wallets/AntelopeWallets.spec.ts b/test/jest/__tests__/antelope/wallets/AntelopeWallets.spec.ts index 39bd6d326..b3e195dd8 100644 --- a/test/jest/__tests__/antelope/wallets/AntelopeWallets.spec.ts +++ b/test/jest/__tests__/antelope/wallets/AntelopeWallets.spec.ts @@ -8,15 +8,15 @@ import { jest } from '@jest/globals'; import { useFeedbackStore, - createTraceFunction, - isTracingAll, } from 'test/jest/utils/antelope/store-feedback'; +import { + createTraceFunction, +} from 'test/jest/utils/antelope/debug'; + // Mocking the createTraceFunction -jest.mock('src/antelope/stores/feedback', () => ({ - useFeedbackStore, +jest.mock('src/antelope/config', () => ({ createTraceFunction, - isTracingAll, })); import { @@ -36,6 +36,7 @@ import { jest.mock('src/antelope', () => ({ useChainStore: useChainStore, CURRENT_CONTEXT: 'mockedCurrentContext', + useFeedbackStore: useFeedbackStore, })); // Import the real artifact to test @@ -51,22 +52,6 @@ describe('AntelopeWallets', () => { wallets = new AntelopeWallets(); }); - /* - // Code to test: - export class AntelopeWallets { - - private trace: (message: string, ...args: unknown[]) => void; - private authenticators: Map = new Map(); - constructor() { - this.trace = createTraceFunction(name); - } - - init() { - useFeedbackStore().setDebug(name, isTracingAll()); - } - } - */ - describe('Initial state', () => { it('should have the correct initial state', () => { // trace should be a function @@ -78,14 +63,6 @@ describe('AntelopeWallets', () => { }); }); - describe('init function', () => { - it('should initialize the feedback store', () => { - const setDebugSpy = jest.spyOn(useFeedbackStore(), 'setDebug'); - wallets.init(); - expect(setDebugSpy).toHaveBeenCalledWith('AntelopeWallets', false); - }); - }); - /* // Code to test: diff --git a/test/jest/utils/antelope/debug.ts b/test/jest/utils/antelope/debug.ts new file mode 100644 index 000000000..1df3a0632 --- /dev/null +++ b/test/jest/utils/antelope/debug.ts @@ -0,0 +1,3 @@ + +export const createTraceFunction = jest.fn().mockImplementation(() => jest.fn()); +export const errorToString = jest.fn().mockImplementation(e => e); diff --git a/test/jest/utils/antelope/index.ts b/test/jest/utils/antelope/index.ts index 770721a7b..14bf02454 100644 --- a/test/jest/utils/antelope/index.ts +++ b/test/jest/utils/antelope/index.ts @@ -1,16 +1,39 @@ // -------- Core -------- -export const getAntelope = jest.fn().mockImplementation(() => ({ + +const onEventSubscribe = jest.fn(); +const AntelopeMock = { events: { onAccountChanged: { - subscribe: jest.fn(), + subscribe: onEventSubscribe, pipe: jest.fn().mockImplementation(() => ({ - subscribe: jest.fn(), + subscribe: onEventSubscribe, })), }, + onClear: { + subscribe: onEventSubscribe, + }, + onLoggedIn: { + subscribe: onEventSubscribe, + }, + onLoggedOut: { + subscribe: onEventSubscribe, + }, + onNetworkChanged: { + subscribe: onEventSubscribe, + }, + onChainIndexerReady: { + subscribe: onEventSubscribe, + }, + onErrorMessage: { + subscribe: onEventSubscribe, + }, }, config: { errorToStringHandler: jest.fn(), transactionError: jest.fn(), + debug: { + trace: jest.fn(), + }, }, wallets: { getAuthenticator: () => ({ @@ -20,14 +43,12 @@ export const getAntelope = jest.fn().mockImplementation(() => ({ }), }), }, -})); +}; +export const getAntelope = jest.fn().mockImplementation(() => AntelopeMock); export const CURRENT_CONTEXT = 'current'; -jest.mock('src/antelope/config', () => ({ - errorToString: jest.fn().mockImplementation(e => e), -})); - +export * from 'test/jest/utils/antelope/debug'; export * from 'test/jest/utils/antelope/mockData'; export * from 'test/jest/utils/antelope/store-account'; export * from 'test/jest/utils/antelope/store-contract'; diff --git a/test/jest/utils/antelope/store-feedback.ts b/test/jest/utils/antelope/store-feedback.ts index cd251cb91..8dac10709 100644 --- a/test/jest/utils/antelope/store-feedback.ts +++ b/test/jest/utils/antelope/store-feedback.ts @@ -15,16 +15,13 @@ const FeedbackStore = { ...FeedbackGetters, ...FeedbackActions }; const useFeedbackStore = jest.fn().mockImplementation(() => FeedbackStore); -const createTraceFunction = jest.fn().mockImplementation(() => jest.fn()); - -const isTracingAll = jest.fn().mockImplementation(() => false); - +jest.mock('src/antelope/stores/feedback', () => ({ + useFeedbackStore, +})); export { FeedbackStore, FeedbackGetters, FeedbackActions, useFeedbackStore, - createTraceFunction, - isTracingAll, };