mirror of
https://github.com/emo2007/block-accounting.git
synced 2024-11-10 04:36:26 +00:00
multisig ready, deploy and interact controller
This commit is contained in:
parent
01371286d0
commit
ed2f6b9eca
43
chain-api/package-lock.json
generated
43
chain-api/package-lock.json
generated
@ -17,6 +17,8 @@
|
|||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/swagger": "^7.3.1",
|
"@nestjs/swagger": "^7.3.1",
|
||||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
@ -28,7 +30,7 @@
|
|||||||
"@nomicfoundation/hardhat-ethers": "^3.0.5",
|
"@nomicfoundation/hardhat-ethers": "^3.0.5",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.12.11",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
@ -5138,9 +5140,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.12.10",
|
"version": "20.12.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
|
||||||
"integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==",
|
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@ -5235,6 +5237,11 @@
|
|||||||
"@types/superagent": "*"
|
"@types/superagent": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/validator": {
|
||||||
|
"version": "13.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz",
|
||||||
|
"integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw=="
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.32",
|
"version": "17.0.32",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
|
||||||
@ -6714,6 +6721,21 @@
|
|||||||
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/class-transformer": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||||
|
},
|
||||||
|
"node_modules/class-validator": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/validator": "^13.11.8",
|
||||||
|
"libphonenumber-js": "^1.10.53",
|
||||||
|
"validator": "^13.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clean-stack": {
|
"node_modules/clean-stack": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
@ -11544,6 +11566,11 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libphonenumber-js": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz",
|
||||||
|
"integrity": "sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw=="
|
||||||
|
},
|
||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
@ -16173,6 +16200,14 @@
|
|||||||
"spdx-expression-parse": "^3.0.0"
|
"spdx-expression-parse": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/validator": {
|
||||||
|
"version": "13.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
|
||||||
|
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/swagger": "^7.3.1",
|
"@nestjs/swagger": "^7.3.1",
|
||||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
@ -39,7 +41,7 @@
|
|||||||
"@nomicfoundation/hardhat-ethers": "^3.0.5",
|
"@nomicfoundation/hardhat-ethers": "^3.0.5",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.12.11",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
@ -11,6 +11,7 @@ import { ContractFactoryService } from './contract-factory.service';
|
|||||||
import { CreateContractFactoryDto } from './dto/create-contract-factory.dto';
|
import { CreateContractFactoryDto } from './dto/create-contract-factory.dto';
|
||||||
import { UpdateContractFactoryDto } from './dto/update-contract-factory.dto';
|
import { UpdateContractFactoryDto } from './dto/update-contract-factory.dto';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { MultiSigWalletDto } from 'src/hardhat/modules/dto/multi-sig.dto';
|
||||||
@ApiTags('contract-factory')
|
@ApiTags('contract-factory')
|
||||||
@Controller('contract-factory')
|
@Controller('contract-factory')
|
||||||
export class ContractFactoryController {
|
export class ContractFactoryController {
|
||||||
@ -18,8 +19,8 @@ export class ContractFactoryController {
|
|||||||
private readonly contractFactoryService: ContractFactoryService,
|
private readonly contractFactoryService: ContractFactoryService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('')
|
@Post('multi-sig')
|
||||||
create(@Body() createContractFactoryDto: CreateContractFactoryDto) {
|
create(@Body() createContractFactoryDto: MultiSigWalletDto) {
|
||||||
return this.contractFactoryService.create(createContractFactoryDto);
|
return this.contractFactoryService.createMultiSig(createContractFactoryDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HardhatModule } from '../hardhat/module/hardhat.module';
|
import { HardhatModule } from '../hardhat/modules/hardhat.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ContractFactoryService } from './contract-factory.service';
|
import { ContractFactoryService } from './contract-factory.service';
|
||||||
import { ContractFactoryController } from './contract-factory.controller';
|
import { ContractFactoryController } from './contract-factory.controller';
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
import { HardhatService } from '../hardhat/module/hardhat.service';
|
import { HardhatService } from '../hardhat/modules/hardhat.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CreateContractFactoryDto } from './dto/create-contract-factory.dto';
|
import { CreateContractFactoryDto } from './dto/create-contract-factory.dto';
|
||||||
|
import { SalariesService } from 'src/hardhat/modules/salary.service';
|
||||||
|
import { MultiSigWalletService } from 'src/hardhat/modules/multi-sig/multi-sig.service';
|
||||||
|
import { MultiSigWalletDto } from 'src/hardhat/modules/dto/multi-sig.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ContractFactoryService {
|
export class ContractFactoryService {
|
||||||
constructor(private readonly hhService: HardhatService) {}
|
constructor(
|
||||||
async create(createContractFactoryDto: CreateContractFactoryDto) {
|
private readonly salaryService: SalariesService,
|
||||||
return await this.hhService.deploySalaryContract();
|
private readonly multiSigService: MultiSigWalletService,
|
||||||
|
) {}
|
||||||
|
async createSalary(createContractFactoryDto: CreateContractFactoryDto) {
|
||||||
|
return await this.salaryService.deploy();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createMultiSig(dto: MultiSigWalletDto) {
|
||||||
|
return await this.multiSigService.deploy(dto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Patch,
|
|
||||||
Param,
|
|
||||||
Delete,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ContractInteractService } from './contract-interact.service';
|
|
||||||
import { CreateContractInteractDto } from './dto/create-contract-interact.dto';
|
|
||||||
import { UpdateContractInteractDto } from './dto/update-contract-interact.dto';
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
@ApiTags('contract-interact')
|
|
||||||
@Controller('contract-interact')
|
|
||||||
export class ContractInteractController {
|
|
||||||
constructor(
|
|
||||||
private readonly contractInteractService: ContractInteractService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
create(@Body() createContractInteractDto: CreateContractInteractDto) {
|
|
||||||
return this.contractInteractService.create(createContractInteractDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
findAll() {
|
|
||||||
return this.contractInteractService.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
|
||||||
findOne(@Param('id') id: string) {
|
|
||||||
return this.contractInteractService.findOne(+id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch(':id')
|
|
||||||
update(
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() updateContractInteractDto: UpdateContractInteractDto,
|
|
||||||
) {
|
|
||||||
return this.contractInteractService.update(+id, updateContractInteractDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(':id')
|
|
||||||
remove(@Param('id') id: string) {
|
|
||||||
return this.contractInteractService.remove(+id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ContractInteractService } from './contract-interact.service';
|
import { ContractInteractService } from './contract-interact.service';
|
||||||
import { ContractInteractController } from './contract-interact.controller';
|
|
||||||
|
import { HardhatModule } from 'src/hardhat/modules/hardhat.module';
|
||||||
|
import { MultiSigInteractController } from './multi-sig-interact.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ContractInteractController],
|
imports: [HardhatModule],
|
||||||
|
controllers: [MultiSigInteractController],
|
||||||
providers: [ContractInteractService],
|
providers: [ContractInteractService],
|
||||||
})
|
})
|
||||||
export class ContractInteractModule {}
|
export class ContractInteractModule {}
|
||||||
|
18
chain-api/src/contract-interact/dto/multi-sig.dto.ts
Normal file
18
chain-api/src/contract-interact/dto/multi-sig.dto.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class SubmitTransactionDto {
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty()
|
||||||
|
contractAddress: string;
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
destination: string;
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty()
|
||||||
|
value: string;
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
// @ApiProperty()
|
||||||
|
data: string;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||||
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { MultiSigWalletService } from 'src/hardhat/modules/multi-sig/multi-sig.service';
|
||||||
|
import { SubmitTransactionDto } from './dto/multi-sig.dto';
|
||||||
|
@ApiTags('multi-sig-interact')
|
||||||
|
@Controller()
|
||||||
|
export class MultiSigInteractController {
|
||||||
|
constructor(private readonly multiSigWalletService: MultiSigWalletService) {}
|
||||||
|
|
||||||
|
@Get('owners/:address')
|
||||||
|
async getOwners(@Param('address') address: string) {
|
||||||
|
return this.multiSigWalletService.getOwners(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('submit-transaction')
|
||||||
|
async submitTransaction(@Body() dto: SubmitTransactionDto) {
|
||||||
|
return this.multiSigWalletService.submitTransaction(dto);
|
||||||
|
}
|
||||||
|
}
|
0
chain-api/src/hardhat/contracts/Agreement.sol
Normal file
0
chain-api/src/hardhat/contracts/Agreement.sol
Normal file
@ -8,7 +8,7 @@ pragma solidity ^0.8.19;
|
|||||||
contract MultiSigWallet {
|
contract MultiSigWallet {
|
||||||
event Deposit(address indexed sender, uint amount, uint balance);
|
event Deposit(address indexed sender, uint amount, uint balance);
|
||||||
event SubmitTransaction(
|
event SubmitTransaction(
|
||||||
address indexed owener,
|
address indexed owner,
|
||||||
uint indexed txIndex,
|
uint indexed txIndex,
|
||||||
address indexed to,
|
address indexed to,
|
||||||
uint value,
|
uint value,
|
||||||
@ -38,36 +38,36 @@ contract MultiSigWallet {
|
|||||||
Transaction[] public transactions;
|
Transaction[] public transactions;
|
||||||
|
|
||||||
modifier onlyOwner() {
|
modifier onlyOwner() {
|
||||||
require(isOwner[msg.sender], "not owner");
|
require(isOwner[msg.sender], 'not owner');
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier txExists(uint _txIndex) {
|
modifier txExists(uint _txIndex) {
|
||||||
require(_txIndex < transactions.length, "tx does not exist");
|
require(_txIndex < transactions.length, 'tx does not exist');
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier notConfirmed(uint _txIndex) {
|
modifier notConfirmed(uint _txIndex) {
|
||||||
require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
|
require(!isConfirmed[_txIndex][msg.sender], 'tx already confirmed');
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier notExecuted(uint _txIndex) {
|
modifier notExecuted(uint _txIndex) {
|
||||||
require(!transactions[_txIndex].executed, "tx already confirmed");
|
require(!transactions[_txIndex].executed, 'tx already confirmed');
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(address[] memory _owners, uint _numConfirmationsRequired) {
|
constructor(address[] memory _owners, uint _numConfirmationsRequired) {
|
||||||
require(_owners.length > 0, "owners required");
|
require(_owners.length > 0, 'owners required');
|
||||||
require(
|
require(
|
||||||
_numConfirmationsRequired > 0 &&
|
_numConfirmationsRequired > 0 &&
|
||||||
_numConfirmationsRequired <= _owners.length,
|
_numConfirmationsRequired <= _owners.length,
|
||||||
"invalid number of required confirmations"
|
'invalid number of required confirmations'
|
||||||
);
|
);
|
||||||
for (uint i = 0; i < _owners.length; i++) {
|
for (uint i = 0; i < _owners.length; i++) {
|
||||||
address owner = _owners[i];
|
address owner = _owners[i];
|
||||||
require(owner != address(0), "invalid owner");
|
require(owner != address(0), 'invalid owner');
|
||||||
require(!isOwner[owner], "owner not unique");
|
require(!isOwner[owner], 'owner not unique');
|
||||||
isOwner[owner] = true;
|
isOwner[owner] = true;
|
||||||
owners.push(owner);
|
owners.push(owner);
|
||||||
}
|
}
|
||||||
@ -117,13 +117,13 @@ contract MultiSigWallet {
|
|||||||
Transaction storage transaction = transactions[_txIndex];
|
Transaction storage transaction = transactions[_txIndex];
|
||||||
require(
|
require(
|
||||||
transaction.numConfirmations >= numConfirmationsRequired,
|
transaction.numConfirmations >= numConfirmationsRequired,
|
||||||
"cannot execute tx"
|
'cannot execute tx'
|
||||||
);
|
);
|
||||||
transaction.executed = true;
|
transaction.executed = true;
|
||||||
(bool success, ) = transaction.to.call{value: transaction.value}(
|
(bool success, ) = transaction.to.call{value: transaction.value}(
|
||||||
transaction.data
|
transaction.data
|
||||||
);
|
);
|
||||||
require(success, "tx failed");
|
require(success, 'tx failed');
|
||||||
emit ExecuteTransaction(msg.sender, _txIndex);
|
emit ExecuteTransaction(msg.sender, _txIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ contract MultiSigWallet {
|
|||||||
uint _txIndex
|
uint _txIndex
|
||||||
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
|
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
|
||||||
Transaction storage transaction = transactions[_txIndex];
|
Transaction storage transaction = transactions[_txIndex];
|
||||||
require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");
|
require(isConfirmed[_txIndex][msg.sender], 'tx not confirmed');
|
||||||
transaction.numConfirmations -= 1;
|
transaction.numConfirmations -= 1;
|
||||||
isConfirmed[_txIndex][msg.sender] = false;
|
isConfirmed[_txIndex][msg.sender] = false;
|
||||||
|
|
||||||
|
@ -1,26 +1,62 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.7;
|
pragma solidity ^0.8.7;
|
||||||
|
|
||||||
import {AggregatorV3Interface} from '@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol';
|
import {AggregatorV3Interface} from '@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol';
|
||||||
|
|
||||||
contract Salaries {
|
contract Salaries {
|
||||||
AggregatorV3Interface internal dataFeed;
|
AggregatorV3Interface internal dataFeed;
|
||||||
|
address public multisigWallet;
|
||||||
|
mapping(address => uint) public salaries;
|
||||||
|
|
||||||
constructor() {
|
constructor(address _multisigWallet, address _priceFeedAddress) {
|
||||||
dataFeed = AggregatorV3Interface(
|
multisigWallet = _multisigWallet;
|
||||||
0xF0d50568e3A7e8259E16663972b11910F89BD8e7
|
dataFeed = AggregatorV3Interface(_priceFeedAddress);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChainlinkDataFeedLatestAnswer() public view returns (int) {
|
modifier onlyMultisig() {
|
||||||
// prettier-ignore
|
require(msg.sender == multisigWallet, 'Unauthorized');
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLatestUSDTPriceInETH() public view returns (int) {
|
||||||
(
|
(
|
||||||
/* uint80 roundID */,
|
,
|
||||||
int answer,
|
/* uint80 roundID */ int answer /* uint startedAt */ /* uint timeStamp */ /* uint80 answeredInRound */,
|
||||||
/*uint startedAt*/,
|
,
|
||||||
/*uint timeStamp*/,
|
,
|
||||||
/*uint80 answeredInRound*/
|
|
||||||
) = dataFeed.latestRoundData();
|
) = dataFeed.latestRoundData();
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSalary(
|
||||||
|
address employee,
|
||||||
|
uint salaryInUSDT
|
||||||
|
) external onlyMultisig {
|
||||||
|
salaries[employee] = salaryInUSDT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function payoutInETH(address employee) external onlyMultisig {
|
||||||
|
uint salaryInUSDT = salaries[employee];
|
||||||
|
require(salaryInUSDT > 0, 'No salary set');
|
||||||
|
|
||||||
|
int ethToUSDT = getLatestUSDTPriceInETH();
|
||||||
|
require(ethToUSDT > 0, 'Invalid price data');
|
||||||
|
|
||||||
|
// Convert salary from USDT to ETH based on the latest price
|
||||||
|
uint salaryInETH = uint(salaryInUSDT * 1e18) / uint(ethToUSDT);
|
||||||
|
|
||||||
|
// Check sufficient balance
|
||||||
|
require(
|
||||||
|
address(this).balance >= salaryInETH,
|
||||||
|
'Insufficient contract balance'
|
||||||
|
);
|
||||||
|
|
||||||
|
salaries[employee] = 0; // Reset salary after payment
|
||||||
|
payable(employee).transfer(salaryInETH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to receive ETH
|
||||||
|
receive() external payable {}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { HardhatService } from './hardhat.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [],
|
|
||||||
controllers: [],
|
|
||||||
providers: [HardhatService],
|
|
||||||
exports: [HardhatService],
|
|
||||||
})
|
|
||||||
export class HardhatModule {}
|
|
12
chain-api/src/hardhat/modules/base-contract.service.ts
Normal file
12
chain-api/src/hardhat/modules/base-contract.service.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ProviderService } from 'src/provider/provider.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export abstract class BaseContractService {
|
||||||
|
constructor(
|
||||||
|
public readonly configService: ConfigService,
|
||||||
|
public readonly providerService: ProviderService,
|
||||||
|
) {}
|
||||||
|
abstract deploy(dto: object): Promise<any>;
|
||||||
|
}
|
11
chain-api/src/hardhat/modules/dto/multi-sig.dto.ts
Normal file
11
chain-api/src/hardhat/modules/dto/multi-sig.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsNumber } from 'class-validator';
|
||||||
|
|
||||||
|
export class MultiSigWalletDto {
|
||||||
|
@IsArray()
|
||||||
|
@ApiProperty()
|
||||||
|
owners: string[];
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty()
|
||||||
|
confirmations: number;
|
||||||
|
}
|
15
chain-api/src/hardhat/modules/hardhat.module.ts
Normal file
15
chain-api/src/hardhat/modules/hardhat.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { HardhatService } from './hardhat.service';
|
||||||
|
import { ProviderModule } from 'src/provider/provider.module';
|
||||||
|
import { MultiSigWalletService } from './multi-sig/multi-sig.service';
|
||||||
|
import { SalariesService } from './salary.service';
|
||||||
|
import { BaseContractService } from './base-contract.service';
|
||||||
|
import { MultiSigModule } from './multi-sig/multi-sig.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ProviderModule, MultiSigModule],
|
||||||
|
controllers: [],
|
||||||
|
providers: [HardhatService, SalariesService],
|
||||||
|
exports: [HardhatService, SalariesService, MultiSigModule],
|
||||||
|
})
|
||||||
|
export class HardhatModule {}
|
4
chain-api/src/hardhat/modules/hardhat.service.ts
Normal file
4
chain-api/src/hardhat/modules/hardhat.service.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HardhatService {}
|
15
chain-api/src/hardhat/modules/multi-sig/multi-sig.module.ts
Normal file
15
chain-api/src/hardhat/modules/multi-sig/multi-sig.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ProviderModule } from 'src/provider/provider.module';
|
||||||
|
|
||||||
|
import { BaseContractService } from '../base-contract.service';
|
||||||
|
import { ProviderService } from 'src/provider/provider.service';
|
||||||
|
import { MultiSigWalletService } from './multi-sig.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ProviderModule],
|
||||||
|
controllers: [],
|
||||||
|
providers: [MultiSigWalletService],
|
||||||
|
exports: [MultiSigWalletService],
|
||||||
|
})
|
||||||
|
export class MultiSigModule {}
|
68
chain-api/src/hardhat/modules/multi-sig/multi-sig.service.ts
Normal file
68
chain-api/src/hardhat/modules/multi-sig/multi-sig.service.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { MultiSigWallet } from '../../typechain-types/contracts/MultiSigWallet';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import * as hre from 'hardhat';
|
||||||
|
import { BaseContractService } from '../base-contract.service';
|
||||||
|
import { MultiSigWalletDto } from '../dto/multi-sig.dto';
|
||||||
|
import { SubmitTransactionDto } from 'src/contract-interact/dto/multi-sig.dto';
|
||||||
|
|
||||||
|
export class MultiSigWalletService extends BaseContractService {
|
||||||
|
async deploy(dto: MultiSigWalletDto) {
|
||||||
|
const { abi, bytecode } =
|
||||||
|
await hre.artifacts.readArtifact('MultiSigWallet');
|
||||||
|
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const salaryContract = new ethers.ContractFactory(abi, bytecode, signer);
|
||||||
|
|
||||||
|
const myContract = await salaryContract.deploy(
|
||||||
|
dto.owners,
|
||||||
|
dto.confirmations,
|
||||||
|
);
|
||||||
|
await myContract.waitForDeployment();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'🚀 ~ HardhatService ~ deploySalaryContract ~ myContract:',
|
||||||
|
myContract,
|
||||||
|
);
|
||||||
|
const address = myContract.getAddress();
|
||||||
|
console.log('🚀 ~ SalariesService ~ deploy ~ address:', address);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOwners(address: string) {
|
||||||
|
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
||||||
|
const multiSigContract = new ethers.Contract(address, abi);
|
||||||
|
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(address, abi, signer);
|
||||||
|
|
||||||
|
const owners = await contract.getOwners();
|
||||||
|
|
||||||
|
return owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitTransaction(dto: SubmitTransactionDto) {
|
||||||
|
const { destination, value, data, contractAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
||||||
|
const multiSigContract = new ethers.Contract(contractAddress, abi);
|
||||||
|
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
console.log(
|
||||||
|
'🚀 ~ MultiSigWalletService ~ submitTransaction ~ contract:',
|
||||||
|
contract.interface,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tx = await contract.submitTransaction(
|
||||||
|
destination,
|
||||||
|
value,
|
||||||
|
new TextEncoder().encode(data),
|
||||||
|
);
|
||||||
|
console.log('🚀 ~ MultiSigWalletService ~ submitTransaction ~ tx:', tx);
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,19 @@
|
|||||||
// const hre = require('hardhat');
|
|
||||||
import * as hre from 'hardhat';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import * as hre from 'hardhat';
|
||||||
|
import { BaseContractService } from './base-contract.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HardhatService {
|
export class SalariesService extends BaseContractService {
|
||||||
constructor(private readonly configService: ConfigService) {}
|
getSalaries() {}
|
||||||
async deploySalaryContract() {
|
|
||||||
const provider = new ethers.JsonRpcProvider(
|
async deploy() {
|
||||||
'https://polygon-amoy.g.alchemy.com/v2/pEtFFy_Qr_NrM1vMnlzSXmYXkozVNzLy',
|
const provider = await this.providerService.getProvider();
|
||||||
80002,
|
|
||||||
);
|
|
||||||
|
|
||||||
const salary = await hre.artifacts.readArtifact('Salaries');
|
const salary = await hre.artifacts.readArtifact('Salaries');
|
||||||
const abi = salary.abi;
|
const abi = salary.abi;
|
||||||
console.log('🚀 ~ HardhatService ~ deploySalaryContract ~ abi:', abi);
|
|
||||||
const bytecode = salary.deployedBytecode;
|
const bytecode = salary.deployedBytecode;
|
||||||
console.log(
|
|
||||||
'🚀 ~ HardhatService ~ deploySalaryContract ~ bytecode:',
|
|
||||||
bytecode,
|
|
||||||
);
|
|
||||||
const signer = new ethers.Wallet(
|
const signer = new ethers.Wallet(
|
||||||
this.configService.getOrThrow('POLYGON_PK'),
|
this.configService.getOrThrow('POLYGON_PK'),
|
||||||
provider,
|
provider,
|
||||||
@ -31,12 +25,17 @@ export class HardhatService {
|
|||||||
signer,
|
signer,
|
||||||
);
|
);
|
||||||
|
|
||||||
const myContract = await salaryContract.deploy();
|
const myContract = await salaryContract.deploy(
|
||||||
|
'multisig address',
|
||||||
|
this.configService.getOrThrow('CHAINLINK_AGGREGATOR_V3'),
|
||||||
|
);
|
||||||
await myContract.waitForDeployment();
|
await myContract.waitForDeployment();
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'🚀 ~ HardhatService ~ deploySalaryContract ~ myContract:',
|
'🚀 ~ HardhatService ~ deploySalaryContract ~ myContract:',
|
||||||
myContract,
|
myContract,
|
||||||
);
|
);
|
||||||
|
const address = myContract.getAddress();
|
||||||
|
console.log('🚀 ~ SalariesService ~ deploy ~ address:', address);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
@ -11,7 +12,9 @@ async function bootstrap() {
|
|||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api', app, document);
|
SwaggerModule.setup('api', app, document);
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
|
|
||||||
console.log('Swagger avaliable at http://localhost:3000/api');
|
console.log('Swagger avaliable at http://localhost:3000/api');
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
10
chain-api/src/provider/provider.module.ts
Normal file
10
chain-api/src/provider/provider.module.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ProviderService } from './provider.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
controllers: [],
|
||||||
|
providers: [ProviderService],
|
||||||
|
exports: [ProviderService],
|
||||||
|
})
|
||||||
|
export class ProviderModule {}
|
38
chain-api/src/provider/provider.service.ts
Normal file
38
chain-api/src/provider/provider.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
@Injectable()
|
||||||
|
export class ProviderService {
|
||||||
|
public provider: ethers.JsonRpcProvider;
|
||||||
|
public networkId: number;
|
||||||
|
private nodeUrl: string;
|
||||||
|
constructor(private readonly configService: ConfigService) {
|
||||||
|
this.networkId = parseInt(
|
||||||
|
this.configService.getOrThrow('POLYGON_NETWORK_ID'),
|
||||||
|
);
|
||||||
|
this.nodeUrl = this.configService.getOrThrow('POLYGON_NODE');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProvider() {
|
||||||
|
if (this.provider) {
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
const polygonProvider = new ethers.JsonRpcProvider(
|
||||||
|
this.nodeUrl,
|
||||||
|
this.networkId,
|
||||||
|
);
|
||||||
|
this.provider = polygonProvider;
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSigner() {
|
||||||
|
if (!this.provider) {
|
||||||
|
await this.getProvider();
|
||||||
|
}
|
||||||
|
const signer = new ethers.Wallet(
|
||||||
|
this.configService.getOrThrow('POLYGON_PK'),
|
||||||
|
this.provider,
|
||||||
|
);
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user