mirror of
https://github.com/emo2007/block-accounting.git
synced 2024-11-10 04:36:26 +00:00
salaries ready, started data feed chainlink
This commit is contained in:
parent
958bf670ae
commit
a2b4823fcf
@ -1,5 +1,6 @@
|
|||||||
![LOGIN FLOW](./login-flow.png "Login")
|
![LOGIN FLOW](./login-flow.png "Login")
|
||||||
![Example architecture](./arch.png "Arch")
|
|
||||||
|
[//]: # (![Example architecture](./arch.png "Arch"))
|
||||||
![License](./license.png "Arch")
|
![License](./license.png "Arch")
|
||||||
![Salaries](./salaries.png "Arch")
|
![Salaries](./salaries.png "Arch")
|
||||||
|
|
||||||
|
@ -11,9 +11,13 @@ const config = {
|
|||||||
accounts: [process.env.POLYGON_PK || ''],
|
accounts: [process.env.POLYGON_PK || ''],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
typechain: {
|
||||||
|
outDir: 'typechain',
|
||||||
|
target: 'ethers-v6',
|
||||||
|
},
|
||||||
paths: {
|
paths: {
|
||||||
sources: './src/hardhat/contracts',
|
sources: './src/hardhat/contracts',
|
||||||
// tests: './src/hardhat/test',
|
tests: './src/hardhat/test',
|
||||||
ignition: './src/hardhat/ignition',
|
ignition: './src/hardhat/ignition',
|
||||||
cache: './src/hardhat/cache',
|
cache: './src/hardhat/cache',
|
||||||
artifacts: './src/hardhat/artifacts',
|
artifacts: './src/hardhat/artifacts',
|
||||||
|
8
chain-api/package-lock.json
generated
8
chain-api/package-lock.json
generated
@ -6600,15 +6600,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chai-as-promised": {
|
"node_modules/chai-as-promised": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz",
|
||||||
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
|
"integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"check-error": "^1.0.2"
|
"check-error": "^1.0.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"chai": ">= 2.1.2 < 5"
|
"chai": ">= 2.1.2 < 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
|
@ -42,3 +42,7 @@ export class DepositContractDto {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
export class DeployMultiSigResponseDto {
|
||||||
|
@IsString()
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
@ -3,8 +3,9 @@ import { TransactionReceipt, ethers } from 'ethers';
|
|||||||
export const parseLogs = (
|
export const parseLogs = (
|
||||||
txReceipt: TransactionReceipt,
|
txReceipt: TransactionReceipt,
|
||||||
contract: ethers.Contract,
|
contract: ethers.Contract,
|
||||||
|
eventName: string,
|
||||||
) => {
|
) => {
|
||||||
return txReceipt.logs
|
return txReceipt.logs
|
||||||
.map((log) => contract.interface.parseLog(log))
|
.map((log) => contract.interface.parseLog(log))
|
||||||
.find((log) => !!log);
|
.find((log) => !!log && log.fragment.name === eventName);
|
||||||
};
|
};
|
||||||
|
101
chain-api/src/hardhat/contracts/License.sol
Normal file
101
chain-api/src/hardhat/contracts/License.sol
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
|
||||||
|
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
|
||||||
|
*/
|
||||||
|
contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, ConfirmedOwner {
|
||||||
|
using Chainlink for Chainlink.Request;
|
||||||
|
|
||||||
|
address private oracleAddress;
|
||||||
|
bytes32 private jobId;
|
||||||
|
uint256 private fee;
|
||||||
|
|
||||||
|
constructor() ConfirmedOwner(msg.sender) {
|
||||||
|
_setChainlinkToken(0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904);
|
||||||
|
setOracleAddress(0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46);
|
||||||
|
setJobId("8ced832954544a3c98543c94a51d6a8d");
|
||||||
|
setFeeInHundredthsOfLink(0); // 0 LINK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a request to the Chainlink oracle
|
||||||
|
function request() public {
|
||||||
|
|
||||||
|
Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector);
|
||||||
|
|
||||||
|
// DEFINE THE REQUEST PARAMETERS (example)
|
||||||
|
req._add('method', 'POST');
|
||||||
|
req._add('url', 'https://httpbin.org/post');
|
||||||
|
req._add('headers', '["accept", "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('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:
|
||||||
|
// 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}]}'
|
||||||
|
|
||||||
|
// PROCESS THE RESULT (example)
|
||||||
|
req._add('path', 'json,data,0,name');
|
||||||
|
|
||||||
|
// Send the request to the Chainlink oracle
|
||||||
|
_sendOperatorRequest(req, fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes public responseBytes;
|
||||||
|
|
||||||
|
// Receive the result from the Chainlink oracle
|
||||||
|
event RequestFulfilled(bytes32 indexed requestId);
|
||||||
|
function fulfill(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
|
||||||
|
// 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
|
||||||
|
responseBytes = bytesData; // example value: 0x426974636f696e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the response data as a string
|
||||||
|
function getResponseString() public view onlyOwner returns (string memory) {
|
||||||
|
return string(responseBytes); // example value: Bitcoin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update oracle address
|
||||||
|
function setOracleAddress(address _oracleAddress) public onlyOwner {
|
||||||
|
oracleAddress = _oracleAddress;
|
||||||
|
_setChainlinkOracle(_oracleAddress);
|
||||||
|
}
|
||||||
|
function getOracleAddress() public view onlyOwner returns (address) {
|
||||||
|
return oracleAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update jobId
|
||||||
|
function setJobId(string memory _jobId) public onlyOwner {
|
||||||
|
jobId = bytes32(bytes(_jobId));
|
||||||
|
}
|
||||||
|
function getJobId() public view onlyOwner returns (string memory) {
|
||||||
|
return string(abi.encodePacked(jobId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fees
|
||||||
|
function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
|
||||||
|
fee = _feeInJuels;
|
||||||
|
}
|
||||||
|
function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner {
|
||||||
|
setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
|
||||||
|
}
|
||||||
|
function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) {
|
||||||
|
return (fee * 100) / LINK_DIVISIBILITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdrawLink() public onlyOwner {
|
||||||
|
LinkTokenInterface link = LinkTokenInterface(_chainlinkTokenAddress());
|
||||||
|
require(
|
||||||
|
link.transfer(msg.sender, link.balanceOf(address(this))),
|
||||||
|
"Unable to transfer"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
|
||||||
pragma solidity ^0.8.24;
|
|
||||||
|
|
||||||
// Uncomment this line to use console.log
|
|
||||||
// import "hardhat/console.sol";
|
|
||||||
|
|
||||||
contract Lock {
|
|
||||||
uint public unlockTime;
|
|
||||||
address payable public owner;
|
|
||||||
|
|
||||||
event Withdrawal(uint amount, uint when);
|
|
||||||
|
|
||||||
constructor(uint _unlockTime) payable {
|
|
||||||
require(
|
|
||||||
block.timestamp < _unlockTime,
|
|
||||||
"Unlock time should be in the future"
|
|
||||||
);
|
|
||||||
|
|
||||||
unlockTime = _unlockTime;
|
|
||||||
owner = payable(msg.sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
function withdraw() public {
|
|
||||||
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
|
|
||||||
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);
|
|
||||||
|
|
||||||
require(block.timestamp >= unlockTime, "You can't withdraw yet");
|
|
||||||
require(msg.sender == owner, "You aren't the owner");
|
|
||||||
|
|
||||||
emit Withdrawal(address(this).balance, block.timestamp);
|
|
||||||
|
|
||||||
owner.transfer(address(this).balance);
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,8 +20,6 @@ contract MultiSigWallet {
|
|||||||
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);
|
||||||
event ExecuteTransactionFailed(address indexed owner, uint indexed txIndex, string reason);
|
event ExecuteTransactionFailed(address indexed owner, uint indexed txIndex, string reason);
|
||||||
event Payout(address indexed employee, uint salaryInETH);
|
|
||||||
event PayoutFailed(address indexed employee, uint salaryInETH, string reason);
|
|
||||||
|
|
||||||
address[] public owners;
|
address[] public owners;
|
||||||
|
|
||||||
@ -132,9 +130,6 @@ contract MultiSigWallet {
|
|||||||
if (success) {
|
if (success) {
|
||||||
transaction.executed = true;
|
transaction.executed = true;
|
||||||
emit ExecuteTransaction(msg.sender, _txIndex);
|
emit ExecuteTransaction(msg.sender, _txIndex);
|
||||||
if (returnData.length > 0) {
|
|
||||||
emitEventFromReturnData(returnData);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Get the revert reason and emit it
|
// Get the revert reason and emit it
|
||||||
if (returnData.length > 0) {
|
if (returnData.length > 0) {
|
||||||
@ -150,30 +145,6 @@ contract MultiSigWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitEventFromReturnData(bytes memory returnData) internal {
|
|
||||||
// Decode the selector from returnData
|
|
||||||
bytes4 selector;
|
|
||||||
assembly {
|
|
||||||
selector := mload(add(returnData, 32))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match the selector to the known events
|
|
||||||
if (selector == Payout.selector) {
|
|
||||||
(address employee, uint salaryInETH) = abi.decode(slice(returnData, 4, returnData.length), (address, uint));
|
|
||||||
emit Payout(employee, salaryInETH);
|
|
||||||
} else if (selector == PayoutFailed.selector) {
|
|
||||||
(address employee, uint salaryInETH, string memory reason) = abi.decode(slice(returnData, 4, returnData.length), (address, uint, string));
|
|
||||||
emit PayoutFailed(employee, salaryInETH, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function slice(bytes memory data, uint start, uint length) internal pure returns (bytes memory) {
|
|
||||||
bytes memory result = new bytes(length);
|
|
||||||
for (uint i = 0; i < length; i++) {
|
|
||||||
result[i] = data[start + i];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function revokeConfirmation(
|
function revokeConfirmation(
|
||||||
uint _txIndex
|
uint _txIndex
|
||||||
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
|
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
|
||||||
|
18
chain-api/src/hardhat/contracts/PriceFeedMock.sol
Normal file
18
chain-api/src/hardhat/contracts/PriceFeedMock.sol
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.7;
|
||||||
|
|
||||||
|
contract PriceFeedMock {
|
||||||
|
function latestRoundData()
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (
|
||||||
|
uint80 roundId,
|
||||||
|
int answer,
|
||||||
|
uint startedAt,
|
||||||
|
uint updatedAt,
|
||||||
|
uint80 answeredInRound
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return (0, 3087, 0, 0, 0); // Mock data, 1 ETH = 3087 USDT
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ contract Salaries {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSalary(address employee) public view returns(uint) {
|
function getUsdtSalary(address employee) public view returns(uint) {
|
||||||
return salaries[employee];
|
return salaries[employee];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,16 +43,18 @@ contract Salaries {
|
|||||||
salaries[employee] = salaryInUSDT;
|
salaries[employee] = salaryInUSDT;
|
||||||
}
|
}
|
||||||
|
|
||||||
function payoutInETH(address payable employee) external onlyMultisig {
|
function getEmployeeSalaryInEth(address employee) public view returns(uint){
|
||||||
uint salaryInUSDT = salaries[employee];
|
uint salaryInUSDT = salaries[employee];
|
||||||
require(salaryInUSDT > 0, 'No salary set');
|
require(salaryInUSDT > 0, 'No salary set');
|
||||||
|
|
||||||
int ethToUSDT = getLatestUSDTPriceInETH();
|
int ethToUSDT = getLatestUSDTPriceInETH();
|
||||||
require(ethToUSDT > 0, 'Invalid price data');
|
require(ethToUSDT > 0, 'Invalid price data');
|
||||||
|
|
||||||
// Convert salary from USDT to ETH based on the latest price
|
|
||||||
uint salaryInETH = uint(salaryInUSDT * 1e18) / uint(ethToUSDT);
|
uint salaryInETH = uint(salaryInUSDT * 1e18) / uint(ethToUSDT);
|
||||||
|
return salaryInETH * 1e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
function payoutInETH(address payable employee) external onlyMultisig {
|
||||||
|
uint salaryInETH = getEmployeeSalaryInEth(employee);
|
||||||
// Check sufficient balance
|
// Check sufficient balance
|
||||||
require(
|
require(
|
||||||
address(this).balance >= salaryInETH,
|
address(this).balance >= salaryInETH,
|
||||||
@ -67,10 +69,6 @@ contract Salaries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dummy() public pure returns (uint){
|
|
||||||
return 1337;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to receive ETH
|
// Fallback to receive ETH
|
||||||
receive() external payable {}
|
receive() external payable {}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
|||||||
import { MultiSigWalletService } from 'src/hardhat/modules/multi-sig/multi-sig.service';
|
import { MultiSigWalletService } from 'src/hardhat/modules/multi-sig/multi-sig.service';
|
||||||
import {
|
import {
|
||||||
ConfirmTransactionDto,
|
ConfirmTransactionDto,
|
||||||
|
DeployMultiSigResponseDto,
|
||||||
DepositContractDto,
|
DepositContractDto,
|
||||||
ExecuteTransactionDto,
|
ExecuteTransactionDto,
|
||||||
GetTransactionDto,
|
GetTransactionDto,
|
||||||
@ -15,9 +16,17 @@ import { MultiSigWalletDto } from './multi-sig.dto';
|
|||||||
export class MultiSigInteractController {
|
export class MultiSigInteractController {
|
||||||
constructor(private readonly multiSigWalletService: MultiSigWalletService) {}
|
constructor(private readonly multiSigWalletService: MultiSigWalletService) {}
|
||||||
|
|
||||||
|
@ApiOkResponse({
|
||||||
|
type: DeployMultiSigResponseDto,
|
||||||
|
})
|
||||||
@Post('deploy')
|
@Post('deploy')
|
||||||
async deploy(@Body() dto: MultiSigWalletDto) {
|
async deploy(
|
||||||
return this.multiSigWalletService.deploy(dto);
|
@Body() dto: MultiSigWalletDto,
|
||||||
|
): Promise<DeployMultiSigResponseDto> {
|
||||||
|
const addr = await this.multiSigWalletService.deploy(dto);
|
||||||
|
return {
|
||||||
|
address: addr,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@Get('owners/:address')
|
@Get('owners/:address')
|
||||||
async getOwners(@Param('address') address: string) {
|
async getOwners(@Param('address') address: string) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { TransactionReceipt, ethers, parseEther } from 'ethers';
|
import { ethers, parseEther, TransactionReceipt } from 'ethers';
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import * as hre from 'hardhat';
|
import * as hre from 'hardhat';
|
||||||
import { BaseContractService } from '../base-contract.service';
|
import { BaseContractService } from '../base-contract.service';
|
||||||
import { MultiSigWalletDto } from './multi-sig.dto';
|
import { MultiSigWalletDto } from './multi-sig.dto';
|
||||||
@ -27,8 +26,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
dto.confirmations,
|
dto.confirmations,
|
||||||
);
|
);
|
||||||
await myContract.waitForDeployment();
|
await myContract.waitForDeployment();
|
||||||
const address = myContract.getAddress();
|
return myContract.getAddress();
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOwners(address: string) {
|
async getOwners(address: string) {
|
||||||
@ -39,9 +37,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
|
|
||||||
const contract = new ethers.Contract(address, abi, signer);
|
const contract = new ethers.Contract(address, abi, signer);
|
||||||
|
|
||||||
const owners = await contract.getOwners();
|
return await contract.getOwners();
|
||||||
|
|
||||||
return owners;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitTransaction(dto: SubmitTransactionDto) {
|
async submitTransaction(dto: SubmitTransactionDto) {
|
||||||
@ -54,7 +50,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
const tx = await contract.submitTransaction(destination, value, data);
|
const tx = await contract.submitTransaction(destination, value, data);
|
||||||
const txResponse: TransactionReceipt = await tx.wait();
|
const txResponse: TransactionReceipt = await tx.wait();
|
||||||
|
|
||||||
const eventParse = parseLogs(txResponse, contract);
|
const eventParse = parseLogs(txResponse, contract, 'SubmitTransaction');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
txHash: txResponse.hash,
|
txHash: txResponse.hash,
|
||||||
@ -77,7 +73,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
|
|
||||||
const txResponse: TransactionReceipt = await tx.wait();
|
const txResponse: TransactionReceipt = await tx.wait();
|
||||||
|
|
||||||
const eventParse = parseLogs(txResponse, contract);
|
const eventParse = parseLogs(txResponse, contract, 'ConfirmTransaction');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
txHash: txResponse.hash,
|
txHash: txResponse.hash,
|
||||||
@ -96,8 +92,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
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:99) txResponse', txResponse.logs);
|
const eventParse = parseLogs(txResponse, contract, 'ExecuteTransaction');
|
||||||
const eventParse = parseLogs(txResponse, contract);
|
|
||||||
return {
|
return {
|
||||||
txHash: txResponse.hash,
|
txHash: txResponse.hash,
|
||||||
sender: eventParse.args[0].toString(),
|
sender: eventParse.args[0].toString(),
|
||||||
@ -155,7 +150,7 @@ export class MultiSigWalletService extends BaseContractService {
|
|||||||
|
|
||||||
const txResponse: TransactionReceipt = await tx.wait();
|
const txResponse: TransactionReceipt = await tx.wait();
|
||||||
|
|
||||||
const eventParse = parseLogs(txResponse, contract);
|
const eventParse = parseLogs(txResponse, contract, 'ExecuteTransaction');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
txHash: txResponse.hash,
|
txHash: txResponse.hash,
|
||||||
|
@ -2,20 +2,32 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
|||||||
import { SalariesService } from './salaries.service';
|
import { SalariesService } from './salaries.service';
|
||||||
import {
|
import {
|
||||||
CreatePayoutDto,
|
CreatePayoutDto,
|
||||||
|
DeployContractResponseDto,
|
||||||
GetEmployeeSalariesDto,
|
GetEmployeeSalariesDto,
|
||||||
SalariesDeployDto,
|
SalariesDeployDto,
|
||||||
SetSalaryDto,
|
SetSalaryDto,
|
||||||
} from './salaries.dto';
|
} from './salaries.dto';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { DepositContractDto } from '../../../contract-interact/dto/multi-sig.dto';
|
import {
|
||||||
|
DeployMultiSigResponseDto,
|
||||||
|
DepositContractDto,
|
||||||
|
} from '../../../contract-interact/dto/multi-sig.dto';
|
||||||
@ApiTags('salaries')
|
@ApiTags('salaries')
|
||||||
@Controller('salaries')
|
@Controller('salaries')
|
||||||
export class SalariesController {
|
export class SalariesController {
|
||||||
constructor(private readonly salariesService: SalariesService) {}
|
constructor(private readonly salariesService: SalariesService) {}
|
||||||
|
|
||||||
|
@ApiOkResponse({
|
||||||
|
type: DeployContractResponseDto,
|
||||||
|
})
|
||||||
@Post('deploy')
|
@Post('deploy')
|
||||||
async deploy(@Body() dto: SalariesDeployDto) {
|
async deploy(
|
||||||
return this.salariesService.deploy(dto);
|
@Body() dto: SalariesDeployDto,
|
||||||
|
): Promise<DeployContractResponseDto> {
|
||||||
|
const address = await this.salariesService.deploy(dto);
|
||||||
|
return {
|
||||||
|
address,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('usdt-price/:contractAddress')
|
@Get('usdt-price/:contractAddress')
|
||||||
|
@ -35,3 +35,8 @@ export class CreatePayoutDto extends GeneralEmpoyeeSalaryDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
multiSigWallet: string;
|
multiSigWallet: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DeployContractResponseDto {
|
||||||
|
@IsString()
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@ export class SalariesService extends BaseContractService {
|
|||||||
) {
|
) {
|
||||||
super(providerService);
|
super(providerService);
|
||||||
}
|
}
|
||||||
async deploy(dto: SalariesDeployDto): Promise<any> {
|
async deploy(dto: SalariesDeployDto) {
|
||||||
const { abi, bytecode } = await hre.artifacts.readArtifact('Salaries');
|
const { abi, bytecode } = await hre.artifacts.readArtifact('Salaries');
|
||||||
|
|
||||||
const signer = await this.providerService.getSigner();
|
const signer = await this.providerService.getSigner();
|
||||||
@ -71,7 +71,7 @@ export class SalariesService extends BaseContractService {
|
|||||||
|
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
const answer: BigInt = await contract.getSalary(employeeAddress);
|
const answer: BigInt = await contract.getUsdtSalary(employeeAddress);
|
||||||
return {
|
return {
|
||||||
salaryInUsd: answer.toString(),
|
salaryInUsd: answer.toString(),
|
||||||
};
|
};
|
||||||
@ -79,7 +79,6 @@ export class SalariesService extends BaseContractService {
|
|||||||
|
|
||||||
async createPayout(dto: CreatePayoutDto) {
|
async createPayout(dto: CreatePayoutDto) {
|
||||||
const { employeeAddress, contractAddress, multiSigWallet } = dto;
|
const { employeeAddress, contractAddress, multiSigWallet } = dto;
|
||||||
console.log('=>(salaries.service.ts:82) employeeAddress', employeeAddress);
|
|
||||||
const ISubmitMultiSig = new ethers.Interface([
|
const ISubmitMultiSig = new ethers.Interface([
|
||||||
'function payoutInETH(address employee)',
|
'function payoutInETH(address employee)',
|
||||||
]);
|
]);
|
||||||
|
69
chain-api/src/hardhat/test/Salaries.test.ts
Normal file
69
chain-api/src/hardhat/test/Salaries.test.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { PriceFeedMock, Salaries } from '../../../typechain';
|
||||||
|
|
||||||
|
const { ethers } = require('hardhat');
|
||||||
|
const { expect } = require('chai');
|
||||||
|
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
|
||||||
|
|
||||||
|
describe('Salaries', function () {
|
||||||
|
let salaries: Salaries;
|
||||||
|
let owner: SignerWithAddress;
|
||||||
|
let multisigWallet: SignerWithAddress;
|
||||||
|
let addr1: SignerWithAddress;
|
||||||
|
let priceFeedMock: PriceFeedMock;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
[owner, multisigWallet, addr1] = await ethers.getSigners();
|
||||||
|
|
||||||
|
const PriceFeedMockFactory =
|
||||||
|
await ethers.getContractFactory('PriceFeedMock');
|
||||||
|
priceFeedMock = await PriceFeedMockFactory.deploy();
|
||||||
|
await priceFeedMock.getDeployedCode();
|
||||||
|
// Deploy the Salaries contract
|
||||||
|
const SalariesFactory = await ethers.getContractFactory('Salaries');
|
||||||
|
salaries = (await SalariesFactory.deploy(
|
||||||
|
multisigWallet.address,
|
||||||
|
await priceFeedMock.getAddress(),
|
||||||
|
)) as Salaries;
|
||||||
|
await salaries.getDeployedCode();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set and get salary correctly', async function () {
|
||||||
|
await salaries.connect(multisigWallet).setSalary(addr1.address, 1000);
|
||||||
|
expect(await salaries.getUsdtSalary(addr1.address)).to.equal(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should payout in ETH correctly', async function () {
|
||||||
|
// Set the salary in USDT
|
||||||
|
await salaries.connect(multisigWallet).setSalary(addr1.address, 100);
|
||||||
|
expect(await salaries.getUsdtSalary(addr1.address)).to.equal(100);
|
||||||
|
|
||||||
|
// Fund the contract with ETH
|
||||||
|
await owner.sendTransaction({
|
||||||
|
to: await salaries.getAddress(),
|
||||||
|
value: ethers.parseEther('1'), // 1 ETH
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(() =>
|
||||||
|
salaries.connect(multisigWallet).payoutInETH(addr1.address),
|
||||||
|
).to.changeEtherBalances(
|
||||||
|
[salaries, addr1],
|
||||||
|
['-32393909944930353', '32393909944930353'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check events
|
||||||
|
expect(salaries.connect(multisigWallet).payoutInETH(addr1.address));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PriceFeedMock', function () {
|
||||||
|
it('Should return the mocked price', async function () {
|
||||||
|
const PriceFeedMockFactory =
|
||||||
|
await ethers.getContractFactory('PriceFeedMock');
|
||||||
|
const priceFeedMock = await PriceFeedMockFactory.deploy();
|
||||||
|
await priceFeedMock.getDeployedCode();
|
||||||
|
|
||||||
|
expect((await priceFeedMock.latestRoundData())[1].toString()).to.equal(
|
||||||
|
'3087',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProviderService {
|
export class ProviderService {
|
||||||
public provider: ethers.JsonRpcProvider;
|
public provider: ethers.JsonRpcProvider;
|
||||||
@ -29,10 +30,9 @@ export class ProviderService {
|
|||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
await this.getProvider();
|
await this.getProvider();
|
||||||
}
|
}
|
||||||
const signer = new ethers.Wallet(
|
return new ethers.Wallet(
|
||||||
this.configService.getOrThrow('POLYGON_PK'),
|
this.configService.getOrThrow('POLYGON_PK'),
|
||||||
this.provider,
|
this.provider,
|
||||||
);
|
);
|
||||||
return signer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user