diff --git a/.npmignore b/.npmignore index 41f3042..918a0d6 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,13 @@ __tests__/ .github/ coverage/ -dist/*.tsbuildinfo -dist/*.js.map +*.tsbuildinfo +*.js.map src/ +.eslintrc.js +.gitignore +.prettierrc .travis.yml -CONTRIBUTING.md -README.md +nest-cli.json +tsconfig.build.json +tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c478d2..e1d20bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,5 @@ -RELEASE 0.1.1 \ No newline at end of file +# Changelog + +## 0.1.0 +Published by **[jarcodallo](https://github.com/jarcodallo)** on **TODO** +- TODO \ No newline at end of file diff --git a/README.md b/README.md index 76525dc..b22e1db 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,16 @@ -

NestJS-Ethers

- -

- - npm - - - Travis - - - Coverage Status - - - Snyk Vulnerabilities for npm package - - David - Dependabot - Supported platforms: Express & Fastify -

+NestJS-Ethers +============= + +[![npm](https://img.shields.io/npm/v/nestjs-ethers)](https://www.npmjs.com/package/nestjs-ethers) +[![travis](https://api.travis-ci.com/jarcodallo/nestjs-ethers.svg?branch=main)](https://travis-ci.com/github/jarcodallo/nestjs-ethers) +[![coverage](https://coveralls.io/repos/github/jarcodallo/nestjs-ethers/badge.svg?branch=main)](https://coveralls.io/github/jarcodallo/nestjs-ethers?branch=main) +[![vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/nestjs-ethers)](https://snyk.io/test/github/jarcodallo/nestjs-ethers) +[![dependencies](https://img.shields.io/david/jarcodallo/nestjs-ethers)](https://img.shields.io/david/jarcodallo/nestjs-ethers) +[![dependabot](https://badgen.net/dependabot/jarcodallo/nestjs-ethers/?icon=dependabot)](https://badgen.net/dependabot/jarcodallo/nestjs-ethers/?icon=dependabot) +[![supported platforms](https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green)](https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green) + + +Ethereum wallet implementation and utilities for NestJS based on [Ethers.js](https://github.com/ethers-io/ethers.js/) ## Install @@ -24,6 +18,262 @@ npm i nestjs-ethers ``` +## Register module + +### Zero configuration + +Just import `EthersModule` to your module: + +```ts +import { EthersModule } from 'nestjs-ethers'; + +@Module({ + imports: [EthersModule.forRoot()], + ... +}) +class MyModule {} +``` + +**NOTE:** *By default `EthersModule` will try to connect using [getDefaultProvider](https://docs.ethers.io/v5/api/providers/#providers-getDefaultProvider). It's the safest, easiest way to begin developing on Ethereum. It creates a [FallbackProvider](https://docs.ethers.io/v5/api/providers/other/#FallbackProvider) connected to as many backend services as possible.* + +### Configuration params + +`nestjs-ethers` can be configured with this options: + +```ts +interface EthersModuleOptions { + /** + * Optional parameter for connection, can be a Network object + * or the name of a common network as a string (e.g. "homestead") + * If no network is provided, homestead (i.e. mainnet) is used. + * The network may also be a URL to connect to, + * such as http://localhost:8545 or wss://example.com. + * @see {@link https://docs.ethers.io/v5/api/providers/types/#providers-Networkish} + */ + network?: Network | string; + + /** + * Optional parameter to name the module context, + */ + providerName?: string; + + /** + * Optional parameter for Alchemy API Token + * @see {@link https://alchemyapi.io} + */ + alchemy?: string; + + /** + * Optional parameter for Etherscan API Token + * @see {@link https://etherscan.io} + */ + etherscan?: string; + + /** + * Optional parameter for use Cloudflare Provider + * @see {@link https://cloudflare-eth.com} + */ + cloudflare?: boolean; + + /** + * Optional parameter for Infura Project ID + * or InfuraProviderOptions(applicationId, applicationSecretKey) + * @see {@link https://infura.io} + */ + infura?: InfuraProviderOptions | string; + + /** + * Optional parameter for Pocket Network Application ID + * or PocketProviderOptions(projectId, projectSecret) + * @see {@link https://pokt.network} + */ + pocket?: PocketProviderOptions | string; + + /** + * Optional parameter the number of backends that must agree + * (default: 2 for mainnet, 1 for testnets) + */ + quorum?: number; + + /** + * Optional parameter if this option is false, EthersModule will try to connect + * with the credentials provided in options. If you define more than one provider, + * EthersModule will use the FallbackProvider to send multiple requests simultaneously. + */ + useDefaultProvider?: boolean; +} +``` + +### Synchronous configuration + +Use `EthersModule.forRoot` method with [Options interface](#configuration-params): + +```ts +import { EthersModule } from 'nestjs-ethers'; + +@Module({ + imports: [ + EthersModule.forRoot({ + network: 'rinkeby', + providerName: 'MyModule', + alchemy: '845ce4ed0120d68eb5740c9160f08f98', + etherscan: 'e8cce313c1cfbd085f68be509451f1bab8', + cloudflare: true, + infura: { + projectId: 'd71b3d93c2fcfa7cab4924e63298575a', + projectSecret: 'ed6baa9f7a09877998a24394a12bf3dc', + }, + pocket: { + applicationId: '9b0afc55221c429104d04ef9', + applicationSecretKey: 'b5e6d6a55426712a42a93f39555973fc', + }, + quorum: 1, + useDefaultProvider: true, + }) + ], + ... +}) +class MyModule {} +``` + +### Asynchronous configuration + +With `EthersModule.forRootAsync` you can, for example, import your `ConfigModule` and inject `ConfigService` to use it in `useFactory` method. + +`useFactory` should return object with [Options interface](#configuration-params) or undefined + +Here's an example: + +```ts +import { EthersModule } from 'nestjs-ethers'; + +@Injectable() +class ConfigService { + public readonly infura = { + projectId: 'd71b3d93c2fcfa7cab4924e63298575a', + projectSecret: 'ed6baa9f7a09877998a24394a12bf3dc', + }; +} + +@Module({ + providers: [ConfigService], + exports: [ConfigService] +}) +class ConfigModule {} + +@Module({ + imports: [ + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (config: ConfigService) => { + await somePromise(); + return { + network: 'rinkeby', + infura: config.infura, + useDefaultProvider: false, + }; + } + }) + ], + ... +}) +class TestModule {} +``` + +Or you can just pass `ConfigService` to `providers`, if you don't have any `ConfigModule`: + +```ts +import { EthersModule } from 'nestjs-ethers'; + +@Injectable() +class ConfigService { + public readonly pocket: { + applicationId: '9b0afc55221c429104d04ef9', + applicationSecretKey: 'b5e6d6a55426712a42a93f39555973fc', + }; +} + +@Module({ + imports: [ + EthersModule.forRootAsync({ + providers: [ConfigService], + inject: [ConfigService], + useFactory: (config: ConfigService) => { + return { + network: 'rinkeby', + pocket: config.pocket, + useDefaultProvider: false, + }; + } + }) + ], + controllers: [TestController] +}) +class TestModule {} +``` + +You can also pass multiple `ethersjs` configs, if you want to use the `FallbackProvider` to send multiple requests simultaneously: + +```ts +import { EthersModule } from 'nestjs-ethers'; + +@Injectable() +class ConfigService { + public readonly infura = { + projectId: 'd71b3d93c2fcfa7cab4924e63298575a', + projectSecret: 'ed6baa9f7a09877998a24394a12bf3dc', + }; + public readonly pocket: { + applicationId: '9b0afc55221c429104d04ef9', + applicationSecretKey: 'b5e6d6a55426712a42a93f39555973fc', + }; +} + +@Module({ + providers: [ConfigService], + exports: [ConfigService] +}) +class ConfigModule {} + +@Module({ + imports: [ + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (config: ConfigService) => { + await somePromise(); + return { + network: 'rinkeby', + infura: config.infura, + pocket: config.pocket, + useDefaultProvider: false, + }; + } + }) + ], + ... +}) +class TestModule {} +``` + +#### Testing a class that uses @InjectEthersProvider + +This package exposes a getEthersToken() function that returns a prepared injection token based on the provided context. +Using this token, you can easily provide a mock implementation of the `EthersBaseProvider` using any of the standard custom provider techniques, including useClass, useValue, and useFactory. + +```ts + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MyService, + { + provide: getEthersToken(MyService.name), + useValue: mockProvider, + }, + ], + }).compile(); +``` + ## Change Log See [Changelog](CHANGELOG.md) for more information. @@ -38,4 +288,4 @@ Contributions welcome! See [Contributing](CONTRIBUTING.md). ## License -Licensed under the Apache 2.0 - see the [LICENSE](LICENSE) file for details. \ No newline at end of file +Licensed under the Apache 2.0 - see the [LICENSE](LICENSE) file for details. diff --git a/__tests__/ethers.decorators.spec.ts b/__tests__/ethers.decorators.spec.ts index 024bb2e..0160d52 100644 --- a/__tests__/ethers.decorators.spec.ts +++ b/__tests__/ethers.decorators.spec.ts @@ -1,16 +1,33 @@ import { NestFactory } from '@nestjs/core'; import { Module, Controller, Get, Injectable } from '@nestjs/common'; import * as request from 'supertest'; +import * as nock from 'nock'; import { EthersModule, InjectEthersProvider, EthersBaseProvider, Network, + ETHERS_MAINNET_NAME, } from '../src'; import { platforms } from './utils/platforms'; import { extraWait } from './utils/extraWait'; describe('InjectEthersProvider', () => { + beforeEach(() => nock.cleanAll()); + + beforeAll(() => { + if (!nock.isActive()) { + nock.activate(); + } + + nock.disableNetConnect(); + nock.enableNetConnect('127.0.0.1'); + }); + + afterAll(() => { + nock.restore(); + }); + for (const PlatformAdapter of platforms) { describe(PlatformAdapter.name, () => { it('should inject ethers provider in a service successfully', async () => { @@ -57,7 +74,10 @@ describe('InjectEthersProvider', () => { .expect(200) .expect((res) => { expect(res.body.network).toBeDefined(); - expect(res.body.network).toHaveProperty('name', 'homestead'); + expect(res.body.network).toHaveProperty( + 'name', + ETHERS_MAINNET_NAME, + ); expect(res.body.network).toHaveProperty('chainId', 1); expect(res.body.network).toHaveProperty('ensAddress'); }); @@ -100,7 +120,10 @@ describe('InjectEthersProvider', () => { .expect(200) .expect((res) => { expect(res.body.network).toBeDefined(); - expect(res.body.network).toHaveProperty('name', 'homestead'); + expect(res.body.network).toHaveProperty( + 'name', + ETHERS_MAINNET_NAME, + ); expect(res.body.network).toHaveProperty('chainId', 1); expect(res.body.network).toHaveProperty('ensAddress'); }); diff --git a/__tests__/ethers.module.spec.ts b/__tests__/ethers.module.spec.ts index 0c41c68..680a1a3 100644 --- a/__tests__/ethers.module.spec.ts +++ b/__tests__/ethers.module.spec.ts @@ -3,7 +3,13 @@ import { NestFactory } from '@nestjs/core'; import { Module, Controller, Get, Injectable } from '@nestjs/common'; import * as request from 'supertest'; import * as nock from 'nock'; -import { EthersModule, InjectEthersProvider, EthersBaseProvider } from '../src'; +import { + EthersModule, + InjectEthersProvider, + EthersBaseProvider, + ETHERS_MAINNET_NAME, + Network, +} from '../src'; import { platforms } from './utils/platforms'; import { extraWait } from './utils/extraWait'; import { @@ -12,14 +18,18 @@ import { RINKEBY_ALCHEMY_POKT_URL, RINKEBY_POKT_API_KEY, RINKEBY_POKT_SECRET_KEY, - RINKEBY_ETHERSCAN_POKT_URL, + RINKEBY_ETHERSCAN_URL, RINKEBY_ETHERSCAN_API_KEY, - RINKEBY_INFURA_POKT_URL, + RINKEBY_INFURA_URL, + CLOUDFLARE_URL, RINKEBY_INFURA_PROJECT_ID, RINKEBY_INFURA_PROJECT_SECRET, ETHERSCAN_GET_GAS_PRICE_QUERY, PROVIDER_GET_GAS_PRICE_BODY, PROVIDER_GET_GAS_PRICE_RESPONSE, + ETHERSCAN_GET_BLOCK_NUMBER_QUERY, + PROVIDER_GET_BLOCK_NUMBER_BODY, + PROVIDER_GET_BLOCK_NUMBER_RESPONSE, } from './utils/constants'; import { BigNumber } from '@ethersproject/bignumber'; @@ -31,14 +41,11 @@ describe('Ethers Module Initialization', () => { nock.activate(); } - // nock.recorder.rec({ dont_print: true }); nock.disableNetConnect(); nock.enableNetConnect('127.0.0.1'); }); afterAll(() => { - // console.log(nock.recorder.play()); - // nock.recorder.clear(); nock.restore(); }); @@ -54,7 +61,7 @@ describe('Ethers Module Initialization', () => { ) {} @Get() async get() { - const network = await this.ethersProvider.getNetwork(); + const network: Network = await this.ethersProvider.getNetwork(); return { network }; } @@ -80,7 +87,10 @@ describe('Ethers Module Initialization', () => { .expect(200) .expect((res) => { expect(res.body.network).toBeDefined(); - expect(res.body.network).toHaveProperty('name', 'homestead'); + expect(res.body.network).toHaveProperty( + 'name', + ETHERS_MAINNET_NAME, + ); expect(res.body.network).toHaveProperty('chainId', 1); expect(res.body.network).toHaveProperty('ensAddress'); }); @@ -108,7 +118,8 @@ describe('Ethers Module Initialization', () => { } @Module({ imports: [ - EthersModule.forRoot('rinkeby', { + EthersModule.forRoot({ + network: 'rinkeby', alchemy: RINKEBY_ALCHEMY_API_KEY, useDefaultProvider: false, }), @@ -158,7 +169,8 @@ describe('Ethers Module Initialization', () => { } @Module({ imports: [ - EthersModule.forRoot('rinkeby', { + EthersModule.forRoot({ + network: 'rinkeby', pocket: { applicationId: RINKEBY_POKT_API_KEY, applicationSecretKey: RINKEBY_POKT_SECRET_KEY, @@ -194,7 +206,7 @@ describe('Ethers Module Initialization', () => { describe('forRootAsync', () => { it('should compile properly with useFactory', async () => { - nock(RINKEBY_ETHERSCAN_POKT_URL) + nock(RINKEBY_ETHERSCAN_URL) .get('') .query(ETHERSCAN_GET_GAS_PRICE_QUERY) .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE); @@ -230,6 +242,7 @@ describe('Ethers Module Initialization', () => { inject: [ConfigService], useFactory: (config: ConfigService) => { return { + network: 'rinkeby', etherscan: config.etherscan, useDefaultProvider: false, }; @@ -261,8 +274,8 @@ describe('Ethers Module Initialization', () => { await app.close(); }); - it('should work properly when pass deps via providers', async () => { - nock(RINKEBY_INFURA_POKT_URL) + it('should work properly when pass dependencies via providers', async () => { + nock(RINKEBY_INFURA_URL) .post(`/${RINKEBY_INFURA_PROJECT_ID}`, PROVIDER_GET_GAS_PRICE_BODY) .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE); @@ -328,9 +341,8 @@ describe('Ethers Module Initialization', () => { }); it('should work properly when useFactory returns Promise', async () => { - nock(RINKEBY_ETHERSCAN_POKT_URL) - .get('') - .query(ETHERSCAN_GET_GAS_PRICE_QUERY) + nock(CLOUDFLARE_URL) + .post('/', PROVIDER_GET_GAS_PRICE_BODY) .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE); @Controller('/') @@ -349,7 +361,7 @@ describe('Ethers Module Initialization', () => { @Injectable() class ConfigService { - public readonly etherscan = RINKEBY_ETHERSCAN_API_KEY; + public readonly cloudflare = true; } @Module({ @@ -366,7 +378,99 @@ describe('Ethers Module Initialization', () => { await new Promise((r) => setTimeout(r, 20)); return { + cloudflare: config.cloudflare, + useDefaultProvider: false, + }; + }, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + const app = await NestFactory.create( + TestModule, + new PlatformAdapter(), + { logger: false }, + ); + const server = app.getHttpServer(); + + await app.init(); + await extraWait(PlatformAdapter, app); + + await request(server) + .get('/') + .expect(200) + .expect((res) => { + expect(res.body).toBeDefined(); + expect(res.body).toHaveProperty('gasPrice', '1000000000'); + }); + + await app.close(); + }); + + it('should work properly when useFactory uses more than one Provider', async () => { + nock(RINKEBY_INFURA_URL) + .post(`/${RINKEBY_INFURA_PROJECT_ID}`, { + ...PROVIDER_GET_GAS_PRICE_BODY, + id: 43, + }) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .post( + `/${RINKEBY_INFURA_PROJECT_ID}`, + PROVIDER_GET_BLOCK_NUMBER_BODY, + ) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE); + + nock(RINKEBY_ETHERSCAN_URL) + .get('/') + .query({ + ...ETHERSCAN_GET_GAS_PRICE_QUERY, + id: 43, + }) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .get('/') + .query(ETHERSCAN_GET_BLOCK_NUMBER_QUERY) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE); + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: EthersBaseProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.ethersProvider.getGasPrice(); + + return { gasPrice: gasPrice.toString() }; + } + } + + @Injectable() + class ConfigService { + public readonly etherscan = RINKEBY_ETHERSCAN_API_KEY; + public readonly infura = { + projectId: RINKEBY_INFURA_PROJECT_ID, + projectSecret: RINKEBY_INFURA_PROJECT_SECRET, + }; + } + + @Module({ + providers: [ConfigService], + exports: [ConfigService], + }) + class ConfigModule {} + @Module({ + imports: [ + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService) => { + return { + network: 'rinkeby', etherscan: config.etherscan, + infura: config.infura, useDefaultProvider: false, }; }, diff --git a/__tests__/utils/constants.ts b/__tests__/utils/constants.ts index 48b8377..4979ff0 100644 --- a/__tests__/utils/constants.ts +++ b/__tests__/utils/constants.ts @@ -3,8 +3,9 @@ import { randomBytes } from 'crypto'; export const RINKEBY_ALCHEMY_BASE_URL = 'https://eth-rinkeby.alchemyapi.io/v2'; export const RINKEBY_ALCHEMY_POKT_URL = 'https://eth-rinkeby.gateway.pokt.network/v1'; -export const RINKEBY_ETHERSCAN_POKT_URL = 'https://api.etherscan.io/api'; -export const RINKEBY_INFURA_POKT_URL = 'https://rinkeby.infura.io/v3'; +export const RINKEBY_ETHERSCAN_URL = 'https://api-rinkeby.etherscan.io/api'; +export const RINKEBY_INFURA_URL = 'https://rinkeby.infura.io/v3'; +export const CLOUDFLARE_URL = 'https://cloudflare-eth.com'; export const RINKEBY_ETHERSCAN_API_KEY = randomBytes(17).toString('hex'); export const RINKEBY_ALCHEMY_API_KEY = randomBytes(16).toString('hex'); export const RINKEBY_POKT_API_KEY = randomBytes(12).toString('hex'); @@ -27,3 +28,19 @@ export const ETHERSCAN_GET_GAS_PRICE_QUERY = { action: 'eth_gasPrice', apikey: RINKEBY_ETHERSCAN_API_KEY, }; +export const ETHERSCAN_GET_BLOCK_NUMBER_QUERY = { + module: 'proxy', + action: 'eth_blockNumber', + apikey: RINKEBY_ETHERSCAN_API_KEY, +}; +export const PROVIDER_GET_BLOCK_NUMBER_BODY = { + method: 'eth_blockNumber', + params: [], + id: 42, + jsonrpc: '2.0', +}; +export const PROVIDER_GET_BLOCK_NUMBER_RESPONSE = { + jsonrpc: '2.0', + id: 42, + result: '0x802f1c', +}; diff --git a/package.json b/package.json index 7ef56ef..5306493 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,11 @@ { "name": "nestjs-ethers", - "version": "0.1.0-alpha.1", + "version": "0.1.0-rc.5", "description": "The ethers.js library for NestJS", "author": "Jose Ramirez ", "license": "Apache", "readmeFilename": "README.md", "main": "dist/index.js", - "files": [ - "dist/**/*", - "*.md" - ], "engineStrict": false, "engines": { "node": "^10.24 || >=12.22 || >=14.16 || >=15.14" diff --git a/src/ethers-core.module.ts b/src/ethers-core.module.ts index 7ce7d04..4166c07 100644 --- a/src/ethers-core.module.ts +++ b/src/ethers-core.module.ts @@ -6,7 +6,7 @@ import { Inject, } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { Network, BaseProvider } from '@ethersproject/providers'; +import { BaseProvider } from '@ethersproject/providers'; import { EthersModuleOptions, EthersModuleAsyncOptions, @@ -27,11 +27,8 @@ export class EthersCoreModule implements OnApplicationShutdown { private readonly moduleRef: ModuleRef, ) {} - static forRoot( - network: Network | string = 'homestead', - options: EthersModuleOptions = {}, - ): DynamicModule { - const ethersProvider = createEthersProvider(network, options); + static forRoot(options: EthersModuleOptions = {}): DynamicModule { + const ethersProvider = createEthersProvider(options); const providerName = options?.providerName ?? ''; return { diff --git a/src/ethers.constants.ts b/src/ethers.constants.ts index 25780e3..45a9eee 100644 --- a/src/ethers.constants.ts +++ b/src/ethers.constants.ts @@ -1,3 +1,4 @@ export const DECORATED_PREFIX = 'EthersJS'; export const ETHERS_PROVIDER_NAME = 'EthersProviderName'; export const ETHERS_MODULE_OPTIONS = 'EthersModuleOptions'; +export const ETHERS_MAINNET_NAME = 'homestead'; diff --git a/src/ethers.interface.ts b/src/ethers.interface.ts index ae2b33e..cc4e71f 100644 --- a/src/ethers.interface.ts +++ b/src/ethers.interface.ts @@ -2,13 +2,13 @@ import { ModuleMetadata } from '@nestjs/common/interfaces'; import { Network } from '@ethersproject/providers'; export interface InfuraProviderOptions { - projectId: string; - projectSecret: string; + projectId?: string; + projectSecret?: string; } export interface PocketProviderOptions { - applicationId: string; - applicationSecretKey: string; + applicationId?: string; + applicationSecretKey?: string; } export interface EthersModuleOptions extends Record { @@ -16,13 +16,13 @@ export interface EthersModuleOptions extends Record { providerName?: string; alchemy?: string; etherscan?: string; - cloudflare?: string; + cloudflare?: boolean; infura?: InfuraProviderOptions | string; pocket?: PocketProviderOptions | string; quorum?: number; - useCloudflareProvider?: boolean; useDefaultProvider?: boolean; } + export interface EthersModuleAsyncOptions extends Pick { providerName?: string; diff --git a/src/ethers.module.ts b/src/ethers.module.ts index 8bdcbb6..3e2bd40 100644 --- a/src/ethers.module.ts +++ b/src/ethers.module.ts @@ -1,5 +1,4 @@ import { Module, DynamicModule } from '@nestjs/common'; -import { Network } from '@ethersproject/providers'; import { EthersCoreModule } from './ethers-core.module'; import { EthersModuleOptions, @@ -8,13 +7,10 @@ import { @Module({}) export class EthersModule { - static forRoot( - network: Network | string = 'homestead', - options: EthersModuleOptions = {}, - ): DynamicModule { + static forRoot(options: EthersModuleOptions = {}): DynamicModule { return { module: EthersModule, - imports: [EthersCoreModule.forRoot(network, options)], + imports: [EthersCoreModule.forRoot(options)], }; } diff --git a/src/ethers.providers.ts b/src/ethers.providers.ts index 74b8aa0..124914f 100644 --- a/src/ethers.providers.ts +++ b/src/ethers.providers.ts @@ -3,7 +3,6 @@ import { Provider } from '@nestjs/common'; import { BaseProvider, getDefaultProvider, - Network, FallbackProvider, AlchemyProvider, CloudflareProvider, @@ -20,29 +19,24 @@ import { getEthersToken } from './ethers.utils'; import { ETHERS_MODULE_OPTIONS, ETHERS_PROVIDER_NAME, + ETHERS_MAINNET_NAME, } from './ethers.constants'; -export async function createDefaultProvider( - network: Network | string = 'homestead', +export async function createBaseProvider( options: EthersModuleOptions = {}, ): Promise { const { + network = ETHERS_MAINNET_NAME, alchemy, etherscan, infura, pocket, - cloudflare, + cloudflare = false, quorum = 1, useDefaultProvider = true, } = options; if (!useDefaultProvider) { - /** - * If you decided to not use the DefaultProvider, you can controll what Providers to enabled - * FallbackProvider uses a quorum and connects to multiple Providers as backends, - * each configured with a priority and a weight. - * @see {@link https://docs.ethers.io/v5/api/providers/other/#FallbackProvider} - */ const providers: Array = []; if (alchemy) { @@ -81,8 +75,8 @@ export async function createDefaultProvider( providers.push(pocketProvider); } - if (cloudflare) { - const cloudflareProvider = new CloudflareProvider(network, cloudflare); + if (cloudflare && network === ETHERS_MAINNET_NAME) { + const cloudflareProvider = new CloudflareProvider(network); // wait until the node is up and running smoothly. await cloudflareProvider.ready; @@ -91,6 +85,10 @@ export async function createDefaultProvider( } if (providers.length > 1) { + /** + * FallbackProvider with selected providers. + * @see {@link https://docs.ethers.io/v5/api/providers/other/#FallbackProvider} + */ return new FallbackProvider(providers, quorum); } @@ -114,15 +112,12 @@ export async function createDefaultProvider( } export function createEthersProvider( - network: Network | string, options: EthersModuleOptions = {}, ): Provider { return { provide: getEthersToken(options?.providerName ?? ''), useFactory: async (): Promise => { - return await defer(() => - createDefaultProvider(network, options), - ).toPromise(); + return await defer(() => createBaseProvider(options)).toPromise(); }, }; } @@ -131,9 +126,7 @@ export function createEthersAsyncProvider(providerName = ''): Provider { return { provide: getEthersToken(providerName ?? ''), useFactory: async (options: EthersModuleOptions): Promise => { - return await defer(() => - createDefaultProvider(options?.network, options), - ).toPromise(); + return await defer(() => createBaseProvider(options)).toPromise(); }, inject: [ETHERS_MODULE_OPTIONS], }; diff --git a/src/index.ts b/src/index.ts index c17b693..1e97c7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,8 +6,25 @@ export { EthersModuleOptions, EthersModuleAsyncOptions, } from './ethers.interface'; +export { ETHERS_MAINNET_NAME } from './ethers.constants'; export { getEthersToken } from './ethers.utils'; export { BaseProvider as EthersBaseProvider, Network, } from '@ethersproject/providers'; +export { BigNumber, BigNumberish } from '@ethersproject/bignumber'; +export { + Block, + BlockTag, + BlockWithTransactions, + TransactionReceipt, + TransactionRequest, + TransactionResponse, +} from '@ethersproject/abstract-provider'; +export { + commify, + formatUnits, + parseUnits, + formatEther, + parseEther, +} from '@ethersproject/units'; diff --git a/tsconfig.build.json b/tsconfig.build.json index 6ce8c6c..b90fc83 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "**/*mock.ts"] + "include": ["src"] }