agreement update, contract tested and fixed, readme init

This commit is contained in:
emochka2007 2024-05-24 12:19:19 +03:00
parent 7809a91416
commit 0f995d4617
46 changed files with 11033 additions and 135 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
.idea
.env
.env
.vscode

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/block-accounting.iml" filepath="$PROJECT_DIR$/.idea/block-accounting.iml" />
</modules>
</component>
</project>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
<option name="myRunOnSave" value="true" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,3 +0,0 @@
{
"solidity.compileUsingRemoteVersion": "v0.8.25+commit.b61c2a91"
}

View File

@ -1,11 +1,14 @@
![LOGIN FLOW](./login-flow.png "Login")
### CHAIN-API
[//]: # (![Example architecture]&#40;./arch.png "Arch"&#41;)
![License](./license.png "Arch")
![Salaries](./salaries.png "Arch")
- ### Multi-Sig Deploy
![Alt Text](./excalidraw/multisig.png)
# Registration Flow
- On First Login - Owner inputs his SEED_KEY (mnemonic), creates an organization, we save its seed hash for future login and signing internal txs.
- When inviting an employee to organization- we generate an invitation link, then after clicking on this link - the user is asked for seed, if he's already registered or able to generate a seed for new account.
-
- ### Payroll Deploy
![Alt Text](./excalidraw/payroll-deploy.png)
- ### Payroll
![set-salary.png](excalidraw%2Fset-salary.png)
![payroll.png](excalidraw%2Fpayroll.png)
- ### License
![license-deploy.png](excalidraw%2Flicense-deploy.png)
![data-request-license.png](excalidraw%2Fdata-request-license.png)
![license-payout-2of3steps.png](excalidraw%2Flicense-payout-2of3steps.png)![3step-license-payout.png](excalidraw%2F3step-license-payout.png)

View File

@ -4,6 +4,8 @@ import { AppService } from './app.service';
import { ContractInteractModule } from './contract-interact/contract-interact.module';
import { ConfigModule } from '@nestjs/config';
import { EthereumModule } from './ethereum/ethereum.module';
import { AgreementModule } from './contract-interact/agreement/agreement.module';
@Module({
imports: [
@ -11,6 +13,8 @@ import { ConfigModule } from '@nestjs/config';
isGlobal: true,
}),
ContractInteractModule,
EthereumModule,
AgreementModule,
],
controllers: [AppController],
providers: [AppService],

View File

@ -4,7 +4,7 @@ import { ProviderModule } from './provider/provider.module';
@Module({
imports: [ProviderModule],
controllers: [],
providers: [ProviderModule],
providers: [],
exports: [ProviderModule],
})
export class BaseModule {}

View File

@ -18,11 +18,7 @@ export class ProviderService {
if (this.provider) {
return this.provider;
}
const polygonProvider = new ethers.JsonRpcProvider(
this.nodeUrl,
this.networkId,
);
this.provider = polygonProvider;
this.provider = new ethers.JsonRpcProvider(this.nodeUrl, this.networkId);
return this.provider;
}

View File

@ -7,6 +7,7 @@ export const CHAINLINK = {
},
JOB_IDS: {
UINT: 'a8356f48569c434eaa4ac5fcb4db5cc0',
BOOL: '43309009a154495cb2ed794233e6ff56',
},
},
};

View File

@ -0,0 +1,29 @@
import { Body, Controller, Post, Get, Param } from '@nestjs/common';
import { AgreementService } from './agreement.service';
import {
DeployAgreementDto,
GetAgreementInfoDto,
RequestAgreementDto,
} from './agreement.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Agreement')
@Controller('agreements')
export class AgreementController {
constructor(private readonly agreementService: AgreementService) {}
@Post('deploy')
async deployAgreement(@Body() deployDto: DeployAgreementDto) {
return await this.agreementService.deploy(deployDto);
}
@Get(':contractAddress')
async getAgreementResponse(
@Param('contractAddress') contractAddress: string,
) {
return await this.agreementService.getResponse({ contractAddress });
}
@Post('request')
async requestAgreement(@Body() requestDto: RequestAgreementDto) {
return await this.agreementService.request(requestDto);
}
}

View File

@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsUrl } from 'class-validator';
export class DeployAgreementDto {
@ApiProperty()
@IsString()
multiSigWallet: string;
}
export class GetAgreementInfoDto {
@ApiProperty()
@IsString()
contractAddress: string;
}
export class RequestAgreementDto extends GetAgreementInfoDto {
@ApiProperty()
@IsString()
multiSigWallet: string;
@ApiProperty()
@IsUrl()
url: string;
}

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { AgreementController } from './agreement.controller';
import { AgreementService } from './agreement.service';
import { BaseModule } from '../../base/base.module';
import { MultiSigModule } from '../multi-sig/multi-sig.module';
@Module({
imports: [BaseModule, MultiSigModule],
controllers: [AgreementController],
providers: [AgreementService],
exports: [],
})
export class AgreementModule {}

View File

@ -0,0 +1,75 @@
import { Injectable } from '@nestjs/common';
import { BaseContractService } from '../../base/base-contract.service';
import * as hre from 'hardhat';
import { ethers } from 'ethers';
import { CHAINLINK } from '../../config/chainlink.config';
import {
DeployAgreementDto,
GetAgreementInfoDto,
RequestAgreementDto,
} from './agreement.dto';
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
import { ProviderService } from '../../base/provider/provider.service';
@Injectable()
export class AgreementService extends BaseContractService {
constructor(
public readonly providerService: ProviderService,
public readonly multiSigService: MultiSigWalletService,
) {
super(providerService);
}
async deploy(dto: DeployAgreementDto): Promise<any> {
const { multiSigWallet } = dto;
const { bytecode } = await hre.artifacts.readArtifact('Agreement');
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const abiEncodedConstructorArguments = abiCoder.encode(
['address', 'address', 'string', 'uint', 'address'],
[
CHAINLINK.AMOY.CHAINLINK_TOKEN,
CHAINLINK.AMOY.ORACLE_ADDRESS,
CHAINLINK.AMOY.JOB_IDS.BOOL,
0,
multiSigWallet,
],
);
const fullBytecode = bytecode + abiEncodedConstructorArguments.substring(2);
const submitData = await this.multiSigService.submitTransaction({
contractAddress: multiSigWallet,
destination: null,
value: '0',
data: fullBytecode,
});
delete submitData.data;
return submitData;
}
async getResponse(dto: GetAgreementInfoDto) {
const { contractAddress } = dto;
const { abi } = await hre.artifacts.readArtifact('Agreement');
const signer = await this.providerService.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
const answer = await contract.response();
return answer.toString();
}
async request(dto: RequestAgreementDto) {
const { multiSigWallet, contractAddress, url } = dto;
const ISubmitMultiSig = new ethers.Interface([
'function request(string memory url)',
]);
const data = ISubmitMultiSig.encodeFunctionData('request', [url]);
return await this.multiSigService.submitTransaction({
contractAddress: multiSigWallet,
destination: contractAddress,
value: '0',
data,
});
}
}

View File

@ -1,4 +1,4 @@
import { IsArray, IsNumber, IsString } from 'class-validator';
import { IsArray, IsNumber, IsString, IsUrl } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class GetLicenseInfoDto {
@ApiProperty()
@ -24,6 +24,9 @@ export class RequestLicenseDto extends GetLicenseInfoDto {
@ApiProperty()
@IsString()
multiSigWallet: string;
@ApiProperty()
@IsUrl()
url: string;
}
export class GetLicenseResponseDto extends GetLicenseInfoDto {}

View File

@ -10,23 +10,25 @@ import {
RequestLicenseDto,
SetPayoutContractDto,
} from './license.dto';
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
import { ProviderService } from '../../base/provider/provider.service';
import { CHAINLINK } from '../../config/chainlink.config';
import { ProviderService } from '../../base/provider/provider.service';
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
@Injectable()
export class LicenseService extends BaseContractService {
constructor(
private readonly multiSigService: MultiSigWalletService,
public readonly providerService: ProviderService,
public readonly multiSigService: MultiSigWalletService,
) {
super(providerService);
}
async request(dto: RequestLicenseDto) {
const { multiSigWallet, contractAddress } = dto;
const { multiSigWallet, contractAddress, url } = dto;
const ISubmitMultiSig = new ethers.Interface(['function request()']);
const data = ISubmitMultiSig.encodeFunctionData('request');
const ISubmitMultiSig = new ethers.Interface([
'function request(string memory url)',
]);
const data = ISubmitMultiSig.encodeFunctionData('request', [url]);
return await this.multiSigService.submitTransaction({
contractAddress: multiSigWallet,
@ -46,7 +48,6 @@ export class LicenseService extends BaseContractService {
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();
}

View File

@ -7,17 +7,17 @@ import {
SetSalaryDto,
} from './salaries.dto';
import * as hre from 'hardhat';
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
import { BaseContractService } from '../../base/base-contract.service';
import { ProviderService } from '../../base/provider/provider.service';
import { DepositContractDto } from '../multi-sig.dto';
import { CHAINLINK } from '../../config/chainlink.config';
import { ProviderService } from '../../base/provider/provider.service';
import { MultiSigWalletService } from '../multi-sig/multi-sig.service';
@Injectable()
export class SalariesService extends BaseContractService {
constructor(
private readonly multiSigWalletService: MultiSigWalletService,
public readonly providerService: ProviderService,
public readonly multiSigService: MultiSigWalletService,
) {
super(providerService);
}
@ -57,7 +57,7 @@ export class SalariesService extends BaseContractService {
salary,
]);
return await this.multiSigWalletService.submitTransaction({
return await this.multiSigService.submitTransaction({
contractAddress: multiSigWallet,
destination: contractAddress,
value: '0',
@ -87,7 +87,7 @@ export class SalariesService extends BaseContractService {
employeeAddress,
]);
return await this.multiSigWalletService.submitTransaction({
return await this.multiSigService.submitTransaction({
contractAddress: multiSigWallet,
destination: contractAddress,
value: '0',

View File

@ -0,0 +1,12 @@
import { Controller, Get, Param } from '@nestjs/common';
import { EthereumService } from './ethereum.service';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Ethereum')
@Controller()
export class EthereumController {
constructor(private readonly ethereumService: EthereumService) {}
@Get('/address/:privateKey')
async getAddressFromPrivateKey(@Param('privateKey') privateKey: string) {
return this.ethereumService.getAddressFromPrivateKey(privateKey);
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { EthereumController } from './ethereum.controller';
import { EthereumService } from './ethereum.service';
@Module({
imports: [],
controllers: [EthereumController],
providers: [EthereumService],
exports: [],
})
export class EthereumModule {}

View File

@ -0,0 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
@Injectable()
export class EthereumService {
async getAddressFromPrivateKey(privateKey: string) {
const wallet = new ethers.Wallet(privateKey);
return wallet.address;
}
}

View File

@ -23,7 +23,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
const responseBody = {
statusCode: httpStatus,
error: exception?.info?.error.message || exception.toString(),
error: exception?.info?.error?.message || exception.toString(),
timestamp: new Date().toISOString(),
};

View File

@ -4,30 +4,20 @@ 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 LinkWellBoolConsumerContractExample is ChainlinkClient, ConfirmedOwner {
contract Agreement is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
address private oracleAddress;
bytes32 private jobId;
uint256 private fee;
address public multisigWallet;
constructor(
address _chainLinkToken,
address _oracleAddress,
string memory _jobId,
uint _fee,
address _multiSigAddress,
address[] memory _owners,
uint[] memory _shares
address _multiSigAddress
) ConfirmedOwner(_multiSigAddress) {
_setChainlinkToken(_chainLinkToken);
@ -41,32 +31,18 @@ contract LinkWellBoolConsumerContractExample is ChainlinkClient, ConfirmedOwner
multisigWallet = _multiSigAddress;
}
constructor() ConfirmedOwner(msg.sender) {
_setChainlinkToken(0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904);
setOracleAddress(0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46);
setJobId("43309009a154495cb2ed794233e6ff56");
setFeeInHundredthsOfLink(0); // 0 LINK
}
// Send a request to the Chainlink oracle
function request() public {
function request(string memory url) 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":[{"coin":"BTC","isActive":false},{"coin":"ETH","isActive":false},{"coin":"LINK","isActive":true}]}');
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":[{"coin":"BTC","isActive":false},{"coin":"ETH","isActive":false},{"coin":"LINK","isActive":true}]}'
// PROCESS THE RESULT (example)
req._add('path', 'json,data,2,isActive');
// Send the request to the Chainlink oracle
req._add('method', 'GET');
req._add('url', url);
req._add('headers', '["content-type", "application/json"]');
req._add('body', '');
req._add('contact', '');
req._add('path', '');
_sendOperatorRequest(req, fee);
}
@ -75,9 +51,8 @@ contract LinkWellBoolConsumerContractExample is ChainlinkClient, ConfirmedOwner
// Receive the result from the Chainlink oracle
event RequestFulfilled(bytes32 indexed requestId);
function fulfill(bytes32 requestId, bool data) 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
response = data; // example value: true
emit RequestFulfilled(requestId);
response = data;
}
// Update oracle address

View File

@ -55,10 +55,6 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner {
owners.push(_owners[i]);
}
}
//get share
//update share
//change payout address
//
modifier hasValidPayoutContract() {
require(address(payoutContract) != address(0), "payoutContract not initialized");
_;
@ -73,26 +69,20 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner {
payoutContract = Payroll(_payoutAddress);
}
// Send a request to the Chainlink oracle
function request() external onlyOwner{
function request(string memory url) external onlyOwner{
Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector);
// DEFINE THE REQUEST PARAMETERS (example)
req._add('method', 'GET');
req._add('url', 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR');
req._add('headers', '["content-type", "application/json", "set-cookie", "sid=14A52"]');
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('url', url);
// The following curl command simulates the above request parameters:
// 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)
req._add('path', 'ETH,USD');
//if returns just int - then empty path
req._add('path', '');
req._addInt('multiplier', 10 ** 18);
// Send the request to the Chainlink oracle
req._add('headers', '["content-type", "application/json"]');
req._add('body', '');
req._add('contact', '');
_sendOperatorRequest(req, fee);
}
@ -102,9 +92,7 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner {
event RequestFulfilled(bytes32 indexed requestId);
function fulfill(bytes32 requestId, uint256 data) 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
totalPayoutInUSD = data / 1e18 / 100; // example value: 1875870000000000000000 (1875.87 before "multiplier" is applied)
totalPayoutInUSD = data / 1e18;
}
function payout() external onlyOwner hasValidPayoutContract{

View File

@ -1,4 +0,0 @@
# links
chainlink-feeds amoy
https://docs.chain.link/data-feeds/price-feeds/addresses?network=polygon&page=1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 238 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

BIN
excalidraw/multisig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff

BIN
excalidraw/payroll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

File diff suppressed because it is too large Load Diff

BIN
excalidraw/set-salary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB