diff --git a/package.json b/package.json index 50e2bc3857..df748e70c1 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "bignumber.js": "^9.1.1", + "chalk": "4.1.2", "reflect-metadata": "^0.1.13", "typedi": "^0.10.0" }, diff --git a/src/service/faucet/faucet.ts b/src/service/faucet/faucet.ts index 3ce8d663ce..a64c5a06cb 100644 --- a/src/service/faucet/faucet.ts +++ b/src/service/faucet/faucet.ts @@ -1,7 +1,7 @@ import { Container, Service } from 'typedi' import { TatumConnector } from '../../connector/tatum.connector' -import { CONFIG, ErrorUtils, ResponseDto } from '../../util' +import { CONFIG, ErrorUtils, ResponseDto, TatumLogger } from '../../util' import { Network, TatumConfig } from '../tatum' import { TxIdResponse } from './faucet.dto' @@ -22,6 +22,13 @@ export class Faucet { } async fund(address: string): Promise> { + if (!this.config.quiet && !this.config.apiKey?.v4) { + TatumLogger.error( + 'Unable to make Faucet Calls, get an api key to successfully use this feature - ', + 'https://co.tatum.io/signup', + ) + } + const chain = this.convertToFaucetChain(this.config.network) return ErrorUtils.tryFail(async () => { diff --git a/src/service/tatum/tatum.dto.ts b/src/service/tatum/tatum.dto.ts index e5770c5821..e236a75148 100644 --- a/src/service/tatum/tatum.dto.ts +++ b/src/service/tatum/tatum.dto.ts @@ -72,6 +72,11 @@ export interface TatumConfig { * Optional list of TatumSdkWalletExtensions. */ configureWalletProviders?: WalletProviderConstructorOrConfig[] + + /** + * If this is set to `true`, you will not be receiving verbose logs such as welcome message or additional information about errors. + */ + quiet?: boolean } export enum ApiVersion { diff --git a/src/service/tatum/tatum.ts b/src/service/tatum/tatum.ts index 3da799b8cf..62f226e2bb 100644 --- a/src/service/tatum/tatum.ts +++ b/src/service/tatum/tatum.ts @@ -1,6 +1,6 @@ import { Container, Service } from 'typedi' import { isLoadBalancerNetwork } from '../../dto' -import { CONFIG, Constant, Utils } from '../../util' +import { CONFIG, Constant, TatumLogger, Utils } from '../../util' import { ExtensionConstructor, ExtensionConstructorOrConfig, @@ -83,23 +83,31 @@ export class TatumSDK { } const mergedConfig = Utils.deepMerge(defaultConfig, config) as TatumConfig + const keyProvided = !!mergedConfig.apiKey - // TODO: check when rpc is customized if there is allowedBlocksBehind if not throw error or set default - // TODO: Check if rpc works for other chains and all configurations are set correctly + try { + if (!mergedConfig.quiet) TatumLogger.welcome(keyProvided) - const id = TatumSDK.generateRandomString() - Container.of(id).set(CONFIG, mergedConfig) - if (isLoadBalancerNetwork(mergedConfig.network)) { - const loadBalancer = Container.of(id).get(LoadBalancer) - await loadBalancer.init() - } + // TODO: check when rpc is customized if there is allowedBlocksBehind if not throw error or set default + // TODO: Check if rpc works for other chains and all configurations are set correctly + + const id = TatumSDK.generateRandomString() + Container.of(id).set(CONFIG, mergedConfig) + if (isLoadBalancerNetwork(mergedConfig.network)) { + const loadBalancer = Container.of(id).get(LoadBalancer) + await loadBalancer.init() + } - const containerInstance = new TatumSdkContainer(Container.of(id)) + const containerInstance = new TatumSdkContainer(Container.of(id)) - await this.configureExtensions(config, id, containerInstance) - await this.addBuiltInExtensions(id, containerInstance) + await this.configureExtensions(config, id, containerInstance) + await this.addBuiltInExtensions(id, containerInstance) - return Utils.getClient(id, mergedConfig.network) + return Utils.getClient(id, mergedConfig.network) + } catch (e) { + if (!mergedConfig.quiet) TatumLogger.welcome(keyProvided, true) + throw e + } } private static builtInExtensions: ExtensionConstructor[] = [MetaMask] diff --git a/src/service/walletProvider/metaMask/metamask.wallet.provider.ts b/src/service/walletProvider/metaMask/metamask.wallet.provider.ts index 9de6571819..3d22773c31 100644 --- a/src/service/walletProvider/metaMask/metamask.wallet.provider.ts +++ b/src/service/walletProvider/metaMask/metamask.wallet.provider.ts @@ -6,7 +6,7 @@ import { CreateFungibleToken, CreateNftCollection, } from '../../../dto/walletProvider' -import { Constant, Utils } from '../../../util' +import { Constant, TatumLogger, Utils } from '../../../util' import { ITatumSdkContainer, TatumSdkWalletProvider } from '../../extensions' import { EvmRpc } from '../../rpc' import { TatumConfig } from '../../tatum' @@ -30,6 +30,12 @@ export class MetaMask extends TatumSdkWalletProvider { * @returns address of the connected account. */ async getWallet(): Promise { + if (!this.config.quiet) { + TatumLogger.info( + 'You can get FREE testnet tokens to work with on any number of chains - ', + 'https://co.tatum.io/faucets', + ) + } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (typeof window.ethereum === 'undefined') { diff --git a/src/util/index.ts b/src/util/index.ts index d772bd24be..3f3f1d33df 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,5 +1,6 @@ export * from './constant' export * from './di.tokens' export * from './error' +export * from './logger' export * from './tatum.utils' export * from './util.shared' diff --git a/src/util/logger.ts b/src/util/logger.ts new file mode 100644 index 0000000000..fbe00c338f --- /dev/null +++ b/src/util/logger.ts @@ -0,0 +1,200 @@ +import chalk from 'chalk' + +type Color = 'yellow' | 'green' | 'cyan' | 'red' | 'blue' + +type LogType = 'error' | 'warning' | 'info' + +const log = console.log + +const isBrowser = () => typeof window !== 'undefined' + +// eslint-disable-next-line no-control-regex +const removeAnsi = (text: string) => text.replace(/\x1B\[\d+m|\n/g, '') + +const getRandomStr = (strs: string[]) => strs[Math.floor(Math.random() * strs.length)] + +const getConfigByType = (type: LogType): { color: Color; label: string } => { + switch (type) { + case 'error': + return { color: 'red', label: 'ERROR' } + case 'warning': + return { color: 'yellow', label: 'WARNING' } + case 'info': + return { color: 'cyan', label: 'INFO' } + } +} + +const getStyleByType = (type?: LogType) => { + const common = 'color: white; font-weight: bold;' + + switch (type) { + case 'error': + return `${common} background-color: red` + case 'warning': + return `${common} background-color: darkorange` + case 'info': + return `${common} background-color: steelblue` + default: + return `${common} background-image: linear-gradient(126deg,#513bff 9%,#89ffca 97%);` + } +} + +const getStyleByColor = (color: Color) => { + switch (color) { + case 'red': + return 'color: red;' + case 'blue': + return 'color: blue;' + case 'cyan': + return 'color: cyan;' + case 'yellow': + return 'color: yellow;' + case 'green': + return 'color: green;' + } +} + +const center = (message: string, pl = 0) => { + const messageWithoutAnsi = removeAnsi(message) + const align = ((process?.stdout?.columns || 0) - (messageWithoutAnsi?.length || 0)) / 2 + + if (!align || align <= pl) return message + + try { + return ' '.repeat(align + pl) + message + } catch { + return message + } +} + +const colorize = (message: string, color: Color, block?: boolean) => { + if (isBrowser()) { + return `%c${message}%c` + } + return block ? chalk.bgKeyword(color).bold.white(message) : chalk[color](message) +} + +const link = (message: string) => colorize(message, 'cyan') + +const highlight = (message: string) => colorize(message, 'yellow') + +const getLine = (count?: number) => { + const cols = count || process?.stdout?.columns || 53 + return center(chalk.blue('-'.repeat(cols))) +} + +const getBorder = () => `\n${getLine()}\n` + +const getLogo = () => { + if (isBrowser()) { + return '%c Tatum %c ' + } + + const logoExtraPadding = 5 + const logoHorizontal = '#'.repeat(logoExtraPadding + 2) + + const logoGreenHorizontal = chalk.green(logoHorizontal) + const logoBlueHorizontal = chalk.blue(logoHorizontal) + + return ( + center(logoGreenHorizontal + '\n', logoExtraPadding) + + center(`${logoBlueHorizontal} ${logoGreenHorizontal}\n`) + + center(logoBlueHorizontal + '\n\n', 0 - logoExtraPadding) + + center(chalk.blue('#'.repeat(logoExtraPadding)) + '\n').repeat(4) + ) +} + +const printLog = (message: string, type: LogType, url?: string) => { + const config = getConfigByType(type) + + const label = colorize(` ${config.label} `, config.color, true) + + if (isBrowser()) { + log( + `${getLogo()} ${label}\n\n${message}${url}`, + getStyleByType(), + undefined, + getStyleByType(type), + undefined, + ) + } else { + const border = getBorder() + + log(border) + log(`${label} ${message}${url ? link(url) : ''}`) + log(border) + } +} + +export const TatumLogger = { + info: (message: string, url?: string) => printLog(message, 'info', url), + warning: (message: string, url?: string) => printLog(message, 'warning', url), + error: (message: string, url?: string) => printLog(message, 'error', url), + welcome: (keyProvided: boolean, err?: boolean) => { + const logo = getLogo() + const hello = ' Welcome to Tatum! 👋' + + const start = err + ? `Kick off your development journey by making your first call with ${highlight('Tatum')} - ${link( + 'https://tatum-get-started.io', + )}` + : getRandomStr([ + `Kickstart your development journey with Tatum, check out the ${highlight( + 'available features', + )} - ${link('https://tatum-features.io')}`, + `The possibilities are endless, explore ${highlight( + 'applications', + )} you can build with Tatum - ${link('https://tatum-applications.io')}`, + ]) + + const dashboard = keyProvided + ? `Effortlessly track your usage & insights on your ${highlight('Tatum Dashboard')} - ${link( + 'https://tatum-dashboard-usage.io', + )}` + : `Unlock higher limits: Generate an API Key by accessing your ${highlight('Dashboard')} - ${link( + 'https://tatum-dashboard.io', + )}` + + const features = `Try our new ${highlight('Tatum Faucets')} to get ${highlight( + 'Testnet Tokens', + )} for free on over 5 chains - ${link('https://tatum-faucets.io')}` + + if (isBrowser()) { + log( + `${logo}${hello}\n\n${start}\n\n${dashboard}\n\n${features}`, + getStyleByType(), + '', + getStyleByColor('yellow'), + '', + '', + '', + getStyleByColor('yellow'), + '', + '', + '', + getStyleByColor('yellow'), + '', + getStyleByColor('yellow'), + '', + '', + '', + ) + } else { + const titleLine = getLine(21) + const border = getBorder() + + log(border) + log(logo) + + log(titleLine) + log(center(chalk.green(hello))) + log(titleLine, '\n') + + log(center(start), '\n') + log(center(dashboard), '\n') + log(center(features)) + + log(border) + } + }, +} diff --git a/yarn.lock b/yarn.lock index 28d4910343..cd23b556cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1111,6 +1111,14 @@ caniuse-lite@^1.0.30001541: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz" integrity sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA== +chalk@4.1.2, chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -1120,14 +1128,6 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"