ваше сообщение коммита
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json"
|
||||
"buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/ae6e57b918547db576362d7ba12641eb.json"
|
||||
"buildInfo": "../../build-info/ab387c71734b3d3e5e7817d328027586.json"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
658
backend/cache/solidity-files-cache.json
vendored
658
backend/cache/solidity-files-cache.json
vendored
@@ -2,8 +2,8 @@
|
||||
"_format": "hh-sol-cache-2",
|
||||
"files": {
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
|
||||
"lastModificationDate": 1755277189432,
|
||||
"contentHash": "03b4ac83148b5b1246f64c54fa0e959c",
|
||||
"lastModificationDate": 1755280436490,
|
||||
"contentHash": "f676e9964a39b0fccdc62a9114266863",
|
||||
"sourceName": "contracts/DLE.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
@@ -160,47 +160,6 @@
|
||||
"ECDSA"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "51c2083b160453420aaa0a046c16d5ca",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../ERC20.sol",
|
||||
"../../../governance/utils/Votes.sol",
|
||||
"../../../utils/structs/Checkpoints.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ERC20Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "b1a8fc63b83ce00408e0c9ed1230b717",
|
||||
@@ -244,6 +203,47 @@
|
||||
"ERC20Permit"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "51c2083b160453420aaa0a046c16d5ca",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../ERC20.sol",
|
||||
"../../../governance/utils/Votes.sol",
|
||||
"../../../utils/structs/Checkpoints.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ERC20Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Context.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "67bfbc07588eb8683b3fd8f6f909563e",
|
||||
@@ -396,91 +396,6 @@
|
||||
"IERC20Metadata"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/Votes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "95aceafdc639babdd22576e5e3774d64",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../../interfaces/IERC5805.sol",
|
||||
"../../utils/Context.sol",
|
||||
"../../utils/Nonces.sol",
|
||||
"../../utils/cryptography/EIP712.sol",
|
||||
"../../utils/structs/Checkpoints.sol",
|
||||
"../../utils/math/SafeCast.sol",
|
||||
"../../utils/cryptography/ECDSA.sol",
|
||||
"../../utils/types/Time.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/structs/Checkpoints.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "657c6dfea3bae1af948de6113ba01cea",
|
||||
"sourceName": "@openzeppelin/contracts/utils/structs/Checkpoints.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Checkpoints"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Nonces.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "c32d108058718efb9061b88e83a83f79",
|
||||
@@ -518,46 +433,6 @@
|
||||
"Nonces"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5805.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "65ba9f89b1057e2192e341b286d4e261",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5805.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../governance/utils/IVotes.sol",
|
||||
"./IERC6372.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5805"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/EIP712.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "8dbb261c55f358342798c4d1803d4f8e",
|
||||
@@ -599,10 +474,10 @@
|
||||
"EIP712"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "2adca1150f58fc6f3d1f0a0f22ee7cca",
|
||||
"sourceName": "@openzeppelin/contracts/utils/math/SafeCast.sol",
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "94ec15baf0d5df863f45b8f351937ec7",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
@@ -633,158 +508,7 @@
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"SafeCast"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/types/Time.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "d83e7814a059fc1287fd765f424ce004",
|
||||
"sourceName": "@openzeppelin/contracts/utils/types/Time.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol",
|
||||
"../math/SafeCast.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Time"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "905ffceb29869fee4b5a649abe7e2927",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/IVotes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IVotes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC6372.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "414cd6acf090e4009cf016ff62ecbd88",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC6372.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC6372"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5267.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "94364524cb1a39dcbc3d3afff6d8e53e",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5267.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5267"
|
||||
"IERC20Permit"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/ShortStrings.sol": {
|
||||
@@ -826,6 +550,43 @@
|
||||
"ShortStrings"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5267.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "94364524cb1a39dcbc3d3afff6d8e53e",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5267.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5267"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol": {
|
||||
"lastModificationDate": 1754306764465,
|
||||
"contentHash": "86fd93657e4e27ff76c38699e9b9fcef",
|
||||
@@ -983,6 +744,43 @@
|
||||
"Math"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "2adca1150f58fc6f3d1f0a0f22ee7cca",
|
||||
"sourceName": "@openzeppelin/contracts/utils/math/SafeCast.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"SafeCast"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "ae3528afb8bdb0a7dcfba5b115ee8074",
|
||||
@@ -1059,10 +857,175 @@
|
||||
"Panic"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "94ec15baf0d5df863f45b8f351937ec7",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/Votes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "95aceafdc639babdd22576e5e3774d64",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../../interfaces/IERC5805.sol",
|
||||
"../../utils/Context.sol",
|
||||
"../../utils/Nonces.sol",
|
||||
"../../utils/cryptography/EIP712.sol",
|
||||
"../../utils/structs/Checkpoints.sol",
|
||||
"../../utils/math/SafeCast.sol",
|
||||
"../../utils/cryptography/ECDSA.sol",
|
||||
"../../utils/types/Time.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/structs/Checkpoints.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "657c6dfea3bae1af948de6113ba01cea",
|
||||
"sourceName": "@openzeppelin/contracts/utils/structs/Checkpoints.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Checkpoints"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5805.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "65ba9f89b1057e2192e341b286d4e261",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5805.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../governance/utils/IVotes.sol",
|
||||
"./IERC6372.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5805"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/types/Time.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "d83e7814a059fc1287fd765f424ce004",
|
||||
"sourceName": "@openzeppelin/contracts/utils/types/Time.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol",
|
||||
"../math/SafeCast.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Time"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "905ffceb29869fee4b5a649abe7e2927",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/IVotes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
@@ -1093,7 +1056,44 @@
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC20Permit"
|
||||
"IVotes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC6372.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "414cd6acf090e4009cf016ff62ecbd88",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC6372.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC6372"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/FactoryDeployer.sol": {
|
||||
|
||||
@@ -26,7 +26,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
struct DLEInfo {
|
||||
string name;
|
||||
string symbol;
|
||||
string tokenImage; // Картинка токена (base64 или URL)
|
||||
string location;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
@@ -39,7 +38,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
struct DLEConfig {
|
||||
string name;
|
||||
string symbol;
|
||||
string tokenImage; // Картинка токена (base64 или URL)
|
||||
string location;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
@@ -91,7 +89,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
event DLEInitialized(
|
||||
string name,
|
||||
string symbol,
|
||||
string tokenImage,
|
||||
string location,
|
||||
string coordinates,
|
||||
uint256 jurisdiction,
|
||||
@@ -112,7 +109,7 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId);
|
||||
event ChainAdded(uint256 chainId);
|
||||
event ChainRemoved(uint256 chainId);
|
||||
event DLEInfoUpdated(string name, string symbol, string tokenImage, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp);
|
||||
event DLEInfoUpdated(string name, string symbol, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp);
|
||||
event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
|
||||
event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
||||
|
||||
@@ -129,7 +126,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
dleInfo = DLEInfo({
|
||||
name: config.name,
|
||||
symbol: config.symbol,
|
||||
tokenImage: config.tokenImage,
|
||||
location: config.location,
|
||||
coordinates: config.coordinates,
|
||||
jurisdiction: config.jurisdiction,
|
||||
@@ -166,7 +162,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
emit DLEInitialized(
|
||||
config.name,
|
||||
config.symbol,
|
||||
config.tokenImage,
|
||||
config.location,
|
||||
config.coordinates,
|
||||
config.jurisdiction,
|
||||
@@ -509,11 +504,11 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
// Декодируем операцию
|
||||
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
|
||||
|
||||
if (selector == bytes4(keccak256("updateDLEInfo(string,string,string,string,string,uint256,uint256,string[],uint256)"))) {
|
||||
if (selector == bytes4(keccak256("updateDLEInfo(string,string,string,string,uint256,string[],uint256)"))) {
|
||||
// Операция обновления информации DLE
|
||||
(string memory name, string memory symbol, string memory tokenImage, string memory location, string memory coordinates,
|
||||
uint256 jurisdiction, string[] memory okvedCodes, uint256 kpp) = abi.decode(data, (string, string, string, string, string, uint256, string[], uint256));
|
||||
_updateDLEInfo(name, symbol, tokenImage, location, coordinates, jurisdiction, okvedCodes, kpp);
|
||||
(string memory name, string memory symbol, string memory location, string memory coordinates,
|
||||
uint256 jurisdiction, string[] memory okvedCodes, uint256 kpp) = abi.decode(data, (string, string, string, string, uint256, string[], uint256));
|
||||
_updateDLEInfo(name, symbol, location, coordinates, jurisdiction, okvedCodes, kpp);
|
||||
} else if (selector == bytes4(keccak256("updateQuorumPercentage(uint256)"))) {
|
||||
// Операция обновления процента кворума
|
||||
(uint256 newQuorumPercentage) = abi.decode(data, (uint256));
|
||||
@@ -550,7 +545,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
* @dev Обновить информацию DLE
|
||||
* @param _name Новое название
|
||||
* @param _symbol Новый символ
|
||||
* @param _tokenImage Новая картинка токена
|
||||
* @param _location Новое местонахождение
|
||||
* @param _coordinates Новые координаты
|
||||
* @param _jurisdiction Новая юрисдикция
|
||||
@@ -560,7 +554,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
function _updateDLEInfo(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
string memory _tokenImage,
|
||||
string memory _location,
|
||||
string memory _coordinates,
|
||||
uint256 _jurisdiction,
|
||||
@@ -575,14 +568,13 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
|
||||
dleInfo.name = _name;
|
||||
dleInfo.symbol = _symbol;
|
||||
dleInfo.tokenImage = _tokenImage;
|
||||
dleInfo.location = _location;
|
||||
dleInfo.coordinates = _coordinates;
|
||||
dleInfo.jurisdiction = _jurisdiction;
|
||||
dleInfo.okvedCodes = _okvedCodes;
|
||||
dleInfo.kpp = _kpp;
|
||||
|
||||
emit DLEInfoUpdated(_name, _symbol, _tokenImage, _location, _coordinates, _jurisdiction, _okvedCodes, _kpp);
|
||||
emit DLEInfoUpdated(_name, _symbol, _location, _coordinates, _jurisdiction, _okvedCodes, _kpp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -195,8 +195,12 @@ router.post('/verify', async (req, res) => {
|
||||
return res.status(401).json({ success: false, error: 'Invalid signature' });
|
||||
}
|
||||
|
||||
// СРАЗУ проверяем наличие админских токенов
|
||||
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
||||
logger.info(`[verify] Admin status for ${normalizedAddress}: ${adminStatus}`);
|
||||
|
||||
let userId;
|
||||
let isAdmin = false;
|
||||
let isAdmin = adminStatus;
|
||||
|
||||
// Проверяем, авторизован ли пользователь уже
|
||||
if (req.session.authenticated && req.session.userId) {
|
||||
@@ -214,11 +218,11 @@ router.post('/verify', async (req, res) => {
|
||||
`[verify] Wallet ${normalizedAddress} linked to user ${userId}: already exists`
|
||||
);
|
||||
} else {
|
||||
// Находим или создаем пользователя, если не авторизован
|
||||
const result = await authService.findOrCreateUser(address);
|
||||
// Находим или создаем пользователя с уже известной ролью
|
||||
const result = await authService.findOrCreateUser(address, adminStatus);
|
||||
userId = result.userId;
|
||||
isAdmin = result.isAdmin;
|
||||
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress}`);
|
||||
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress} with admin status: ${isAdmin}`);
|
||||
}
|
||||
|
||||
// Сохраняем идентификаторы гостевой сессии
|
||||
@@ -230,14 +234,6 @@ router.post('/verify', async (req, res) => {
|
||||
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
||||
}
|
||||
|
||||
// Проверяем наличие админских токенов
|
||||
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
||||
|
||||
if (adminStatus) {
|
||||
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
isAdmin = true;
|
||||
}
|
||||
|
||||
// Обновляем сессию
|
||||
req.session.userId = userId;
|
||||
req.session.authenticated = true;
|
||||
|
||||
@@ -217,10 +217,11 @@ router.post('/get-proposals', async (req, res) => {
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
|
||||
// ABI для чтения предложений (только читаемые поля)
|
||||
// ABI для чтения предложений (используем правильные функции из смарт-контракта)
|
||||
const dleAbi = [
|
||||
"function proposals(uint256) external view returns (uint256 id, string description, uint256 forVotes, uint256 againstVotes, bool executed, uint256 deadline, address initiator, bytes operation)",
|
||||
"function checkProposalResult(uint256 _proposalId) external view returns (bool)",
|
||||
"function getProposalSummary(uint256 _proposalId) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targets)",
|
||||
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
|
||||
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
|
||||
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
|
||||
];
|
||||
|
||||
@@ -250,8 +251,9 @@ router.post('/get-proposals', async (req, res) => {
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
proposal = await dle.proposals(proposalId);
|
||||
isPassed = await dle.checkProposalResult(proposalId);
|
||||
proposal = await dle.getProposalSummary(proposalId);
|
||||
const result = await dle.checkProposalResult(proposalId);
|
||||
isPassed = result.passed;
|
||||
break; // Успешно прочитали
|
||||
} catch (error) {
|
||||
retryCount++;
|
||||
@@ -264,19 +266,18 @@ router.post('/get-proposals', async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// governanceChainId не сохраняется в предложении, используем текущую цепочку
|
||||
const governanceChainId = 11155111; // Sepolia chain ID
|
||||
|
||||
console.log(`[Blockchain] Данные предложения ${proposalId}:`, {
|
||||
id: Number(proposal.id),
|
||||
description: proposal.description,
|
||||
forVotes: Number(proposal.forVotes),
|
||||
againstVotes: Number(proposal.againstVotes),
|
||||
executed: proposal.executed,
|
||||
canceled: proposal.canceled,
|
||||
deadline: Number(proposal.deadline),
|
||||
initiator: proposal.initiator,
|
||||
operation: proposal.operation,
|
||||
governanceChainId: Number(governanceChainId)
|
||||
governanceChainId: Number(proposal.governanceChainId),
|
||||
snapshotTimepoint: Number(proposal.snapshotTimepoint),
|
||||
targets: proposal.targets
|
||||
});
|
||||
|
||||
const proposalInfo = {
|
||||
@@ -285,10 +286,12 @@ router.post('/get-proposals', async (req, res) => {
|
||||
forVotes: Number(proposal.forVotes),
|
||||
againstVotes: Number(proposal.againstVotes),
|
||||
executed: proposal.executed,
|
||||
canceled: proposal.canceled,
|
||||
deadline: Number(proposal.deadline),
|
||||
initiator: proposal.initiator,
|
||||
operation: proposal.operation,
|
||||
governanceChainId: Number(governanceChainId),
|
||||
governanceChainId: Number(proposal.governanceChainId),
|
||||
snapshotTimepoint: Number(proposal.snapshotTimepoint),
|
||||
targetChains: proposal.targets.map(chainId => Number(chainId)),
|
||||
isPassed: isPassed,
|
||||
blockNumber: events[i].blockNumber
|
||||
};
|
||||
|
||||
@@ -39,7 +39,6 @@ async function main() {
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
tokenImage: params.tokenImage || '',
|
||||
location: params.location,
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
|
||||
@@ -58,9 +58,10 @@ class AuthService {
|
||||
/**
|
||||
* Находит или создает пользователя по адресу кошелька
|
||||
* @param {string} address - Адрес кошелька
|
||||
* @param {boolean} isAdmin - Предварительно проверенный статус админа
|
||||
* @returns {Promise<{userId: number, isAdmin: boolean}>}
|
||||
*/
|
||||
async findOrCreateUser(address) {
|
||||
async findOrCreateUser(address, isAdmin = null) {
|
||||
try {
|
||||
// Нормализуем адрес - всегда приводим к нижнему регистру
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
@@ -78,15 +79,15 @@ class AuthService {
|
||||
}
|
||||
const userData = user[0];
|
||||
|
||||
// Проверяем роль администратора при каждой аутентификации
|
||||
const isAdmin = await checkAdminRole(normalizedAddress);
|
||||
// Используем предварительно проверенный статус админа или проверяем заново
|
||||
const adminStatus = isAdmin !== null ? isAdmin : await checkAdminRole(normalizedAddress);
|
||||
|
||||
// Если статус админа изменился, обновляем роль в базе данных
|
||||
if (userData.role === 'admin' && !isAdmin) {
|
||||
if (userData.role === 'admin' && !adminStatus) {
|
||||
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['user', userData.id]);
|
||||
logger.info(`Updated user ${userData.id} role to user (admin tokens no longer present)`);
|
||||
return { userId: userData.id, isAdmin: false };
|
||||
} else if (userData.role !== 'admin' && isAdmin) {
|
||||
} else if (userData.role !== 'admin' && adminStatus) {
|
||||
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userData.id]);
|
||||
logger.info(`Updated user ${userData.id} role to admin (admin tokens found)`);
|
||||
return { userId: userData.id, isAdmin: true };
|
||||
@@ -98,8 +99,11 @@ class AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
// Если пользователь не найден, создаем нового
|
||||
const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', ['user']);
|
||||
// Если пользователь не найден, создаем нового с правильной ролью
|
||||
const adminStatus = isAdmin !== null ? isAdmin : await checkAdminRole(normalizedAddress);
|
||||
const initialRole = adminStatus ? 'admin' : 'user';
|
||||
|
||||
const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', [initialRole]);
|
||||
const userId = newUserResult.rows[0].id;
|
||||
|
||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||
@@ -109,21 +113,11 @@ class AuthService {
|
||||
provider_id: normalizedAddress
|
||||
});
|
||||
|
||||
// Проверяем, есть ли у пользователя роль админа
|
||||
const isAdmin = await checkAdminRole(normalizedAddress);
|
||||
logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`);
|
||||
|
||||
// Если у пользователя есть админские токены, обновляем его роль
|
||||
if (isAdmin) {
|
||||
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
logger.info(
|
||||
`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`
|
||||
);
|
||||
}
|
||||
logger.info(`New user ${userId} created with role: ${initialRole} for wallet ${normalizedAddress}`);
|
||||
|
||||
broadcastContactsUpdate();
|
||||
|
||||
return { userId, isAdmin };
|
||||
return { userId, isAdmin: adminStatus };
|
||||
} catch (error) {
|
||||
logger.error('Error finding or creating user:', error);
|
||||
throw error;
|
||||
|
||||
@@ -225,18 +225,7 @@ class DLEV2Service {
|
||||
throw new Error('Должна быть выбрана хотя бы одна сеть для деплоя');
|
||||
}
|
||||
|
||||
// Проверяем размер картинки токена (если передана)
|
||||
if (params.tokenImage && params.tokenImage.trim() !== '') {
|
||||
const base64Size = params.tokenImage.length;
|
||||
if (base64Size > 350) {
|
||||
throw new Error(`Размер картинки токена превышает лимит: ${base64Size} байт. Максимальный размер: 350 байт`);
|
||||
}
|
||||
|
||||
// Проверяем, что это валидный base64
|
||||
if (!params.tokenImage.startsWith('data:image/')) {
|
||||
throw new Error('Картинка токена должна быть в формате base64 data URL');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
const WebSocket = require('ws');
|
||||
const authService = require('./services/auth-service');
|
||||
|
||||
let wss = null;
|
||||
// Храним клиентов по userId для персонализированных уведомлений
|
||||
@@ -57,6 +58,11 @@ function initWSS(server) {
|
||||
timestamp: data.timestamp
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.type === 'request_token_balances' && data.address) {
|
||||
// Запрос балансов токенов
|
||||
handleTokenBalancesRequest(ws, data.address, data.userId);
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
|
||||
}
|
||||
@@ -402,6 +408,49 @@ function broadcastAuthTokenUpdated(tokenData) {
|
||||
broadcastToAllClients(message);
|
||||
}
|
||||
|
||||
// Функции для балансов токенов
|
||||
function broadcastTokenBalancesUpdate(userId, balances) {
|
||||
const message = JSON.stringify({
|
||||
type: 'token_balances_updated',
|
||||
data: {
|
||||
balances: balances,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
// Отправляем конкретному пользователю
|
||||
const userClients = wsClients.get(userId.toString());
|
||||
if (userClients) {
|
||||
for (const ws of userClients) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastTokenBalanceChanged(userId, tokenAddress, newBalance, network) {
|
||||
const message = JSON.stringify({
|
||||
type: 'token_balance_changed',
|
||||
data: {
|
||||
tokenAddress: tokenAddress,
|
||||
balance: newBalance,
|
||||
network: network,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
// Отправляем конкретному пользователю
|
||||
const userClients = wsClients.get(userId.toString());
|
||||
if (userClients) {
|
||||
for (const ws of userClients) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initWSS,
|
||||
broadcastContactsUpdate,
|
||||
@@ -418,6 +467,42 @@ module.exports = {
|
||||
broadcastAuthTokenAdded,
|
||||
broadcastAuthTokenDeleted,
|
||||
broadcastAuthTokenUpdated,
|
||||
broadcastTokenBalancesUpdate,
|
||||
broadcastTokenBalanceChanged,
|
||||
getConnectedUsers,
|
||||
getStats
|
||||
};
|
||||
};
|
||||
|
||||
// Обработчик запроса балансов токенов
|
||||
async function handleTokenBalancesRequest(ws, address, userId) {
|
||||
try {
|
||||
console.log(`[WebSocket] Запрос балансов для адреса: ${address}`);
|
||||
|
||||
// Получаем балансы через authService
|
||||
const balances = await authService.getUserTokenBalances(address);
|
||||
|
||||
// Отправляем ответ клиенту
|
||||
ws.send(JSON.stringify({
|
||||
type: 'token_balances_response',
|
||||
data: {
|
||||
address: address,
|
||||
balances: balances,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
|
||||
console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов');
|
||||
} catch (error) {
|
||||
console.error('[WebSocket] Ошибка при получении балансов:', error);
|
||||
|
||||
// Отправляем ошибку клиенту
|
||||
ws.send(JSON.stringify({
|
||||
type: 'token_balances_error',
|
||||
data: {
|
||||
address: address,
|
||||
error: error.message,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,19 @@
|
||||
:isAuthenticated="auth.isAuthenticated.value"
|
||||
:identities="auth.identities.value"
|
||||
:tokenBalances="tokenBalances"
|
||||
:isLoadingTokens="isLoadingTokens"
|
||||
:isLoadingTokens="isLoadingTokens"
|
||||
:formattedLastUpdate="formattedLastUpdate"
|
||||
@auth-action-completed="handleAuthActionCompleted"
|
||||
/>
|
||||
</RouterView>
|
||||
|
||||
<!-- Отладочная информация -->
|
||||
<div v-if="false" style="position: fixed; top: 10px; right: 10px; background: white; padding: 10px; border: 1px solid black; z-index: 9999;">
|
||||
<h4>Debug Info:</h4>
|
||||
<p>isAuthenticated: {{ auth.isAuthenticated.value }}</p>
|
||||
<p>tokenBalances: {{ JSON.stringify(tokenBalances) }}</p>
|
||||
<p>isLoadingTokens: {{ isLoadingTokens }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,7 +42,7 @@
|
||||
import { ref, watch, onMounted, computed, onUnmounted } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { useAuth, provideAuth } from './composables/useAuth';
|
||||
import { fetchTokenBalances } from './services/tokens';
|
||||
import { useTokenBalancesWebSocket } from './composables/useTokenBalancesWebSocket';
|
||||
import eventBus from './utils/eventBus';
|
||||
import wsClient from './utils/websocket';
|
||||
|
||||
@@ -46,12 +55,26 @@
|
||||
// Состояние загрузки
|
||||
const isLoading = ref(false);
|
||||
|
||||
// Проверка наличия MetaMask
|
||||
const isMetaMaskAvailable = ref(false);
|
||||
|
||||
// Использование composable для аутентификации
|
||||
const auth = useAuth();
|
||||
|
||||
// --- Логика загрузки баланса токенов ---
|
||||
const tokenBalances = ref({});
|
||||
const isLoadingTokens = ref(false);
|
||||
// --- Логика загрузки баланса токенов через WebSocket ---
|
||||
// Предоставляем auth контекст
|
||||
provideAuth();
|
||||
|
||||
// Инициализируем WebSocket composable
|
||||
const {
|
||||
tokenBalances,
|
||||
isLoadingTokens,
|
||||
lastUpdateTime,
|
||||
formattedLastUpdate,
|
||||
requestTokenBalances,
|
||||
startAutoUpdate,
|
||||
stopAutoUpdate
|
||||
} = useTokenBalancesWebSocket();
|
||||
|
||||
const identities = computed(() => auth.identities.value);
|
||||
|
||||
@@ -66,60 +89,57 @@
|
||||
return identity ? identity.provider_id : null;
|
||||
};
|
||||
|
||||
const refreshTokenBalances = async () => {
|
||||
const refreshTokenBalances = () => {
|
||||
if (!hasIdentityType('wallet') || !auth.isAuthenticated.value) {
|
||||
tokenBalances.value = {}; // Очищаем, если нет кошелька или не авторизован
|
||||
console.log('[App] Нет кошелька или не авторизован');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingTokens.value = true;
|
||||
try {
|
||||
const walletAddress = getIdentityValue('wallet');
|
||||
// console.log('[App] Обновление балансов для адреса:', walletAddress);
|
||||
|
||||
const balances = await fetchTokenBalances(walletAddress);
|
||||
// console.log('[App] Полученные балансы:', balances);
|
||||
|
||||
tokenBalances.value = balances || {};
|
||||
} catch (error) {
|
||||
// console.error('[App] Ошибка при получении балансов:', error);
|
||||
tokenBalances.value = {};
|
||||
} finally {
|
||||
isLoadingTokens.value = false;
|
||||
}
|
||||
const walletAddress = getIdentityValue('wallet');
|
||||
console.log('[App] Запрашиваем обновление балансов через WebSocket для:', walletAddress, 'userId:', auth.userId.value);
|
||||
requestTokenBalances(walletAddress, auth.userId.value);
|
||||
};
|
||||
|
||||
// Следим за изменениями в идентификаторах
|
||||
watch(identities, (newIdentities, oldIdentities) => {
|
||||
console.log('[App] identities changed:', { newIdentities, oldIdentities });
|
||||
if (auth.isAuthenticated.value) {
|
||||
const newWalletId = getIdentityValue('wallet');
|
||||
const oldWalletIdentity = oldIdentities ? oldIdentities.find(id => id.provider === 'wallet') : null;
|
||||
const oldWalletId = oldWalletIdentity ? oldWalletIdentity.provider_id : null;
|
||||
|
||||
console.log('[App] wallet IDs comparison:', { newWalletId, oldWalletId });
|
||||
|
||||
if (newWalletId !== oldWalletId) {
|
||||
// console.log('[App] Обнаружено изменение идентификатора кошелька, обновляем балансы');
|
||||
refreshTokenBalances();
|
||||
} else if (hasIdentityType('wallet') && Object.keys(tokenBalances.value).length === 0 && !isLoadingTokens.value) {
|
||||
// Если кошелек есть, но баланс пустой и не грузится - пробуем загрузить
|
||||
// console.log('[App] Кошелек есть, но баланс пуст, пытаемся загрузить.');
|
||||
refreshTokenBalances();
|
||||
console.log('[App] Обнаружено изменение идентификатора кошелька, обновляем балансы');
|
||||
if (newWalletId) {
|
||||
startAutoUpdate(newWalletId, auth.userId.value);
|
||||
} else {
|
||||
stopAutoUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
// Мониторинг изменений состояния аутентификации
|
||||
watch(auth.isAuthenticated, (isAuth) => {
|
||||
// console.log('[App] Состояние аутентификации изменилось:', isAuth);
|
||||
console.log('[App] Состояние аутентификации изменилось:', isAuth);
|
||||
if (isAuth) {
|
||||
// Убираем задержку, полагаемся на watch(identities) или прямо вызываем
|
||||
// setTimeout(refreshTokenBalances, 500);
|
||||
refreshTokenBalances(); // Вызываем сразу, если нужно обновить при смене auth
|
||||
} else {
|
||||
// Очищаем баланс при выходе
|
||||
tokenBalances.value = {};
|
||||
}
|
||||
});
|
||||
|
||||
// Проверка наличия MetaMask при загрузке
|
||||
const checkMetaMaskAvailability = () => {
|
||||
try {
|
||||
isMetaMaskAvailable.value = !!window.ethereum && window.ethereum.isMetaMask;
|
||||
console.log('[App] MetaMask доступен:', isMetaMaskAvailable.value);
|
||||
} catch (error) {
|
||||
console.error('[App] Ошибка проверки MetaMask:', error);
|
||||
isMetaMaskAvailable.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// --- Возвращаем и улучшаем функцию-обработчик ---
|
||||
const handleAuthActionCompleted = async () => {
|
||||
// console.log('[App] Auth action completed, triggering updates...');
|
||||
@@ -129,8 +149,8 @@
|
||||
await auth.checkAuth();
|
||||
// console.log('[App] auth.checkAuth() completed. isAuthenticated:', auth.isAuthenticated.value);
|
||||
|
||||
// 2. Обновляем баланс (использует обновленные identities)
|
||||
await refreshTokenBalances();
|
||||
// 2. Обновляем баланс через WebSocket
|
||||
refreshTokenBalances();
|
||||
// console.log('[App] refreshTokenBalances() completed.');
|
||||
|
||||
// 3. Явно оповещаем компоненты об изменении состояния авторизации
|
||||
@@ -152,7 +172,13 @@
|
||||
|
||||
// Первичная загрузка баланса при монтировании, если пользователь уже авторизован
|
||||
onMounted(() => {
|
||||
console.log('[App] onMounted - auth.isAuthenticated:', auth.isAuthenticated.value);
|
||||
console.log('[App] onMounted - identities:', auth.identities.value);
|
||||
|
||||
// Проверяем наличие MetaMask
|
||||
checkMetaMaskAvailability();
|
||||
if (auth.isAuthenticated.value) {
|
||||
console.log('[App] onMounted - вызываем refreshTokenBalances');
|
||||
refreshTokenBalances();
|
||||
}
|
||||
|
||||
@@ -164,27 +190,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Подписываемся на WebSocket события для токенов
|
||||
wsClient.onAuthTokenAdded(() => {
|
||||
console.log('[App] WebSocket: токен добавлен, обновляем балансы');
|
||||
if (auth.isAuthenticated.value) {
|
||||
refreshTokenBalances();
|
||||
}
|
||||
});
|
||||
|
||||
wsClient.onAuthTokenDeleted(() => {
|
||||
console.log('[App] WebSocket: токен удален, обновляем балансы');
|
||||
if (auth.isAuthenticated.value) {
|
||||
refreshTokenBalances();
|
||||
}
|
||||
});
|
||||
|
||||
wsClient.onAuthTokenUpdated(() => {
|
||||
console.log('[App] WebSocket: токен обновлен, обновляем балансы');
|
||||
if (auth.isAuthenticated.value) {
|
||||
refreshTokenBalances();
|
||||
}
|
||||
});
|
||||
// WebSocket события для токенов обрабатываются в useTokenBalancesWebSocket
|
||||
|
||||
// Отписываемся при размонтировании компонента
|
||||
onUnmounted(() => {
|
||||
@@ -193,8 +199,6 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
provideAuth();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -85,6 +85,13 @@ provide('identities', computed(() => props.identities));
|
||||
provide('tokenBalances', computed(() => props.tokenBalances));
|
||||
provide('isLoadingTokens', computed(() => props.isLoadingTokens));
|
||||
|
||||
// Отладочная информация
|
||||
console.log('[BaseLayout] Props received:', {
|
||||
isAuthenticated: props.isAuthenticated,
|
||||
tokenBalances: props.tokenBalances,
|
||||
isLoadingTokens: props.isLoadingTokens
|
||||
});
|
||||
|
||||
// Callback после успешной аутентификации/привязки через Email/Telegram
|
||||
const handleAuthFlowSuccess = (authType) => {
|
||||
// console.log(`[BaseLayout] Auth flow success: ${authType}`);
|
||||
@@ -151,7 +158,21 @@ const handleWalletAuth = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('[BaseLayout] Ошибка при подключении кошелька:', error);
|
||||
showErrorMessage('Произошла ошибка при подключении кошелька');
|
||||
|
||||
// Улучшенная обработка ошибок MetaMask
|
||||
let errorMessage = 'Произошла ошибка при подключении кошелька';
|
||||
|
||||
if (error.message && error.message.includes('MetaMask extension not found')) {
|
||||
errorMessage = 'Расширение MetaMask не найдено. Пожалуйста, установите MetaMask и обновите страницу.';
|
||||
} else if (error.message && error.message.includes('Failed to connect to MetaMask')) {
|
||||
errorMessage = 'Не удалось подключиться к MetaMask. Проверьте, что расширение установлено и активно.';
|
||||
} else if (error.message && error.message.includes('Браузерный кошелек не установлен')) {
|
||||
errorMessage = 'Браузерный кошелек не установлен. Пожалуйста, установите MetaMask.';
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
showErrorMessage(errorMessage);
|
||||
} finally {
|
||||
isConnectingWallet.value = false;
|
||||
}
|
||||
|
||||
@@ -117,12 +117,14 @@
|
||||
Загрузка балансов...
|
||||
</div>
|
||||
<div v-else-if="!tokenBalances || tokenBalances.length === 0" class="token-no-data">
|
||||
Баланс не доступен
|
||||
Баланс не доступен (tokenBalances: {{ tokenBalances }}, length: {{ tokenBalances?.length }})
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="token-balance-header">
|
||||
<small class="last-update">Обновлено: {{ formattedLastUpdate }}</small>
|
||||
<small class="debug-info">Debug: {{ tokenBalances.length }} токенов</small>
|
||||
</div>
|
||||
<div v-for="(token, index) in tokenBalances.data || []" :key="token.tokenAddress ? token.tokenAddress : 'token-' + index" class="token-balance-row">
|
||||
<div v-for="(token, index) in tokenBalances" :key="token.tokenAddress ? token.tokenAddress : 'token-' + index" class="token-balance-row">
|
||||
<span class="token-name">{{ token.tokenName }}</span>
|
||||
<span class="token-network">{{ token.network }}</span>
|
||||
<span class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span>
|
||||
@@ -172,9 +174,10 @@ const props = defineProps({
|
||||
isAuthenticated: Boolean,
|
||||
telegramAuth: Object,
|
||||
emailAuth: Object,
|
||||
tokenBalances: Object,
|
||||
tokenBalances: Array,
|
||||
identities: Array,
|
||||
isLoadingTokens: Boolean
|
||||
isLoadingTokens: Boolean,
|
||||
formattedLastUpdate: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'wallet-auth', 'disconnect-wallet', 'telegram-auth', 'email-auth', 'cancel-email-auth']);
|
||||
@@ -239,11 +242,20 @@ const handleDeleteIdentity = async (provider, providerId) => {
|
||||
|
||||
// Добавляем watch для отслеживания props
|
||||
watch(() => props.tokenBalances, (newVal, oldVal) => {
|
||||
// console.log('[Sidebar] tokenBalances prop changed:', JSON.stringify(newVal));
|
||||
console.log('[Sidebar] tokenBalances prop changed:', JSON.stringify(newVal));
|
||||
}, { deep: true });
|
||||
|
||||
watch(() => props.isLoadingTokens, (newVal, oldVal) => {
|
||||
// console.log(`[Sidebar] isLoadingTokens prop changed: ${newVal}`);
|
||||
console.log(`[Sidebar] isLoadingTokens prop changed: ${newVal}`);
|
||||
});
|
||||
|
||||
// Добавляем отладочную информацию при монтировании
|
||||
onMounted(() => {
|
||||
console.log('[Sidebar] Mounted with props:', {
|
||||
isAuthenticated: props.isAuthenticated,
|
||||
tokenBalances: props.tokenBalances,
|
||||
isLoadingTokens: props.isLoadingTokens
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -67,7 +67,21 @@
|
||||
}
|
||||
} catch (err) {
|
||||
// console.error('Error connecting wallet:', err);
|
||||
error.value = err.message || 'Произошла ошибка при подключении кошелька';
|
||||
|
||||
// Улучшенная обработка ошибок MetaMask
|
||||
let errorMessage = 'Произошла ошибка при подключении кошелька';
|
||||
|
||||
if (err.message && err.message.includes('MetaMask extension not found')) {
|
||||
errorMessage = 'Расширение MetaMask не найдено. Пожалуйста, установите MetaMask и обновите страницу.';
|
||||
} else if (err.message && err.message.includes('Failed to connect to MetaMask')) {
|
||||
errorMessage = 'Не удалось подключиться к MetaMask. Проверьте, что расширение установлено и активно.';
|
||||
} else if (err.message && err.message.includes('Браузерный кошелек не установлен')) {
|
||||
errorMessage = 'Браузерный кошелек не установлен. Пожалуйста, установите MetaMask.';
|
||||
} else if (err.message) {
|
||||
errorMessage = err.message;
|
||||
}
|
||||
|
||||
error.value = errorMessage;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
146
frontend/src/composables/useTokenBalancesWebSocket.js
Normal file
146
frontend/src/composables/useTokenBalancesWebSocket.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import wsClient from '../utils/websocket';
|
||||
|
||||
export function useTokenBalancesWebSocket() {
|
||||
// Состояние балансов
|
||||
const tokenBalances = ref([]);
|
||||
const isLoadingTokens = ref(false);
|
||||
const lastUpdateTime = ref(null);
|
||||
|
||||
// Запрос балансов через WebSocket
|
||||
const requestTokenBalances = (address, userId) => {
|
||||
if (!address) {
|
||||
console.log('[useTokenBalancesWebSocket] Нет адреса для запроса');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[useTokenBalancesWebSocket] Запрашиваем балансы для:', address, 'userId:', userId);
|
||||
isLoadingTokens.value = true;
|
||||
|
||||
const message = {
|
||||
type: 'request_token_balances',
|
||||
address: address,
|
||||
userId: userId
|
||||
};
|
||||
|
||||
console.log('[useTokenBalancesWebSocket] Отправляем WebSocket сообщение:', message);
|
||||
wsClient.ws.send(JSON.stringify(message));
|
||||
};
|
||||
|
||||
// Обработчик ответа с балансами
|
||||
const handleTokenBalancesResponse = (data) => {
|
||||
console.log('[useTokenBalancesWebSocket] Получены балансы:', data);
|
||||
console.log('[useTokenBalancesWebSocket] data.balances:', data.balances);
|
||||
tokenBalances.value = data.balances || [];
|
||||
isLoadingTokens.value = false;
|
||||
lastUpdateTime.value = new Date();
|
||||
console.log('[useTokenBalancesWebSocket] Обновлен tokenBalances.value:', tokenBalances.value);
|
||||
};
|
||||
|
||||
// Обработчик ошибки
|
||||
const handleTokenBalancesError = (data) => {
|
||||
console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data.error);
|
||||
isLoadingTokens.value = false;
|
||||
tokenBalances.value = [];
|
||||
};
|
||||
|
||||
// Обработчик обновления балансов
|
||||
const handleTokenBalancesUpdated = (data) => {
|
||||
console.log('[useTokenBalancesWebSocket] Обновление балансов:', data);
|
||||
tokenBalances.value = data.balances || [];
|
||||
lastUpdateTime.value = new Date();
|
||||
};
|
||||
|
||||
// Обработчик изменения конкретного баланса
|
||||
const handleTokenBalanceChanged = (data) => {
|
||||
console.log('[useTokenBalancesWebSocket] Изменение баланса токена:', data);
|
||||
|
||||
// Обновляем конкретный токен в списке
|
||||
const tokenIndex = tokenBalances.value.findIndex(
|
||||
token => token.tokenAddress === data.tokenAddress && token.network === data.network
|
||||
);
|
||||
|
||||
if (tokenIndex !== -1) {
|
||||
tokenBalances.value[tokenIndex].balance = data.balance;
|
||||
lastUpdateTime.value = new Date();
|
||||
}
|
||||
};
|
||||
|
||||
// Вычисляемое свойство для форматированного времени обновления
|
||||
const formattedLastUpdate = computed(() => {
|
||||
if (!lastUpdateTime.value) return 'Не обновлялось';
|
||||
return lastUpdateTime.value.toLocaleTimeString();
|
||||
});
|
||||
|
||||
// Автоматическое обновление каждые 5 минут
|
||||
let autoUpdateInterval = null;
|
||||
|
||||
const startAutoUpdate = (address, userId) => {
|
||||
stopAutoUpdate();
|
||||
|
||||
// Первоначальный запрос
|
||||
if (address) {
|
||||
requestTokenBalances(address, userId);
|
||||
}
|
||||
|
||||
// Автообновление каждые 5 минут
|
||||
autoUpdateInterval = setInterval(() => {
|
||||
if (address) {
|
||||
console.log('[useTokenBalancesWebSocket] Автообновление балансов');
|
||||
requestTokenBalances(address, userId);
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 минут
|
||||
};
|
||||
|
||||
const stopAutoUpdate = () => {
|
||||
if (autoUpdateInterval) {
|
||||
clearInterval(autoUpdateInterval);
|
||||
autoUpdateInterval = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Подписка на WebSocket события
|
||||
onMounted(() => {
|
||||
// Подписываемся на события WebSocket
|
||||
wsClient.on('token_balances_response', handleTokenBalancesResponse);
|
||||
wsClient.on('token_balances_error', handleTokenBalancesError);
|
||||
wsClient.on('token_balances_updated', handleTokenBalancesUpdated);
|
||||
wsClient.on('token_balance_changed', handleTokenBalanceChanged);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// Отписываемся от событий
|
||||
wsClient.off('token_balances_response');
|
||||
wsClient.off('token_balances_error');
|
||||
wsClient.off('token_balances_updated');
|
||||
wsClient.off('token_balance_changed');
|
||||
|
||||
// Останавливаем автообновление
|
||||
stopAutoUpdate();
|
||||
});
|
||||
|
||||
return {
|
||||
// Состояние
|
||||
tokenBalances: computed(() => tokenBalances.value),
|
||||
isLoadingTokens: computed(() => isLoadingTokens.value),
|
||||
lastUpdateTime: computed(() => lastUpdateTime.value),
|
||||
formattedLastUpdate,
|
||||
|
||||
// Методы
|
||||
requestTokenBalances,
|
||||
startAutoUpdate,
|
||||
stopAutoUpdate
|
||||
};
|
||||
}
|
||||
@@ -43,5 +43,27 @@ app.use(ElementPlus);
|
||||
// console.log('API URL:', axios.defaults.baseURL);
|
||||
// console.log('main.js: Starting application with router');
|
||||
|
||||
// Глобальная обработка ошибок MetaMask
|
||||
window.addEventListener('error', (event) => {
|
||||
if (event.error && event.error.message &&
|
||||
(event.error.message.includes('MetaMask extension not found') ||
|
||||
event.error.message.includes('Failed to connect to MetaMask'))) {
|
||||
console.warn('[MetaMask] Ошибка MetaMask перехвачена:', event.error.message);
|
||||
// Предотвращаем показ ошибки в консоли
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка необработанных промисов
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
if (event.reason && event.reason.message &&
|
||||
(event.reason.message.includes('MetaMask extension not found') ||
|
||||
event.reason.message.includes('Failed to connect to MetaMask'))) {
|
||||
console.warn('[MetaMask] Необработанная ошибка MetaMask перехвачена:', event.reason.message);
|
||||
// Предотвращаем показ ошибки в консоли
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
// console.log('main.js: Application with router mounted');
|
||||
|
||||
@@ -108,6 +108,30 @@ export async function connectWithWallet() {
|
||||
return verificationResponse.data;
|
||||
} catch (error) {
|
||||
// console.error('Error connecting wallet:', error);
|
||||
throw error;
|
||||
|
||||
// Улучшенная обработка ошибок MetaMask
|
||||
let errorMessage = 'Произошла ошибка при подключении кошелька.';
|
||||
|
||||
if (error.message && error.message.includes('MetaMask extension not found')) {
|
||||
errorMessage = 'Расширение MetaMask не найдено. Пожалуйста, установите MetaMask и обновите страницу.';
|
||||
} else if (error.message && error.message.includes('Failed to connect to MetaMask')) {
|
||||
errorMessage = 'Не удалось подключиться к MetaMask. Проверьте, что расширение установлено и активно.';
|
||||
} else if (error.code === 4001) {
|
||||
errorMessage = 'Вы отклонили запрос на подключение в MetaMask.';
|
||||
} else if (error.message && error.message.includes('No accounts found')) {
|
||||
errorMessage = 'Аккаунты не найдены. Пожалуйста, разблокируйте MetaMask и попробуйте снова.';
|
||||
} else if (error.message && error.message.includes('MetaMask not detected')) {
|
||||
errorMessage = 'MetaMask не обнаружен. Пожалуйста, установите расширение MetaMask.';
|
||||
} else if (error.response && error.response.data && error.response.data.error) {
|
||||
errorMessage = error.response.data.error;
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
// Возвращаем объект с ошибкой вместо выброса исключения
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,21 @@ export async function checkWalletConnection() {
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка подключения к кошельку:', error);
|
||||
throw error;
|
||||
|
||||
// Улучшенная обработка ошибок MetaMask
|
||||
let errorMessage = 'Ошибка подключения к кошельку.';
|
||||
|
||||
if (error.message && error.message.includes('MetaMask extension not found')) {
|
||||
errorMessage = 'Расширение MetaMask не найдено. Пожалуйста, установите MetaMask и обновите страницу.';
|
||||
} else if (error.message && error.message.includes('Failed to connect to MetaMask')) {
|
||||
errorMessage = 'Не удалось подключиться к MetaMask. Проверьте, что расширение установлено и активно.';
|
||||
} else if (error.message && error.message.includes('Браузерный кошелек не установлен')) {
|
||||
errorMessage = 'Браузерный кошелек не установлен. Пожалуйста, установите MetaMask.';
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,8 +151,16 @@ export const connectWallet = async () => {
|
||||
// Формируем понятное сообщение об ошибке
|
||||
let errorMessage = 'Произошла ошибка при подключении кошелька.';
|
||||
|
||||
if (error.code === 4001) {
|
||||
if (error.message && error.message.includes('MetaMask extension not found')) {
|
||||
errorMessage = 'Расширение MetaMask не найдено. Пожалуйста, установите MetaMask и обновите страницу.';
|
||||
} else if (error.message && error.message.includes('Failed to connect to MetaMask')) {
|
||||
errorMessage = 'Не удалось подключиться к MetaMask. Проверьте, что расширение установлено и активно.';
|
||||
} else if (error.code === 4001) {
|
||||
errorMessage = 'Вы отклонили запрос на подпись в MetaMask.';
|
||||
} else if (error.message && error.message.includes('No accounts found')) {
|
||||
errorMessage = 'Аккаунты не найдены. Пожалуйста, разблокируйте MetaMask и попробуйте снова.';
|
||||
} else if (error.message && error.message.includes('MetaMask not detected')) {
|
||||
errorMessage = 'MetaMask не обнаружен. Пожалуйста, установите расширение MetaMask.';
|
||||
} else if (error.response && error.response.data && error.response.data.error) {
|
||||
errorMessage = error.response.data.error;
|
||||
} else if (error.message) {
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
<h5>Добавленные коды ОКВЭД:</h5>
|
||||
<ul class="codes-list">
|
||||
<li v-for="(code, index) in dleSettings.selectedOkved" :key="index" class="code-item">
|
||||
<span>{{ getOkvedTitle(code) }}</span>
|
||||
<span>{{ code }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-sm"
|
||||
@@ -325,37 +325,7 @@
|
||||
<small class="form-help">3-10 символов для токена управления (Governance Token)</small>
|
||||
</div>
|
||||
|
||||
<!-- Картинка токена -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="tokenImage">Картинка токена:</label>
|
||||
<div class="token-image-upload">
|
||||
<input
|
||||
type="file"
|
||||
id="tokenImage"
|
||||
ref="tokenImageInput"
|
||||
class="form-control"
|
||||
accept="image/*"
|
||||
@change="handleTokenImageUpload"
|
||||
style="display: none;"
|
||||
>
|
||||
<div class="upload-area" @click="triggerImageUpload">
|
||||
<div v-if="!dleSettings.tokenImage" class="upload-placeholder">
|
||||
<i class="fas fa-image"></i>
|
||||
<p>Нажмите для выбора картинки токена</p>
|
||||
<small>Поддерживаются форматы: JPG, PNG, GIF (макс. 1MB, 200x200px)</small>
|
||||
</div>
|
||||
<div v-else class="image-preview">
|
||||
<img :src="dleSettings.tokenImage" alt="Картинка токена" class="token-image">
|
||||
<div class="image-overlay">
|
||||
<button type="button" @click.stop="removeTokenImage" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-help">Загрузите картинку для вашего токена (макс. 350 байт в base64, автоматически сжимается до 200x200px)</small>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -379,13 +349,24 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group flex-grow">
|
||||
<label class="form-label">Адрес кошелька:</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="partner.address"
|
||||
class="form-control"
|
||||
placeholder="0x..."
|
||||
@input="validateEthereumAddress(partner, index)"
|
||||
>
|
||||
<div class="address-input-group">
|
||||
<input
|
||||
type="text"
|
||||
v-model="partner.address"
|
||||
class="form-control"
|
||||
placeholder="0x..."
|
||||
@input="validateEthereumAddress(partner, index)"
|
||||
>
|
||||
<button
|
||||
v-if="index === 0 && address"
|
||||
@click="useMyWalletAddress"
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="Использовать мой адрес кошелька"
|
||||
>
|
||||
<i class="fas fa-wallet"></i> Мой кошелек
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Количество токенов:</label>
|
||||
@@ -688,12 +669,7 @@
|
||||
<strong>🪙 Токен:</strong> {{ dleSettings.tokenSymbol }}
|
||||
</div>
|
||||
|
||||
<div v-if="dleSettings.tokenImage" class="preview-item">
|
||||
<strong>🖼️ Картинка токена:</strong>
|
||||
<div class="token-image-preview">
|
||||
<img :src="dleSettings.tokenImage" alt="Картинка токена" class="preview-token-image">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -928,7 +904,6 @@ const dleSettings = reactive({
|
||||
selectedOkved: [], // ОКВЭД - дополнительные коды деятельности
|
||||
name: '', // Имя DLE
|
||||
tokenSymbol: '', // Символ токена
|
||||
tokenImage: '', // Картинка токена (base64 или URL)
|
||||
partners: [{ address: '', amount: 1 }], // Партнеры и их доли токенов
|
||||
governanceQuorum: 51, // Кворум для принятия решений (%)
|
||||
|
||||
@@ -1460,7 +1435,7 @@ const loadFormData = () => {
|
||||
selectedOkved: parsedData.selectedOkved || [],
|
||||
name: parsedData.name || '',
|
||||
tokenSymbol: parsedData.tokenSymbol || '',
|
||||
tokenImage: parsedData.tokenImage || '',
|
||||
|
||||
partners: parsedData.partners || [{ address: '', amount: 1 }],
|
||||
governanceQuorum: parsedData.governanceQuorum || 51,
|
||||
// Координаты
|
||||
@@ -1541,7 +1516,7 @@ const clearAllData = () => {
|
||||
dleSettings.selectedOkved = [];
|
||||
dleSettings.name = '';
|
||||
dleSettings.tokenSymbol = '';
|
||||
dleSettings.tokenImage = ''; // Очищаем картинку токена
|
||||
|
||||
dleSettings.partners = [{ address: '', amount: 1 }]; // Сброс к одному пустому партнеру
|
||||
dleSettings.governanceQuorum = 51; // Сброс кворума к значению по умолчанию
|
||||
|
||||
@@ -1815,88 +1790,7 @@ const formatTokenSymbol = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Функции для работы с картинкой токена
|
||||
const tokenImageInput = ref(null);
|
||||
|
||||
// Запуск выбора файла
|
||||
const triggerImageUpload = () => {
|
||||
tokenImageInput.value?.click();
|
||||
};
|
||||
|
||||
// Обработка загрузки изображения
|
||||
const handleTokenImageUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Проверка типа файла
|
||||
if (!file.type.startsWith('image/')) {
|
||||
alert('Пожалуйста, выберите файл изображения (JPG, PNG, GIF)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка размера файла (максимум 1MB)
|
||||
const maxSize = 1 * 1024 * 1024; // 1MB
|
||||
if (file.size > maxSize) {
|
||||
alert('Размер файла не должен превышать 1MB');
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем canvas для сжатия изображения
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
// Ограничиваем размер изображения
|
||||
const maxWidth = 200;
|
||||
const maxHeight = 200;
|
||||
|
||||
let { width, height } = img;
|
||||
if (width > maxWidth) {
|
||||
height = (height * maxWidth) / width;
|
||||
width = maxWidth;
|
||||
}
|
||||
if (height > maxHeight) {
|
||||
width = (width * maxHeight) / height;
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
|
||||
// Конвертируем в base64 с сжатием
|
||||
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.7); // 70% качество
|
||||
|
||||
// Проверяем размер base64 (максимум 350 байт)
|
||||
const base64Size = compressedDataUrl.length;
|
||||
if (base64Size > 350) {
|
||||
alert(`Изображение слишком большое (${base64Size} байт). Максимальный размер: 350 байт. Попробуйте уменьшить размер или качество изображения.`);
|
||||
return;
|
||||
}
|
||||
|
||||
dleSettings.tokenImage = compressedDataUrl;
|
||||
// Сохраняем в localStorage
|
||||
saveFormData();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
alert('Ошибка при загрузке изображения');
|
||||
};
|
||||
|
||||
img.src = URL.createObjectURL(file);
|
||||
};
|
||||
|
||||
// Удаление картинки токена
|
||||
const removeTokenImage = () => {
|
||||
dleSettings.tokenImage = '';
|
||||
// Очищаем input
|
||||
if (tokenImageInput.value) {
|
||||
tokenImageInput.value.value = '';
|
||||
}
|
||||
// Сохраняем в localStorage
|
||||
saveFormData();
|
||||
};
|
||||
|
||||
|
||||
// Функция загрузки стран
|
||||
@@ -2398,9 +2292,13 @@ onMounted(() => {
|
||||
loadRussianClassifiers();
|
||||
}
|
||||
|
||||
// Автозаполнение первого партнера подключенным кошельком (если данные не были загружены)
|
||||
if (!dataLoaded && address.value && dleSettings.partners[0] && !dleSettings.partners[0].address) {
|
||||
dleSettings.partners[0].address = address.value;
|
||||
// Автозаполнение первого партнера подключенным кошельком
|
||||
if (address.value && dleSettings.partners[0]) {
|
||||
// Если адрес пустой или это новый пользователь, подставляем адрес кошелька
|
||||
if (!dleSettings.partners[0].address || !dataLoaded) {
|
||||
dleSettings.partners[0].address = address.value;
|
||||
console.log('Автоматически подставлен адрес кошелька:', address.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем слушатель события видимости страницы для обновления списка сетей
|
||||
@@ -2417,8 +2315,12 @@ onUnmounted(() => {
|
||||
|
||||
// Watcher для автоматического обновления адреса первого партнера при подключении кошелька
|
||||
watch(address, (newAddress) => {
|
||||
if (newAddress && dleSettings.partners[0] && !dleSettings.partners[0].address) {
|
||||
dleSettings.partners[0].address = newAddress;
|
||||
if (newAddress && dleSettings.partners[0]) {
|
||||
// Подставляем адрес, если поле пустое или пользователь только что подключил кошелек
|
||||
if (!dleSettings.partners[0].address) {
|
||||
dleSettings.partners[0].address = newAddress;
|
||||
console.log('Кошелек подключен, подставлен адрес:', newAddress);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2469,6 +2371,16 @@ const validateEthereumAddress = (partner, index) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для подставления адреса кошелька в первого партнера
|
||||
const useMyWalletAddress = () => {
|
||||
if (address.value && dleSettings.partners[0]) {
|
||||
dleSettings.partners[0].address = address.value;
|
||||
console.log('Подставлен адрес кошелька:', address.value);
|
||||
} else {
|
||||
alert('Кошелек не подключен. Пожалуйста, подключите кошелек сначала.');
|
||||
}
|
||||
};
|
||||
|
||||
// Маскированный приватный ключ для превью (устаревшее)
|
||||
const maskedPrivateKey = computed(() => {
|
||||
if (!dleSettings.privateKey) return '';
|
||||
@@ -2498,7 +2410,7 @@ const deploySmartContracts = async () => {
|
||||
// Основная информация DLE
|
||||
name: dleSettings.name,
|
||||
symbol: dleSettings.tokenSymbol,
|
||||
tokenImage: dleSettings.tokenImage, // Картинка токена
|
||||
|
||||
location: dleSettings.addressData.fullAddress || 'Не указан',
|
||||
coordinates: dleSettings.coordinates || '0,0',
|
||||
jurisdiction: parseInt(dleSettings.jurisdiction) || 0,
|
||||
@@ -2706,6 +2618,21 @@ const validateCoordinates = (coordinates) => {
|
||||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
||||
}
|
||||
|
||||
.address-input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.address-input-group .form-control {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.address-input-group .btn {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input-icon-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<option value="succeeded">Принятые</option>
|
||||
<option value="defeated">Отклоненные</option>
|
||||
<option value="executed">Выполненные</option>
|
||||
<option value="canceled">Отмененные</option>
|
||||
</select>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
@@ -657,11 +658,11 @@ async function loadDleData() {
|
||||
|
||||
// Преобразуем данные из API в формат для frontend
|
||||
proposals.value = proposalsData.map(proposal => {
|
||||
const transformedProposal = {
|
||||
...proposal,
|
||||
status: getProposalStatus(proposal),
|
||||
deadline: proposal.deadline || (proposal.startTime + proposal.duration)
|
||||
};
|
||||
const transformedProposal = {
|
||||
...proposal,
|
||||
status: getProposalStatus(proposal),
|
||||
deadline: proposal.deadline || 0
|
||||
};
|
||||
console.log('[Frontend] Преобразованное предложение:', transformedProposal);
|
||||
return transformedProposal;
|
||||
});
|
||||
@@ -776,11 +777,28 @@ function getProposalStatus(proposal) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const deadline = proposal.deadline || 0;
|
||||
|
||||
// Проверяем отменено ли предложение
|
||||
if (proposal.canceled) {
|
||||
return 'canceled';
|
||||
}
|
||||
|
||||
// Проверяем выполнено ли предложение
|
||||
if (proposal.executed) {
|
||||
return 'executed';
|
||||
}
|
||||
|
||||
// Проверяем, достигнут ли кворум
|
||||
// Используем isPassed из API, если доступно
|
||||
if (proposal.isPassed !== undefined) {
|
||||
if (proposal.isPassed) {
|
||||
return 'succeeded';
|
||||
} else if (deadline > 0 && now >= deadline) {
|
||||
return 'defeated';
|
||||
} else {
|
||||
return 'active';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback логика для старых данных
|
||||
const quorumPercentage = getQuorumPercentage(proposal);
|
||||
const requiredQuorum = getRequiredQuorum();
|
||||
const hasReachedQuorum = quorumPercentage >= requiredQuorum;
|
||||
@@ -793,7 +811,8 @@ function getProposalStatus(proposal) {
|
||||
deadlinePassed: deadline > 0 && now >= deadline,
|
||||
quorumPercentage,
|
||||
requiredQuorum,
|
||||
hasReachedQuorum
|
||||
hasReachedQuorum,
|
||||
isPassed: proposal.isPassed
|
||||
});
|
||||
|
||||
// Если кворум достигнут, предложение можно выполнить
|
||||
@@ -815,7 +834,8 @@ function getProposalStatusText(status) {
|
||||
'active': 'Активно',
|
||||
'succeeded': 'Принято',
|
||||
'defeated': 'Отклонено',
|
||||
'executed': 'Выполнено'
|
||||
'executed': 'Выполнено',
|
||||
'canceled': 'Отменено'
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
@@ -1719,11 +1739,21 @@ onUnmounted(() => {
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.proposal-status.executed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.proposal-status.defeated {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.proposal-status.canceled {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.proposal-details {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user