diff --git a/README.md b/README.md index 9e3e0fe..cd4a10b 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,13 @@ interface EthersModuleOptions { * EthersModule will use the FallbackProvider to send multiple requests simultaneously. */ useDefaultProvider?: boolean; + + /** + * Optional parameter to associate a token name to EthersProvider, + * the token is used to request an instance of a class by the same name. + * This can be useful when you want multiple intances of EthersProvider. + */ + token?: string; } ``` @@ -619,9 +626,189 @@ class TestService { } ``` +## Multichain mode + +You can use the `token` property to use multiple instances of Ethers. This can be helpful when you want to connect with more than an EVN compatible chain like `BSC`, `Polygon` or `Fantom`. + +If you know what you're doing, you can enable it like so: + +### Synchronous + +```ts +import { Module, Controller, Get } from '@nestjs/common' +import { + EthersModule, + InjectEthersProvider, + InjectEthersProvider, + InjectEthersProvider, + PocketProvider, + AlchemyProvider, + StaticJsonRpcProvider, + BigNumber, + RINKEBY_NETWORK, + MUMBAI_NETWORK, + BNB_TESTNET_NETWORK, +} from 'nestjs-ethers'; + +@Controller('/') +class TestController { + constructor( + @InjectEthersProvider('eth') + private readonly pocketProvider: PocketProvider, + @InjectEthersProvider('poly') + private readonly alchemyProvider: AlchemyProvider, + @InjectEthersProvider('bsc') + private readonly customProvider: StaticJsonRpcProvider, + ) {} + @Get() + async get() { + const pocketGasPrice: BigNumber = await this.pocketProvider.getGasPrice() + const alchemyGasPrice: BigNumber = await this.alchemyProvider.getGasPrice() + const bscGasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { + pocketGasPrice: pocketGasPrice.toString(), + alchemyGasPrice: alchemyGasPrice.toString(), + bscGasPrice: bscGasPrice.toString(), + } + } +} + +@Module({ + imports: [ + EthersModule.forRoot({ + token: 'eth', + network: RINKEBY_NETWORK, + pocket: { + applicationId: '9b0afc55221c429104d04ef9', + applicationSecretKey: 'b5e6d6a55426712a42a93f39555973fc', + }, + useDefaultProvider: false, + }), + EthersModule.forRoot({ + token: 'poly', + network: MUMBAI_NETWORK, + alchemy: '845ce4ed0120d68eb5740c9160f08f98', + useDefaultProvider: false, + }), + EthersModule.forRoot({ + token: 'bsc', + network: BNB_TESTNET_NETWORK, + custom: 'https://data-seed-prebsc-1-s1.binance.org:8545', + useDefaultProvider: false, + }), + ], + controllers: [TestController], +}) +class TestModule {} +``` + +### Asynchronous configuration + +```ts +import { Module, Controller, Get } from '@nestjs/common' +import { + EthersModule, + InjectEthersProvider, + InjectEthersProvider, + InjectEthersProvider, + PocketProvider, + AlchemyProvider, + StaticJsonRpcProvider, + BigNumber, + RINKEBY_NETWORK, + MUMBAI_NETWORK, + BNB_TESTNET_NETWORK, +} from 'nestjs-ethers'; + +@Controller('/') +class TestController { + constructor( + @InjectEthersProvider('eth') + private readonly pocketProvider: PocketProvider, + @InjectEthersProvider('poly') + private readonly alchemyProvider: AlchemyProvider, + @InjectEthersProvider('bsc') + private readonly customProvider: StaticJsonRpcProvider, + ) {} + @Get() + async get() { + const pocketGasPrice: BigNumber = await this.pocketProvider.getGasPrice() + const alchemyGasPrice: BigNumber = await this.alchemyProvider.getGasPrice() + const bscGasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { + pocketGasPrice: pocketGasPrice.toString(), + alchemyGasPrice: alchemyGasPrice.toString(), + bscGasPrice: bscGasPrice.toString(), + } + } +} + +@Injectable() +class ConfigService { + public readonly applicationId: '9b0afc55221c429104d04ef9' + public readonly applicationSecretKey: 'b5e6d6a55426712a42a93f39555973fc' + public readonly alchemy: '845ce4ed0120d68eb5740c9160f08f98' + public readonly custom: 'https://data-seed-prebsc-1-s1.binance.org:8545' +} + +@Module({ + providers: [ConfigService], + exports: [ConfigService], +}) +class ConfigModule {} + +@Module({ + imports: [ + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'eth', + useFactory: (config: ConfigService) => { + return { + network: RINKEBY_NETWORK, + pocket: { + applicationId: config.applicationId, + applicationSecretKey: config.applicationSecretKey, + }, + useDefaultProvider: false, + } + }, + }), + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'poly', + useFactory: (config: ConfigService) => { + return { + network: MUMBAI_NETWORK, + alchemy: config.alchemy, + useDefaultProvider: false, + } + }, + }), + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'bsc', + useFactory: (config: ConfigService) => { + return { + network: BNB_TESTNET_NETWORK, + custom: config.custom, + useDefaultProvider: false, + } + }, + }), + ], + controllers: [TestController], +}) +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. +This package exposes a getEthersToken(token?: string) function that returns a prepared injection token based on the provided context. Using this token, you can easily provide a mock implementation of the `BaseProvider` using any of the standard custom provider techniques, including useClass, useValue, and useFactory. ```ts diff --git a/__tests__/ethers.contract.spec.ts b/__tests__/ethers.contract.spec.ts index 5c6af0c..edf9b0d 100644 --- a/__tests__/ethers.contract.spec.ts +++ b/__tests__/ethers.contract.spec.ts @@ -2,13 +2,20 @@ import { Module, Controller, Get, Injectable } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import * as nock from 'nock' import * as request from 'supertest' -import { EthersModule, EthersContract, SmartContract, EthersSigner } from '../src' +import { + EthersModule, + EthersContract, + Contract, + EthersSigner, + InjectContractProvider, + InjectSignerProvider, +} from '../src' import * as ABI from './utils/ABI.json' import { ETHERS_ADDRESS, ETHERS_PRIVATE_KEY } from './utils/constants' import { extraWait } from './utils/extraWait' import { platforms } from './utils/platforms' -describe('EthersSigner', () => { +describe('EthersContract', () => { beforeEach(() => nock.cleanAll()) beforeAll(() => { @@ -26,105 +33,242 @@ describe('EthersSigner', () => { for (const PlatformAdapter of platforms) { describe(PlatformAdapter.name, () => { - it('should create an instance of the SmartContract attached to an address with a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersContract: EthersContract) {} - async someMethod(): Promise { - const contract: SmartContract = this.ethersContract.create(ETHERS_ADDRESS, ABI) - - if (!contract?.provider?.getNetwork()) { - throw new Error('No provider injected') - } + describe('forRoot', () => { + it('should create an instance of the SmartContract attached to an address with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + async someMethod(): Promise { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + if (!contract?.provider?.getNetwork()) { + throw new Error('No provider injected') + } - return contract.address + return contract.address + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address: address.toLowerCase() } + return { address: address.toLowerCase() } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + + it('should be able to set a Wallet into a SmartContract', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWallet(ETHERS_PRIVATE_KEY) + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI, wallet) + + if (!contract?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + if (!contract?.signer.provider?.getNetwork()) { + throw new Error('No signer injected') + } + + return contract.address + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) - await app.close() + await app.close() + }) }) - it('should be able to set a Wallet into a SmartContract', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersContract: EthersContract, private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = this.ethersSigner.createWallet(ETHERS_PRIVATE_KEY) - const contract: SmartContract = this.ethersContract.create(ETHERS_ADDRESS, ABI, wallet) + describe('forRootAsync', () => { + it('should create an instance of the SmartContract attached to an address with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + async someMethod(): Promise { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + if (!contract?.provider?.getNetwork()) { + throw new Error('No provider injected') + } - if (!contract?.provider?.getNetwork()) { - throw new Error('No provider injected') + return contract.address } + } - if (!contract?.signer.provider?.getNetwork()) { - throw new Error('No signer injected') + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should be able to set a Wallet into a SmartContract', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWallet(ETHERS_PRIVATE_KEY) + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI, wallet) + + if (!contract?.provider?.getNetwork()) { + throw new Error('No provider injected') + } - return contract.address + if (!contract?.signer.provider?.getNetwork()) { + throw new Error('No signer injected') + } + + return contract.address + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address: address.toLowerCase() } + return { address: address.toLowerCase() } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], - }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { logger: false }) + const server = app.getHttpServer() - await app.close() + await app.init() + await extraWait(PlatformAdapter, app) + await request(server) + .get('/') + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) }) }) } diff --git a/__tests__/ethers.decorators.spec.ts b/__tests__/ethers.decorators.spec.ts index 9ea7931..63603de 100644 --- a/__tests__/ethers.decorators.spec.ts +++ b/__tests__/ethers.decorators.spec.ts @@ -2,7 +2,20 @@ import { Module, Controller, Get, Injectable } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import * as nock from 'nock' import * as request from 'supertest' -import { EthersModule, InjectEthersProvider, BaseProvider, Network, MAINNET_NETWORK } from '../src' +import { + EthersModule, + InjectEthersProvider, + InjectContractProvider, + InjectSignerProvider, + BaseProvider, + Network, + MAINNET_NETWORK, + EthersContract, + EthersSigner, + Contract, +} from '../src' +import * as ABI from './utils/ABI.json' +import { ETHERS_ADDRESS } from './utils/constants' import { extraWait } from './utils/extraWait' import { platforms } from './utils/platforms' @@ -24,91 +37,562 @@ describe('InjectEthersProvider', () => { for (const PlatformAdapter of platforms) { describe(PlatformAdapter.name, () => { - it('should inject ethers provider in a service successfully', async () => { - @Injectable() - class TestService { - constructor( - @InjectEthersProvider() - private readonly ethersProvider: BaseProvider, - ) {} - async someMethod(): Promise { - return this.ethersProvider.getNetwork() - } - } - - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const network = await this.service.someMethod() - - return { network } - } - } - - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], + describe('forRoot', () => { + it('should inject ethers provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: BaseProvider, + ) {} + async someMethod(): Promise { + return this.ethersProvider.getNetwork() + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const network = await this.service.someMethod() + + return { network } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + 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.network).toBeDefined() + expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) + expect(res.body.network).toHaveProperty('chainId', 1) + expect(res.body.network).toHaveProperty('ensAddress') + }) + + await app.close() }) - 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.network).toBeDefined() - expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) - expect(res.body.network).toHaveProperty('chainId', 1) - expect(res.body.network).toHaveProperty('ensAddress') + + it('should inject ethers provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: BaseProvider, + ) {} + @Get() + async get() { + const network = await this.ethersProvider.getNetwork() + + return { network } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + 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.network).toBeDefined() + expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) + expect(res.body.network).toHaveProperty('chainId', 1) + expect(res.body.network).toHaveProperty('ensAddress') + }) + + await app.close() + }) + + it('should inject contract provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + async someMethod(): Promise { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + return contract.address + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should inject contract provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + @Get() + async get() { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + return { address: contract.address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should inject signer provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) + + return wallet.getAddress() + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should inject signer provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + @Get() + async get() { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) + + return { address: wallet.address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) - await app.close() + await app.close() + }) }) - it('should inject ethers provider in a controller successfully', async () => { - @Controller('/') - class TestController { - constructor( - @InjectEthersProvider() - private readonly ethersProvider: BaseProvider, - ) {} - @Get() - async get() { - const network = await this.ethersProvider.getNetwork() + describe('forRootAsync', () => { + it('should inject ethers provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: BaseProvider, + ) {} + async someMethod(): Promise { + return this.ethersProvider.getNetwork() + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const network = await this.service.someMethod() + + return { network } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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.network).toBeDefined() + expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) + expect(res.body.network).toHaveProperty('chainId', 1) + expect(res.body.network).toHaveProperty('ensAddress') + }) + + await app.close() + }) + + it('should inject ethers provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: BaseProvider, + ) {} + @Get() + async get() { + const network = await this.ethersProvider.getNetwork() + + return { network } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + 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.network).toBeDefined() + expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) + expect(res.body.network).toHaveProperty('chainId', 1) + expect(res.body.network).toHaveProperty('ensAddress') + }) + + await app.close() + }) + + it('should inject contract provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + async someMethod(): Promise { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + return contract.address + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should inject contract provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectContractProvider() + private readonly contract: EthersContract, + ) {} + @Get() + async get() { + const contract: Contract = this.contract.create(ETHERS_ADDRESS, ABI) + + return { address: contract.address.toLowerCase() } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should inject signer provider in a service successfully', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) - return { network } + return wallet.getAddress() + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() }) - 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.network).toBeDefined() - expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) - expect(res.body.network).toHaveProperty('chainId', 1) - expect(res.body.network).toHaveProperty('ensAddress') + + it('should inject signer provider in a controller successfully', async () => { + @Controller('/') + class TestController { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + @Get() + async get() { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) + + return { address: wallet.address.toLowerCase() } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], }) + class TestModule {} - await app.close() + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) }) }) } diff --git a/__tests__/ethers.module.spec.ts b/__tests__/ethers.module.spec.ts index 64ed64a..3b6893a 100644 --- a/__tests__/ethers.module.spec.ts +++ b/__tests__/ethers.module.spec.ts @@ -16,11 +16,14 @@ import { FallbackProvider, StaticJsonRpcProvider, BNB_TESTNET_NETWORK, + MUMBAI_NETWORK, + PocketProvider, + AlchemyProvider, } from '../src' import { - RINKEBY_ALCHEMY_BASE_URL, + RINKEBY_ALCHEMY_URL, RINKEBY_ALCHEMY_API_KEY, - RINKEBY_ALCHEMY_POKT_URL, + RINKEBY_POCKET_URL, RINKEBY_POKT_API_KEY, RINKEBY_POKT_SECRET_KEY, RINKEBY_ETHERSCAN_URL, @@ -39,6 +42,7 @@ import { CUSTOM_BSC_1_URL, CUSTOM_BSC_2_URL, CUSTOM_BSC_3_URL, + MUMBAI_ALCHEMY_URL, } from './utils/constants' import { extraWait } from './utils/extraWait' import { platforms } from './utils/platforms' @@ -102,7 +106,7 @@ describe('Ethers Module Initialization', () => { }) it('should work with alchemy provider', async () => { - nock(RINKEBY_ALCHEMY_BASE_URL) + nock(RINKEBY_ALCHEMY_URL) .post(`/${RINKEBY_ALCHEMY_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) @@ -149,7 +153,7 @@ describe('Ethers Module Initialization', () => { }) it('should work with pocket provider', async () => { - nock(RINKEBY_ALCHEMY_POKT_URL) + nock(RINKEBY_POCKET_URL) .post(`/${RINKEBY_POKT_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) @@ -366,6 +370,394 @@ describe('Ethers Module Initialization', () => { await app.close() }) + + it('should work with bscscan provider', async () => { + nock(TESTNET_BSCSCAN_URL) + .get('') + .query(ETHERSCAN_GET_GAS_PRICE_QUERY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly bscProvider: BscscanProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.bscProvider.getGasPrice() + + return { gasPrice: gasPrice.toString() } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: BINANCE_TESTNET_NETWORK, + bscscan: RINKEBY_ETHERSCAN_API_KEY, + 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 use the default bsc provider without community token', async () => { + nock(TESTNET_BSCSCAN_URL) + .get('') + .query(ETHERSCAN_GET_GAS_PRICE_QUERY) + .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 bscProvider: FallbackProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.bscProvider.getGasPrice() + + return { gasPrice: gasPrice.toString() } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: BINANCE_TESTNET_NETWORK, + bscscan: RINKEBY_ETHERSCAN_API_KEY, + useDefaultProvider: true, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { + logger: false, + abortOnError: 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 use the default bsc provider', async () => { + nock(TESTNET_BSCSCAN_URL) + .get('') + .query({ + ...ETHERSCAN_GET_GAS_PRICE_QUERY, + apikey: 'EVTS3CU31AATZV72YQ55TPGXGMVIFUQ9M9', + }) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .get('') + .query({ + ...ETHERSCAN_GET_BLOCK_NUMBER_QUERY, + apikey: 'EVTS3CU31AATZV72YQ55TPGXGMVIFUQ9M9', + }) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly bscProvider: FallbackProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.bscProvider.getGasPrice() + + return { gasPrice: gasPrice.toString() } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: BINANCE_TESTNET_NETWORK, + useDefaultProvider: true, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { + logger: false, + abortOnError: 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 throw an error if the network is different to Mainnet with Cloudflare', async () => { + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: BaseProvider, + ) {} + @Get() + async get() { + const network: Network = await this.ethersProvider.getNetwork() + + return { network } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: RINKEBY_NETWORK, + cloudflare: true, + useDefaultProvider: false, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + await expect( + NestFactory.create(TestModule, new PlatformAdapter(), { logger: false, abortOnError: false }), + ).rejects.toThrow(Error) + }) + + it('should work with one custom provider', async () => { + nock(CUSTOM_BSC_1_URL).post('/', PROVIDER_GET_GAS_PRICE_BODY).reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly customProvider: StaticJsonRpcProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { gasPrice: gasPrice.toString() } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: BNB_TESTNET_NETWORK, + custom: CUSTOM_BSC_1_URL, + 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 with more than one custom provider', async () => { + nock(CUSTOM_BSC_1_URL) + .post('/', PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .post('/', PROVIDER_GET_BLOCK_NUMBER_BODY) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE) + + nock(CUSTOM_BSC_2_URL) + .post('/', PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .post('/', PROVIDER_GET_BLOCK_NUMBER_BODY) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE) + + nock(CUSTOM_BSC_3_URL) + .post('/', PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + .post('/', PROVIDER_GET_BLOCK_NUMBER_BODY) + .reply(200, PROVIDER_GET_BLOCK_NUMBER_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly customProvider: FallbackProvider, + ) {} + @Get() + async get() { + const gasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { gasPrice: gasPrice.toString() } + } + } + + @Module({ + imports: [ + EthersModule.forRoot({ + network: BNB_TESTNET_NETWORK, + custom: [CUSTOM_BSC_1_URL, CUSTOM_BSC_2_URL, CUSTOM_BSC_3_URL], + 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 with multiple instances of ethers provider', async () => { + nock(RINKEBY_POCKET_URL) + .post(`/${RINKEBY_POKT_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + nock(MUMBAI_ALCHEMY_URL) + .post(`/${RINKEBY_ALCHEMY_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + nock(CUSTOM_BSC_1_URL).post('/', PROVIDER_GET_GAS_PRICE_BODY).reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider('eth') + private readonly pocketProvider: PocketProvider, + @InjectEthersProvider('poly') + private readonly alchemyProvider: AlchemyProvider, + @InjectEthersProvider('bsc') + private readonly customProvider: StaticJsonRpcProvider, + ) {} + @Get() + async get() { + const pocketGasPrice: BigNumber = await this.pocketProvider.getGasPrice() + const alchemyGasPrice: BigNumber = await this.alchemyProvider.getGasPrice() + const bscGasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { + pocketGasPrice: pocketGasPrice.toString(), + alchemyGasPrice: alchemyGasPrice.toString(), + bscGasPrice: bscGasPrice.toString(), + } + } + } + @Module({ + imports: [ + EthersModule.forRoot({ + token: 'eth', + network: RINKEBY_NETWORK, + pocket: { + applicationId: RINKEBY_POKT_API_KEY, + applicationSecretKey: RINKEBY_POKT_SECRET_KEY, + }, + useDefaultProvider: false, + }), + EthersModule.forRoot({ + token: 'poly', + network: MUMBAI_NETWORK, + alchemy: RINKEBY_ALCHEMY_API_KEY, + useDefaultProvider: false, + }), + EthersModule.forRoot({ + token: 'bsc', + network: BNB_TESTNET_NETWORK, + custom: CUSTOM_BSC_1_URL, + useDefaultProvider: false, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { + logger: false, + abortOnError: 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('pocketGasPrice', '1000000000') + expect(res.body).toHaveProperty('alchemyGasPrice', '1000000000') + expect(res.body).toHaveProperty('bscGasPrice', '1000000000') + }) + + await app.close() + }) }) describe('forRootAsync', () => { @@ -834,6 +1226,53 @@ describe('Ethers Module Initialization', () => { ).rejects.toThrow(Error) }) + it('should not wait until providers are connected', async () => { + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider() + private readonly ethersProvider: FallbackProvider, + ) {} + @Get() + async get() { + const network: Network = await this.ethersProvider.getNetwork() + + return { network } + } + } + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + waitUntilIsConnected: 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.network).toBeDefined() + expect(res.body.network).toHaveProperty('name', MAINNET_NETWORK.name) + expect(res.body.network).toHaveProperty('chainId', 1) + expect(res.body.network).toHaveProperty('ensAddress') + }) + + await app.close() + }) + it('should work with bscscan provider', async () => { nock(TESTNET_BSCSCAN_URL) .get('') @@ -916,14 +1355,9 @@ describe('Ethers Module Initialization', () => { ) {} @Get() async get() { - try { - const gasPrice: BigNumber = await this.bscProvider.getGasPrice() + const gasPrice: BigNumber = await this.bscProvider.getGasPrice() - return { gasPrice: gasPrice.toString() } - } catch (error) { - console.error(error) - return { gasPrice: '' } - } + return { gasPrice: gasPrice.toString() } } } @@ -999,14 +1433,9 @@ describe('Ethers Module Initialization', () => { ) {} @Get() async get() { - try { - const gasPrice: BigNumber = await this.bscProvider.getGasPrice() + const gasPrice: BigNumber = await this.bscProvider.getGasPrice() - return { gasPrice: gasPrice.toString() } - } catch (error) { - console.error(error) - return { gasPrice: '' } - } + return { gasPrice: gasPrice.toString() } } } @@ -1112,14 +1541,9 @@ describe('Ethers Module Initialization', () => { ) {} @Get() async get() { - try { - const gasPrice: BigNumber = await this.customProvider.getGasPrice() + const gasPrice: BigNumber = await this.customProvider.getGasPrice() - return { gasPrice: gasPrice.toString() } - } catch (error) { - console.error(error) - return { gasPrice: '' } - } + return { gasPrice: gasPrice.toString() } } } @@ -1195,14 +1619,9 @@ describe('Ethers Module Initialization', () => { ) {} @Get() async get() { - try { - const gasPrice: BigNumber = await this.customProvider.getGasPrice() + const gasPrice: BigNumber = await this.customProvider.getGasPrice() - return { gasPrice: gasPrice.toString() } - } catch (error) { - console.error(error) - return { gasPrice: '' } - } + return { gasPrice: gasPrice.toString() } } } @@ -1250,6 +1669,123 @@ describe('Ethers Module Initialization', () => { await app.close() }) + + it('should work with multiple instances of ethers provider', async () => { + nock(RINKEBY_POCKET_URL) + .post(`/${RINKEBY_POKT_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + nock(MUMBAI_ALCHEMY_URL) + .post(`/${RINKEBY_ALCHEMY_API_KEY}`, PROVIDER_GET_GAS_PRICE_BODY) + .reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + nock(CUSTOM_BSC_1_URL).post('/', PROVIDER_GET_GAS_PRICE_BODY).reply(200, PROVIDER_GET_GAS_PRICE_RESPONSE) + + @Controller('/') + class TestController { + constructor( + @InjectEthersProvider('eth') + private readonly pocketProvider: PocketProvider, + @InjectEthersProvider('poly') + private readonly alchemyProvider: AlchemyProvider, + @InjectEthersProvider('bsc') + private readonly customProvider: StaticJsonRpcProvider, + ) {} + @Get() + async get() { + const pocketGasPrice: BigNumber = await this.pocketProvider.getGasPrice() + const alchemyGasPrice: BigNumber = await this.alchemyProvider.getGasPrice() + const bscGasPrice: BigNumber = await this.customProvider.getGasPrice() + + return { + pocketGasPrice: pocketGasPrice.toString(), + alchemyGasPrice: alchemyGasPrice.toString(), + bscGasPrice: bscGasPrice.toString(), + } + } + } + + @Injectable() + class ConfigService { + public readonly applicationId = RINKEBY_POKT_API_KEY + public readonly applicationSecretKey = RINKEBY_POKT_SECRET_KEY + public readonly alchemy = RINKEBY_ALCHEMY_API_KEY + public readonly custom = CUSTOM_BSC_1_URL + } + + @Module({ + providers: [ConfigService], + exports: [ConfigService], + }) + class ConfigModule {} + + @Module({ + imports: [ + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'eth', + useFactory: (config: ConfigService) => { + return { + network: RINKEBY_NETWORK, + pocket: { + applicationId: config.applicationId, + applicationSecretKey: config.applicationSecretKey, + }, + useDefaultProvider: false, + } + }, + }), + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'poly', + useFactory: (config: ConfigService) => { + return { + network: MUMBAI_NETWORK, + alchemy: config.alchemy, + useDefaultProvider: false, + } + }, + }), + EthersModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + token: 'bsc', + useFactory: (config: ConfigService) => { + return { + network: BNB_TESTNET_NETWORK, + custom: config.custom, + useDefaultProvider: false, + } + }, + }), + ], + controllers: [TestController], + }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { + logger: false, + abortOnError: 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('pocketGasPrice', '1000000000') + expect(res.body).toHaveProperty('alchemyGasPrice', '1000000000') + expect(res.body).toHaveProperty('bscGasPrice', '1000000000') + }) + + await app.close() + }) }) }) } diff --git a/__tests__/ethers.signer.spec.ts b/__tests__/ethers.signer.spec.ts index 5c6a94a..c47e6e7 100644 --- a/__tests__/ethers.signer.spec.ts +++ b/__tests__/ethers.signer.spec.ts @@ -2,7 +2,7 @@ import { Module, Controller, Get, Injectable } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import * as nock from 'nock' import * as request from 'supertest' -import { EthersModule, EthersSigner } from '../src' +import { EthersModule, EthersSigner, InjectSignerProvider } from '../src' import { ETHERS_ADDRESS, ETHERS_PRIVATE_KEY, @@ -31,247 +31,564 @@ describe('EthersSigner', () => { for (const PlatformAdapter of platforms) { describe(PlatformAdapter.name, () => { - it('should create a wallet from a private ket with a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = this.ethersSigner.createWallet(ETHERS_PRIVATE_KEY) - - if (!wallet?.provider?.getNetwork()) { - throw new Error('No provider injected') + describe('forRoot', () => { + it('should create a wallet from a private ket with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWallet(ETHERS_PRIVATE_KEY) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() } - - return wallet.getAddress() } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address: address.toLowerCase() } + return { address: address.toLowerCase() } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], - }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} - await app.close() - }) + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { logger: false }) + const server = app.getHttpServer() - it('should create a random wallet With a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = this.ethersSigner.createRandomWallet() + await app.init() + await extraWait(PlatformAdapter, app) + await request(server) + .get('/') + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('address', ETHERS_ADDRESS) + }) - if (!wallet?.provider?.getNetwork()) { - throw new Error('No provider injected') - } + await app.close() + }) - return wallet.getAddress() + it('should create a random wallet With a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createRandomWallet() + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address } + return { address } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], - }) - 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.address).toBeDefined() + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} - await app.close() - }) + 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.address).toBeDefined() + }) - it('should create a wallet from an encrypted JSON with a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = await this.ethersSigner.createWalletfromEncryptedJson( - ETHERS_JSON_WALLET, - ETHERS_JSON_WALLET_PASSWORD, - ) - - if (!wallet?.provider?.getNetwork()) { - throw new Error('No provider injected') + await app.close() + }) + + it('should create a wallet from an encrypted JSON with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = await this.signer.createWalletfromEncryptedJson( + ETHERS_JSON_WALLET, + ETHERS_JSON_WALLET_PASSWORD, + ) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() } + } - return wallet.getAddress() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + class TestModule {} - return { address: address.toLowerCase() } + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should create a wallet from a mnemonic with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWalletfromMnemonic(ETHERS_MNEMONIC) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + + it('should create a void signer from an address with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [EthersModule.forRoot()], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} - await app.close() + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) }) - it('should create a wallet from a mnemonic with a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = this.ethersSigner.createWalletfromMnemonic(ETHERS_MNEMONIC) + describe('forRootAsync', () => { + it('should create a wallet from a private ket with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWallet(ETHERS_PRIVATE_KEY) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } + } - if (!wallet?.provider?.getNetwork()) { - throw new Error('No provider injected') + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) - return wallet.getAddress() + await app.close() + }) + + it('should create a random wallet With a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createRandomWallet() + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address: address.toLowerCase() } + return { address } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], - }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} - await app.close() - }) + 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.address).toBeDefined() + }) + + await app.close() + }) + + it('should create a wallet from an encrypted JSON with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = await this.signer.createWalletfromEncryptedJson( + ETHERS_JSON_WALLET, + ETHERS_JSON_WALLET_PASSWORD, + ) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } + } - it('should create a void signer from an address with a provider injected', async () => { - @Injectable() - class TestService { - constructor(private readonly ethersSigner: EthersSigner) {} - async someMethod(): Promise { - const wallet = this.ethersSigner.createVoidSigner(ETHERS_ADDRESS) + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - if (!wallet?.provider?.getNetwork()) { - throw new Error('No provider injected') + return { address: address.toLowerCase() } } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + class TestModule {} - return wallet.getAddress() + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) + + it('should create a wallet from a mnemonic with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createWalletfromMnemonic(ETHERS_MNEMONIC) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } } - } - @Controller('/') - class TestController { - constructor(private readonly service: TestService) {} - @Get() - async get() { - const address = await this.service.someMethod() + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() - return { address: address.toLowerCase() } + return { address: address.toLowerCase() } + } } - } - @Module({ - imports: [EthersModule.forRoot()], - controllers: [TestController], - providers: [TestService], + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], + }) + 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).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() }) - 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).toHaveProperty('address', ETHERS_ADDRESS) + + it('should create a void signer from an address with a provider injected', async () => { + @Injectable() + class TestService { + constructor( + @InjectSignerProvider() + private readonly signer: EthersSigner, + ) {} + async someMethod(): Promise { + const wallet = this.signer.createVoidSigner(ETHERS_ADDRESS) + + if (!wallet?.provider?.getNetwork()) { + throw new Error('No provider injected') + } + + return wallet.getAddress() + } + } + + @Controller('/') + class TestController { + constructor(private readonly service: TestService) {} + @Get() + async get() { + const address = await this.service.someMethod() + + return { address: address.toLowerCase() } + } + } + + @Module({ + imports: [ + EthersModule.forRootAsync({ + useFactory: () => { + return { + useDefaultProvider: true, + } + }, + }), + ], + controllers: [TestController], + providers: [TestService], }) + class TestModule {} + + const app = await NestFactory.create(TestModule, new PlatformAdapter(), { logger: false }) + const server = app.getHttpServer() - await app.close() + await app.init() + await extraWait(PlatformAdapter, app) + await request(server) + .get('/') + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('address', ETHERS_ADDRESS) + }) + + await app.close() + }) }) }) } diff --git a/__tests__/utils/constants.ts b/__tests__/utils/constants.ts index 342a3f4..9c10c17 100644 --- a/__tests__/utils/constants.ts +++ b/__tests__/utils/constants.ts @@ -1,7 +1,7 @@ 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_ALCHEMY_URL = 'https://eth-rinkeby.alchemyapi.io/v2' +export const RINKEBY_POCKET_URL = 'https://eth-rinkeby.gateway.pokt.network/v1' 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' @@ -9,6 +9,7 @@ export const TESTNET_BSCSCAN_URL = 'http://api-testnet.bscscan.com/api' export const CUSTOM_BSC_1_URL = 'https://data-seed-prebsc-1-s1.binance.org:8545' export const CUSTOM_BSC_2_URL = 'https://data-seed-prebsc-1-s3.binance.org:8545' export const CUSTOM_BSC_3_URL = 'https://data-seed-prebsc-2-s2.binance.org:8545' +export const MUMBAI_ALCHEMY_URL = 'https://polygon-mumbai.g.alchemy.com/v2/' 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') diff --git a/package.json b/package.json index 6e41a63..43b2a53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nestjs-ethers", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "The ethers.js library for NestJS", "author": "Jose Ramirez ", "license": "Apache", @@ -80,7 +80,7 @@ }, "jest": { "verbose": true, - "testTimeout": 20000, + "testTimeout": 80000, "moduleFileExtensions": [ "js", "json", diff --git a/src/ethers-core.module.ts b/src/ethers-core.module.ts index 27875bb..8724125 100644 --- a/src/ethers-core.module.ts +++ b/src/ethers-core.module.ts @@ -1,62 +1,66 @@ -import { BaseProvider } from '@ethersproject/providers' -import { DynamicModule, Global, Module, OnApplicationShutdown, Inject } from '@nestjs/common' -import { ModuleRef } from '@nestjs/core' -import { ETHERS_PROVIDER_NAME } from './ethers.constants' +import { Provider as AbstractProvider } from '@ethersproject/providers' +import { DynamicModule, Global, Module, OnApplicationShutdown } from '@nestjs/common' +import { DiscoveryModule, DiscoveryService } from '@nestjs/core' import { EthersContract } from './ethers.contract' import { EthersModuleOptions, EthersModuleAsyncOptions } from './ethers.interface' import { createEthersProvider, createEthersAsyncProvider, createAsyncOptionsProvider, - createProviderName, + createContractProvider, + createSignerProvider, } from './ethers.providers' import { EthersSigner } from './ethers.signer' @Global() -@Module({ - providers: [EthersSigner, EthersContract], - exports: [EthersSigner, EthersContract], -}) +@Module({}) export class EthersCoreModule implements OnApplicationShutdown { - constructor( - @Inject(ETHERS_PROVIDER_NAME) private readonly providerName: string, - private readonly moduleRef: ModuleRef, - ) {} + constructor(private readonly discoveryService: DiscoveryService) {} static forRoot(options: EthersModuleOptions): DynamicModule { const ethersProvider = createEthersProvider(options) + const contractProvider = createContractProvider(options.token) + const signerProvider = createSignerProvider(options.token) return { module: EthersCoreModule, - providers: [EthersSigner, EthersContract, ethersProvider, createProviderName()], - exports: [EthersSigner, EthersContract, ethersProvider], + imports: [DiscoveryModule], + providers: [EthersSigner, EthersContract, ethersProvider, contractProvider, signerProvider], + exports: [EthersSigner, EthersContract, ethersProvider, contractProvider, signerProvider], } } static forRootAsync(options: EthersModuleAsyncOptions): DynamicModule { - const ethersProvider = createEthersAsyncProvider() + const ethersProvider = createEthersAsyncProvider(options.token) const asyncOptionsProvider = createAsyncOptionsProvider(options) + const contractProvider = createContractProvider(options.token) + const signerProvider = createSignerProvider(options.token) return { module: EthersCoreModule, - imports: options.imports, + imports: [DiscoveryModule, ...(options.imports || [])], providers: [ EthersSigner, EthersContract, asyncOptionsProvider, ethersProvider, - createProviderName(), + contractProvider, + signerProvider, ...(options.providers || []), ], - exports: [EthersSigner, EthersContract, ethersProvider], + exports: [EthersSigner, EthersContract, ethersProvider, contractProvider, signerProvider], } } async onApplicationShutdown() { - const provider = this.moduleRef.get(this.providerName) + const providers = this.discoveryService.getProviders() ?? [] - if (provider) { - provider.removeAllListeners() - } + providers.forEach((provider) => { + const { instance } = provider ?? {} + + if (provider.isDependencyTreeStatic() && instance && instance instanceof AbstractProvider) { + instance.removeAllListeners() + } + }) } } diff --git a/src/ethers.constants.ts b/src/ethers.constants.ts index 848378e..add1d9f 100644 --- a/src/ethers.constants.ts +++ b/src/ethers.constants.ts @@ -1,8 +1,8 @@ import { Network } from '@ethersproject/providers' export const DECORATED_PREFIX = 'EthersJS' -export const ETHERS_PROVIDER_NAME = 'EthersProviderName' export const ETHERS_MODULE_OPTIONS = 'EthersModuleOptions' +export const DEFAULT_TOKEN = 'default' export const HOMESTEAD_NETWORK: Network = { chainId: 1, name: 'homestead', diff --git a/src/ethers.contract.ts b/src/ethers.contract.ts index 9f71b17..f03f2e7 100644 --- a/src/ethers.contract.ts +++ b/src/ethers.contract.ts @@ -1,23 +1,16 @@ import { VoidSigner } from '@ethersproject/abstract-signer' import { Contract, ContractInterface } from '@ethersproject/contracts' -import { BaseProvider } from '@ethersproject/providers' +import { Provider as AbstractProvider } from '@ethersproject/providers' import { Wallet } from '@ethersproject/wallet' -import { Injectable } from '@nestjs/common' -import { InjectEthersProvider } from './ethers.decorators' -export class SmartContract extends Contract { - constructor(address: string, abi: ContractInterface, provider: BaseProvider, signer?: Wallet | VoidSigner) { - const signerOrProvider: BaseProvider | Wallet | VoidSigner = signer ?? provider +export class EthersContract { + private readonly provider: AbstractProvider - super(address, abi, signerOrProvider) + constructor(provider: AbstractProvider) { + this.provider = provider } -} - -@Injectable() -export class EthersContract { - constructor(@InjectEthersProvider() private readonly provider: BaseProvider) {} - create(address: string, abi: ContractInterface, signer?: Wallet | VoidSigner): SmartContract { - return new SmartContract(address, abi, this.provider, signer) + create(address: string, abi: ContractInterface, signer?: Wallet | VoidSigner): Contract { + return new Contract(address, abi, signer ?? this.provider) } } diff --git a/src/ethers.decorators.ts b/src/ethers.decorators.ts index 9d760b8..12da342 100644 --- a/src/ethers.decorators.ts +++ b/src/ethers.decorators.ts @@ -1,4 +1,14 @@ import { Inject } from '@nestjs/common' -import { getEthersToken } from './ethers.utils' +import { getContractToken, getEthersToken, getSigneroken } from './ethers.utils' -export const InjectEthersProvider = () => Inject(getEthersToken()) +export const InjectEthersProvider = (token?: string) => { + return Inject(getEthersToken(token)) +} + +export const InjectContractProvider = (token?: string) => { + return Inject(getContractToken(token)) +} + +export const InjectSignerProvider = (token?: string) => { + return Inject(getSigneroken(token)) +} diff --git a/src/ethers.interface.ts b/src/ethers.interface.ts index 15b8d6b..c8d5e79 100644 --- a/src/ethers.interface.ts +++ b/src/ethers.interface.ts @@ -26,12 +26,14 @@ export interface EthersModuleOptions extends Record { bscscan?: string | undefined custom?: ConnectionInfo | string | (ConnectionInfo | string)[] | undefined quorum?: number | undefined + token?: string | undefined waitUntilIsConnected?: boolean | undefined useDefaultProvider?: boolean | undefined } export interface EthersModuleAsyncOptions extends Pick { - useFactory: (...args: any[]) => EthersModuleOptions | Promise + token?: string | undefined + useFactory: (...args: any[]) => Omit | Promise> inject?: any[] } diff --git a/src/ethers.providers.ts b/src/ethers.providers.ts index 2b783de..0515188 100644 --- a/src/ethers.providers.ts +++ b/src/ethers.providers.ts @@ -21,15 +21,11 @@ import { import { Provider } from '@nestjs/common' import { ConnectionInfo } from 'ethers/lib/utils' import { defer, lastValueFrom } from 'rxjs' -import { - ETHERS_MODULE_OPTIONS, - ETHERS_PROVIDER_NAME, - MAINNET_NETWORK, - BINANCE_NETWORK, - BINANCE_TESTNET_NETWORK, -} from './ethers.constants' +import { ETHERS_MODULE_OPTIONS, MAINNET_NETWORK, BINANCE_NETWORK, BINANCE_TESTNET_NETWORK } from './ethers.constants' +import { EthersContract } from './ethers.contract' import { EthersModuleOptions, EthersModuleAsyncOptions } from './ethers.interface' -import { getEthersToken } from './ethers.utils' +import { EthersSigner } from './ethers.signer' +import { getEthersToken, getContractToken, getSigneroken } from './ethers.utils' function validateBscNetwork(network: Networkish) { if (typeof network === 'number') { @@ -154,16 +150,16 @@ export async function createBaseProvider(options: EthersModuleOptions): Promise< export function createEthersProvider(options: EthersModuleOptions): Provider { return { - provide: getEthersToken(), + provide: getEthersToken(options.token), useFactory: async (): Promise => { return await lastValueFrom(defer(() => createBaseProvider(options))) }, } } -export function createEthersAsyncProvider(): Provider { +export function createEthersAsyncProvider(token?: string): Provider { return { - provide: getEthersToken(), + provide: getEthersToken(token), useFactory: async (options: EthersModuleOptions): Promise => { return await lastValueFrom(defer(() => createBaseProvider(options))) }, @@ -179,9 +175,22 @@ export function createAsyncOptionsProvider(options: EthersModuleAsyncOptions): P } } -export function createProviderName(): Provider { +export function createContractProvider(token?: string): Provider { return { - provide: ETHERS_PROVIDER_NAME, - useValue: getEthersToken(), + provide: getContractToken(token), + useFactory: async (provider: AbstractProvider): Promise => { + return await lastValueFrom(defer(async () => new EthersContract(provider))) + }, + inject: [getEthersToken(token)], + } +} + +export function createSignerProvider(token?: string): Provider { + return { + provide: getSigneroken(token), + useFactory: async (provider: AbstractProvider): Promise => { + return await lastValueFrom(defer(async () => new EthersSigner(provider))) + }, + inject: [getEthersToken(token)], } } diff --git a/src/ethers.signer.ts b/src/ethers.signer.ts index 39b542d..1586158 100644 --- a/src/ethers.signer.ts +++ b/src/ethers.signer.ts @@ -1,17 +1,18 @@ import { ExternallyOwnedAccount, VoidSigner } from '@ethersproject/abstract-signer' import { BytesLike } from '@ethersproject/bytes' import { ProgressCallback } from '@ethersproject/json-wallets' -import { BaseProvider } from '@ethersproject/providers' +import { Provider as AbstractProvider } from '@ethersproject/providers' import { SigningKey } from '@ethersproject/signing-key' import { Wallet } from '@ethersproject/wallet' import { Wordlist } from '@ethersproject/wordlists' -import { Injectable } from '@nestjs/common' -import { InjectEthersProvider } from './ethers.decorators' import { RandomWalletOptions } from './ethers.interface' -@Injectable() export class EthersSigner { - constructor(@InjectEthersProvider() private readonly provider: BaseProvider) {} + private readonly provider: AbstractProvider + + constructor(provider: AbstractProvider) { + this.provider = provider + } createWallet(privateKey: BytesLike | ExternallyOwnedAccount | SigningKey): Wallet { return new Wallet(privateKey, this.provider) diff --git a/src/ethers.utils.ts b/src/ethers.utils.ts index 705c297..b939e25 100644 --- a/src/ethers.utils.ts +++ b/src/ethers.utils.ts @@ -1,5 +1,13 @@ -import { DECORATED_PREFIX } from './ethers.constants' +import { DECORATED_PREFIX, DEFAULT_TOKEN } from './ethers.constants' -export function getEthersToken(): string { - return `${DECORATED_PREFIX}:Provider` +export function getEthersToken(token?: string): string { + return `${DECORATED_PREFIX}:Provider:${token || DEFAULT_TOKEN}` +} + +export function getContractToken(token?: string): string { + return `${DECORATED_PREFIX}:Contract:${token || DEFAULT_TOKEN}` +} + +export function getSigneroken(token?: string): string { + return `${DECORATED_PREFIX}:Signer:${token || DEFAULT_TOKEN}` } diff --git a/src/index.ts b/src/index.ts index 95100e1..b61bb16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { EthersModule } from './ethers.module' -export { InjectEthersProvider } from './ethers.decorators' +export { InjectEthersProvider, InjectContractProvider, InjectSignerProvider } from './ethers.decorators' export { InfuraProviderOptions, PocketProviderOptions, @@ -30,9 +30,9 @@ export { BINANCE_NETWORK, BINANCE_TESTNET_NETWORK, } from './ethers.constants' -export { getEthersToken } from './ethers.utils' +export { getEthersToken, getContractToken, getSigneroken } from './ethers.utils' export { EthersSigner } from './ethers.signer' -export { SmartContract, EthersContract } from './ethers.contract' +export { EthersContract } from './ethers.contract' export * from '@ethersproject/abi' export * from '@ethersproject/abstract-provider' export * from '@ethersproject/abstract-signer'