diff --git a/server/src/app.module.ts b/server/src/app.module.ts index f511350..13de576 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -6,6 +6,7 @@ import { GhostModule } from './port/module/ghost.module'; import * as path from 'path'; import {UserModule} from './port/module/user.module'; import {DatabaseModule} from './port/module/database.module'; +import {EventSubscriberModule} from './port/adapter/service/blockchain/ethereum/event/event.subscriber.module'; @Module({ imports: [ @@ -13,8 +14,10 @@ import {DatabaseModule} from './port/module/database.module'; GhostModule, UserModule, DatabaseModule, + EventSubscriberModule, ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule { +} diff --git a/server/src/config/auction.ts b/server/src/config/auction.ts new file mode 100644 index 0000000..a9bd750 --- /dev/null +++ b/server/src/config/auction.ts @@ -0,0 +1,7 @@ +export default { + abi: [{ + name: 'auction', + type: 'function', + }], + address: 'ax1234', +}; diff --git a/server/src/config/ghost.ts b/server/src/config/ghost.ts new file mode 100644 index 0000000..e06b0ba --- /dev/null +++ b/server/src/config/ghost.ts @@ -0,0 +1,7 @@ +export default { + abi: [{ + name: 'ghost', + type: 'function', + }], + address: 'ax1234', +}; diff --git a/server/src/config/subscriber.ts b/server/src/config/subscriber.ts new file mode 100644 index 0000000..7edd576 --- /dev/null +++ b/server/src/config/subscriber.ts @@ -0,0 +1,5 @@ +export default { + startBlock: 5853011, + endBlock: 'latest', + network: 'testNet', +}; diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.module.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.module.ts deleted file mode 100644 index 48f7f64..0000000 --- a/server/src/port/adapter/service/blockchain/ethereum/event/event.module.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Module } from '@nestjs/common'; - -@Module({}) -export class EventModule {} diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.service.spec.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.service.spec.ts deleted file mode 100644 index 78cb816..0000000 --- a/server/src/port/adapter/service/blockchain/ethereum/event/event.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EventService } from './event.service'; - -describe('EventService', () => { - let service: EventService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [EventService], - }).compile(); - - service = module.get(EventService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.service.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.service.ts deleted file mode 100644 index 04be74e..0000000 --- a/server/src/port/adapter/service/blockchain/ethereum/event/event.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class EventService { - constructor() {} -} diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.module.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.module.ts new file mode 100644 index 0000000..e1ed133 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.module.ts @@ -0,0 +1,17 @@ +import {Module} from '@nestjs/common'; +import {EventSubscriber} from './event.subscriber'; +import {Web3Module} from '../web3/web3.module'; +import {Web3Config} from '../web3/web3.config'; + +export const eventSubscriber = { + provide: 'EVENT_SUBSCRIBER', + useClass: EventSubscriber, +}; + +@Module({ + providers: [EventSubscriber, Web3Config, eventSubscriber], + imports: [Web3Module], + exports: [eventSubscriber], +}) +export class EventSubscriberModule { +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.spec.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.spec.ts new file mode 100644 index 0000000..9367b79 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.spec.ts @@ -0,0 +1,24 @@ +import {Test, TestingModule} from '@nestjs/testing'; +import {EventSubscriber} from './event.subscriber'; +import {Web3Module} from '../web3/web3.module'; +import * as path from 'path'; +import {ConfigModule} from 'nestjs-config'; + +describe('EventSubscriber', () => { + const testUrl = 'ws://localhost:8545'; + let service: EventSubscriber; + + afterAll(() => setTimeout(() => process.exit(), 1000)); + + describe('dependency resolve', () => { + it('should be defined', async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [EventSubscriber], + imports: [ + Web3Module, ConfigModule.load(path.resolve('/test/socket', 'config', '**/!(*.d).{ts,js}')), + ], + }).compile(); + service = module.get(EventSubscriber); + }); + }); +}); diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.ts b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.ts new file mode 100644 index 0000000..4a6a0f5 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/event/event.subscriber.ts @@ -0,0 +1,33 @@ +import {Injectable} from '@nestjs/common'; +import {Contracts} from '../web3/contract/contracts'; +import {EventHandler} from './handler/event.handler'; +import {EventData} from 'web3-eth-contract'; + +@Injectable() +export class EventSubscriber { + constructor(private eventHandler: EventHandler) { + this.contractList = this.eventHandler.getContractList(); + } + + contractList: Contracts[]; + currentBlockNum: number; + + async trackEvents(): Promise { + this.currentBlockNum = await this.eventHandler.getBlockNum(); + for (const index in this.contractList) { + if (this.contractList[index].blockNum !== this.currentBlockNum) { + const datas: EventData[] + = await this.contractList[index].contract.getPastEvents('allEvents', { + fromBlock: this.contractList[index].blockNum, + toBlock: 'latest', + }); + this.contractList[index].blockNum++; + await this.eventHandler.parseData(datas); + } + } + } + + subscribeEvents(): void { + setInterval(() => this.trackEvents(), 1000); + } +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/handler/event.handler.ts b/server/src/port/adapter/service/blockchain/ethereum/event/handler/event.handler.ts new file mode 100644 index 0000000..220dc3d --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/event/handler/event.handler.ts @@ -0,0 +1,123 @@ +import {EventData} from 'web3-eth-contract'; +import {Inject} from '@nestjs/common'; +import Web3 from 'web3'; +import {ContractFactory} from '../../web3/contract/contract.factory'; +import {Contracts} from '../../web3/contract/contracts'; + +export class EventHandler { + constructor(@Inject('WEB3') private web3: Web3, private contracts: ContractFactory) { + this.contractList = contracts.getContracts(); + } + + contractList: Contracts[]; + + getContractList(): Contracts[] { + return this.contractList; + } + + async getBlockNum(): Promise { + return await this.web3.eth.getBlockNumber(); + } + + async parseData(datas: EventData[]): Promise { + let parsedData: { [key: string]: any; }; + + for (const data of datas) { + const hexData = data.raw.data; + switch (data.event) { + case 'AuctionCreated': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'uint256', + name: 'gene', + }, { + type: 'uint256', + name: 'duration', + }, { + type: 'uint256', + name: 'auctionType', + }], hexData); + // auctionCreated + // createAuction(parsedData.gene, parsedData.duration, parsedData.auctionType); + break; + case 'AuctionSuccessful': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'uint256', + name: 'gene', + }, { + type: 'uint256', + name: 'maxPrice', + }, { + type: 'address', + name: 'winner', + }], hexData); + // auctionSuccessed + // updateWinner(parsedData.gene, parsedData.winner, parsedData.maxPrice); + break; + case 'AuctionCancelled': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'uint256', + name: 'gene', + }], hexData); + // auctionCancelled + // cancelAuction(parsedData.gene); + break; + case 'Birth': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'address', + name: 'owner', + }, { + type: 'uint256', + name: 'tokenId', + }, { + type: 'uint256', + name: 'gene', + }], hexData); + // 알 생성 + // createEgg(parsedData.owner, parsedData.gene); + break; + case 'LevelUp': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'address', + name: 'owner', + }, { + type: 'uint256', + name: 'gene', + }, { + type: 'uint256', + name: 'level', + }], hexData); + // level up + // levelUp(parsedData.owner, parsedData.gene, parsedData.level); + break; + case 'Transfer': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'address', + name: 'from', + }, { + type: 'address', + name: 'to', + }, { + type: 'uint256', + name: 'gene', + }], hexData); + // transfer + // transfer(parsedData.from, parsedData.to, parsedData.gene); + break; + case 'Approval': + parsedData = await this.web3.eth.abi.decodeParameters([{ + type: 'address', + name: 'from', + }, { + type: 'address', + name: 'to', + }, { + type: 'uint256', + name: 'tokenId', + }], hexData); + // approval + // ? + break; + } + } + } +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/event/test/config/subscriber.ts b/server/src/port/adapter/service/blockchain/ethereum/event/test/config/subscriber.ts new file mode 100644 index 0000000..8188223 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/event/test/config/subscriber.ts @@ -0,0 +1,4 @@ +export default { + startBlock: 5853011, + endBlock: 'latest', +}; diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.spec.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.spec.ts new file mode 100644 index 0000000..df71513 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.spec.ts @@ -0,0 +1,28 @@ +import {Test, TestingModule} from '@nestjs/testing'; +import * as path from 'path'; +import {ConfigModule} from 'nestjs-config'; +import {ContractFactory} from './contract.factory'; +import {Web3Module} from '../web3.module'; +import Web3 from 'web3'; + +describe('ContractFactory', () => { + let contractFactory: ContractFactory; + let web3: Web3; + + afterAll(() => setTimeout(() => process.exit(), 1000)); + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ContractFactory], + imports: [ + Web3Module, ConfigModule.load(path.resolve(__dirname + '/test/socket', 'config', '**/!(*.d).{ts,js}')), + ], + }).compile(); + contractFactory = module.get(ContractFactory); + web3 = new Web3(new Web3.providers.WebsocketProvider('ws://localhost:8545')); + }); + + describe('#getContract()', () => { + it('should return specific Contract', async () => { + }); + }); +}); diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.ts new file mode 100644 index 0000000..faf2a1d --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.factory.ts @@ -0,0 +1,24 @@ +import {Inject, Injectable} from '@nestjs/common'; +import Web3 from 'web3'; +import {InjectConfig} from 'nestjs-config'; +import {Contracts} from './contracts'; + +@Injectable() +export class ContractFactory { + contracts: Contracts[]; + + constructor(@Inject('WEB3') private web3: Web3, @InjectConfig() private config) { + this.setContracts(); + } + + setContracts(): void { + this.contracts[0] = new Contracts('Auction', this.config.get('subscriber.startBlock'), + new this.web3.eth.Contract(this.config.get('auction.abi'), this.config.get('auction.address'))); + this.contracts[1] = new Contracts('Ghost', this.config.get('subscriber.startBlock'), + new this.web3.eth.Contract(this.config.get('ghost.abi'), this.config.get('ghost.address'))); + } + + getContracts(): Contracts[] { + return this.contracts; + } +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.module.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.module.ts new file mode 100644 index 0000000..16226b9 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contract.module.ts @@ -0,0 +1,17 @@ +import {Module} from '@nestjs/common'; +import {ContractFactory} from './contract.factory'; +import {Web3Config} from '../web3.config'; +import {Web3Module} from '../web3.module'; + +export const contractProvider = { + provide: 'CONTRACT', + useClass: ContractFactory, +}; + +@Module({ + providers: [Web3Config, contractProvider], + exports: [contractProvider], + imports: [Web3Module], +}) +export class ContractFactoryModule { +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contracts.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contracts.ts new file mode 100644 index 0000000..96aa060 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/contract/contracts.ts @@ -0,0 +1,13 @@ +import {Contract} from 'web3-eth-contract'; + +export class Contracts { + contractName: string; + blockNum: number; + contract: Contract; + + constructor(contractName, blockNum, contract) { + this.contractName = contractName; + this.blockNum = blockNum; + this.contract = contract; + } +} diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/auction.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/auction.ts new file mode 100644 index 0000000..5f9d8f2 --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/auction.ts @@ -0,0 +1,5 @@ +export default { + abi : [{ + test: 'test', + }], +}; diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/ghost.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/ghost.ts new file mode 100644 index 0000000..3c7943e --- /dev/null +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/ghost.ts @@ -0,0 +1,5 @@ +export default { + abi: [{ + test: 'test', + }], +}; diff --git a/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/web3.ts b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/web3.ts index d83afa3..6afb8f2 100644 --- a/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/web3.ts +++ b/server/src/port/adapter/service/blockchain/ethereum/web3/test/socket/config/web3.ts @@ -1,4 +1,7 @@ export default { - url: 'localhost:8545', + url: 'ws://localhost:8545', type: 'socket', + abi : [{ + test: 'test', + }], };