ваше сообщение коммита
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "Ownable",
|
||||
"sourceName": "@openzeppelin/contracts/access/Ownable.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnableInvalidOwner",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnableUnauthorizedAccount",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "ERC20Permit",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ECDSAInvalidSignature",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "length",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ECDSAInvalidSignatureLength",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "ECDSAInvalidSignatureS",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "allowance",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "needed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InsufficientAllowance",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "needed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InsufficientBalance",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "approver",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidApprover",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "receiver",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidReceiver",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidSender",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidSpender",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC2612ExpiredSignature",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "signer",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC2612InvalidSigner",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "currentNonce",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "InvalidAccountNonce",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidShortString",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "str",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "StringTooLong",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [],
|
||||
"name": "EIP712DomainChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "eip712Domain",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes1",
|
||||
"name": "fields",
|
||||
"type": "bytes1"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "version",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "chainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "verifyingContract",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "salt",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "extensions",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "nonces",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "permit",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IERC20Permit",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "nonces",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "permit",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
backend/artifacts/contracts/DLE.sol/DLE.dbg.json
Normal file
4
backend/artifacts/contracts/DLE.sol/DLE.dbg.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/6575c8c945d0e94a5880cf71f96c424f.json"
|
||||
}
|
||||
3177
backend/artifacts/contracts/DLE.sol/DLE.json
Normal file
3177
backend/artifacts/contracts/DLE.sol/DLE.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
2356
backend/cache/solidity-files-cache.json
vendored
2356
backend/cache/solidity-files-cache.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
||||
import "@openzeppelin/contracts/governance/Governor.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
||||
import "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
||||
import "@openzeppelin/contracts/utils/Nonces.sol";
|
||||
|
||||
/**
|
||||
* @title DLE (Digital Legal Entity)
|
||||
* @dev Основной смарт-контракт DLE согласно требованиям SMART_CONTRACTS.md
|
||||
*
|
||||
* Функции:
|
||||
* - ERC-20 токен управления с мультиподписью
|
||||
* - Система голосования с кворумом
|
||||
* - Казначейские функции
|
||||
* - Коммуникационные функции
|
||||
* - Настраиваемые таймлоки
|
||||
* - Модульная система
|
||||
* @dev Основной контракт DLE с отдельным модулем TimelockController.
|
||||
*/
|
||||
contract DLE is
|
||||
ERC20Permit,
|
||||
ERC20Votes,
|
||||
Governor,
|
||||
GovernorSettings,
|
||||
@@ -32,184 +22,210 @@ contract DLE is
|
||||
GovernorVotesQuorumFraction,
|
||||
GovernorTimelockControl
|
||||
{
|
||||
// Структура для хранения информации о DLE
|
||||
struct DLEInfo {
|
||||
string name;
|
||||
string symbol;
|
||||
string location;
|
||||
string[] isicCodes;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
uint256 oktmo;
|
||||
string[] okvedCodes;
|
||||
uint256 kpp;
|
||||
uint256 creationTimestamp;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// Структура для предложений
|
||||
struct Proposal {
|
||||
bytes operation; // Операция для выполнения
|
||||
uint256[] targetChains; // Целевые сети для исполнения
|
||||
uint256 timelock; // Время исполнения (timestamp)
|
||||
uint256 governanceChain; // Сеть где проходит голосование
|
||||
address initiator; // Инициатор предложения
|
||||
bytes[] signatures; // Подписи токен-холдеров
|
||||
bool executed; // Статус исполнения
|
||||
uint256 quorumRequired; // Требуемый кворум
|
||||
uint256 signaturesCount; // Количество собранных подписей
|
||||
struct DLEConfig {
|
||||
string name;
|
||||
string symbol;
|
||||
string location;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
uint256 oktmo;
|
||||
string[] okvedCodes;
|
||||
uint256 kpp;
|
||||
uint48 votingDelay;
|
||||
uint32 votingPeriod;
|
||||
uint256 proposalThreshold;
|
||||
uint256 quorumPercentage;
|
||||
address[] initialPartners;
|
||||
uint256[] initialAmounts;
|
||||
}
|
||||
|
||||
struct Proposal {
|
||||
bytes operation;
|
||||
uint256[] targetChains;
|
||||
uint256 timelock;
|
||||
uint256 governanceChain;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
}
|
||||
|
||||
struct TokenDistributionProposal {
|
||||
address[] partners;
|
||||
uint256[] amounts;
|
||||
uint256 timelock;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
string description;
|
||||
}
|
||||
|
||||
struct TreasuryProposal {
|
||||
address recipient;
|
||||
uint256 amount;
|
||||
uint256 timelock;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
string description;
|
||||
}
|
||||
|
||||
// Информация о DLE
|
||||
DLEInfo public dleInfo;
|
||||
|
||||
// Таймлок контроллер
|
||||
TimelockController public timelockController;
|
||||
|
||||
// Маппинг предложений
|
||||
mapping(uint256 => Proposal) public proposals;
|
||||
mapping(uint256 => TokenDistributionProposal) public tokenDistributionProposals;
|
||||
mapping(uint256 => TreasuryProposal) public treasuryProposals;
|
||||
uint256 public proposalCounter;
|
||||
|
||||
// Настройки кворума
|
||||
uint256 public tokenDistributionProposalCounter;
|
||||
uint256 public treasuryProposalCounter;
|
||||
uint256 public quorumPercentage;
|
||||
|
||||
// События
|
||||
bool public initialTokensDistributed = false;
|
||||
|
||||
// Казначейские функции
|
||||
mapping(address => uint256) public lastWithdrawalBlock; // Последний блок вывода для каждого адреса
|
||||
uint256 public totalTreasuryBalance; // Общий баланс казны
|
||||
|
||||
event DLEInitialized(
|
||||
string name,
|
||||
string symbol,
|
||||
string location,
|
||||
string coordinates,
|
||||
uint256 jurisdiction,
|
||||
uint256 oktmo,
|
||||
string[] okvedCodes,
|
||||
uint256 kpp,
|
||||
address tokenAddress,
|
||||
address timelockAddress,
|
||||
address governorAddress
|
||||
);
|
||||
|
||||
event TokensDistributed(address[] partners, uint256[] amounts);
|
||||
event InitialTokensDistributed(address[] partners, uint256[] amounts);
|
||||
event TokensDepositedToTreasury(address depositor, uint256 amount);
|
||||
event TreasuryProposalCreated(uint256 proposalId, address initiator, address recipient, uint256 amount, string description);
|
||||
event TreasuryProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event TreasuryProposalExecuted(uint256 proposalId, address recipient, uint256 amount);
|
||||
event TokenDistributionProposalCreated(uint256 proposalId, address initiator, address[] partners, uint256[] amounts, string description);
|
||||
event TokenDistributionProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event TokenDistributionProposalExecuted(uint256 proposalId, address[] partners, uint256[] amounts);
|
||||
event ProposalCreated(uint256 proposalId, address initiator, bytes operation);
|
||||
event ProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event ProposalExecuted(uint256 proposalId);
|
||||
event ModuleInstalled(string moduleName, address moduleAddress);
|
||||
|
||||
/**
|
||||
* @dev Конструктор DLE
|
||||
* @param _name Название DLE
|
||||
* @param _symbol Символ токена управления
|
||||
* @param _location Местонахождение DLE
|
||||
* @param _isicCodes Коды деятельности ISIC
|
||||
* @param _votingDelay Задержка голосования в блоках
|
||||
* @param _votingPeriod Период голосования в блоках
|
||||
* @param _proposalThreshold Порог для создания предложений
|
||||
* @param _quorumPercentage Процент кворума
|
||||
* @param _minTimelockDelay Минимальная задержка таймлока в секундах
|
||||
*/
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
string memory _location,
|
||||
string[] memory _isicCodes,
|
||||
uint48 _votingDelay,
|
||||
uint32 _votingPeriod,
|
||||
uint256 _proposalThreshold,
|
||||
uint256 _quorumPercentage,
|
||||
uint256 _minTimelockDelay
|
||||
DLEConfig memory config,
|
||||
address timelockAddress
|
||||
)
|
||||
ERC20(_name, _symbol)
|
||||
ERC20Permit(_name)
|
||||
Governor(_name)
|
||||
GovernorSettings(_votingDelay, _votingPeriod, _proposalThreshold)
|
||||
GovernorVotesQuorumFraction(_quorumPercentage)
|
||||
ERC20(config.name, config.symbol)
|
||||
Governor(config.name)
|
||||
GovernorSettings(config.votingDelay, config.votingPeriod, config.proposalThreshold)
|
||||
GovernorVotesQuorumFraction(config.quorumPercentage)
|
||||
GovernorTimelockControl(TimelockController(payable(timelockAddress)))
|
||||
GovernorVotes(IVotes(address(this)))
|
||||
{
|
||||
// Инициализируем информацию о DLE
|
||||
dleInfo = DLEInfo({
|
||||
name: _name,
|
||||
symbol: _symbol,
|
||||
location: _location,
|
||||
isicCodes: _isicCodes,
|
||||
name: config.name,
|
||||
symbol: config.symbol,
|
||||
location: config.location,
|
||||
coordinates: config.coordinates,
|
||||
jurisdiction: config.jurisdiction,
|
||||
oktmo: config.oktmo,
|
||||
okvedCodes: config.okvedCodes,
|
||||
kpp: config.kpp,
|
||||
creationTimestamp: block.timestamp,
|
||||
isActive: true
|
||||
});
|
||||
quorumPercentage = config.quorumPercentage;
|
||||
|
||||
// Устанавливаем кворум
|
||||
quorumPercentage = _quorumPercentage;
|
||||
// Автоматически распределяем начальные токены партнерам при деплое
|
||||
require(config.initialPartners.length == config.initialAmounts.length, "Arrays length mismatch");
|
||||
require(config.initialPartners.length > 0, "No initial partners");
|
||||
|
||||
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
||||
require(config.initialPartners[i] != address(0), "Zero address");
|
||||
require(config.initialAmounts[i] > 0, "Zero amount");
|
||||
_mint(config.initialPartners[i], config.initialAmounts[i]);
|
||||
}
|
||||
|
||||
initialTokensDistributed = true;
|
||||
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
|
||||
|
||||
// Создаем таймлок контроллер
|
||||
address[] memory proposers = new address[](1);
|
||||
address[] memory executors = new address[](1);
|
||||
proposers[0] = address(this); // DLE контракт может предлагать
|
||||
executors[0] = address(0); // Любой может выполнять
|
||||
|
||||
timelockController = new TimelockController(
|
||||
_minTimelockDelay,
|
||||
proposers,
|
||||
executors,
|
||||
address(0) // Нет админа для децентрализации
|
||||
emit DLEInitialized(
|
||||
config.name,
|
||||
config.symbol,
|
||||
config.location,
|
||||
config.coordinates,
|
||||
config.jurisdiction,
|
||||
config.oktmo,
|
||||
config.okvedCodes,
|
||||
config.kpp,
|
||||
address(this),
|
||||
timelockAddress,
|
||||
address(this)
|
||||
);
|
||||
|
||||
// Отказываемся от роли админа в таймлоке
|
||||
timelockController.renounceRole(timelockController.DEFAULT_ADMIN_ROLE(), address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Распределяет начальные токены между партнерами
|
||||
* @dev Создать предложение на распределение токенов
|
||||
* @param _partners Массив адресов партнеров
|
||||
* @param _amounts Массив сумм токенов для каждого партнера
|
||||
* @param _timelock Время исполнения (timestamp)
|
||||
* @param _description Описание предложения
|
||||
*/
|
||||
function distributeInitialTokens(
|
||||
function createTokenDistributionProposal(
|
||||
address[] memory _partners,
|
||||
uint256[] memory _amounts
|
||||
) external {
|
||||
uint256[] memory _amounts,
|
||||
uint256 _timelock,
|
||||
string memory _description
|
||||
) external returns (uint256) {
|
||||
require(_partners.length == _amounts.length, "Arrays length mismatch");
|
||||
require(_partners.length > 0, "Empty arrays");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
|
||||
uint256 totalSupply = 0;
|
||||
for (uint256 i = 0; i < _partners.length; i++) {
|
||||
require(_partners[i] != address(0), "Zero address");
|
||||
require(_amounts[i] > 0, "Zero amount");
|
||||
|
||||
totalSupply += _amounts[i];
|
||||
_mint(_partners[i], _amounts[i]);
|
||||
}
|
||||
|
||||
emit TokensDistributed(_partners, _amounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Создает новое предложение
|
||||
* @param _operation Операция для выполнения
|
||||
* @param _targetChains Целевые сети для исполнения
|
||||
* @param _timelockDelay Задержка таймлока
|
||||
* @return proposalId ID созданного предложения
|
||||
*/
|
||||
function createProposal(
|
||||
bytes calldata _operation,
|
||||
uint256[] calldata _targetChains,
|
||||
uint256 _timelockDelay
|
||||
) external onlyTokenHolder returns (uint256 proposalId) {
|
||||
require(_operation.length > 0, "Empty operation");
|
||||
require(_targetChains.length > 0, "No target chains");
|
||||
require(_timelockDelay > 0, "Invalid timelock");
|
||||
|
||||
proposalId = proposalCounter++;
|
||||
uint256 proposalId = tokenDistributionProposalCounter++;
|
||||
|
||||
proposals[proposalId] = Proposal({
|
||||
operation: _operation,
|
||||
targetChains: _targetChains,
|
||||
timelock: block.timestamp + _timelockDelay,
|
||||
governanceChain: block.chainid,
|
||||
tokenDistributionProposals[proposalId] = TokenDistributionProposal({
|
||||
partners: _partners,
|
||||
amounts: _amounts,
|
||||
timelock: _timelock,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0
|
||||
signaturesCount: 0,
|
||||
description: _description
|
||||
});
|
||||
|
||||
emit ProposalCreated(proposalId, msg.sender, _operation);
|
||||
emit TokenDistributionProposalCreated(proposalId, msg.sender, _partners, _amounts, _description);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Подписывает предложение
|
||||
* @dev Подписать предложение на распределение токенов
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function signProposal(uint256 _proposalId) external onlyTokenHolder {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
function signTokenDistributionProposal(uint256 _proposalId) external {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Timelock expired");
|
||||
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
|
||||
// Проверяем, что пользователь еще не подписал
|
||||
for (uint256 i = 0; i < proposal.signatures.length; i++) {
|
||||
require(
|
||||
@@ -219,110 +235,256 @@ contract DLE is
|
||||
);
|
||||
}
|
||||
|
||||
// Добавляем подпись
|
||||
proposal.signatures.push(abi.encode(msg.sender));
|
||||
proposal.signaturesCount += balanceOf(msg.sender);
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
|
||||
emit ProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
emit TokenDistributionProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
|
||||
// Проверяем, достигнут ли кворум
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
_executeTokenDistribution(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполняет предложение
|
||||
* @dev Выполнить распределение токенов после достижения кворума
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeProposal(uint256 _proposalId) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
function _executeTokenDistribution(uint256 _proposalId) internal {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(proposal.executed, "Proposal not executed");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
for (uint256 i = 0; i < proposal.partners.length; i++) {
|
||||
require(proposal.partners[i] != address(0), "Zero address");
|
||||
require(proposal.amounts[i] > 0, "Zero amount");
|
||||
_mint(proposal.partners[i], proposal.amounts[i]);
|
||||
}
|
||||
|
||||
emit TokenDistributionProposalExecuted(_proposalId, proposal.partners, proposal.amounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполнить предложение на распределение токенов после истечения таймлока
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeTokenDistributionProposal(uint256 _proposalId) external {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
proposal.executed = true;
|
||||
_executeTokenDistribution(_proposalId);
|
||||
}
|
||||
|
||||
// Здесь будет логика выполнения операции
|
||||
// В зависимости от типа операции
|
||||
function createProposal(
|
||||
bytes memory _operation,
|
||||
uint256[] memory _targetChains,
|
||||
uint256 _timelock,
|
||||
uint256 _governanceChain
|
||||
) external returns (uint256) {
|
||||
require(_operation.length > 0, "Empty operation");
|
||||
require(_targetChains.length > 0, "Empty target chains");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
uint256 proposalId = proposalCounter++;
|
||||
proposals[proposalId] = Proposal({
|
||||
operation: _operation,
|
||||
targetChains: _targetChains,
|
||||
timelock: _timelock,
|
||||
governanceChain: _governanceChain,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0
|
||||
});
|
||||
emit ProposalCreated(proposalId, msg.sender, _operation);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
emit ProposalExecuted(_proposalId);
|
||||
function signProposal(uint256 _proposalId) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
emit ProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
emit IGovernor.ProposalExecuted(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
function installModule(string memory _moduleName, address _moduleAddress) external {
|
||||
emit ModuleInstalled(_moduleName, _moduleAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Получает информацию о DLE
|
||||
* @return Информация о DLE
|
||||
* @dev Внести токены в казну DLE
|
||||
* @param _amount Количество токенов для внесения
|
||||
*/
|
||||
function getDLEInfo() external view returns (DLEInfo memory) {
|
||||
return dleInfo;
|
||||
function depositToTreasury(uint256 _amount) external {
|
||||
require(_amount > 0, "Amount must be greater than 0");
|
||||
require(balanceOf(msg.sender) >= _amount, "Insufficient balance");
|
||||
|
||||
_transfer(msg.sender, address(this), _amount);
|
||||
totalTreasuryBalance += _amount;
|
||||
|
||||
emit TokensDepositedToTreasury(msg.sender, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Получает адрес таймлока
|
||||
* @return Адрес таймлок контроллера
|
||||
* @dev Создать предложение на вывод средств из казны
|
||||
* @param _recipient Адрес получателя
|
||||
* @param _amount Количество токенов для вывода
|
||||
* @param _timelock Время исполнения (timestamp)
|
||||
* @param _description Описание предложения
|
||||
*/
|
||||
function getTimelockAddress() external view returns (address) {
|
||||
return address(timelockController);
|
||||
function createTreasuryProposal(
|
||||
address _recipient,
|
||||
uint256 _amount,
|
||||
uint256 _timelock,
|
||||
string memory _description
|
||||
) external returns (uint256) {
|
||||
require(_recipient != address(0), "Zero address");
|
||||
require(_amount > 0, "Amount must be greater than 0");
|
||||
require(_amount <= totalTreasuryBalance, "Insufficient treasury balance");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
|
||||
uint256 proposalId = treasuryProposalCounter++;
|
||||
|
||||
treasuryProposals[proposalId] = TreasuryProposal({
|
||||
recipient: _recipient,
|
||||
amount: _amount,
|
||||
timelock: _timelock,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0,
|
||||
description: _description
|
||||
});
|
||||
|
||||
emit TreasuryProposalCreated(proposalId, msg.sender, _recipient, _amount, _description);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Модификатор для проверки владения токенами
|
||||
* @dev Подписать предложение на вывод средств из казны
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
modifier onlyTokenHolder() {
|
||||
require(balanceOf(msg.sender) > 0, "Not a token holder");
|
||||
_;
|
||||
function signTreasuryProposal(uint256 _proposalId) external {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
|
||||
// Проверяем, что пользователь еще не подписал
|
||||
for (uint256 i = 0; i < proposal.signatures.length; i++) {
|
||||
require(
|
||||
proposal.signatures[i].length == 0 ||
|
||||
abi.decode(proposal.signatures[i], (address)) != msg.sender,
|
||||
"Already signed"
|
||||
);
|
||||
}
|
||||
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
|
||||
emit TreasuryProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
|
||||
// Проверяем, достигнут ли кворум
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
_executeTreasuryProposal(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
// Переопределения, необходимые для корректной работы токена голосования
|
||||
function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
|
||||
/**
|
||||
* @dev Выполнить предложение на вывод средств из казны
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function _executeTreasuryProposal(uint256 _proposalId) internal {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(proposal.executed, "Proposal not executed");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
require(proposal.amount <= totalTreasuryBalance, "Insufficient treasury balance");
|
||||
|
||||
totalTreasuryBalance -= proposal.amount;
|
||||
_transfer(address(this), proposal.recipient, proposal.amount);
|
||||
|
||||
emit TreasuryProposalExecuted(_proposalId, proposal.recipient, proposal.amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполнить предложение на вывод средств после истечения таймлока
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeTreasuryProposal(uint256 _proposalId) external {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
proposal.executed = true;
|
||||
_executeTreasuryProposal(_proposalId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @dev Получить доступную для вывода сумму для адреса (пропорционально доле)
|
||||
* @param _address Адрес для проверки
|
||||
* @return Доступная сумма для вывода
|
||||
*/
|
||||
function getAvailableWithdrawal(address _address) public view returns (uint256) {
|
||||
uint256 userBalance = balanceOf(_address);
|
||||
if (userBalance == 0 || totalTreasuryBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Пропорционально доле в общем количестве токенов
|
||||
uint256 userShare = (userBalance * totalTreasuryBalance) / totalSupply();
|
||||
return userShare;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Переопределения для совместимости с ERC-6372
|
||||
function CLOCK_MODE() public pure override(Governor, GovernorVotes, Votes) returns (string memory) {
|
||||
return "mode=blocknumber&from=default";
|
||||
}
|
||||
function clock() public view override(Governor, GovernorVotes, Votes) returns (uint48) {
|
||||
return uint48(block.number);
|
||||
}
|
||||
function _update(address from, address to, uint256 amount) internal override(ERC20Votes) {
|
||||
super._update(from, to, amount);
|
||||
}
|
||||
|
||||
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
|
||||
function nonces(address owner) public view override(Nonces) returns (uint256) {
|
||||
return super.nonces(owner);
|
||||
}
|
||||
|
||||
// Переопределения для Governor
|
||||
function votingDelay()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function name() public view override(ERC20, Governor) returns (string memory) {
|
||||
return super.name();
|
||||
}
|
||||
function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.votingDelay();
|
||||
}
|
||||
|
||||
function votingPeriod()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.votingPeriod();
|
||||
}
|
||||
|
||||
function quorum(uint256 blockNumber)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorVotesQuorumFraction)
|
||||
returns (uint256)
|
||||
{
|
||||
function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) {
|
||||
return super.quorum(blockNumber);
|
||||
}
|
||||
|
||||
function state(uint256 proposalId)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (ProposalState)
|
||||
{
|
||||
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||
return super.state(proposalId);
|
||||
}
|
||||
|
||||
function proposalThreshold()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.proposalThreshold();
|
||||
}
|
||||
|
||||
function _cancel(
|
||||
address[] memory targets,
|
||||
uint256[] memory values,
|
||||
@@ -331,34 +493,15 @@ contract DLE is
|
||||
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _executor()
|
||||
internal
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (address)
|
||||
{
|
||||
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||
return super._executor();
|
||||
}
|
||||
|
||||
function supportsInterface(bytes4 interfaceId)
|
||||
public
|
||||
view
|
||||
override(Governor)
|
||||
returns (bool)
|
||||
{
|
||||
function supportsInterface(bytes4 interfaceId) public view override(Governor) returns (bool) {
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function proposalNeedsQueuing(uint256 proposalId)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (bool)
|
||||
{
|
||||
function proposalNeedsQueuing(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (bool) {
|
||||
return super.proposalNeedsQueuing(proposalId);
|
||||
}
|
||||
|
||||
function _queueOperations(
|
||||
uint256 proposalId,
|
||||
address[] memory targets,
|
||||
@@ -368,7 +511,6 @@ contract DLE is
|
||||
) internal override(Governor, GovernorTimelockControl) returns (uint48) {
|
||||
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _executeOperations(
|
||||
uint256 proposalId,
|
||||
address[] memory targets,
|
||||
|
||||
@@ -64,10 +64,13 @@ class EmailBotService {
|
||||
user: settings.smtp_user,
|
||||
pass: settings.smtp_password,
|
||||
},
|
||||
pool: true,
|
||||
maxConnections: 3,
|
||||
maxMessages: 5,
|
||||
pool: false, // Отключаем пул соединений
|
||||
maxConnections: 1, // Ограничиваем до 1 соединения
|
||||
maxMessages: 1, // Ограничиваем до 1 сообщения на соединение
|
||||
tls: { rejectUnauthorized: false },
|
||||
connectionTimeout: 30000, // 30 секунд на подключение
|
||||
greetingTimeout: 30000, // 30 секунд на приветствие
|
||||
socketTimeout: 60000, // 60 секунд на операции сокета
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,8 +145,8 @@ class EmailBotService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем непрочитанные письма
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
// Ищем все письма и проверяем их флаги вручную
|
||||
this.imap.search(['ALL'], (err, results) => {
|
||||
if (err) {
|
||||
logger.error(`Error searching messages: ${err}`);
|
||||
this.imap.end();
|
||||
@@ -151,173 +154,106 @@ class EmailBotService {
|
||||
}
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
logger.info('No new messages found');
|
||||
logger.info('No messages found');
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const f = this.imap.fetch(results, { bodies: '' });
|
||||
// Фильтруем только непрочитанные сообщения
|
||||
const f = this.imap.fetch(results, {
|
||||
bodies: '',
|
||||
markSeen: true, // Помечаем как прочитанные
|
||||
struct: true // Получаем структуру для Message-ID
|
||||
});
|
||||
|
||||
f.on('message', (msg, seqno) => {
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
logger.error(`Error parsing message: ${err}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
const subject = parsed.subject || '';
|
||||
const text = parsed.text || '';
|
||||
const html = parsed.html || '';
|
||||
// 1. Найти или создать пользователя
|
||||
const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`Email от заблокированного пользователя ${userId} проигнорирован.`);
|
||||
return;
|
||||
}
|
||||
// 1.1 Найти или создать беседу
|
||||
let conversationResult = await encryptedDb.getData(
|
||||
'conversations',
|
||||
{ user_id: userId },
|
||||
1,
|
||||
'updated_at DESC, created_at DESC'
|
||||
);
|
||||
let conversation;
|
||||
if (conversationResult.length === 0) {
|
||||
const title = `Чат с пользователем ${userId}`;
|
||||
const newConv = await encryptedDb.saveData(
|
||||
'conversations',
|
||||
{ user_id: userId, title: title, created_at: new Date(), updated_at: new Date() }
|
||||
);
|
||||
conversation = newConv;
|
||||
} else {
|
||||
conversation = conversationResult[0];
|
||||
}
|
||||
|
||||
// Проверяем, что conversation создан успешно
|
||||
if (!conversation || !conversation.id) {
|
||||
logger.error(`[EmailBot] Conversation is undefined or has no id for user ${userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Сохранять все сообщения с conversation_id
|
||||
let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
|
||||
if (hasAttachments) {
|
||||
for (const att of parsed.attachments) {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
attachment_filename: att.filename,
|
||||
attachment_mimetype: att.contentType,
|
||||
attachment_size: att.size,
|
||||
attachment_data: att.content,
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
}
|
||||
// 3. Получить ответ от ИИ (RAG + LLM)
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let ragTableId = null;
|
||||
if (aiSettings && aiSettings.selected_rag_tables) {
|
||||
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
|
||||
? aiSettings.selected_rag_tables[0]
|
||||
: aiSettings.selected_rag_tables;
|
||||
}
|
||||
let aiResponse;
|
||||
if (ragTableId) {
|
||||
// Сначала ищем ответ через RAG
|
||||
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: text });
|
||||
if (ragResult && ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= 0.3) {
|
||||
aiResponse = ragResult.answer;
|
||||
} else {
|
||||
aiResponse = await generateLLMResponse({
|
||||
userQuestion: text,
|
||||
context: ragResult && ragResult.context ? ragResult.context : '',
|
||||
answer: ragResult && ragResult.answer ? ragResult.answer : '',
|
||||
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||
history: null,
|
||||
model: aiSettings ? aiSettings.model : undefined,
|
||||
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
aiResponse = await aiAssistant.getResponse(text, 'auto');
|
||||
}
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`[EmailBot] Пользователь ${userId} заблокирован — ответ ИИ не отправляется.`);
|
||||
return;
|
||||
}
|
||||
// 4. Сохранить ответ в БД с conversation_id
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'assistant',
|
||||
content: aiResponse,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'out',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
// 5. Отправить ответ на email
|
||||
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
|
||||
// После каждого успешного создания пользователя:
|
||||
broadcastContactsUpdate();
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
let unreadMessages = [];
|
||||
let processedCount = 0;
|
||||
let totalMessages = results.length;
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error(`Fetch error: ${err}`);
|
||||
});
|
||||
|
||||
f.once('end', () => {
|
||||
try {
|
||||
this.imap.end();
|
||||
} catch (e) {
|
||||
logger.error(`Error ending IMAP connection: ${e.message}`);
|
||||
f.on('message', (msg, seqno) => {
|
||||
let messageId = null;
|
||||
let uid = null;
|
||||
let flags = [];
|
||||
|
||||
// Получаем UID, Message-ID и флаги
|
||||
msg.once('attributes', (attrs) => {
|
||||
uid = attrs.uid;
|
||||
flags = attrs.flags || [];
|
||||
if (attrs['x-gm-msgid']) {
|
||||
messageId = attrs['x-gm-msgid'];
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`Error fetching messages: ${e.message}`);
|
||||
try {
|
||||
this.imap.end();
|
||||
} catch (e) {
|
||||
// Игнорируем ошибки при закрытии
|
||||
}
|
||||
}
|
||||
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
logger.error(`Error parsing message: ${err}`);
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
if (unreadMessages.length === 0) {
|
||||
logger.info('No unread messages found');
|
||||
}
|
||||
this.imap.end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем Message-ID из заголовков
|
||||
if (!messageId && parsed.messageId) {
|
||||
messageId = parsed.messageId;
|
||||
}
|
||||
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
const subject = parsed.subject || '';
|
||||
const text = parsed.text || '';
|
||||
const html = parsed.html || '';
|
||||
|
||||
// Проверяем, что сообщение непрочитанное (нет флага \Seen)
|
||||
const isUnread = !flags.includes('\\Seen');
|
||||
|
||||
logger.info(`[EmailBot] Проверяем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}, Unread=${isUnread}`);
|
||||
|
||||
if (isUnread) {
|
||||
unreadMessages.push({
|
||||
uid,
|
||||
messageId,
|
||||
fromEmail,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
parsed
|
||||
});
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
if (unreadMessages.length === 0) {
|
||||
logger.info('No unread messages found');
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`[EmailBot] Найдено ${unreadMessages.length} непрочитанных сообщений`);
|
||||
|
||||
// Обрабатываем каждое непрочитанное сообщение
|
||||
for (const messageData of unreadMessages) {
|
||||
try {
|
||||
await this.processIncomingEmail(messageData);
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
}
|
||||
|
||||
this.imap.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error(`Error fetching messages: ${err}`);
|
||||
this.imap.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -334,22 +270,261 @@ class EmailBotService {
|
||||
}
|
||||
|
||||
// Метод для отправки email
|
||||
async sendEmail(to, subject, text) {
|
||||
async processIncomingEmail(messageData) {
|
||||
const { uid, messageId, fromEmail, subject, text, html, parsed } = messageData;
|
||||
|
||||
try {
|
||||
const settings = await this.getSettingsFromDb();
|
||||
const transporter = await this.getTransporter();
|
||||
const mailOptions = {
|
||||
from: settings.from_email,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
logger.info(`Email sent to ${to}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error sending email:', error);
|
||||
throw error;
|
||||
logger.info(`[EmailBot] Обрабатываем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}`);
|
||||
|
||||
// Фильтруем системные email адреса
|
||||
const systemEmails = [
|
||||
'mailer-daemon@smtp.hostland.ru',
|
||||
'mailer-daemon@',
|
||||
'noreply@',
|
||||
'no-reply@',
|
||||
'postmaster@',
|
||||
'bounce@',
|
||||
'daemon@'
|
||||
];
|
||||
|
||||
const isSystemEmail = systemEmails.some(systemEmail =>
|
||||
fromEmail && fromEmail.toLowerCase().includes(systemEmail.toLowerCase())
|
||||
);
|
||||
|
||||
if (isSystemEmail) {
|
||||
logger.info(`[EmailBot] Игнорируем системный email от ${fromEmail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что email адрес валидный
|
||||
if (!fromEmail || !fromEmail.includes('@')) {
|
||||
logger.info(`[EmailBot] Игнорируем email с невалидным адресом: ${fromEmail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем время письма - не обрабатываем письма старше 1 часа
|
||||
const emailDate = parsed.date || new Date();
|
||||
const now = new Date();
|
||||
const timeDiff = now.getTime() - emailDate.getTime();
|
||||
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
||||
|
||||
if (hoursDiff > 1) {
|
||||
logger.info(`[EmailBot] Игнорируем старое письмо от ${fromEmail} (${hoursDiff.toFixed(1)} часов назад)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, не обрабатывали ли мы уже это письмо
|
||||
if (messageId) {
|
||||
const existingMessage = await encryptedDb.getData('messages', {
|
||||
metadata: { $like: `%"messageId":"${messageId}"%` }
|
||||
}, 1);
|
||||
|
||||
if (existingMessage.length > 0) {
|
||||
logger.info(`[EmailBot] Письмо с Message-ID ${messageId} уже обработано, пропускаем`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Найти или создать пользователя
|
||||
const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`Email от заблокированного пользователя ${userId} проигнорирован.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 Найти или создать беседу
|
||||
let conversationResult = await encryptedDb.getData(
|
||||
'conversations',
|
||||
{ user_id: userId },
|
||||
1,
|
||||
'updated_at DESC, created_at DESC'
|
||||
);
|
||||
let conversation;
|
||||
if (conversationResult.length === 0) {
|
||||
const title = `Чат с пользователем ${userId}`;
|
||||
const newConv = await encryptedDb.saveData(
|
||||
'conversations',
|
||||
{ user_id: userId, title: title, created_at: new Date(), updated_at: new Date() }
|
||||
);
|
||||
conversation = newConv;
|
||||
} else {
|
||||
conversation = conversationResult[0];
|
||||
}
|
||||
|
||||
// Проверяем, что conversation создан успешно
|
||||
if (!conversation || !conversation.id) {
|
||||
logger.error(`[EmailBot] Conversation is undefined or has no id for user ${userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Сохранять все сообщения с conversation_id
|
||||
let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
|
||||
if (hasAttachments) {
|
||||
for (const att of parsed.attachments) {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
attachment_filename: att.filename,
|
||||
attachment_mimetype: att.contentType,
|
||||
attachment_size: att.size,
|
||||
attachment_data: att.content,
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
messageId: messageId,
|
||||
uid: uid,
|
||||
fromEmail: fromEmail
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
messageId: messageId,
|
||||
uid: uid,
|
||||
fromEmail: fromEmail
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Получить ответ от ИИ (RAG + LLM)
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let ragTableId = null;
|
||||
if (aiSettings && aiSettings.selected_rag_tables) {
|
||||
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
|
||||
? aiSettings.selected_rag_tables[0]
|
||||
: aiSettings.selected_rag_tables;
|
||||
}
|
||||
let aiResponse;
|
||||
if (ragTableId) {
|
||||
// Сначала ищем ответ через RAG
|
||||
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: text });
|
||||
if (ragResult && ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= 0.3) {
|
||||
aiResponse = ragResult.answer;
|
||||
} else {
|
||||
aiResponse = await generateLLMResponse({
|
||||
userQuestion: text,
|
||||
context: ragResult && ragResult.context ? ragResult.context : '',
|
||||
answer: ragResult && ragResult.answer ? ragResult.answer : '',
|
||||
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||
history: null,
|
||||
model: aiSettings ? aiSettings.model : undefined,
|
||||
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
aiResponse = await aiAssistant.getResponse(text, 'auto');
|
||||
}
|
||||
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`[EmailBot] Пользователь ${userId} заблокирован — ответ ИИ не отправляется.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Сохранить ответ в БД с conversation_id
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'assistant',
|
||||
content: aiResponse,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'out',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
originalMessageId: messageId,
|
||||
originalUid: uid,
|
||||
originalFromEmail: fromEmail,
|
||||
isResponse: true
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// 5. Отправить ответ на email
|
||||
try {
|
||||
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
|
||||
logger.info(`[EmailBot] Email response sent successfully to ${fromEmail}`);
|
||||
} catch (emailError) {
|
||||
logger.error(`[EmailBot] Failed to send email response to ${fromEmail}:`, emailError);
|
||||
// Продолжаем выполнение, даже если email не отправлен
|
||||
}
|
||||
|
||||
// После каждого успешного создания пользователя:
|
||||
broadcastContactsUpdate();
|
||||
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
}
|
||||
|
||||
async sendEmail(to, subject, text) {
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 5000; // 5 секунд между попытками
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const settings = await this.getSettingsFromDb();
|
||||
const transporter = await this.getTransporter();
|
||||
|
||||
const mailOptions = {
|
||||
from: settings.from_email,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
logger.info(`Email sent to ${to} (attempt ${attempt})`);
|
||||
|
||||
// Закрываем соединение после успешной отправки
|
||||
transporter.close();
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email (attempt ${attempt}/${maxRetries}):`, error);
|
||||
|
||||
// Если это последняя попытка, выбрасываем ошибку
|
||||
if (attempt === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Если ошибка связана с превышением лимита соединений, ждем дольше
|
||||
const isConnectionLimitError = error.message && (
|
||||
error.message.includes('too many connections') ||
|
||||
error.message.includes('421 4.7.0') ||
|
||||
error.message.includes('EPROTOCOL')
|
||||
);
|
||||
|
||||
const waitTime = isConnectionLimitError ? retryDelay * 2 : retryDelay;
|
||||
logger.info(`Waiting ${waitTime}ms before retry...`);
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,813 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/HB3-ACCELERATOR
|
||||
-->
|
||||
|
||||
# DLE Governance System - Документация
|
||||
|
||||
## 📋 Обзор системы
|
||||
|
||||
DLE (Digital Legal Entity) - это система управления цифровым юридическим лицом с использованием смарт-контрактов и токенов управления. Система включает в себя механизмы голосования, мультиподписи и административного управления.
|
||||
|
||||
## 🏗️ Архитектура системы
|
||||
|
||||
### Текущие смарт-контракты
|
||||
|
||||
#### 1. GovernanceToken.sol
|
||||
```solidity
|
||||
contract GovernanceToken is ERC20Permit, ERC20Votes, Ownable {
|
||||
// Основные функции:
|
||||
- mintInitialSupply(address[] partners, uint256[] amounts) // Начальное распределение токенов
|
||||
- transfer() // Передача токенов
|
||||
- balanceOf() // Проверка баланса
|
||||
- getVotes() // Получение голосов на определенном блоке
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. GovernanceTimelock.sol
|
||||
```solidity
|
||||
contract GovernanceTimelock is TimelockController {
|
||||
// Основные функции:
|
||||
- queue() // Постановка операции в очередь
|
||||
- execute() // Выполнение операции после задержки
|
||||
- cancel() // Отмена операции
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. GovernorContract.sol
|
||||
```solidity
|
||||
contract GovernorContract is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
|
||||
// Основные функции:
|
||||
- propose() // Создание предложения
|
||||
- vote() // Голосование
|
||||
- execute() // Выполнение предложения
|
||||
- cancel() // Отмена предложения
|
||||
}
|
||||
```
|
||||
|
||||
### Текущие параметры системы
|
||||
- **Порог предложения**: 100,000 GT
|
||||
- **Кворум**: 4% от общего количества токенов
|
||||
- **Задержка голосования**: 1 день
|
||||
- **Период голосования**: 7 дней
|
||||
- **Минимальная задержка Timelock**: 2 дня
|
||||
|
||||
## 🔧 Функции приложения
|
||||
|
||||
### Критические функции (требуют голосования)
|
||||
|
||||
#### 🚨 УДАЛЕНИЕ ДАННЫХ
|
||||
|
||||
##### Пользователи
|
||||
```javascript
|
||||
// Удаление пользователя
|
||||
DELETE /api/users/:id
|
||||
- Описание: Полное удаление пользователя и всех связанных данных
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Блокировка пользователя
|
||||
PATCH /api/users/:id/block
|
||||
- Описание: Блокировка пользователя в системе
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
##### Таблицы
|
||||
```javascript
|
||||
// Удаление таблицы
|
||||
DELETE /api/tables/:id
|
||||
- Описание: Удаление таблицы и всех связанных данных
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление строк таблицы
|
||||
DELETE /api/tables/:id/rows
|
||||
- Описание: Массовое удаление строк
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление столбцов таблицы
|
||||
DELETE /api/tables/:id/columns
|
||||
- Описание: Удаление столбцов таблицы
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
##### DLE (Digital Legal Entity)
|
||||
```javascript
|
||||
// Удаление DLE
|
||||
DELETE /api/dle/:tokenAddress
|
||||
- Описание: Удаление DLE по адресу токена
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление пустого DLE
|
||||
DELETE /api/dle/empty/:fileName
|
||||
- Описание: Удаление пустого DLE по имени файла
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
##### Системные настройки
|
||||
```javascript
|
||||
// Удаление RPC провайдера
|
||||
DELETE /api/settings/rpc/:networkId
|
||||
- Описание: Удаление RPC конфигурации
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление токена аутентификации
|
||||
DELETE /api/settings/auth-token/:address/:network
|
||||
- Описание: Удаление токена для аутентификации
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление AI настроек
|
||||
DELETE /api/settings/ai-settings/:provider
|
||||
- Описание: Удаление настроек AI провайдера
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Удаление правил AI ассистента
|
||||
DELETE /api/settings/ai-assistant-rules/:id
|
||||
- Описание: Удаление набора правил AI
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
#### 🏗️ СОЗДАНИЕ КРИТИЧНЫХ СУЩНОСТЕЙ
|
||||
|
||||
##### DLE
|
||||
```javascript
|
||||
// Создание нового DLE
|
||||
POST /api/dle
|
||||
- Описание: Создание нового Digital Legal Entity
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
- Параметры: name, symbol, location, isicCodes, partners, amounts, network, etc.
|
||||
```
|
||||
|
||||
##### Таблицы
|
||||
```javascript
|
||||
// Создание таблицы
|
||||
POST /api/tables
|
||||
- Описание: Создание новой таблицы
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
##### Системные настройки
|
||||
```javascript
|
||||
// Добавление RPC провайдера
|
||||
POST /api/settings/rpc
|
||||
- Описание: Добавление нового RPC провайдера
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Добавление токена аутентификации
|
||||
POST /api/settings/auth-token
|
||||
- Описание: Добавление нового токена для аутентификации
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
#### ⚙️ ИЗМЕНЕНИЕ СИСТЕМНЫХ НАСТРОЕК
|
||||
|
||||
##### База данных
|
||||
```javascript
|
||||
// Изменение настроек БД
|
||||
PUT /api/settings/db-settings
|
||||
- Описание: Изменение параметров подключения к базе данных
|
||||
- Критичность: 🔴 ВЫСОКАЯ
|
||||
- Требует голосования: ДА
|
||||
- Параметры: db_host, db_port, db_name, db_user, db_password
|
||||
```
|
||||
|
||||
##### AI настройки
|
||||
```javascript
|
||||
// Изменение настроек AI ассистента
|
||||
PUT /api/settings/ai-assistant
|
||||
- Описание: Изменение глобальных настроек AI ассистента
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Изменение AI провайдера
|
||||
PUT /api/settings/ai-settings/:provider
|
||||
- Описание: Изменение настроек конкретного AI провайдера
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
##### Коммуникации
|
||||
```javascript
|
||||
// Настройки Email
|
||||
PUT /api/identities/email-settings
|
||||
- Описание: Изменение настроек SMTP/IMAP
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
|
||||
// Настройки Telegram
|
||||
PUT /api/identities/telegram-settings
|
||||
- Описание: Изменение настроек Telegram бота
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ДА
|
||||
```
|
||||
|
||||
### Умеренные функции (опциональное голосование)
|
||||
|
||||
#### 📝 РЕДАКТИРОВАНИЕ ДАННЫХ
|
||||
|
||||
```javascript
|
||||
// Редактирование пользователя
|
||||
PATCH /api/users/:id
|
||||
- Описание: Изменение данных пользователя
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ОПЦИОНАЛЬНО
|
||||
|
||||
// Редактирование таблицы
|
||||
PATCH /api/tables/:id
|
||||
- Описание: Изменение структуры таблицы
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ОПЦИОНАЛЬНО
|
||||
|
||||
// Импорт пользователей
|
||||
POST /api/users/import
|
||||
- Описание: Массовый импорт пользователей
|
||||
- Критичность: 🟡 СРЕДНЯЯ
|
||||
- Требует голосования: ОПЦИОНАЛЬНО
|
||||
```
|
||||
|
||||
### Обычные функции (не требуют голосования)
|
||||
|
||||
#### 📖 ПРОСМОТР ДАННЫХ
|
||||
|
||||
```javascript
|
||||
// Список пользователей
|
||||
GET /api/users
|
||||
- Описание: Получение списка пользователей
|
||||
- Критичность: 🟢 НИЗКАЯ
|
||||
- Требует голосования: НЕТ
|
||||
|
||||
// Список таблиц
|
||||
GET /api/tables
|
||||
- Описание: Получение списка таблиц
|
||||
- Критичность: 🟢 НИЗКАЯ
|
||||
- Требует голосования: НЕТ
|
||||
|
||||
// Список DLE
|
||||
GET /api/dle
|
||||
- Описание: Получение списка DLE
|
||||
- Критичность: 🟢 НИЗКАЯ
|
||||
- Требует голосования: НЕТ
|
||||
|
||||
// Детали пользователя
|
||||
GET /api/users/:id
|
||||
- Описание: Получение детальной информации о пользователе
|
||||
- Критичность: 🟢 НИЗКАЯ
|
||||
- Требует голосования: НЕТ
|
||||
```
|
||||
|
||||
#### ✏️ РЕДАКТИРОВАНИЕ ЯЧЕЕК
|
||||
|
||||
```javascript
|
||||
// Изменение значения ячейки
|
||||
PATCH /api/tables/cell
|
||||
- Описание: Изменение значения конкретной ячейки таблицы
|
||||
- Критичность: 🟢 НИЗКАЯ
|
||||
- Требует голосования: НЕТ
|
||||
```
|
||||
|
||||
## 🎯 Система голосования
|
||||
|
||||
### Текущая реализация
|
||||
|
||||
#### Процесс голосования
|
||||
1. **Создание предложения** - пользователь с достаточным количеством токенов создает предложение
|
||||
2. **Период голосования** - 7 дней для голосования
|
||||
3. **Подсчет голосов** - проверка кворума (4% от общего количества токенов)
|
||||
4. **Постановка в очередь** - успешные предложения ставятся в очередь Timelock
|
||||
5. **Выполнение** - после задержки (2 дня) предложение выполняется
|
||||
|
||||
#### Параметры голосования
|
||||
- **Порог для создания предложений**: 100,000 GT
|
||||
- **Кворум**: 4% от общего количества токенов
|
||||
- **Задержка голосования**: 1 день (в блоках)
|
||||
- **Период голосования**: 7 дней (в блоках)
|
||||
- **Минимальная задержка Timelock**: 2 дня
|
||||
|
||||
### Предлагаемые улучшения
|
||||
|
||||
#### Новый модуль: AdminGovernanceModule
|
||||
|
||||
```solidity
|
||||
contract AdminGovernanceModule {
|
||||
struct AdminAction {
|
||||
string actionType; // Тип действия
|
||||
string targetId; // ID цели
|
||||
bytes actionData; // Данные действия
|
||||
uint256 proposalId; // ID предложения
|
||||
bool executed; // Выполнено ли
|
||||
uint256 createdAt; // Время создания
|
||||
}
|
||||
|
||||
mapping(uint256 => AdminAction) public adminActions;
|
||||
mapping(string => bool) public actionTypesRequiringVote;
|
||||
|
||||
// Функции
|
||||
function proposeAdminAction(string memory actionType, string memory targetId, bytes memory actionData) external returns (uint256);
|
||||
function executeAdminAction(uint256 actionId) external;
|
||||
function canExecuteAction(address executor, string memory actionType) external view returns (bool);
|
||||
}
|
||||
```
|
||||
|
||||
#### Типы административных действий
|
||||
|
||||
```solidity
|
||||
// Критические действия (обязательное голосование)
|
||||
"DELETE_USER" // Удаление пользователя
|
||||
"DELETE_DLE" // Удаление DLE
|
||||
"DELETE_TABLE" // Удаление таблицы
|
||||
"CREATE_DLE" // Создание DLE
|
||||
"UPDATE_DB_SETTINGS" // Изменение настроек БД
|
||||
"UPDATE_RPC" // Изменение RPC настроек
|
||||
"UPDATE_AUTH_TOKENS" // Изменение токенов аутентификации
|
||||
|
||||
// Важные действия (опциональное голосование)
|
||||
"CREATE_TABLE" // Создание таблицы
|
||||
"UPDATE_AI_SETTINGS" // Изменение AI настроек
|
||||
"UPDATE_EMAIL_SETTINGS" // Изменение email настроек
|
||||
"UPDATE_TELEGRAM_SETTINGS" // Изменение telegram настроек
|
||||
"IMPORT_USERS" // Импорт пользователей
|
||||
```
|
||||
|
||||
## 🔐 Система доступа
|
||||
|
||||
### Текущая система ролей
|
||||
|
||||
#### Роли пользователей
|
||||
- **Администратор** - полный доступ ко всем функциям
|
||||
- **Пользователь** - ограниченный доступ
|
||||
|
||||
#### Проверка прав
|
||||
```javascript
|
||||
// Middleware для проверки администратора
|
||||
requireAdmin(req, res, next) {
|
||||
// Проверка через сессию
|
||||
if (req.session.isAdmin) return next();
|
||||
|
||||
// Проверка через кошелек
|
||||
if (req.session.address) {
|
||||
const isAdmin = await authService.checkAdminTokens(req.session.address);
|
||||
if (isAdmin) return next();
|
||||
}
|
||||
|
||||
// Проверка через ID пользователя
|
||||
if (req.session.userId) {
|
||||
const user = await db.getQuery()('SELECT role FROM users WHERE id = $1', [req.session.userId]);
|
||||
if (user.rows[0].role === 'admin') return next();
|
||||
}
|
||||
|
||||
return res.status(403).json({ error: 'Доступ запрещен' });
|
||||
}
|
||||
```
|
||||
|
||||
### Предлагаемая система доступа с токенами
|
||||
|
||||
#### Проверка через смарт-контракт
|
||||
```solidity
|
||||
function canExecuteAction(address executor, string memory actionType) public view returns (bool) {
|
||||
// Проверка баланса токенов
|
||||
uint256 balance = governanceToken.balanceOf(executor);
|
||||
|
||||
// Проверка типа действия
|
||||
if (actionTypesRequiringVote[actionType]) {
|
||||
return balance >= proposalThreshold;
|
||||
}
|
||||
|
||||
// Для некритических действий достаточно быть администратором
|
||||
return adminService.isAdmin(executor);
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Модули DLE
|
||||
|
||||
### Текущие модули
|
||||
|
||||
#### Доступные модули
|
||||
1. **Контракт на активы** - токенизация физических активов
|
||||
2. **Мультиподпись** - повышенная безопасность
|
||||
3. **Дивиденды** - распределение дивидендов между держателями
|
||||
4. **Стейкинг** - стейкинг токенов для получения наград
|
||||
5. **Прием платежей** - прием оплаты в выбранных токенах
|
||||
|
||||
#### Предлагаемый модуль: Административное голосование
|
||||
```javascript
|
||||
{
|
||||
name: 'Административное голосование',
|
||||
description: 'Добавляет систему голосования для административных действий',
|
||||
installed: false,
|
||||
features: [
|
||||
'Голосование за удаление пользователей',
|
||||
'Голосование за создание таблиц',
|
||||
'Голосование за изменение системных настроек',
|
||||
'Интеграция с существующими контрактами DLE'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 План реализации
|
||||
|
||||
### Этап 1: Анализ и проектирование
|
||||
- [x] Анализ текущих функций приложения
|
||||
- [x] Определение критических операций
|
||||
- [x] Проектирование архитектуры системы голосования
|
||||
|
||||
### Этап 2: Разработка смарт-контрактов
|
||||
- [ ] Создание AdminGovernanceModule
|
||||
- [ ] Расширение GovernorContract
|
||||
- [ ] Создание интерфейсов для взаимодействия
|
||||
|
||||
### Этап 3: Интеграция с приложением
|
||||
- [ ] Создание сервиса для работы с голосованием
|
||||
- [ ] Интеграция с существующими API
|
||||
- [ ] Обновление middleware для проверки прав
|
||||
|
||||
### Этап 4: Обновление UI
|
||||
- [ ] Создание интерфейса для управления предложениями
|
||||
- [ ] Интеграция с модульной системой DLE
|
||||
- [ ] Добавление уведомлений о голосованиях
|
||||
|
||||
### Этап 5: Тестирование и развертывание
|
||||
- [ ] Тестирование смарт-контрактов
|
||||
- [ ] Интеграционное тестирование
|
||||
- [ ] Развертывание в production
|
||||
|
||||
## 📝 Заключение
|
||||
|
||||
Текущая система DLE предоставляет базовую функциональность управления через токены, но требует расширения для полной интеграции с административными функциями приложения. Предлагаемая система голосования позволит обеспечить безопасность и прозрачность при выполнении критических операций.
|
||||
|
||||
### Ключевые преимущества предлагаемого решения:
|
||||
1. **Безопасность** - все критические операции требуют голосования
|
||||
2. **Прозрачность** - все действия записываются в блокчейн
|
||||
3. **Гибкость** - возможность настройки уровней критичности
|
||||
4. **Интеграция** - использование существующей инфраструктуры DLE
|
||||
5. **Модульность** - возможность включения/выключения функций
|
||||
|
||||
---
|
||||
|
||||
# 🏗️ Архитектура DLE как цифрового акционерного общества
|
||||
|
||||
## 🎯 Концепция DLE
|
||||
|
||||
DLE (Digital Legal Entity) - это **цифровое акционерное общество**, где смарт-контракт сам по себе является **цифровым ИНН организации** со всеми функциями традиционного АО.
|
||||
|
||||
### **Основная аналогия:**
|
||||
```
|
||||
Акционерное общество:
|
||||
├── ИНН (идентификатор)
|
||||
├── Банковский счет (финансы)
|
||||
├── Юридический адрес
|
||||
├── Контакты (телефон, email)
|
||||
├── Коды деятельности (ОКВЭД)
|
||||
├── Акционеры (владельцы)
|
||||
├── Управление (голосование)
|
||||
└── Участие в других организациях
|
||||
|
||||
DLE (цифровой аналог):
|
||||
├── Адрес контракта (идентификатор)
|
||||
├── Токены управления (акции)
|
||||
├── Цифровой адрес (блокчейн)
|
||||
├── Контакты (веб-интерфейс)
|
||||
├── Коды деятельности (ISIC)
|
||||
├── Держатели токенов (акционеры)
|
||||
├── Система голосования
|
||||
└── Участие в других DLE
|
||||
```
|
||||
|
||||
## 📋 Формы для ручного деплоя смарт-контрактов
|
||||
|
||||
### **Основная форма деплоя DLE:**
|
||||
|
||||
#### **Блок 1: Идентификация (ИНН)**
|
||||
```
|
||||
- Название организации (name)
|
||||
- Символ токена (symbol)
|
||||
- Описание деятельности (businessDescription)
|
||||
- Юридический адрес (legalAddress)
|
||||
- Контактная информация (contactInfo)
|
||||
```
|
||||
|
||||
#### **Блок 2: Коды деятельности (ISIC)**
|
||||
```
|
||||
- Выбор секций ISIC (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U)
|
||||
- Выбор разделов (2-значные коды)
|
||||
- Выбор групп (3-значные коды)
|
||||
- Выбор классов (4-значные коды)
|
||||
- Дополнительные коды деятельности
|
||||
```
|
||||
|
||||
#### **Блок 3: Финансы (банковский счет)**
|
||||
```
|
||||
- Начальное распределение токенов (partners[], amounts[])
|
||||
- Общее количество токенов (totalSupply)
|
||||
- Минимальный баланс для голосования (proposalThreshold)
|
||||
- Кворум для принятия решений (quorumPercentage)
|
||||
```
|
||||
|
||||
#### **Блок 4: Управление (акционерное собрание)**
|
||||
```
|
||||
- Задержка голосования (votingDelay)
|
||||
- Период голосования (votingPeriod)
|
||||
- Минимальная задержка выполнения (timelockMinDelay)
|
||||
- Тип управления (демократическое/олигархическое)
|
||||
```
|
||||
|
||||
#### **Блок 5: Мультиподпись**
|
||||
```
|
||||
- Количество требуемых подписей (requiredSignatures)
|
||||
- Список авторизованных подписантов (authorizedSigners[])
|
||||
- Временные ограничения на подписание
|
||||
- Типы операций, требующие мультиподпись
|
||||
```
|
||||
|
||||
#### **Блок 6: Коммуникация**
|
||||
```
|
||||
- Веб-сайт организации (website)
|
||||
- Email для связи (contactEmail)
|
||||
- Телефон для связи (contactPhone)
|
||||
- Адрес для сообщений (communicationAddress)
|
||||
```
|
||||
|
||||
## 🏗️ Типы и функции смарт-контрактов
|
||||
|
||||
### **Основной контракт: DigitalLegalEntity.sol**
|
||||
|
||||
#### **A. Функции идентификации:**
|
||||
```solidity
|
||||
- getName() - получение названия
|
||||
- getSymbol() - получение символа
|
||||
- getContractAddress() - получение адреса (ИНН)
|
||||
- getBusinessDescription() - получение описания
|
||||
- getLegalAddress() - получение юр. адреса
|
||||
- getContactInfo() - получение контактов
|
||||
```
|
||||
|
||||
#### **B. Функции кодов деятельности:**
|
||||
```solidity
|
||||
- getIsicCodes() - получение кодов ISIC
|
||||
- addIsicCode(string code) - добавление кода
|
||||
- removeIsicCode(string code) - удаление кода
|
||||
- updateBusinessDescription(string description) - обновление описания
|
||||
```
|
||||
|
||||
#### **C. Финансовые функции:**
|
||||
```solidity
|
||||
- receive() - прием ETH
|
||||
- receiveERC20(address token, uint256 amount) - прием токенов
|
||||
- getBalance() - получение баланса
|
||||
- distributePayment(address[] recipients, uint256[] amounts) - распределение
|
||||
- mintTokens(address to, uint256 amount) - эмиссия токенов
|
||||
- transferTokens(address to, uint256 amount) - передача токенов
|
||||
- getTotalSupply() - общее количество токенов
|
||||
```
|
||||
|
||||
#### **D. Функции управления:**
|
||||
```solidity
|
||||
- createProposal(string title, string description, ProposalType type) - создание предложения
|
||||
- vote(uint256 proposalId, bool support) - голосование
|
||||
- executeProposal(uint256 proposalId) - выполнение предложения
|
||||
- cancelProposal(uint256 proposalId) - отмена предложения
|
||||
- getProposalStatus(uint256 proposalId) - статус предложения
|
||||
- getVotingPower(address account) - голосующая сила
|
||||
```
|
||||
|
||||
#### **E. Функции участия в других DLE:**
|
||||
```solidity
|
||||
- participateInDLE(address dleAddress, uint256 tokens) - участие в другом DLE
|
||||
- voteInOtherDLE(address dleAddress, uint256 proposalId, bool support) - голосование в другом DLE
|
||||
- getParticipation(address dleAddress) - получение информации об участии
|
||||
- withdrawFromDLE(address dleAddress, uint256 tokens) - выход из DLE
|
||||
```
|
||||
|
||||
#### **F. Функции мультиподписи:**
|
||||
```solidity
|
||||
- requireMultiSignature(bytes32 operationId, uint256 requiredSignatures) - требование мультиподписи
|
||||
- signOperation(bytes32 operationId) - подписание операции
|
||||
- getSignatureCount(bytes32 operationId) - количество подписей
|
||||
- addAuthorizedSigner(address signer) - добавление подписанта
|
||||
- removeAuthorizedSigner(address signer) - удаление подписанта
|
||||
```
|
||||
|
||||
#### **G. Коммуникационные функции:**
|
||||
```solidity
|
||||
- sendMessage(string message, string senderInfo) - отправка сообщения
|
||||
- respondToMessage(uint256 messageId, string response) - ответ на сообщение
|
||||
- initiateCall(string callerInfo, string purpose) - запрос звонка
|
||||
- acceptCall(uint256 callId, string response) - принятие звонка
|
||||
- rejectCall(uint256 callId, string reason) - отклонение звонка
|
||||
```
|
||||
|
||||
## 🧩 Модули для настройки смарт-контракта
|
||||
|
||||
### **Базовые модули:**
|
||||
|
||||
#### **Модуль 1: Управление токенами**
|
||||
```
|
||||
Функции:
|
||||
- Эмиссия новых токенов
|
||||
- Сжигание токенов
|
||||
- Замораживание/размораживание токенов
|
||||
- Делегирование голосов
|
||||
- Настройка прав доступа
|
||||
|
||||
Настройки:
|
||||
- Максимальное количество токенов
|
||||
- Минимальный баланс для голосования
|
||||
- Период блокировки токенов
|
||||
- Комиссии за операции
|
||||
```
|
||||
|
||||
#### **Модуль 2: Система голосования**
|
||||
```
|
||||
Функции:
|
||||
- Создание различных типов предложений
|
||||
- Настройка параметров голосования
|
||||
- Автоматическое выполнение решений
|
||||
- Отмена предложений
|
||||
- Делегирование голосов
|
||||
|
||||
Настройки:
|
||||
- Кворум для разных типов решений
|
||||
- Период голосования
|
||||
- Задержка выполнения
|
||||
- Порог для создания предложений
|
||||
```
|
||||
|
||||
#### **Модуль 3: Мультиподпись**
|
||||
```
|
||||
Функции:
|
||||
- Настройка количества подписей
|
||||
- Управление подписантами
|
||||
- Временные ограничения
|
||||
- Отмена подписей
|
||||
- Автоматическое выполнение
|
||||
|
||||
Настройки:
|
||||
- Количество требуемых подписей
|
||||
- Список авторизованных подписантов
|
||||
- Время действия подписей
|
||||
- Типы операций, требующие мультиподписи
|
||||
```
|
||||
|
||||
#### **Модуль 4: Финансовое управление**
|
||||
```
|
||||
Функции:
|
||||
- Управление балансами
|
||||
- Распределение дивидендов
|
||||
- Инвестирование средств
|
||||
- Конвертация валют
|
||||
- Управление портфелем
|
||||
|
||||
Настройки:
|
||||
- Автоматическое распределение дивидендов
|
||||
- Инвестиционные лимиты
|
||||
- Валютные пары для конвертации
|
||||
- Комиссии за операции
|
||||
```
|
||||
|
||||
#### **Модуль 5: Участие в других DLE**
|
||||
```
|
||||
Функции:
|
||||
- Покупка токенов других DLE
|
||||
- Голосование в других DLE
|
||||
- Управление портфелем участий
|
||||
- Автоматическое голосование
|
||||
- Отчетность по участиям
|
||||
|
||||
Настройки:
|
||||
- Лимиты на участие в других DLE
|
||||
- Стратегии голосования
|
||||
- Автоматические действия
|
||||
- Уведомления о важных событиях
|
||||
```
|
||||
|
||||
#### **Модуль 6: Коммуникация**
|
||||
```
|
||||
Функции:
|
||||
- Управление сообщениями
|
||||
- Настройка уведомлений
|
||||
- Автоматические ответы
|
||||
- Интеграция с внешними сервисами
|
||||
- Архив коммуникаций
|
||||
|
||||
Настройки:
|
||||
- Автоматические ответы
|
||||
- Фильтры сообщений
|
||||
- Интеграции (email, telegram, slack)
|
||||
- Шаблоны ответов
|
||||
```
|
||||
|
||||
#### **Модуль 7: Безопасность**
|
||||
```
|
||||
Функции:
|
||||
- Многофакторная аутентификация
|
||||
- Временные блокировки
|
||||
- Аудит операций
|
||||
- Восстановление доступа
|
||||
- Мониторинг безопасности
|
||||
|
||||
Настройки:
|
||||
- Уровни безопасности
|
||||
- Временные ограничения
|
||||
- Список доверенных адресов
|
||||
- Автоматические блокировки
|
||||
```
|
||||
|
||||
#### **Модуль 8: Отчетность**
|
||||
```
|
||||
Функции:
|
||||
- Генерация финансовых отчетов
|
||||
- Отчеты о голосованиях
|
||||
- Отчеты об участиях
|
||||
- Налоговая отчетность
|
||||
- Экспорт данных
|
||||
|
||||
Настройки:
|
||||
- Периодичность отчетов
|
||||
- Форматы экспорта
|
||||
- Автоматическая отправка
|
||||
- Шаблоны отчетов
|
||||
```
|
||||
|
||||
## 🔄 Интеграция модулей
|
||||
|
||||
### **Связи между модулями:**
|
||||
```
|
||||
Управление токенами ↔ Система голосования
|
||||
Финансовое управление ↔ Участие в других DLE
|
||||
Мультиподпись ↔ Все критические операции
|
||||
Безопасность ↔ Все модули
|
||||
Отчетность ↔ Все модули
|
||||
Коммуникация ↔ Все модули
|
||||
```
|
||||
|
||||
### **Настройка модулей:**
|
||||
```
|
||||
1. Активация/деактивация модулей
|
||||
2. Настройка параметров каждого модуля
|
||||
3. Установка связей между модулями
|
||||
4. Тестирование функциональности
|
||||
5. Развертывание в production
|
||||
```
|
||||
|
||||
## 🎯 Ключевые особенности DLE
|
||||
|
||||
### **1. Единый идентификатор:**
|
||||
- Адрес смарт-контракта = ИНН организации
|
||||
- Все функции доступны через один адрес
|
||||
- Полная прозрачность всех операций
|
||||
|
||||
### **2. Автоматизация:**
|
||||
- Автоматическое выполнение решений
|
||||
- Автоматическое распределение дивидендов
|
||||
- Автоматическая отчетность
|
||||
|
||||
### **3. Безопасность:**
|
||||
- Мультиподпись для критических операций
|
||||
- Временные задержки для важных решений
|
||||
- Полный аудит в блокчейне
|
||||
|
||||
### **4. Масштабируемость:**
|
||||
- Участие в неограниченном количестве других DLE
|
||||
- Настраиваемые параметры для разных типов операций
|
||||
- Модульная архитектура для добавления новых функций
|
||||
|
||||
## 🏆 Преимущества DLE над традиционным АО
|
||||
|
||||
### **Эффективность:**
|
||||
- Мгновенные платежи и переводы
|
||||
- Автоматическое выполнение решений
|
||||
- Снижение бюрократических процедур
|
||||
|
||||
### **Прозрачность:**
|
||||
- Все операции видны в блокчейне
|
||||
- Невозможность подделки документов
|
||||
- Полная история всех действий
|
||||
|
||||
### **Глобальность:**
|
||||
- Работа без границ
|
||||
- Участие в международных проектах
|
||||
- Мультивалютные операции
|
||||
|
||||
### **Инновационность:**
|
||||
- Интеграция с DeFi протоколами
|
||||
- Участие в DAO экосистеме
|
||||
- Доступ к новым финансовым инструментам
|
||||
@@ -1,113 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/HB3-ACCELERATOR
|
||||
-->
|
||||
|
||||
# Интеграция RAG-ассистента для бизнеса с поддержкой продуктов, сегментов клиентов и LLM
|
||||
|
||||
## Цель
|
||||
|
||||
Реализовать интеллектуального ассистента для бизнеса, который:
|
||||
- Использует RAG-таблицы для хранения вопросов, ответов, уточняющих вопросов, ответов на возражения и дополнительного контекста.
|
||||
- Поддерживает фильтрацию по продуктам, сегментам клиентов (тегам), приоритету, дате и другим бизнес-полям.
|
||||
- Интегрируется с LLM (Ollama/OpenAI) для генерации финального ответа на основе найденного контекста.
|
||||
- Позволяет настраивать системный промт с плейсхолдерами для гибкой персонализации ответов.
|
||||
- Позволяет генерировать draft-ответ для администратора, который может быть отредактирован и отправлен вручную.
|
||||
|
||||
---
|
||||
|
||||
## Основные требования
|
||||
|
||||
1. **Гибкая структура RAG-таблицы**
|
||||
- Пользователь может создавать таблицы с произвольными столбцами и назначать им специальное значение (purpose) через выпадающий список:
|
||||
- Вопрос (`question`)
|
||||
- Ответ (`answer`)
|
||||
- Ответ с уточняющим вопросом (`clarifyingAnswer`)
|
||||
- Ответ на возражение (`objectionAnswer`)
|
||||
- Теги пользователя/сегмента (`userTags`)
|
||||
- Продукт/услуга (`product`)
|
||||
- Дополнительный контекст (`context`)
|
||||
- Приоритет (`priority`)
|
||||
- Дата (`date`)
|
||||
- Для каждого столбца указывается назначение при создании/редактировании.
|
||||
- Можно добавлять дополнительные столбцы для бизнес-атрибутов.
|
||||
|
||||
2. **Фильтрация и поиск**
|
||||
- При поступлении вопроса пользователя:
|
||||
- Фильтровать строки по продукту, тегам пользователя, приоритету, дате и другим полям.
|
||||
- Выполнять векторный поиск (embedding) только по релевантным строкам (по столбцу "Вопрос").
|
||||
|
||||
3. **Интеграция с LLM**
|
||||
- После поиска по RAG-таблице формировать системный промт с подстановкой найденных данных (через плейсхолдеры).
|
||||
- Передавать промт и вопрос пользователя в LLM (Ollama/OpenAI).
|
||||
- Возвращать финальный ответ пользователю.
|
||||
|
||||
4. **Плейсхолдеры для промта**
|
||||
- Поддерживаются плейсхолдеры:
|
||||
- `{context}` — дополнительная информация
|
||||
- `{answer}` — основной ответ
|
||||
- `{clarifyingAnswer}` — уточняющий вопрос
|
||||
- `{objectionAnswer}` — ответ на возражение
|
||||
- `{question}` — вопрос пользователя
|
||||
- `{userTags}` — теги пользователя
|
||||
- `{product}` — продукт/услуга
|
||||
- `{priority}` — приоритет
|
||||
- `{date}` — дата
|
||||
- `{rules}` — описание применённых правил
|
||||
- `{history}` — история диалога
|
||||
- `{model}` — используемая LLM
|
||||
- `{language}` — язык ответа
|
||||
|
||||
5. **Кэширование embedding**
|
||||
- Для ускорения поиска embedding для вопросов кэшируются в БД.
|
||||
- При изменении вопроса embedding обновляется.
|
||||
|
||||
6. **Логирование и аналитика**
|
||||
- Логируются все этапы работы ассистента: запрос пользователя, найденный контекст, результат LLM, время ответа, id пользователя и т.д.
|
||||
- Вся информация сохраняется для последующего анализа и улучшения качества ответов.
|
||||
|
||||
7. **Автоответ и draft-ответ**
|
||||
- Если найден релевантный вопрос (score > 0.95) — ассистент автоматически отвечает пользователю.
|
||||
- Если нет — ассистент генерирует draft-ответ для администратора (через роут `/ai-draft`), который подставляется в поле ввода и ожидает отправки админом. Draft генерируется всегда, даже если нет точного совпадения в RAG.
|
||||
- Draft-ответ строится с учётом промта, истории и всех доступных данных из RAG.
|
||||
|
||||
---
|
||||
|
||||
## Пример бизнес-сценария
|
||||
|
||||
- Клиент B2B интересуется продуктом "ProductX".
|
||||
- Вопрос: "Как интегрировать ваш продукт с нашей ERP?"
|
||||
- Система фильтрует строки по `product = "ProductX"` и тегу `B2B`.
|
||||
- Векторный поиск проводится только по релевантным строкам.
|
||||
- В системном промте используются плейсхолдеры для подстановки найденных данных.
|
||||
- LLM генерирует финальный ответ с учётом контекста, уточняющих вопросов и ответов на возражения.
|
||||
- Если точного совпадения нет — draft-ответ для администратора формируется на основе промта и истории.
|
||||
|
||||
---
|
||||
|
||||
## Пример системного промта
|
||||
|
||||
```
|
||||
Ты — ассистент компании. Пользователь интересуется продуктом: {product}, сегмент: {userTags}.
|
||||
Используй только релевантные ответы и контекст для этого продукта и типа клиента.
|
||||
Контекст: {context}
|
||||
Ответ: {answer}
|
||||
Уточняющий вопрос: {clarifyingAnswer}
|
||||
Ответ на возражение: {objectionAnswer}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Результат
|
||||
|
||||
- Персонализированные, точные и масштабируемые ответы для разных продуктов и сегментов клиентов.
|
||||
- Гибкая настройка ассистента через UI и системный промт.
|
||||
- Возможность расширения под любые бизнес-сценарии.
|
||||
- Draft-ответы для администратора, которые можно редактировать и отправлять вручную.
|
||||
@@ -1,418 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/HB3-ACCELERATOR
|
||||
-->
|
||||
|
||||
# DApp for Business
|
||||
|
||||
## Краткое описание
|
||||
**DApp for Business** — это современное веб3-приложение, позволяющее предпринимателям использовать цифровое юридическое лицо для обслуживания клиентов, приема безналичных платежей, безопасного управления активами и прозрачного учета с помощью смарт-контрактов и искусственного интеллекта.
|
||||
|
||||
---
|
||||
|
||||
## Основные возможности
|
||||
- Создание и управление цифровым юридическим лицом на блокчейне
|
||||
- Прием безналичных платежей (криптовалюта, стейблкоины)
|
||||
- Безопасное хранение и управление бизнес-активами через смарт-контракты
|
||||
- Прозрачный учет операций и автоматизация отчетности
|
||||
- Интеграция с ИИ для анализа данных и автоматизации бизнес-процессов
|
||||
- Управление клиентской базой и сервисами через децентрализованное приложение
|
||||
|
||||
---
|
||||
|
||||
## Целевая аудитория
|
||||
- Индивидуальные предприниматели
|
||||
- Малый и средний бизнес
|
||||
- Стартапы, работающие с цифровыми активами
|
||||
- Фрилансеры, желающие автоматизировать бизнес-процессы
|
||||
|
||||
---
|
||||
|
||||
## Преимущества
|
||||
- Полная прозрачность и доверие благодаря блокчейну
|
||||
- Автоматизация рутинных задач с помощью ИИ
|
||||
- Безопасное управление активами без посредников
|
||||
- Гибкая интеграция с внешними сервисами и кошельками
|
||||
- Масштабируемость и независимость от традиционных банковских систем
|
||||
|
||||
---
|
||||
|
||||
## Технические требования
|
||||
- Операционная система: Linux, macOS, Windows (рекомендуется WSL2 для Windows)
|
||||
- Docker
|
||||
- Node.js (v16+)
|
||||
- Yarn
|
||||
- Браузер с поддержкой Web3 (например, MetaMask)
|
||||
|
||||
---
|
||||
|
||||
## Установка и запуск
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
```bash
|
||||
git clone https://github.com/your-org/dapp-for-business.git
|
||||
cd dapp-for-business
|
||||
```
|
||||
2. Установите зависимости:
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
3. Запустите приложение в Docker:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
4. Откройте приложение в браузере по адресу: [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
1. Зарегистрируйте цифровое юридическое лицо через интерфейс приложения.
|
||||
2. Подключите криптокошелек (MetaMask или другой Web3-кошелек).
|
||||
3. Настройте параметры бизнеса и добавьте клиентов.
|
||||
4. Начните принимать платежи и управлять активами через смарт-контракты.
|
||||
|
||||
---
|
||||
|
||||
## Структура проекта
|
||||
|
||||
- `frontend/` — клиентская часть на Vue.js (исходный код, конфиги, сборка, nginx)
|
||||
- `backend/` — серверная логика, API, работа с БД, интеграция со смарт-контрактами
|
||||
- `vector-search/` — сервис поиска и работы с векторными данными (Python, FastAPI)
|
||||
- `webssh-agent/` — сервис для работы с SSH-агентом через веб-интерфейс (Node.js)
|
||||
- `scripts/` — bash-скрипты для автоматизации (миграции, обновления DNS и др.)
|
||||
- `md/` — дополнительная документация и технические описания
|
||||
- `docs/` — документация и примеры
|
||||
- `docker-compose.yml`, `Dockerfile` — конфигурация контейнеризации и сборки
|
||||
- `setup.sh`, `clean-logs.sh` — вспомогательные скрипты для установки и обслуживания
|
||||
|
||||
---
|
||||
|
||||
## Часто задаваемые вопросы (FAQ)
|
||||
|
||||
- **Как подключить кошелек?**
|
||||
Используйте MetaMask или любой другой Web3-кошелек, следуя инструкции в интерфейсе приложения.
|
||||
|
||||
- **Какие криптовалюты поддерживаются?**
|
||||
Поддерживаются основные токены стандарта ERC-20 и стейблкоины.
|
||||
|
||||
- **Безопасно ли хранить активы в приложении?**
|
||||
Все операции выполняются через проверенные смарт-контракты, код которых открыт и доступен для аудита.
|
||||
|
||||
---
|
||||
|
||||
## Особенности развертывания и автономной работы
|
||||
|
||||
- Программное обеспечение с ИИ может работать полностью автономно на локальном устройстве без доступа к интернету после установки.
|
||||
- С локального устройства возможно открыть доступ к сервису для интернет-пользователей (например, через проброс портов или настройку прокси).
|
||||
- Приложение может быть установлено и сразу в облачный сервис (VPS, облачные платформы и т.д.) для круглосуточного доступа из любой точки мира.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Управление настройками и доступом
|
||||
|
||||
- Управление всеми настройками приложения осуществляется через удобный веб-интерфейс.
|
||||
- Доступ к настройкам получает только пользователь, чей криптокошелек содержит специальный админ-токен управления смарт-контрактом.
|
||||
- После первого входа в приложение администратор может изменить стандартный админ-токен, создав собственный токен через специальную форму для деплоя смарт-контракта прямо в интерфейсе приложения.
|
||||
- Таким образом, ваш кошелек — это ваш смарт-контракт, ваши токены, ваш управляемый доступ и ваши активы: только вы контролируете все ключевые параметры и права.
|
||||
- Для получения бесплатных обновлений приложения в течение 5 лет администратору необходимо отправить первичный админ-токен на собственный смарт-контракт, созданный в приложении (операция выполняется через интерфейс).
|
||||
|
||||
> **Примечание:** Все смарт-контракты приложения построены на базе проверенных и безопасных библиотек OpenZeppelin.
|
||||
|
||||
---
|
||||
|
||||
## Инструкция по заполнению формы «Блокчейн-настройки»
|
||||
|
||||
1. **Создание нового DLE (Digital Legal Entity)**
|
||||
- **Имя DLE**: Введите уникальное название вашей цифровой организации (например, My DLE).
|
||||
- **Символ токена управления (GT)**: Укажите короткое обозначение токена (3-5 латинских символов, например, MDGT).
|
||||
|
||||
2. **Выбор кода деятельности (ISIC)**
|
||||
- Последовательно выберите секцию, раздел, группу и класс деятельности из выпадающих списков.
|
||||
- После выбора нужного кода нажмите «Добавить код деятельности». Можно добавить несколько кодов.
|
||||
|
||||
3. **Партнёры**
|
||||
- Для каждого партнёра укажите:
|
||||
- **Адрес партнёра** (Ethereum-адрес, например, 0x...).
|
||||
- **Сумму GT для партнёра** (количество токенов управления).
|
||||
- Для добавления нового партнёра используйте кнопку «Добавить партнёра».
|
||||
- Для удаления — «Удалить партнёра».
|
||||
|
||||
4. **RPC-конфигурации**
|
||||
- В разделе «Добавить новую RPC конфигурацию» выберите сеть из списка или укажите пользовательский ID и Chain ID.
|
||||
- Введите RPC URL (например, https://...).
|
||||
- При необходимости воспользуйтесь предложенным URL.
|
||||
- Нажмите «Добавить RPC» для сохранения конфигурации.
|
||||
|
||||
5. **Выбор сети для деплоя**
|
||||
- В выпадающем списке выберите блокчейн-сеть, в которую будет развёрнут смарт-контракт вашей организации.
|
||||
|
||||
6. **Приватный ключ для деплоя**
|
||||
- Введите приватный ключ деплоера (будет использоваться только для развертывания смарт-контракта).
|
||||
|
||||
7. **Пользовательские настройки газа (опционально)**
|
||||
- Если требуется, включите опцию «Использовать пользовательские настройки газа» и укажите лимит газа, максимальную и приоритетную комиссию.
|
||||
|
||||
8. **Сохранение и деплой**
|
||||
- После заполнения всех полей проверьте введённые данные.
|
||||
- Нажмите кнопку деплоя (или «Сохранить»), чтобы развернуть смарт-контракт и завершить настройку.
|
||||
|
||||
**Важно:**
|
||||
- Доступ к настройкам имеют только пользователи с админ-токеном в кошельке.
|
||||
- После деплоя вы сможете управлять организацией через смарт-контракт и веб-интерфейс.
|
||||
|
||||
---
|
||||
|
||||
## Управление DLE и модульными смарт-контрактами
|
||||
|
||||
После создания смарт-контракта с админ-токеном управления в разделе CRM приложения появляется интерфейс для управления функциями смарт-контракта и добавления модульных смарт-контрактов.
|
||||
|
||||
### Как управлять DLE через интерфейс
|
||||
|
||||
1. **Переход к управлению DLE**
|
||||
- В разделе CRM нажмите на блок "Управление DLE" и кнопку "Подробнее" или перейдите по адресу `/dle-management`.
|
||||
|
||||
2. **Выбор DLE**
|
||||
- В списке отобразятся все созданные вами DLE. Выберите нужную организацию для управления.
|
||||
|
||||
3. **Основная информация**
|
||||
- Вкладка "Основная информация" содержит сведения о названии, символе токена, местонахождении, кодах деятельности, дате создания и адресах смарт-контрактов (токен, таймлок, Governor). Можно скопировать адреса или открыть их в обозревателе блокчейна.
|
||||
|
||||
4. **Предложения**
|
||||
- На вкладке "Предложения" можно создавать новые предложения для управления DLE (например, изменение параметров, добавление участников и др.).
|
||||
- Для создания предложения заполните заголовок и описание, затем отправьте на голосование.
|
||||
- Участвуйте в голосовании по предложениям ("За" или "Против"). Статус предложения обновляется автоматически.
|
||||
|
||||
5. **Управление (Governance)**
|
||||
- Вкладка "Управление" позволяет просматривать и изменять параметры управления: порог предложения, кворум, задержку и период голосования.
|
||||
|
||||
6. **Модули**
|
||||
- Вкладка "Модули" предназначена для подключения дополнительных модульных смарт-контрактов (например, токенизация активов, мультиподпись, дивиденды, стейкинг, приём платежей и др.).
|
||||
- Для установки модуля нажмите "Установить" напротив нужного модуля. Для удаления — "Удалить".
|
||||
- Для модуля "Прием платежей" выберите токены, которые будут приниматься, и сохраните настройки.
|
||||
|
||||
7. **Удаление DLE**
|
||||
- При необходимости можно удалить DLE (доступно только администратору). Будьте внимательны: действие необратимо.
|
||||
|
||||
**Важно:**
|
||||
- Все действия с DLE и модулями доступны только пользователям с админ-токеном в кошельке.
|
||||
- Управление DLE реализовано через смарт-контракты на базе OpenZeppelin, что обеспечивает безопасность и прозрачность операций.
|
||||
|
||||
---
|
||||
|
||||
## Настройка и использование ИИ-моделей
|
||||
|
||||
После установки приложения администратор может:
|
||||
- Добавить ключи своих ИИ-моделей по подписке (например, OpenAI, Gemini и др.) в настройках приложения.
|
||||
- Скачать одну из доступных ИИ-моделей и развернуть её локально на своём устройстве для автоматизации обслуживания клиентов бизнеса без рисков утечки конфиденциальных данных.
|
||||
|
||||
Для дообучения добавленных моделей деталям вашего бизнеса предусмотрена возможность:
|
||||
- Создавать собственные таблицы в установленной базе данных через веб-интерфейс приложения.
|
||||
- Заполнять эти таблицы данными, которые ИИ-ассистент сможет использовать для генерации персонализированных сообщений клиентам бизнеса как в чате приложения, так и в других каналах коммуникации (например, email, Telegram и др.).
|
||||
|
||||
Это позволяет максимально адаптировать ИИ-ассистента под специфику вашего бизнеса и обеспечить безопасность корпоративных данных.
|
||||
|
||||
---
|
||||
|
||||
## Интерактивный обмен контентом, публикация и интеграция с ИИ
|
||||
|
||||
- Пользователи могут создавать и публиковать веб-страницы (о компании, продуктах, статьи) с помощью удобной формы.
|
||||
- Каждая страница получает уникальный URL, оптимизирована для SEO и доступна для поиска в интернете и ИИ-системах.
|
||||
- После публикации страницы можно делиться ими в корпоративном чате: появляется интерактивная карточка с кнопкой для просмотра содержимого, а также возможностью задать вопрос по содержимому страницы.
|
||||
- Страницы автоматически интегрируются с RAG: разбиваются на смысловые блоки, векторизуются и используются ИИ-ассистентом для поиска и генерации ответов.
|
||||
- При публикации можно выбрать интеграцию с RAG и добавить Q&A по теме страницы для последующего поиска.
|
||||
- Возможна публикация страниц в соцсетях и блогах (Medium, LinkedIn, Instagram, Telegram и др.) через API с выбором платформ.
|
||||
- Все компоненты реализованы с учётом безопасности и приватности данных, поддерживается удаление и редактирование страниц.
|
||||
- Используются современные RAG-фреймворки (LlamaIndex, LangChain) и актуальные модели для векторизации (OpenAI, Sentence Transformers и др.).
|
||||
- Страницы открыты для индексации поисковыми системами и ИИ-ботами, что обеспечивает максимальную видимость и доступность информации.
|
||||
|
||||
---
|
||||
|
||||
## Управление контактами в CRM
|
||||
|
||||
В разделе "Контакты" CRM администраторы приложения могут:
|
||||
- Отслеживать и управлять всеми выбранными контактами через удобную таблицу.
|
||||
- Использовать фильтры быстрого поиска по имени, email, Telegram, кошельку, типу контакта, дате и тегам.
|
||||
- Применять кнопки быстрых действий для массовой рассылки сообщений, импорта контактов, удаления выбранных записей.
|
||||
- Просматривать подробную информацию о каждом контакте, редактировать имя, email, Telegram, кошелек, язык общения.
|
||||
- Добавлять и удалять теги для контакта, а также создавать новые теги прямо из интерфейса.
|
||||
- Блокировать и разблокировать пользователей, полностью удалять контакт.
|
||||
- Вести чат с каждым контактом прямо в интерфейсе приложения, использовать ИИ-ассистента для генерации ответов.
|
||||
- Все изменения и действия с контактами доступны только администраторам.
|
||||
|
||||
Это позволяет эффективно управлять клиентской базой, быстро находить нужные контакты и автоматизировать коммуникации с помощью встроенных инструментов.
|
||||
|
||||
|
||||
## Контакты и поддержка
|
||||
|
||||
- Email: info@hb3-accelerator.com
|
||||
- Telegram: @yourproject_support
|
||||
- Сайт: [https://hb3-accelerator.com](https://hb3-accelerator.com)
|
||||
|
||||
---
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
офер:
|
||||
|
||||
Привет! Я **Алекс** из венчурного фонда **HB3 Accelerator**.
|
||||
|
||||
Если ваша компания:
|
||||
* использует **CRM** для обслуживания клиентов и имеет штат **продавцов и бухгалтеров**;
|
||||
* нуждается в **безналичных платежах без лимитов, ограничений и с низкими комиссиями**;
|
||||
* ценит **прозрачный учёт и свободное управление активами**,
|
||||
|
||||
то я буду рад предложить вам **программное обеспечение с ИИ и смарт-контрактами**.
|
||||
|
||||
|
||||
примеры вопросов оо клиентов:
|
||||
|
||||
1. Как ваш софт интегрируется с уже существующими CRM-системами? Например, у нас сейчас используется 1С и Bitrix24.
|
||||
2. Какие конкретно задачи автоматизирует ваш ИИ? Это только аналитика или он может, например, помогать продавцам в реальном времени?
|
||||
3. Как обеспечивается безопасность и прозрачность работы со смарт-контрактами? Кто их разрабатывает и кто несёт ответственность в случае ошибки?
|
||||
4. Какой у вас опыт внедрения подобных решений в СНГ? Есть ли кейсы в моей отрасли (например, розничная торговля)?
|
||||
5. Какой порядок внедрения: сколько времени занимает интеграция, кто обучает персонал?
|
||||
6. Какие комиссии по безналичным платежам и с какими банками/платёжными системами вы работаете?
|
||||
7. Какой уровень поддержки вы предоставляете после внедрения?
|
||||
|
||||
|
||||
ответ:
|
||||
|
||||
### Наше комплексное решение для вашего бизнеса
|
||||
|
||||
Наше программное обеспечение включает **встроенную CRM-систему**, куда вы можете легко загрузить необходимые контакты из ваших текущих CRM для **омниканального обслуживания**.
|
||||
|
||||
**ИИ-ассистент**, интегрированный в наш софт, способен обучаться работе с сегментами ваших клиентов и поставщиков. Обучение происходит на основе правил, которые вы устанавливаете и загружаете в **векторную базу данных**. Эта база надёжно хранит ваши конфиденциальные данные либо на **локальном**, либо на **облачном сервере**.
|
||||
|
||||
Мы — **молодой стартап**, представляющий свой первый технологический продукт. Внедрение нашего решения занимает от **нескольких часов до одного года**, в зависимости от сложности интеграции.
|
||||
|
||||
---
|
||||
|
||||
### Условия сотрудничества
|
||||
|
||||
Мы предоставляем **5 лет обновлений** для вашего программного обеспечения.
|
||||
|
||||
Кроме того, если **в течение первого года** мы не сможем настроить софт под индивидуальные потребности вашего бизнеса, вы можете получить **возврат 70% от стоимости**.
|
||||
|
||||
|
||||
вопросы:
|
||||
|
||||
Какой у вас опыт работы с интеграцией в 1С и Bitrix24? Есть ли готовые модули или потребуется доработка под нас?
|
||||
Как реализована миграция данных из старых CRM? Кто этим занимается — ваша команда или наши специалисты?
|
||||
Какой стек технологий вы используете для ИИ и смарт-контрактов? На каких блокчейн-платформах строится ваша система?
|
||||
Какой SLA по поддержке и реагированию на инциденты? Есть ли круглосуточная поддержка?
|
||||
Как лицензируется продукт: это подписка, разовая покупка или гибридная модель?
|
||||
Какой порядок оплаты: аванс, поэтапно, после внедрения?
|
||||
Как вы обеспечиваете соответствие требованиям законодательства РФ/СНГ по хранению и обработке персональных данных?
|
||||
Есть ли демо-доступ или пилотный проект, чтобы мы могли протестировать систему на реальных данных?
|
||||
|
||||
ответы :
|
||||
|
||||
---
|
||||
|
||||
### Особенности нашего решения
|
||||
|
||||
Мы не занимаемся интеграциями с **устаревшими CRM-системами**. Однако ваши сотрудники смогут легко выполнить **миграцию данных** при поддержке нашего **ИИ-ассистента**.
|
||||
|
||||
Наш **ИИ-ассистент** обеспечивает **круглосуточную поддержку**. Инциденты с нашей стороны **исключены**, поскольку приобретаемое вами программное обеспечение является **полностью локальным решением**.
|
||||
|
||||
---
|
||||
|
||||
### Безопасность и конфиденциальность данных
|
||||
|
||||
Хранение персональных данных **соответствует требованиям законодательства**. Все данные **зашифрованы** и хранятся **на вашей территории**.
|
||||
|
||||
---
|
||||
|
||||
### Технологический стек
|
||||
|
||||
Вот основные технологии, которые мы используем в нашем **backend-решении**:
|
||||
|
||||
* **Искусственный интеллект и машинное обучение:** `@anthropic-ai/sdk`, `@google/genai`, `@langchain/community`, `@langchain/core`, `@langchain/ollama`, `langchain`, `openai`
|
||||
* **Блокчейн и смарт-контракты:** `@openzeppelin/contracts`, `ethers`, `siwe`, `viem`
|
||||
* **Веб-сервер и API:** `express`, `cors`, `helmet`, `express-rate-limit`
|
||||
* **Базы данных:** `pg`, `connect-pg-simple`
|
||||
* **Безопасность:** `csurf`, `express-session`, `session-file-store`, `cookie`
|
||||
* **Обработка электронной почты:** `imap`, `mailparser`, `nodemailer`
|
||||
* **Мессенджеры:** `node-telegram-bot-api`, `telegraf`, `ws`
|
||||
* **Утилиты и вспомогательные библиотеки:** `archiver`, `axios`, `cron`, `dotenv`, `multer`, `node-cron`, `semver`, `winston`
|
||||
* **Инструменты разработки:** `nodemon`, `eslint`, `prettier`, `hardhat`, `mocha`, `chai`, `typescript`
|
||||
|
||||
|
||||
вопросы:
|
||||
|
||||
1. Как реализована поддержка и обновления: если решение полностью локальное, как будут устанавливаться апдейты и исправления? Это делается через удалённый доступ, или вы предоставляете инструкции для нашей IT-команды?
|
||||
2. Какой механизм резервного копирования и восстановления данных предусмотрен в вашем решении?
|
||||
3. Если потребуется интеграция с внешними сервисами (например, платёжные шлюзы, государственные системы учёта), возможно ли это реализовать на вашей платформе?
|
||||
4. Какой минимальный и рекомендуемый состав IT-специалистов нужен для поддержки вашего ПО на стороне клиента?
|
||||
5. Какой порядок лицензирования используемых вами open-source библиотек и SDK? Нет ли рисков для конечного пользователя?
|
||||
6. Предусмотрена ли возможность кастомизации интерфейса и бизнес-логики под наши процессы?
|
||||
7. Какой минимальный объём внедрения (по стоимости или количеству пользователей) вы рассматриваете?
|
||||
|
||||
|
||||
ответ: Конечно! Вот ответы на вопросы предпринимателя, составленные на основе документации к продукту DApp for Business:
|
||||
|
||||
---
|
||||
|
||||
### 1. Как реализована поддержка и обновления: если решение полностью локальное, как будут устанавливаться апдейты и исправления? Это делается через удалённый доступ, или вы предоставляете инструкции для нашей IT-команды?
|
||||
|
||||
**Ответ:**
|
||||
Обновления предоставляются бесплатно в течение 5 лет. Программное обеспечение устанавливается и работает полностью локально, без необходимости постоянного интернет-доступа. Для установки обновлений вы можете использовать предоставленные bash-скрипты (`setup.sh`, `clean-logs.sh`) и инструкции из документации. При необходимости можно открыть доступ к сервису для интернет-пользователей (например, через проброс портов или прокси), но это не обязательно. Все инструкции по обновлению и обслуживанию доступны вашей IT-команде, удалённый доступ не требуется.
|
||||
|
||||
---
|
||||
|
||||
### 2. Какой механизм резервного копирования и восстановления данных предусмотрен в вашем решении?
|
||||
|
||||
**Ответ:**
|
||||
В документации прямо не описан отдельный модуль резервного копирования, однако, поскольку все данные хранятся локально (или на вашем облачном сервере), вы полностью контролируете процесс бэкапа. Используются стандартные базы данных (`pg` — PostgreSQL), для которых легко настраиваются регулярные резервные копии с помощью штатных инструментов PostgreSQL или через Docker-скрипты. Также можно использовать bash-скрипты из папки `scripts/` для автоматизации резервного копирования и восстановления.
|
||||
|
||||
---
|
||||
|
||||
### 3. Если потребуется интеграция с внешними сервисами (например, платёжные шлюзы, государственные системы учёта), возможно ли это реализовать на вашей платформе?
|
||||
|
||||
**Ответ:**
|
||||
Да, гибкая интеграция с внешними сервисами и кошельками поддерживается. Приложение масштабируемо и не зависит от традиционных банковских систем. В разделе "Модули" можно подключать дополнительные смарт-контракты, в том числе для приёма платежей, токенизации активов и других задач. Также реализована возможность публикации и интеграции с внешними платформами через API (например, соцсети, мессенджеры, внешние сервисы учёта).
|
||||
|
||||
---
|
||||
|
||||
### 4. Какой минимальный и рекомендуемый состав IT-специалистов нужен для поддержки вашего ПО на стороне клиента?
|
||||
|
||||
**Ответ:**
|
||||
Для базовой эксплуатации достаточно одного системного администратора или DevOps-специалиста, знакомого с Docker, Linux и базовыми инструментами Node.js/PostgreSQL. Вся установка и обслуживание автоматизированы скриптами и не требуют глубоких знаний в программировании. Для расширенной кастомизации или интеграции с внешними сервисами может потребоваться разработчик с опытом работы с Node.js, смарт-контрактами (Solidity) и API.
|
||||
|
||||
---
|
||||
|
||||
### 5. Какой порядок лицензирования используемых вами open-source библиотек и SDK? Нет ли рисков для конечного пользователя?
|
||||
|
||||
**Ответ:**
|
||||
Программное обеспечение распространяется по лицензии MIT, что гарантирует отсутствие ограничений для конечного пользователя. Все используемые библиотеки (например, OpenZeppelin, LangChain, Express, PostgreSQL и др.) также имеют открытые лицензии (MIT, Apache 2.0 и аналогичные), что исключает юридические риски для вашего бизнеса.
|
||||
|
||||
---
|
||||
|
||||
### 6. Предусмотрена ли возможность кастомизации интерфейса и бизнес-логики под наши процессы?
|
||||
|
||||
**Ответ:**
|
||||
Да, архитектура приложения модульная и предусматривает возможность кастомизации. Вы можете добавлять собственные таблицы в базу данных через веб-интерфейс, подключать новые модули смарт-контрактов, настраивать параметры бизнеса, интегрировать свои ИИ-модели и дообучать их на ваших данных. Интерфейс реализован на Vue.js и может быть доработан под ваши задачи.
|
||||
|
||||
---
|
||||
|
||||
### 7. Какой минимальный объём внедрения (по стоимости или количеству пользователей) вы рассматриваете?
|
||||
|
||||
**Ответ:**
|
||||
В документации не указаны ограничения по минимальному объёму внедрения или количеству пользователей. Продукт ориентирован как на индивидуальных предпринимателей, так и на малый и средний бизнес, стартапы и фрилансеров. Вы можете начать с одного пользователя и масштабировать решение по мере роста бизнеса.
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/HB3-ACCELERATOR
|
||||
-->
|
||||
|
||||
# VC HB3 - Accelerator: Детали задания для ИИ-ассистента
|
||||
|
||||
## 1. Описание проекта
|
||||
VC HB3 - Accelerator — это венчурный фонд и поставщик софта, который с помощью акселерационных программ помогает предпринимателям создавать токенизированные стартапы во всех отраслях. Фонд объединяет предпринимателей из разных юрисдикций, предоставляет инструменты для токенизации, прозрачных инвестиций и управления цифровыми юридическими лицами.
|
||||
|
||||
## 2. Цели и задачи ИИ-ассистента
|
||||
- Помогать клиентам быстро находить ответы на вопросы о продуктах, услугах и условиях фонда.
|
||||
- Предлагать релевантные продукты и услуги на основе сегментации клиентов.
|
||||
- Автоматизировать консультации, обработку типовых запросов и навигацию по возможностям фонда.
|
||||
- Содействовать формированию групп акселератора и информировать о возможностях участия.
|
||||
|
||||
## 3. Сегментация клиентов
|
||||
ИИ-ассистент должен различать следующие основные сегменты клиентов:
|
||||
|
||||
### 3.1. Предприниматели
|
||||
- Интересуются запуском стартапа, токенизацией бизнеса, участием в акселераторе.
|
||||
- Могут быть из разных юрисдикций и отраслей.
|
||||
- Ищут инструменты для управления, учета, приема платежей, автоматизации.
|
||||
|
||||
### 3.2. LP (Liquidity Providers)
|
||||
- Инвесторы, желающие получить доступ к метрикам DLE, ранним инвестициям, доле в фонде.
|
||||
- Интересуются прозрачностью, возвратом инвестиций, управлением токенами.
|
||||
|
||||
### 3.3. Стартапы-участники акселератора
|
||||
- Уже приобрели софт, участвуют в акселерационных программах.
|
||||
- Ищут финансирование, экспертизу, партнерство.
|
||||
|
||||
### 3.4. Холдеры админ токенов
|
||||
- Заинтересованы в управлении фондом, получении обновлений, возврате средств.
|
||||
|
||||
### 3.5. Регуляторы и партнеры
|
||||
- Интересуются прозрачностью, юридическими аспектами, сотрудничеством.
|
||||
|
||||
## 4. Примеры типовых запросов и сценариев
|
||||
- Как установить и настроить софт?
|
||||
- Как получить финансирование для стартапа?
|
||||
- Как стать участником акселератора?
|
||||
- Как LP может получить доступ к метрикам?
|
||||
- Как вернуть средства за админ токены?
|
||||
|
||||
---
|
||||
|
||||
## 5. RAG-таблица (для дальнейшего наполнения)
|
||||
| Вопрос | Ответ | Категория |
|
||||
|--------|-------|-----------|
|
||||
| | | |
|
||||
|
||||
## 6. Сценарии взаимодействия
|
||||
(заполняется по мере детализации бизнес-процессов)
|
||||
|
||||
## 7. Использование тегов для сегментации и персонализации
|
||||
Для каждого вопроса и ответа в RAG-таблице добавляется столбец с мультивыбором тегов клиентов. Это позволяет:
|
||||
- Связывать вопросы и ответы с определёнными сегментами пользователей (например, предприниматель, LP, стартап, холдер, регулятор, отрасль, регион и т.д.).
|
||||
- Персонализировать рекомендации ассистента на основе тегов пользователя.
|
||||
- Масштабировать базу знаний, добавляя новые теги и сегменты по мере развития проекта.
|
||||
|
||||
**Пример расширенной структуры RAG-таблицы:**
|
||||
| Вопрос | Ответ | Категория | Теги клиентов |
|
||||
|--------|-------|-----------|--------------|
|
||||
| Как получить финансирование? | ... | Инвестиции | предприниматель, стартап, акселератор |
|
||||
| Как вернуть средства за токены? | ... | Тарифы | холдер, LP |
|
||||
| Как установить софт? | ... | Продукт | предприниматель, стартап |
|
||||
|
||||
## 8. Фильтры для работы с таблицами
|
||||
Для удобства редактирования и поиска в таблицах реализуются фильтры:
|
||||
- По тегам клиентов (мультивыбор).
|
||||
- По категориям вопросов/ответов.
|
||||
- По продуктам и услугам.
|
||||
- По статусу (активен, архив и др.).
|
||||
|
||||
Фильтры позволяют быстро находить, редактировать и анализировать релевантные записи, а также персонализировать отображение данных для разных ролей пользователей.
|
||||
|
||||
## 9. Пользовательские таблицы, multiselect-столбцы и связи
|
||||
|
||||
### 9.1. Гибкая структура пользовательских таблиц
|
||||
- Пользователь может создавать собственные таблицы (user_tables), столбцы (user_columns) и строки (user_rows).
|
||||
- Для каждого столбца можно выбрать тип: text, select, multiselect, relation и др.
|
||||
- Для multiselect/select-столбцов значения (опции) хранятся в поле options столбца и могут добавляться/удаляться прямо из интерфейса.
|
||||
|
||||
### 9.2. Хранение и настройка связей
|
||||
- Для связи между сущностями (например, пользователь — партнеры, вопрос — продукты) используется отдельная таблица связей (например, user_multiselect_values: id, user_id, column_id, value).
|
||||
- Это позволяет хранить любые связи "многие-ко-многим" между пользователями, вопросами, продуктами и т.д.
|
||||
- Для multiselect-столбцов в RAG-таблице вопросы/ответы можно выбирать продукты/услуги, к которым относится запись.
|
||||
|
||||
### 9.3. Фильтрация и производительность
|
||||
- Фильтрация реализуется через SQL-запросы с JOIN по user_tables, user_columns, user_rows, user_cell_values и таблице связей.
|
||||
- Для multiselect-столбцов фильтрация по значениям возможна через LIKE или jsonb-операторы (при хранении массивов).
|
||||
- Производительность обеспечивается индексами по row_id, column_id и, при необходимости, по value (jsonb gin-индекс).
|
||||
- Такая архитектура масштабируется и используется в современных low-code/CRM системах (Notion, Airtable и др.).
|
||||
|
||||
### 9.4. Пример использования
|
||||
- Пользователь создает таблицу "Партнеры" с multiselect-столбцом.
|
||||
- В интерфейсе контакта можно выбрать партнеров для пользователя — значения сохраняются в user_multiselect_values.
|
||||
- В RAG-таблице вопросы/ответы можно связать с продуктами/услугами через multiselect-столбец.
|
||||
- ИИ-ассистент может фильтровать и персонализировать ответы по этим связям.
|
||||
Reference in New Issue
Block a user