mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-01-18 15:36:27 +00:00
license init, deploy block
This commit is contained in:
parent
ebf2edafff
commit
b87cc10c3d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
.env
|
1
chain-api/package-lock.json
generated
1
chain-api/package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainlink/contracts": "^1.1.0",
|
"@chainlink/contracts": "^1.1.0",
|
||||||
|
"@ethersproject/address": "^5.7.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.2",
|
"@nestjs/config": "^3.2.2",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
|
"compile": "npx hardhat compile",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
@ -21,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainlink/contracts": "^1.1.0",
|
"@chainlink/contracts": "^1.1.0",
|
||||||
|
"@ethersproject/address": "^5.7.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.2",
|
"@nestjs/config": "^3.2.2",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ProviderService } from './provider/provider.service';
|
||||||
import { ProviderService } from 'src/provider/provider.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export abstract class BaseContractService {
|
export abstract class BaseContractService {
|
10
chain-api/src/base/base.module.ts
Normal file
10
chain-api/src/base/base.module.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ProviderModule } from './provider/provider.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ProviderModule],
|
||||||
|
controllers: [],
|
||||||
|
providers: [ProviderModule],
|
||||||
|
exports: [ProviderModule],
|
||||||
|
})
|
||||||
|
export class BaseModule {}
|
12
chain-api/src/config/chainlink.config.ts
Normal file
12
chain-api/src/config/chainlink.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const CHAINLINK = {
|
||||||
|
AMOY: {
|
||||||
|
CHAINLINK_TOKEN: '0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904',
|
||||||
|
ORACLE_ADDRESS: '0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46',
|
||||||
|
AGGREGATOR_ADDRESS: {
|
||||||
|
USDT_ETH: '0xF0d50568e3A7e8259E16663972b11910F89BD8e7',
|
||||||
|
},
|
||||||
|
JOB_IDS: {
|
||||||
|
UINT: 'a8356f48569c434eaa4ac5fcb4db5cc0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { HardhatModule } from 'src/hardhat/modules/hardhat.module';
|
import { SalariesModule } from './salaries/salaries.module';
|
||||||
|
import { MultiSigModule } from './multi-sig/multi-sig.module';
|
||||||
|
import { LicenseModule } from './license/license.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HardhatModule],
|
imports: [SalariesModule, MultiSigModule, LicenseModule],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class CreateContractInteractDto {
|
|
||||||
@ApiProperty()
|
|
||||||
contractAddress: string;
|
|
||||||
@ApiProperty()
|
|
||||||
sender: string;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
import { PartialType } from '@nestjs/swagger';
|
|
||||||
import { CreateContractInteractDto } from './create-contract-interact.dto';
|
|
||||||
|
|
||||||
export class UpdateContractInteractDto extends PartialType(CreateContractInteractDto) {}
|
|
@ -1,11 +0,0 @@
|
|||||||
import { TransactionReceipt, ethers } from 'ethers';
|
|
||||||
|
|
||||||
export const parseLogs = (
|
|
||||||
txReceipt: TransactionReceipt,
|
|
||||||
contract: ethers.Contract,
|
|
||||||
eventName: string,
|
|
||||||
) => {
|
|
||||||
return txReceipt.logs
|
|
||||||
.map((log) => contract.interface.parseLog(log))
|
|
||||||
.find((log) => !!log && log.fragment.name === eventName);
|
|
||||||
};
|
|
@ -0,0 +1,42 @@
|
|||||||
|
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||||
|
import { LicenseService } from './license.service';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
DeployLicenseDto,
|
||||||
|
GetShareLicense,
|
||||||
|
RequestLicenseDto,
|
||||||
|
} from './license.dto';
|
||||||
|
@ApiTags('license')
|
||||||
|
@Controller('license')
|
||||||
|
export class LicenseController {
|
||||||
|
constructor(private readonly licenseService: LicenseService) {}
|
||||||
|
@Get('request')
|
||||||
|
async getLicenseRequest(@Body() dto: RequestLicenseDto) {
|
||||||
|
return this.licenseService.request(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('deploy')
|
||||||
|
async deploy(@Body() dto: DeployLicenseDto) {
|
||||||
|
return this.licenseService.deploy(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('total-payout')
|
||||||
|
async getLicenseResponse(@Body() dto: RequestLicenseDto) {
|
||||||
|
return this.licenseService.getTotalPayoutInUSD(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('shares')
|
||||||
|
async getShares(@Body() dto: GetShareLicense) {
|
||||||
|
return this.licenseService.getShares(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('owners')
|
||||||
|
async getOwners(@Body() dto: GetShareLicense) {
|
||||||
|
return this.licenseService.getOwners(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('payout-contract')
|
||||||
|
async getPayoutContract(@Body() dto: GetShareLicense) {
|
||||||
|
return this.licenseService.getPayoutContract(dto);
|
||||||
|
}
|
||||||
|
}
|
37
chain-api/src/contract-interact/license/license.dto.ts
Normal file
37
chain-api/src/contract-interact/license/license.dto.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { IsArray, IsNumber, IsString } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
export class GetLicenseInfoDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
contractAddress: string;
|
||||||
|
}
|
||||||
|
export class DeployLicenseDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
multiSigWallet: string;
|
||||||
|
@ApiProperty()
|
||||||
|
@IsArray()
|
||||||
|
owners: string[];
|
||||||
|
@ApiProperty({
|
||||||
|
isArray: true,
|
||||||
|
type: Number,
|
||||||
|
})
|
||||||
|
@IsNumber({}, { each: true })
|
||||||
|
shares: number[];
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
payrollAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestLicenseDto extends GetLicenseInfoDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
multiSigWallet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetLicenseResponseDto extends GetLicenseInfoDto {}
|
||||||
|
|
||||||
|
export class GetShareLicense extends GetLicenseInfoDto {
|
||||||
|
@IsString()
|
||||||
|
ownerAddress: string;
|
||||||
|
}
|
13
chain-api/src/contract-interact/license/license.module.ts
Normal file
13
chain-api/src/contract-interact/license/license.module.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { LicenseController } from './license.controller';
|
||||||
|
import { LicenseService } from './license.service';
|
||||||
|
import { BaseModule } from '../../base/base.module';
|
||||||
|
import { MultiSigModule } from '../multi-sig/multi-sig.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [BaseModule, MultiSigModule],
|
||||||
|
controllers: [LicenseController],
|
||||||
|
providers: [LicenseService],
|
||||||
|
exports: [LicenseService],
|
||||||
|
})
|
||||||
|
export class LicenseModule {}
|
137
chain-api/src/contract-interact/license/license.service.ts
Normal file
137
chain-api/src/contract-interact/license/license.service.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as hre from 'hardhat';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { BaseContractService } from '../../base/base-contract.service';
|
||||||
|
import {
|
||||||
|
DeployLicenseDto,
|
||||||
|
GetLicenseInfoDto,
|
||||||
|
GetLicenseResponseDto,
|
||||||
|
GetShareLicense,
|
||||||
|
RequestLicenseDto,
|
||||||
|
} from './license.dto';
|
||||||
|
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
|
||||||
|
import { ProviderService } from '../../base/provider/provider.service';
|
||||||
|
import { CHAINLINK } from '../../config/chainlink.config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LicenseService extends BaseContractService {
|
||||||
|
constructor(
|
||||||
|
private readonly multiSigService: MultiSigWalletService,
|
||||||
|
public readonly providerService: ProviderService,
|
||||||
|
) {
|
||||||
|
super(providerService);
|
||||||
|
}
|
||||||
|
async request(dto: RequestLicenseDto) {
|
||||||
|
const { multiSigWallet, contractAddress } = dto;
|
||||||
|
|
||||||
|
const ISubmitMultiSig = new ethers.Interface(['function request()']);
|
||||||
|
const data = ISubmitMultiSig.encodeFunctionData('request');
|
||||||
|
|
||||||
|
return await this.multiSigService.submitTransaction({
|
||||||
|
contractAddress: multiSigWallet,
|
||||||
|
destination: contractAddress,
|
||||||
|
value: '0',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTotalPayoutInUSD(dto: GetLicenseResponseDto) {
|
||||||
|
const { contractAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
|
const answer: bigint = await contract.totalPayoutInUSD();
|
||||||
|
console.log('=>(license.service.ts:45) answer', answer);
|
||||||
|
return answer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deploy(dto: DeployLicenseDto) {
|
||||||
|
console.log('=>(license.service.ts:53) dto', dto);
|
||||||
|
const { multiSigWallet, shares, owners, payrollAddress } = dto;
|
||||||
|
const { abi, bytecode } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const licenseContract = new ethers.ContractFactory(abi, bytecode, signer);
|
||||||
|
|
||||||
|
const myContract = await licenseContract.getDeployTransaction(
|
||||||
|
CHAINLINK.AMOY.CHAINLINK_TOKEN,
|
||||||
|
CHAINLINK.AMOY.ORACLE_ADDRESS,
|
||||||
|
CHAINLINK.AMOY.JOB_IDS.UINT,
|
||||||
|
0,
|
||||||
|
multiSigWallet,
|
||||||
|
owners,
|
||||||
|
shares,
|
||||||
|
payrollAddress,
|
||||||
|
);
|
||||||
|
const submitData = await this.multiSigService.submitTransaction({
|
||||||
|
contractAddress: multiSigWallet,
|
||||||
|
destination: null,
|
||||||
|
value: '0',
|
||||||
|
data: myContract.data,
|
||||||
|
});
|
||||||
|
delete submitData.data;
|
||||||
|
return submitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPayoutContract(dto: GetLicenseInfoDto) {
|
||||||
|
const { contractAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
|
const answer: string = await contract.payoutContract();
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOwners(dto: GetLicenseInfoDto) {
|
||||||
|
const { contractAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
|
const answer: string[] = await contract.owners();
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getShares(dto: GetShareLicense) {
|
||||||
|
const { contractAddress, ownerAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
|
const answer: number = await contract.getShare(ownerAddress);
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTotalPayout(dto: GetLicenseInfoDto) {
|
||||||
|
const { contractAddress } = dto;
|
||||||
|
const { abi } = await hre.artifacts.readArtifact(
|
||||||
|
'StreamingRightsManagement',
|
||||||
|
);
|
||||||
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
|
const answer: number = await contract.totalPayoutInUSD();
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNumber, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class SubmitTransactionDto {
|
export class SubmitTransactionDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -13,7 +13,7 @@ export class SubmitTransactionDto {
|
|||||||
value: string;
|
value: string;
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
// @ApiProperty()
|
@ApiProperty()
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,12 @@ export class ConfirmTransactionDto {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecuteTransactionDto extends ConfirmTransactionDto {}
|
export class ExecuteTransactionDto extends ConfirmTransactionDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@ApiProperty()
|
||||||
|
isDeploy: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class RevokeConfirmationDto extends ConfirmTransactionDto {}
|
export class RevokeConfirmationDto extends ConfirmTransactionDto {}
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { MultiSigWalletService } from 'src/hardhat/modules/multi-sig/multi-sig.service';
|
|
||||||
|
import { MultiSigWalletDto } from './multi-sig.dto';
|
||||||
|
import { MultiSigWalletService } from './multi-sig.service';
|
||||||
import {
|
import {
|
||||||
ConfirmTransactionDto,
|
ConfirmTransactionDto,
|
||||||
DeployMultiSigResponseDto,
|
DeployMultiSigResponseDto,
|
||||||
@ -9,8 +11,7 @@ import {
|
|||||||
GetTransactionDto,
|
GetTransactionDto,
|
||||||
RevokeConfirmationDto,
|
RevokeConfirmationDto,
|
||||||
SubmitTransactionDto,
|
SubmitTransactionDto,
|
||||||
} from '../../../contract-interact/dto/multi-sig.dto';
|
} from '../multi-sig.dto';
|
||||||
import { MultiSigWalletDto } from './multi-sig.dto';
|
|
||||||
@ApiTags('multi-sig')
|
@ApiTags('multi-sig')
|
||||||
@Controller('multi-sig')
|
@Controller('multi-sig')
|
||||||
export class MultiSigInteractController {
|
export class MultiSigInteractController {
|
@ -1,14 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
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';
|
import { MultiSigWalletService } from './multi-sig.service';
|
||||||
import { MultiSigInteractController } from './multi-sig-interact.controller';
|
import { MultiSigInteractController } from './multi-sig-interact.controller';
|
||||||
|
import { BaseModule } from '../../base/base.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ProviderModule],
|
imports: [BaseModule],
|
||||||
controllers: [MultiSigInteractController],
|
controllers: [MultiSigInteractController],
|
||||||
providers: [MultiSigWalletService],
|
providers: [MultiSigWalletService],
|
||||||
exports: [MultiSigWalletService],
|
exports: [MultiSigWalletService],
|
@ -1,6 +1,5 @@
|
|||||||
import { ethers, parseEther, TransactionReceipt } from 'ethers';
|
import { ethers, parseEther, TransactionReceipt } from 'ethers';
|
||||||
import * as hre from 'hardhat';
|
import * as hre from 'hardhat';
|
||||||
import { BaseContractService } from '../base-contract.service';
|
|
||||||
import { MultiSigWalletDto } from './multi-sig.dto';
|
import { MultiSigWalletDto } from './multi-sig.dto';
|
||||||
import {
|
import {
|
||||||
ConfirmTransactionDto,
|
ConfirmTransactionDto,
|
||||||
@ -9,8 +8,10 @@ import {
|
|||||||
GetTransactionDto,
|
GetTransactionDto,
|
||||||
RevokeConfirmationDto,
|
RevokeConfirmationDto,
|
||||||
SubmitTransactionDto,
|
SubmitTransactionDto,
|
||||||
} from 'src/contract-interact/dto/multi-sig.dto';
|
} from 'src/contract-interact/multi-sig.dto';
|
||||||
import { parseLogs } from 'src/contract-interact/ethers.helpers';
|
import { parseLogs } from 'src/ethers-custom/ethers.helpers';
|
||||||
|
import { BaseContractService } from '../../base/base-contract.service';
|
||||||
|
import { getContractAddress } from '@ethersproject/address';
|
||||||
|
|
||||||
export class MultiSigWalletService extends BaseContractService {
|
export class MultiSigWalletService extends BaseContractService {
|
||||||
async deploy(dto: MultiSigWalletDto) {
|
async deploy(dto: MultiSigWalletDto) {
|
||||||
@ -31,7 +32,6 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
|
|
||||||
async getOwners(address: string) {
|
async getOwners(address: string) {
|
||||||
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
||||||
const multiSigContract = new ethers.Contract(address, abi);
|
|
||||||
|
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
@ -47,7 +47,11 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
|
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
const tx = await contract.submitTransaction(destination, value, data);
|
const tx = await contract.submitTransaction(
|
||||||
|
destination || '0x0000000000000000000000000000000000000000',
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
);
|
||||||
const txResponse: TransactionReceipt = await tx.wait();
|
const txResponse: TransactionReceipt = await tx.wait();
|
||||||
|
|
||||||
const eventParse = parseLogs(txResponse, contract, 'SubmitTransaction');
|
const eventParse = parseLogs(txResponse, contract, 'SubmitTransaction');
|
||||||
@ -83,21 +87,40 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async executeTransaction(dto: ExecuteTransactionDto) {
|
async executeTransaction(dto: ExecuteTransactionDto) {
|
||||||
const { index, contractAddress } = dto;
|
const { index, contractAddress, isDeploy } = dto;
|
||||||
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
const { abi } = await hre.artifacts.readArtifact('MultiSigWallet');
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
const deployedAddress = await this.calculateFutureAddress(contractAddress);
|
||||||
|
|
||||||
const tx = await contract.executeTransaction(index);
|
const tx = await contract.executeTransaction(index);
|
||||||
|
|
||||||
const txResponse: TransactionReceipt = await tx.wait();
|
const txResponse: TransactionReceipt = await tx.wait();
|
||||||
|
console.log('=>(multi-sig.service.ts:101) txResponse', txResponse.logs);
|
||||||
|
|
||||||
const eventParse = parseLogs(txResponse, contract, 'ExecuteTransaction');
|
const eventParse = parseLogs(txResponse, contract, 'ExecuteTransaction');
|
||||||
return {
|
const data = {
|
||||||
txHash: txResponse.hash,
|
txHash: txResponse.hash,
|
||||||
sender: eventParse.args[0].toString(),
|
sender: eventParse.args[0].toString(),
|
||||||
txIndex: eventParse.args[1].toString(),
|
txIndex: eventParse.args[1].toString(),
|
||||||
};
|
};
|
||||||
|
if (isDeploy) {
|
||||||
|
return { ...data, deployedAddress };
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateFutureAddress(contractAddress: string) {
|
||||||
|
const provider = await this.providerService.getProvider();
|
||||||
|
|
||||||
|
const nonce = await provider.getTransactionCount(contractAddress);
|
||||||
|
|
||||||
|
return getContractAddress({
|
||||||
|
from: contractAddress,
|
||||||
|
nonce: nonce + 1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async revokeConfirmation(dto: RevokeConfirmationDto) {
|
async revokeConfirmation(dto: RevokeConfirmationDto) {
|
@ -8,10 +8,8 @@ import {
|
|||||||
SetSalaryDto,
|
SetSalaryDto,
|
||||||
} from './salaries.dto';
|
} from './salaries.dto';
|
||||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import {
|
import { DepositContractDto } from '../multi-sig.dto';
|
||||||
DeployMultiSigResponseDto,
|
|
||||||
DepositContractDto,
|
|
||||||
} from '../../../contract-interact/dto/multi-sig.dto';
|
|
||||||
@ApiTags('salaries')
|
@ApiTags('salaries')
|
||||||
@Controller('salaries')
|
@Controller('salaries')
|
||||||
export class SalariesController {
|
export class SalariesController {
|
||||||
@ -54,9 +52,4 @@ export class SalariesController {
|
|||||||
async deposit(@Body() dto: DepositContractDto) {
|
async deposit(@Body() dto: DepositContractDto) {
|
||||||
return this.salariesService.deposit(dto);
|
return this.salariesService.deposit(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('get-license-request')
|
|
||||||
async getLicenseRequest() {
|
|
||||||
return this.salariesService.getLicenseRequest();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { SalariesService } from './salaries.service';
|
import { SalariesService } from './salaries.service';
|
||||||
import { ProviderModule } from 'src/provider/provider.module';
|
|
||||||
import { SalariesController } from './salaries-interact.controller';
|
import { SalariesController } from './salaries-interact.controller';
|
||||||
import { MultiSigModule } from '../multi-sig/multi-sig.module';
|
import { MultiSigModule } from '../multi-sig/multi-sig.module';
|
||||||
|
import { BaseModule } from '../../base/base.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ProviderModule, MultiSigModule],
|
imports: [BaseModule, MultiSigModule],
|
||||||
controllers: [SalariesController],
|
controllers: [SalariesController],
|
||||||
providers: [SalariesService],
|
providers: [SalariesService],
|
||||||
exports: [SalariesService],
|
exports: [SalariesService],
|
@ -1,5 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { BaseContractService } from '../base-contract.service';
|
|
||||||
import { ethers, parseEther, TransactionReceipt } from 'ethers';
|
import { ethers, parseEther, TransactionReceipt } from 'ethers';
|
||||||
import {
|
import {
|
||||||
CreatePayoutDto,
|
CreatePayoutDto,
|
||||||
@ -9,8 +8,10 @@ import {
|
|||||||
} from './salaries.dto';
|
} from './salaries.dto';
|
||||||
import * as hre from 'hardhat';
|
import * as hre from 'hardhat';
|
||||||
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
|
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
|
||||||
import { ProviderService } from '../../../provider/provider.service';
|
import { BaseContractService } from '../../base/base-contract.service';
|
||||||
import { DepositContractDto } from '../../../contract-interact/dto/multi-sig.dto';
|
import { ProviderService } from '../../base/provider/provider.service';
|
||||||
|
import { DepositContractDto } from '../multi-sig.dto';
|
||||||
|
import { CHAINLINK } from '../../config/chainlink.config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SalariesService extends BaseContractService {
|
export class SalariesService extends BaseContractService {
|
||||||
@ -21,7 +22,7 @@ export class SalariesService extends BaseContractService {
|
|||||||
super(providerService);
|
super(providerService);
|
||||||
}
|
}
|
||||||
async deploy(dto: SalariesDeployDto) {
|
async deploy(dto: SalariesDeployDto) {
|
||||||
const { abi, bytecode } = await hre.artifacts.readArtifact('Salaries');
|
const { abi, bytecode } = await hre.artifacts.readArtifact('Payroll');
|
||||||
|
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
@ -29,50 +30,14 @@ export class SalariesService extends BaseContractService {
|
|||||||
|
|
||||||
const myContract = await salaryContract.deploy(
|
const myContract = await salaryContract.deploy(
|
||||||
dto.multiSigWallet,
|
dto.multiSigWallet,
|
||||||
'0xF0d50568e3A7e8259E16663972b11910F89BD8e7',
|
CHAINLINK.AMOY.AGGREGATOR_ADDRESS.USDT_ETH,
|
||||||
);
|
);
|
||||||
await myContract.waitForDeployment();
|
await myContract.waitForDeployment();
|
||||||
return await myContract.getAddress();
|
return await myContract.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLicenseRequest() {
|
|
||||||
const { abi } = await hre.artifacts.readArtifact(
|
|
||||||
'LinkWellStringBytesConsumerContractExample',
|
|
||||||
);
|
|
||||||
const signer = await this.providerService.getSigner();
|
|
||||||
|
|
||||||
const contract = new ethers.Contract(
|
|
||||||
'0xbc3c4fed4C3A977b8868b589662270F1aEA6A777',
|
|
||||||
abi,
|
|
||||||
signer,
|
|
||||||
);
|
|
||||||
|
|
||||||
const answer: string = await contract.request();
|
|
||||||
console.log('=>(salaries.service.ts:45) answer', answer);
|
|
||||||
const licenseres = await this.getLicenseResponse();
|
|
||||||
console.log('=>(salaries.service.ts:53) licenseres', licenseres);
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLicenseResponse() {
|
|
||||||
const { abi } = await hre.artifacts.readArtifact(
|
|
||||||
'LinkWellStringBytesConsumerContractExample',
|
|
||||||
);
|
|
||||||
const signer = await this.providerService.getSigner();
|
|
||||||
|
|
||||||
const contract = new ethers.Contract(
|
|
||||||
'0xbc3c4fed4C3A977b8868b589662270F1aEA6A777',
|
|
||||||
abi,
|
|
||||||
signer,
|
|
||||||
);
|
|
||||||
|
|
||||||
const answer: string = await contract.responseBytes();
|
|
||||||
console.log('=>(salaries.service.ts:45) answer', answer);
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestUSDTPrice(contractAddress: string) {
|
async getLatestUSDTPrice(contractAddress: string) {
|
||||||
const { abi } = await hre.artifacts.readArtifact('Salaries');
|
const { abi } = await hre.artifacts.readArtifact('Payroll');
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
@ -102,12 +67,12 @@ export class SalariesService extends BaseContractService {
|
|||||||
|
|
||||||
async getSalary(dto: GetEmployeeSalariesDto) {
|
async getSalary(dto: GetEmployeeSalariesDto) {
|
||||||
const { employeeAddress, contractAddress } = dto;
|
const { employeeAddress, contractAddress } = dto;
|
||||||
const { abi } = await hre.artifacts.readArtifact('Salaries');
|
const { abi } = await hre.artifacts.readArtifact('Payroll');
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
|
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
const answer: BigInt = await contract.getUsdtSalary(employeeAddress);
|
const answer: bigint = await contract.getUsdtSalary(employeeAddress);
|
||||||
return {
|
return {
|
||||||
salaryInUsd: answer.toString(),
|
salaryInUsd: answer.toString(),
|
||||||
};
|
};
|
13
chain-api/src/ethers-custom/ethers.helpers.ts
Normal file
13
chain-api/src/ethers-custom/ethers.helpers.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { TransactionReceipt, ethers } from 'ethers';
|
||||||
|
|
||||||
|
export const parseLogs = (
|
||||||
|
txReceipt: TransactionReceipt,
|
||||||
|
contract: ethers.Contract,
|
||||||
|
eventName: string,
|
||||||
|
) => {
|
||||||
|
const parsedLogs = txReceipt.logs.map((log) =>
|
||||||
|
contract.interface.parseLog(log),
|
||||||
|
);
|
||||||
|
console.log('=>(ethers.helpers.ts:10) parsedLogs', parsedLogs);
|
||||||
|
return parsedLogs.find((log) => !!log && log.fragment.name === eventName);
|
||||||
|
};
|
@ -3,7 +3,7 @@ pragma solidity ^0.8.17;
|
|||||||
|
|
||||||
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
|
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
|
||||||
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
|
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
|
||||||
|
import "./Payroll.sol";
|
||||||
/**
|
/**
|
||||||
* Request testnet LINK and ETH here: https://faucets.chain.link/
|
* Request testnet LINK and ETH here: https://faucets.chain.link/
|
||||||
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
|
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
|
||||||
@ -13,55 +13,106 @@ import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
|
|||||||
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
|
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, ConfirmedOwner {
|
contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner {
|
||||||
using Chainlink for Chainlink.Request;
|
using Chainlink for Chainlink.Request;
|
||||||
|
|
||||||
address private oracleAddress;
|
address public oracleAddress;
|
||||||
bytes32 private jobId;
|
bytes32 private jobId;
|
||||||
uint256 private fee;
|
uint256 private fee;
|
||||||
|
address public multisigWallet;
|
||||||
|
|
||||||
constructor() ConfirmedOwner(msg.sender) {
|
mapping(address => uint) public ownerShare;
|
||||||
_setChainlinkToken(0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904);
|
address[] public owners;
|
||||||
setOracleAddress(0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46);
|
|
||||||
setJobId("8ced832954544a3c98543c94a51d6a8d");
|
Payroll public payoutContract;
|
||||||
setFeeInHundredthsOfLink(0); // 0 LINK
|
|
||||||
|
constructor(
|
||||||
|
address _chainLinkToken,
|
||||||
|
address _oracleAddress,
|
||||||
|
string memory _jobId,
|
||||||
|
uint _fee,
|
||||||
|
address _multiSigAddress,
|
||||||
|
address[] memory _owners,
|
||||||
|
uint[] memory _shares,
|
||||||
|
address payable _payoutAddress
|
||||||
|
) ConfirmedOwner(_multiSigAddress) {
|
||||||
|
|
||||||
|
_setChainlinkToken(_chainLinkToken);
|
||||||
|
|
||||||
|
setOracleAddress(_oracleAddress);
|
||||||
|
|
||||||
|
setJobId(_jobId);
|
||||||
|
|
||||||
|
setFeeInHundredthsOfLink(_fee);
|
||||||
|
|
||||||
|
multisigWallet = _multiSigAddress;
|
||||||
|
|
||||||
|
payoutContract = Payroll(_payoutAddress);
|
||||||
|
|
||||||
|
require(_owners.length == _shares.length, "Owners and shares length mismatch");
|
||||||
|
|
||||||
|
uint sumShare = 0;
|
||||||
|
|
||||||
|
for(uint i=0; i<_shares.length;i++){
|
||||||
|
sumShare += _shares[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require(sumShare ==100, 'Invalid share percentage');
|
||||||
|
for (uint i = 0; i < _owners.length; i++) {
|
||||||
|
require(_shares[i] > 0, 'Share cannot be less than 0');
|
||||||
|
ownerShare[_owners[i]] = _shares[i];
|
||||||
|
owners.push(_owners[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//get share
|
||||||
|
//update share
|
||||||
|
//change payout address
|
||||||
|
//
|
||||||
|
function getShare(address owner) public returns(uint){
|
||||||
|
return ownerShare[owner];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Send a request to the Chainlink oracle
|
// Send a request to the Chainlink oracle
|
||||||
function request() public {
|
function request() public {
|
||||||
|
|
||||||
Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector);
|
Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector);
|
||||||
|
|
||||||
// DEFINE THE REQUEST PARAMETERS (example)
|
// DEFINE THE REQUEST PARAMETERS (example)
|
||||||
req._add('method', 'POST');
|
req._add('method', 'GET');
|
||||||
req._add('url', 'https://httpbin.org/post');
|
req._add('url', 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR');
|
||||||
req._add('headers', '["accept", "application/json", "set-cookie", "sid=14A52"]');
|
req._add('headers', '["content-type", "application/json", "set-cookie", "sid=14A52"]');
|
||||||
req._add('body', '{"data":[{"id":1,"name":"Bitcoin","price":20194.52},{"id":2,"name":"Ethereum","price":1850.46},{"id":3,"name":"Chainlink","price":18.36}]}');
|
req._add('body', '');
|
||||||
req._add('contact', ''); // PLEASE ENTER YOUR CONTACT INFO. this allows us to notify you in the event of any emergencies related to your request (ie, bugs, downtime, etc.). example values: 'derek_linkwellnodes.io' (Discord handle) OR 'derek@linkwellnodes.io' OR '+1-617-545-4721'
|
req._add('contact', ''); // PLEASE ENTER YOUR CONTACT INFO. this allows us to notify you in the event of any emergencies related to your request (ie, bugs, downtime, etc.). example values: 'derek_linkwellnodes.io' (Discord handle) OR 'derek@linkwellnodes.io' OR '+1-617-545-4721'
|
||||||
|
|
||||||
// The following curl command simulates the above request parameters:
|
// The following curl command simulates the above request parameters:
|
||||||
// curl 'https://httpbin.org/post' --request 'POST' --header 'content-type: application/json' --header 'set-cookie: sid=14A52' --data '{"data":[{"id":1,"name":"Bitcoin","price":20194.52},{"id":2,"name":"Ethereum","price":1850.46},{"id":3,"name":"Chainlink","price":18.36}]}'
|
// curl 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR' --request 'GET' --header 'content-type: application/json' --header 'set-cookie: sid=14A52'
|
||||||
|
|
||||||
// PROCESS THE RESULT (example)
|
// PROCESS THE RESULT (example)
|
||||||
req._add('path', 'json,data,0,name');
|
req._add('path', 'ETH,USD');
|
||||||
|
|
||||||
// Send the request to the Chainlink oracle
|
// Send the request to the Chainlink oracle
|
||||||
_sendOperatorRequest(req, fee);
|
_sendOperatorRequest(req, fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes public responseBytes;
|
uint256 public totalPayoutInUSD;
|
||||||
|
|
||||||
// Receive the result from the Chainlink oracle
|
// Receive the result from the Chainlink oracle
|
||||||
event RequestFulfilled(bytes32 indexed requestId);
|
event RequestFulfilled(bytes32 indexed requestId);
|
||||||
function fulfill(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
|
|
||||||
|
function fulfill(bytes32 requestId, uint256 data) public recordChainlinkFulfillment(requestId) {
|
||||||
// Process the oracle response
|
// Process the oracle response
|
||||||
// emit RequestFulfilled(requestId); // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction
|
// emit RequestFulfilled(requestId); // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction
|
||||||
responseBytes = bytesData; // example value: 0x426974636f696e
|
totalPayoutInUSD = data / 100; // example value: 1875870000000000000000 (1875.87 before "multiplier" is applied)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the response data as a string
|
function payout() external onlyOwner {
|
||||||
function getResponseString() public view onlyOwner returns (string memory) {
|
// using arrays to reduce gas
|
||||||
return string(responseBytes); // example value: Bitcoin
|
uint[] memory shares;
|
||||||
|
|
||||||
|
for(uint i=0; i< owners.length; i++){
|
||||||
|
shares[i] = ownerShare[owners[i]];
|
||||||
|
}
|
||||||
|
payoutContract.oneTimePayout(owners, shares);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update oracle address
|
// Update oracle address
|
||||||
@ -69,7 +120,8 @@ contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, Confirme
|
|||||||
oracleAddress = _oracleAddress;
|
oracleAddress = _oracleAddress;
|
||||||
_setChainlinkOracle(_oracleAddress);
|
_setChainlinkOracle(_oracleAddress);
|
||||||
}
|
}
|
||||||
function getOracleAddress() public view onlyOwner returns (address) {
|
|
||||||
|
function getOracleAddress() public view returns (address) {
|
||||||
return oracleAddress;
|
return oracleAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +129,7 @@ contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, Confirme
|
|||||||
function setJobId(string memory _jobId) public onlyOwner {
|
function setJobId(string memory _jobId) public onlyOwner {
|
||||||
jobId = bytes32(bytes(_jobId));
|
jobId = bytes32(bytes(_jobId));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJobId() public view onlyOwner returns (string memory) {
|
function getJobId() public view onlyOwner returns (string memory) {
|
||||||
return string(abi.encodePacked(jobId));
|
return string(abi.encodePacked(jobId));
|
||||||
}
|
}
|
||||||
@ -85,9 +138,11 @@ contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, Confirme
|
|||||||
function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
|
function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
|
||||||
fee = _feeInJuels;
|
fee = _feeInJuels;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner {
|
function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner {
|
||||||
setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
|
setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) {
|
function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) {
|
||||||
return (fee * 100) / LINK_DIVISIBILITY;
|
return (fee * 100) / LINK_DIVISIBILITY;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// 0x2F9442900d067a3D37A1C2aE99462E055e32c741
|
|
||||||
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 LicensePayout {
|
||||||
AggregatorV3Interface internal dataFeed;
|
AggregatorV3Interface internal dataFeed;
|
||||||
address public multisigWallet;
|
address public multisigWallet;
|
||||||
mapping(address => uint) public salaries;
|
mapping(address => uint) public salaries;
|
||||||
event Payout(address indexed employee, uint salaryInETH);
|
event Payout(address indexed employee, uint salaryInETH);
|
||||||
event PayoutFailed(address indexed employee, uint salaryInETH, string reason);
|
event PayoutFailed(address indexed employee, uint salaryInETH, string reason);
|
||||||
//0xF0d50568e3A7e8259E16663972b11910F89BD8e7
|
|
||||||
constructor(address _multisigWallet, address _priceFeedAddress) {
|
constructor(address _multisigWallet, address _priceFeedAddress) {
|
||||||
multisigWallet = _multisigWallet;
|
multisigWallet = _multisigWallet;
|
||||||
dataFeed = AggregatorV3Interface(_priceFeedAddress);
|
dataFeed = AggregatorV3Interface(_priceFeedAddress);
|
||||||
@ -36,6 +34,10 @@ contract Salaries {
|
|||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function oneTimePayout(address payable employee) external onlyMultisig {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function setSalary(
|
function setSalary(
|
||||||
address employee,
|
address employee,
|
||||||
uint salaryInUSDT
|
uint salaryInUSDT
|
@ -18,8 +18,10 @@ contract MultiSigWallet {
|
|||||||
|
|
||||||
event ConfirmTransaction(address indexed owner, uint indexed txIndex);
|
event ConfirmTransaction(address indexed owner, uint indexed txIndex);
|
||||||
event RevokeConfirmation(address indexed owner, uint indexed txIndex);
|
event RevokeConfirmation(address indexed owner, uint indexed txIndex);
|
||||||
event ExecuteTransaction(address indexed owner, uint indexed txIndex);
|
event ExecuteTransaction(address indexed owner, uint indexed txIndex, address indexed to);
|
||||||
event ExecuteTransactionFailed(address indexed owner, uint indexed txIndex, string reason);
|
event ExecuteTransactionFailed(address indexed owner, uint indexed txIndex, string reason);
|
||||||
|
event ContractDeployed(address indexed contractAddress);
|
||||||
|
|
||||||
|
|
||||||
address[] public owners;
|
address[] public owners;
|
||||||
|
|
||||||
@ -129,7 +131,16 @@ contract MultiSigWallet {
|
|||||||
(bool success, bytes memory returnData) = transaction.to.call{value: transaction.value}(transaction.data);
|
(bool success, bytes memory returnData) = transaction.to.call{value: transaction.value}(transaction.data);
|
||||||
if (success) {
|
if (success) {
|
||||||
transaction.executed = true;
|
transaction.executed = true;
|
||||||
emit ExecuteTransaction(msg.sender, _txIndex);
|
emit ExecuteTransaction(msg.sender, _txIndex, transaction.to);
|
||||||
|
if (returnData.length > 0) {
|
||||||
|
address deployedContractAddress;
|
||||||
|
assembly {
|
||||||
|
deployedContractAddress := mload(add(returnData, 20))
|
||||||
|
}
|
||||||
|
// You can emit an event with the address of the deployed contract
|
||||||
|
emit ContractDeployed(deployedContractAddress);
|
||||||
|
}
|
||||||
|
removeTransaction(_txIndex);
|
||||||
} else {
|
} else {
|
||||||
// Get the revert reason and emit it
|
// Get the revert reason and emit it
|
||||||
if (returnData.length > 0) {
|
if (returnData.length > 0) {
|
||||||
@ -156,6 +167,11 @@ contract MultiSigWallet {
|
|||||||
emit RevokeConfirmation(msg.sender, _txIndex);
|
emit RevokeConfirmation(msg.sender, _txIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeTransaction(uint _txIndex) public onlyOwner {
|
||||||
|
require(_txIndex < transactions.length, "tx does not exist");
|
||||||
|
delete transactions[_txIndex];
|
||||||
|
}
|
||||||
|
|
||||||
function getOwners() public view returns (address[] memory) {
|
function getOwners() public view returns (address[] memory) {
|
||||||
return owners;
|
return owners;
|
||||||
}
|
}
|
||||||
|
100
chain-api/src/hardhat/contracts/Payroll.sol
Normal file
100
chain-api/src/hardhat/contracts/Payroll.sol
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.7;
|
||||||
|
|
||||||
|
import {AggregatorV3Interface} from '@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol';
|
||||||
|
|
||||||
|
contract Payroll {
|
||||||
|
AggregatorV3Interface internal dataFeed;
|
||||||
|
address public authorizedWallet;
|
||||||
|
mapping(address => uint) public salaries;
|
||||||
|
|
||||||
|
event Payout(address indexed employee, uint salaryInETH);
|
||||||
|
event PayoutFailed(address indexed employee, uint salaryInETH, string reason);
|
||||||
|
|
||||||
|
constructor(address _authorizedWallet, address _priceFeedAddress) {
|
||||||
|
authorizedWallet = _authorizedWallet;
|
||||||
|
dataFeed = AggregatorV3Interface(_priceFeedAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyAuthorized() {
|
||||||
|
require(msg.sender == authorizedWallet, 'Unauthorized');
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUsdtSalary(address employee) public view returns (uint) {
|
||||||
|
return salaries[employee];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLatestUSDTPriceInETH() public view returns (int) {
|
||||||
|
(
|
||||||
|
,
|
||||||
|
/* uint80 roundID */ int answer /* uint startedAt */ /* uint timeStamp */ /* uint80 answeredInRound */,
|
||||||
|
,
|
||||||
|
,
|
||||||
|
|
||||||
|
) = dataFeed.latestRoundData();
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// using arrays to reduce gas
|
||||||
|
function oneTimePayout(address[] memory employees, uint[] memory usdAmounts) external onlyAuthorized {
|
||||||
|
require(employees.length == usdAmounts.length, "Mismatched input lengths");
|
||||||
|
int ethToUSDT = getLatestUSDTPriceInETH();
|
||||||
|
require(ethToUSDT > 0, 'Invalid price data');
|
||||||
|
for (uint i = 0; i < employees.length; i++) {
|
||||||
|
uint salaryInUSDT = usdAmounts[i];
|
||||||
|
require(salaryInUSDT > 0, 'No salary set');
|
||||||
|
uint salaryInETH = uint(salaryInUSDT * 1e18) / uint(ethToUSDT);
|
||||||
|
salaryInETH = salaryInETH * 1e8;
|
||||||
|
// Check sufficient balance
|
||||||
|
require(
|
||||||
|
address(this).balance >= salaryInETH,
|
||||||
|
'Insufficient contract balance'
|
||||||
|
);
|
||||||
|
|
||||||
|
(bool success,) = employees[i].call{value: salaryInETH}("");
|
||||||
|
if (success) {
|
||||||
|
emit Payout(employees[i], salaryInETH);
|
||||||
|
} else {
|
||||||
|
emit PayoutFailed(employees[i], salaryInETH, "Transfer failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSalary(
|
||||||
|
address employee,
|
||||||
|
uint salaryInUSDT
|
||||||
|
) external onlyAuthorized {
|
||||||
|
salaries[employee] = salaryInUSDT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmployeeSalaryInEth(address employee) public view returns (uint){
|
||||||
|
uint salaryInUSDT = salaries[employee];
|
||||||
|
require(salaryInUSDT > 0, 'No salary set');
|
||||||
|
|
||||||
|
int ethToUSDT = getLatestUSDTPriceInETH();
|
||||||
|
require(ethToUSDT > 0, 'Invalid price data');
|
||||||
|
uint salaryInETH = uint(salaryInUSDT * 1e18) / uint(ethToUSDT);
|
||||||
|
return salaryInETH * 1e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
function payoutInETH(address payable employee) external onlyAuthorized {
|
||||||
|
uint salaryInETH = getEmployeeSalaryInEth(employee);
|
||||||
|
// Check sufficient balance
|
||||||
|
require(
|
||||||
|
address(this).balance >= salaryInETH,
|
||||||
|
'Insufficient contract balance'
|
||||||
|
);
|
||||||
|
|
||||||
|
(bool success,) = employee.call{value: salaryInETH}("");
|
||||||
|
if (success) {
|
||||||
|
emit Payout(employee, salaryInETH);
|
||||||
|
} else {
|
||||||
|
emit PayoutFailed(employee, salaryInETH, "Transfer failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to receive ETH
|
||||||
|
receive() external payable {}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { HardhatService } from './hardhat.service';
|
|
||||||
import { ProviderModule } from 'src/provider/provider.module';
|
|
||||||
import { MultiSigModule } from './multi-sig/multi-sig.module';
|
|
||||||
import { SalariesModule } from './salaries/salaries.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [ProviderModule, MultiSigModule, SalariesModule],
|
|
||||||
controllers: [],
|
|
||||||
providers: [HardhatService],
|
|
||||||
exports: [HardhatService, MultiSigModule, SalariesModule],
|
|
||||||
})
|
|
||||||
export class HardhatModule {}
|
|
@ -1,4 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HardhatService {}
|
|
@ -1,11 +1,13 @@
|
|||||||
import { PriceFeedMock, Salaries } from '../../../typechain';
|
import { PriceFeedMock, Payroll } from '../../../typechain';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { ethers } = require('hardhat');
|
const { ethers } = require('hardhat');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
|
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
|
||||||
|
|
||||||
describe('Salaries', function () {
|
describe('Salaries', function () {
|
||||||
let salaries: Salaries;
|
let salaries: Payroll;
|
||||||
let owner: SignerWithAddress;
|
let owner: SignerWithAddress;
|
||||||
let multisigWallet: SignerWithAddress;
|
let multisigWallet: SignerWithAddress;
|
||||||
let addr1: SignerWithAddress;
|
let addr1: SignerWithAddress;
|
||||||
@ -19,11 +21,11 @@ describe('Salaries', function () {
|
|||||||
priceFeedMock = await PriceFeedMockFactory.deploy();
|
priceFeedMock = await PriceFeedMockFactory.deploy();
|
||||||
await priceFeedMock.getDeployedCode();
|
await priceFeedMock.getDeployedCode();
|
||||||
// Deploy the Salaries contract
|
// Deploy the Salaries contract
|
||||||
const SalariesFactory = await ethers.getContractFactory('Salaries');
|
const SalariesFactory = await ethers.getContractFactory('Payroll');
|
||||||
salaries = (await SalariesFactory.deploy(
|
salaries = (await SalariesFactory.deploy(
|
||||||
multisigWallet.address,
|
multisigWallet.address,
|
||||||
await priceFeedMock.getAddress(),
|
await priceFeedMock.getAddress(),
|
||||||
)) as Salaries;
|
)) as Payroll;
|
||||||
await salaries.getDeployedCode();
|
await salaries.getDeployedCode();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user