ваше сообщение коммита

This commit is contained in:
2025-08-15 22:36:50 +03:00
parent 5238e1ee55
commit 3765c65a18
29 changed files with 904 additions and 651 deletions

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/29c61b08eb590d8fc12c8139671115f4.json" "buildInfo": "../../../../build-info/ab387c71734b3d3e5e7817d328027586.json"
} }

View File

@@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_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

View File

@@ -2,8 +2,8 @@
"_format": "hh-sol-cache-2", "_format": "hh-sol-cache-2",
"files": { "files": {
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": { "/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
"lastModificationDate": 1755277189432, "lastModificationDate": 1755280436490,
"contentHash": "03b4ac83148b5b1246f64c54fa0e959c", "contentHash": "f676e9964a39b0fccdc62a9114266863",
"sourceName": "contracts/DLE.sol", "sourceName": "contracts/DLE.sol",
"solcConfig": { "solcConfig": {
"version": "0.8.20", "version": "0.8.20",
@@ -160,47 +160,6 @@
"ECDSA" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol": {
"lastModificationDate": 1754306768254, "lastModificationDate": 1754306768254,
"contentHash": "b1a8fc63b83ce00408e0c9ed1230b717", "contentHash": "b1a8fc63b83ce00408e0c9ed1230b717",
@@ -244,6 +203,47 @@
"ERC20Permit" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Context.sol": {
"lastModificationDate": 1754306760451, "lastModificationDate": 1754306760451,
"contentHash": "67bfbc07588eb8683b3fd8f6f909563e", "contentHash": "67bfbc07588eb8683b3fd8f6f909563e",
@@ -396,91 +396,6 @@
"IERC20Metadata" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Nonces.sol": {
"lastModificationDate": 1754306760451, "lastModificationDate": 1754306760451,
"contentHash": "c32d108058718efb9061b88e83a83f79", "contentHash": "c32d108058718efb9061b88e83a83f79",
@@ -518,46 +433,6 @@
"Nonces" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/EIP712.sol": {
"lastModificationDate": 1754306764456, "lastModificationDate": 1754306764456,
"contentHash": "8dbb261c55f358342798c4d1803d4f8e", "contentHash": "8dbb261c55f358342798c4d1803d4f8e",
@@ -599,10 +474,10 @@
"EIP712" "EIP712"
] ]
}, },
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol": {
"lastModificationDate": 1754306764456, "lastModificationDate": 1754306768254,
"contentHash": "2adca1150f58fc6f3d1f0a0f22ee7cca", "contentHash": "94ec15baf0d5df863f45b8f351937ec7",
"sourceName": "@openzeppelin/contracts/utils/math/SafeCast.sol", "sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
"solcConfig": { "solcConfig": {
"version": "0.8.20", "version": "0.8.20",
"settings": { "settings": {
@@ -633,158 +508,7 @@
"^0.8.20" "^0.8.20"
], ],
"artifacts": [ "artifacts": [
"SafeCast" "IERC20Permit"
]
},
"/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"
] ]
}, },
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/ShortStrings.sol": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/ShortStrings.sol": {
@@ -826,6 +550,43 @@
"ShortStrings" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol": {
"lastModificationDate": 1754306764465, "lastModificationDate": 1754306764465,
"contentHash": "86fd93657e4e27ff76c38699e9b9fcef", "contentHash": "86fd93657e4e27ff76c38699e9b9fcef",
@@ -983,6 +744,43 @@
"Math" "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol": {
"lastModificationDate": 1754306764456, "lastModificationDate": 1754306764456,
"contentHash": "ae3528afb8bdb0a7dcfba5b115ee8074", "contentHash": "ae3528afb8bdb0a7dcfba5b115ee8074",
@@ -1059,10 +857,175 @@
"Panic" "Panic"
] ]
}, },
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol": { "/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/Votes.sol": {
"lastModificationDate": 1754306768254, "lastModificationDate": 1754306764456,
"contentHash": "94ec15baf0d5df863f45b8f351937ec7", "contentHash": "95aceafdc639babdd22576e5e3774d64",
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol", "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": { "solcConfig": {
"version": "0.8.20", "version": "0.8.20",
"settings": { "settings": {
@@ -1093,7 +1056,44 @@
"^0.8.20" "^0.8.20"
], ],
"artifacts": [ "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": { "/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/FactoryDeployer.sol": {

View File

@@ -26,7 +26,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
struct DLEInfo { struct DLEInfo {
string name; string name;
string symbol; string symbol;
string tokenImage; // Картинка токена (base64 или URL)
string location; string location;
string coordinates; string coordinates;
uint256 jurisdiction; uint256 jurisdiction;
@@ -39,7 +38,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
struct DLEConfig { struct DLEConfig {
string name; string name;
string symbol; string symbol;
string tokenImage; // Картинка токена (base64 или URL)
string location; string location;
string coordinates; string coordinates;
uint256 jurisdiction; uint256 jurisdiction;
@@ -91,7 +89,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
event DLEInitialized( event DLEInitialized(
string name, string name,
string symbol, string symbol,
string tokenImage,
string location, string location,
string coordinates, string coordinates,
uint256 jurisdiction, uint256 jurisdiction,
@@ -112,7 +109,7 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId); event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId);
event ChainAdded(uint256 chainId); event ChainAdded(uint256 chainId);
event ChainRemoved(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 QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId); event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
@@ -129,7 +126,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
dleInfo = DLEInfo({ dleInfo = DLEInfo({
name: config.name, name: config.name,
symbol: config.symbol, symbol: config.symbol,
tokenImage: config.tokenImage,
location: config.location, location: config.location,
coordinates: config.coordinates, coordinates: config.coordinates,
jurisdiction: config.jurisdiction, jurisdiction: config.jurisdiction,
@@ -166,7 +162,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
emit DLEInitialized( emit DLEInitialized(
config.name, config.name,
config.symbol, config.symbol,
config.tokenImage,
config.location, config.location,
config.coordinates, config.coordinates,
config.jurisdiction, config.jurisdiction,
@@ -509,11 +504,11 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
// Декодируем операцию // Декодируем операцию
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes)); (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 // Операция обновления информации DLE
(string memory name, string memory symbol, string memory tokenImage, string memory location, string memory coordinates, (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, string, uint256, string[], uint256)); uint256 jurisdiction, string[] memory okvedCodes, uint256 kpp) = abi.decode(data, (string, string, string, string, uint256, string[], uint256));
_updateDLEInfo(name, symbol, tokenImage, location, coordinates, jurisdiction, okvedCodes, kpp); _updateDLEInfo(name, symbol, location, coordinates, jurisdiction, okvedCodes, kpp);
} else if (selector == bytes4(keccak256("updateQuorumPercentage(uint256)"))) { } else if (selector == bytes4(keccak256("updateQuorumPercentage(uint256)"))) {
// Операция обновления процента кворума // Операция обновления процента кворума
(uint256 newQuorumPercentage) = abi.decode(data, (uint256)); (uint256 newQuorumPercentage) = abi.decode(data, (uint256));
@@ -550,7 +545,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
* @dev Обновить информацию DLE * @dev Обновить информацию DLE
* @param _name Новое название * @param _name Новое название
* @param _symbol Новый символ * @param _symbol Новый символ
* @param _tokenImage Новая картинка токена
* @param _location Новое местонахождение * @param _location Новое местонахождение
* @param _coordinates Новые координаты * @param _coordinates Новые координаты
* @param _jurisdiction Новая юрисдикция * @param _jurisdiction Новая юрисдикция
@@ -560,7 +554,6 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
function _updateDLEInfo( function _updateDLEInfo(
string memory _name, string memory _name,
string memory _symbol, string memory _symbol,
string memory _tokenImage,
string memory _location, string memory _location,
string memory _coordinates, string memory _coordinates,
uint256 _jurisdiction, uint256 _jurisdiction,
@@ -575,14 +568,13 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
dleInfo.name = _name; dleInfo.name = _name;
dleInfo.symbol = _symbol; dleInfo.symbol = _symbol;
dleInfo.tokenImage = _tokenImage;
dleInfo.location = _location; dleInfo.location = _location;
dleInfo.coordinates = _coordinates; dleInfo.coordinates = _coordinates;
dleInfo.jurisdiction = _jurisdiction; dleInfo.jurisdiction = _jurisdiction;
dleInfo.okvedCodes = _okvedCodes; dleInfo.okvedCodes = _okvedCodes;
dleInfo.kpp = _kpp; dleInfo.kpp = _kpp;
emit DLEInfoUpdated(_name, _symbol, _tokenImage, _location, _coordinates, _jurisdiction, _okvedCodes, _kpp); emit DLEInfoUpdated(_name, _symbol, _location, _coordinates, _jurisdiction, _okvedCodes, _kpp);
} }
/** /**

View File

@@ -195,8 +195,12 @@ router.post('/verify', async (req, res) => {
return res.status(401).json({ success: false, error: 'Invalid signature' }); 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 userId;
let isAdmin = false; let isAdmin = adminStatus;
// Проверяем, авторизован ли пользователь уже // Проверяем, авторизован ли пользователь уже
if (req.session.authenticated && req.session.userId) { 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` `[verify] Wallet ${normalizedAddress} linked to user ${userId}: already exists`
); );
} else { } else {
// Находим или создаем пользователя, если не авторизован // Находим или создаем пользователя с уже известной ролью
const result = await authService.findOrCreateUser(address); const result = await authService.findOrCreateUser(address, adminStatus);
userId = result.userId; userId = result.userId;
isAdmin = result.isAdmin; 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); 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.userId = userId;
req.session.authenticated = true; req.session.authenticated = true;

View File

@@ -217,10 +217,11 @@ router.post('/get-proposals', async (req, res) => {
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения предложений (только читаемые поля) // ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [ 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 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)", "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)" "event ProposalCreated(uint256 proposalId, address initiator, string description)"
]; ];
@@ -250,8 +251,9 @@ router.post('/get-proposals', async (req, res) => {
while (retryCount < maxRetries) { while (retryCount < maxRetries) {
try { try {
proposal = await dle.proposals(proposalId); proposal = await dle.getProposalSummary(proposalId);
isPassed = await dle.checkProposalResult(proposalId); const result = await dle.checkProposalResult(proposalId);
isPassed = result.passed;
break; // Успешно прочитали break; // Успешно прочитали
} catch (error) { } catch (error) {
retryCount++; retryCount++;
@@ -264,19 +266,18 @@ router.post('/get-proposals', async (req, res) => {
} }
} }
// governanceChainId не сохраняется в предложении, используем текущую цепочку
const governanceChainId = 11155111; // Sepolia chain ID
console.log(`[Blockchain] Данные предложения ${proposalId}:`, { console.log(`[Blockchain] Данные предложения ${proposalId}:`, {
id: Number(proposal.id), id: Number(proposal.id),
description: proposal.description, description: proposal.description,
forVotes: Number(proposal.forVotes), forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes), againstVotes: Number(proposal.againstVotes),
executed: proposal.executed, executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline), deadline: Number(proposal.deadline),
initiator: proposal.initiator, initiator: proposal.initiator,
operation: proposal.operation, governanceChainId: Number(proposal.governanceChainId),
governanceChainId: Number(governanceChainId) snapshotTimepoint: Number(proposal.snapshotTimepoint),
targets: proposal.targets
}); });
const proposalInfo = { const proposalInfo = {
@@ -285,10 +286,12 @@ router.post('/get-proposals', async (req, res) => {
forVotes: Number(proposal.forVotes), forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes), againstVotes: Number(proposal.againstVotes),
executed: proposal.executed, executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline), deadline: Number(proposal.deadline),
initiator: proposal.initiator, initiator: proposal.initiator,
operation: proposal.operation, governanceChainId: Number(proposal.governanceChainId),
governanceChainId: Number(governanceChainId), snapshotTimepoint: Number(proposal.snapshotTimepoint),
targetChains: proposal.targets.map(chainId => Number(chainId)),
isPassed: isPassed, isPassed: isPassed,
blockNumber: events[i].blockNumber blockNumber: events[i].blockNumber
}; };

View File

@@ -39,7 +39,6 @@ async function main() {
const dleConfig = { const dleConfig = {
name: params.name, name: params.name,
symbol: params.symbol, symbol: params.symbol,
tokenImage: params.tokenImage || '',
location: params.location, location: params.location,
coordinates: params.coordinates, coordinates: params.coordinates,
jurisdiction: params.jurisdiction, jurisdiction: params.jurisdiction,

View File

@@ -58,9 +58,10 @@ class AuthService {
/** /**
* Находит или создает пользователя по адресу кошелька * Находит или создает пользователя по адресу кошелька
* @param {string} address - Адрес кошелька * @param {string} address - Адрес кошелька
* @param {boolean} isAdmin - Предварительно проверенный статус админа
* @returns {Promise<{userId: number, isAdmin: boolean}>} * @returns {Promise<{userId: number, isAdmin: boolean}>}
*/ */
async findOrCreateUser(address) { async findOrCreateUser(address, isAdmin = null) {
try { try {
// Нормализуем адрес - всегда приводим к нижнему регистру // Нормализуем адрес - всегда приводим к нижнему регистру
const normalizedAddress = ethers.getAddress(address).toLowerCase(); const normalizedAddress = ethers.getAddress(address).toLowerCase();
@@ -78,15 +79,15 @@ class AuthService {
} }
const userData = user[0]; 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]); 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)`); logger.info(`Updated user ${userData.id} role to user (admin tokens no longer present)`);
return { userId: userData.id, isAdmin: false }; 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]); 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)`); logger.info(`Updated user ${userData.id} role to admin (admin tokens found)`);
return { userId: userData.id, isAdmin: true }; 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; const userId = newUserResult.rows[0].id;
// Добавляем идентификатор кошелька (всегда в нижнем регистре) // Добавляем идентификатор кошелька (всегда в нижнем регистре)
@@ -109,21 +113,11 @@ class AuthService {
provider_id: normalizedAddress provider_id: normalizedAddress
}); });
// Проверяем, есть ли у пользователя роль админа logger.info(`New user ${userId} created with role: ${initialRole} for wallet ${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`
);
}
broadcastContactsUpdate(); broadcastContactsUpdate();
return { userId, isAdmin }; return { userId, isAdmin: adminStatus };
} catch (error) { } catch (error) {
logger.error('Error finding or creating user:', error); logger.error('Error finding or creating user:', error);
throw error; throw error;

View File

@@ -225,18 +225,7 @@ class DLEV2Service {
throw new Error('Должна быть выбрана хотя бы одна сеть для деплоя'); 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');
}
}
} }
/** /**

View File

@@ -11,6 +11,7 @@
*/ */
const WebSocket = require('ws'); const WebSocket = require('ws');
const authService = require('./services/auth-service');
let wss = null; let wss = null;
// Храним клиентов по userId для персонализированных уведомлений // Храним клиентов по userId для персонализированных уведомлений
@@ -57,6 +58,11 @@ function initWSS(server) {
timestamp: data.timestamp timestamp: data.timestamp
})); }));
} }
if (data.type === 'request_token_balances' && data.address) {
// Запрос балансов токенов
handleTokenBalancesRequest(ws, data.address, data.userId);
}
} catch (error) { } catch (error) {
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error); // console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
} }
@@ -402,6 +408,49 @@ function broadcastAuthTokenUpdated(tokenData) {
broadcastToAllClients(message); 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 = { module.exports = {
initWSS, initWSS,
broadcastContactsUpdate, broadcastContactsUpdate,
@@ -418,6 +467,42 @@ module.exports = {
broadcastAuthTokenAdded, broadcastAuthTokenAdded,
broadcastAuthTokenDeleted, broadcastAuthTokenDeleted,
broadcastAuthTokenUpdated, broadcastAuthTokenUpdated,
broadcastTokenBalancesUpdate,
broadcastTokenBalanceChanged,
getConnectedUsers, getConnectedUsers,
getStats 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()
}
}));
}
}

View File

@@ -23,9 +23,18 @@
:identities="auth.identities.value" :identities="auth.identities.value"
:tokenBalances="tokenBalances" :tokenBalances="tokenBalances"
:isLoadingTokens="isLoadingTokens" :isLoadingTokens="isLoadingTokens"
:formattedLastUpdate="formattedLastUpdate"
@auth-action-completed="handleAuthActionCompleted" @auth-action-completed="handleAuthActionCompleted"
/> />
</RouterView> </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> </div>
</template> </template>
@@ -33,7 +42,7 @@
import { ref, watch, onMounted, computed, onUnmounted } from 'vue'; import { ref, watch, onMounted, computed, onUnmounted } from 'vue';
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
import { useAuth, provideAuth } from './composables/useAuth'; import { useAuth, provideAuth } from './composables/useAuth';
import { fetchTokenBalances } from './services/tokens'; import { useTokenBalancesWebSocket } from './composables/useTokenBalancesWebSocket';
import eventBus from './utils/eventBus'; import eventBus from './utils/eventBus';
import wsClient from './utils/websocket'; import wsClient from './utils/websocket';
@@ -46,12 +55,26 @@
// Состояние загрузки // Состояние загрузки
const isLoading = ref(false); const isLoading = ref(false);
// Проверка наличия MetaMask
const isMetaMaskAvailable = ref(false);
// Использование composable для аутентификации // Использование composable для аутентификации
const auth = useAuth(); const auth = useAuth();
// --- Логика загрузки баланса токенов --- // --- Логика загрузки баланса токенов через WebSocket ---
const tokenBalances = ref({}); // Предоставляем auth контекст
const isLoadingTokens = ref(false); provideAuth();
// Инициализируем WebSocket composable
const {
tokenBalances,
isLoadingTokens,
lastUpdateTime,
formattedLastUpdate,
requestTokenBalances,
startAutoUpdate,
stopAutoUpdate
} = useTokenBalancesWebSocket();
const identities = computed(() => auth.identities.value); const identities = computed(() => auth.identities.value);
@@ -66,60 +89,57 @@
return identity ? identity.provider_id : null; return identity ? identity.provider_id : null;
}; };
const refreshTokenBalances = async () => { const refreshTokenBalances = () => {
if (!hasIdentityType('wallet') || !auth.isAuthenticated.value) { if (!hasIdentityType('wallet') || !auth.isAuthenticated.value) {
tokenBalances.value = {}; // Очищаем, если нет кошелька или не авторизован console.log('[App] Нет кошелька или не авторизован');
return; return;
} }
isLoadingTokens.value = true;
try {
const walletAddress = getIdentityValue('wallet'); const walletAddress = getIdentityValue('wallet');
// console.log('[App] Обновление балансов для адреса:', walletAddress); console.log('[App] Запрашиваем обновление балансов через WebSocket для:', walletAddress, 'userId:', auth.userId.value);
requestTokenBalances(walletAddress, auth.userId.value);
const balances = await fetchTokenBalances(walletAddress);
// console.log('[App] Полученные балансы:', balances);
tokenBalances.value = balances || {};
} catch (error) {
// console.error('[App] Ошибка при получении балансов:', error);
tokenBalances.value = {};
} finally {
isLoadingTokens.value = false;
}
}; };
// Следим за изменениями в идентификаторах // Следим за изменениями в идентификаторах
watch(identities, (newIdentities, oldIdentities) => { watch(identities, (newIdentities, oldIdentities) => {
console.log('[App] identities changed:', { newIdentities, oldIdentities });
if (auth.isAuthenticated.value) { if (auth.isAuthenticated.value) {
const newWalletId = getIdentityValue('wallet'); const newWalletId = getIdentityValue('wallet');
const oldWalletIdentity = oldIdentities ? oldIdentities.find(id => id.provider === 'wallet') : null; const oldWalletIdentity = oldIdentities ? oldIdentities.find(id => id.provider === 'wallet') : null;
const oldWalletId = oldWalletIdentity ? oldWalletIdentity.provider_id : null; const oldWalletId = oldWalletIdentity ? oldWalletIdentity.provider_id : null;
console.log('[App] wallet IDs comparison:', { newWalletId, oldWalletId });
if (newWalletId !== oldWalletId) { if (newWalletId !== oldWalletId) {
// console.log('[App] Обнаружено изменение идентификатора кошелька, обновляем балансы'); console.log('[App] Обнаружено изменение идентификатора кошелька, обновляем балансы');
refreshTokenBalances(); if (newWalletId) {
} else if (hasIdentityType('wallet') && Object.keys(tokenBalances.value).length === 0 && !isLoadingTokens.value) { startAutoUpdate(newWalletId, auth.userId.value);
// Если кошелек есть, но баланс пустой и не грузится - пробуем загрузить } else {
// console.log('[App] Кошелек есть, но баланс пуст, пытаемся загрузить.'); stopAutoUpdate();
refreshTokenBalances(); }
} }
} }
}, { deep: true }); }, { deep: true });
// Мониторинг изменений состояния аутентификации // Мониторинг изменений состояния аутентификации
watch(auth.isAuthenticated, (isAuth) => { watch(auth.isAuthenticated, (isAuth) => {
// console.log('[App] Состояние аутентификации изменилось:', isAuth); console.log('[App] Состояние аутентификации изменилось:', isAuth);
if (isAuth) { if (isAuth) {
// Убираем задержку, полагаемся на watch(identities) или прямо вызываем
// setTimeout(refreshTokenBalances, 500);
refreshTokenBalances(); // Вызываем сразу, если нужно обновить при смене auth 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 () => { const handleAuthActionCompleted = async () => {
// console.log('[App] Auth action completed, triggering updates...'); // console.log('[App] Auth action completed, triggering updates...');
@@ -129,8 +149,8 @@
await auth.checkAuth(); await auth.checkAuth();
// console.log('[App] auth.checkAuth() completed. isAuthenticated:', auth.isAuthenticated.value); // console.log('[App] auth.checkAuth() completed. isAuthenticated:', auth.isAuthenticated.value);
// 2. Обновляем баланс (использует обновленные identities) // 2. Обновляем баланс через WebSocket
await refreshTokenBalances(); refreshTokenBalances();
// console.log('[App] refreshTokenBalances() completed.'); // console.log('[App] refreshTokenBalances() completed.');
// 3. Явно оповещаем компоненты об изменении состояния авторизации // 3. Явно оповещаем компоненты об изменении состояния авторизации
@@ -152,7 +172,13 @@
// Первичная загрузка баланса при монтировании, если пользователь уже авторизован // Первичная загрузка баланса при монтировании, если пользователь уже авторизован
onMounted(() => { onMounted(() => {
console.log('[App] onMounted - auth.isAuthenticated:', auth.isAuthenticated.value);
console.log('[App] onMounted - identities:', auth.identities.value);
// Проверяем наличие MetaMask
checkMetaMaskAvailability();
if (auth.isAuthenticated.value) { if (auth.isAuthenticated.value) {
console.log('[App] onMounted - вызываем refreshTokenBalances');
refreshTokenBalances(); refreshTokenBalances();
} }
@@ -164,27 +190,7 @@
} }
}); });
// Подписываемся на WebSocket события для токенов // WebSocket события для токенов обрабатываются в useTokenBalancesWebSocket
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();
}
});
// Отписываемся при размонтировании компонента // Отписываемся при размонтировании компонента
onUnmounted(() => { onUnmounted(() => {
@@ -193,8 +199,6 @@
} }
}); });
}); });
provideAuth();
</script> </script>
<style> <style>

View File

@@ -85,6 +85,13 @@ provide('identities', computed(() => props.identities));
provide('tokenBalances', computed(() => props.tokenBalances)); provide('tokenBalances', computed(() => props.tokenBalances));
provide('isLoadingTokens', computed(() => props.isLoadingTokens)); provide('isLoadingTokens', computed(() => props.isLoadingTokens));
// Отладочная информация
console.log('[BaseLayout] Props received:', {
isAuthenticated: props.isAuthenticated,
tokenBalances: props.tokenBalances,
isLoadingTokens: props.isLoadingTokens
});
// Callback после успешной аутентификации/привязки через Email/Telegram // Callback после успешной аутентификации/привязки через Email/Telegram
const handleAuthFlowSuccess = (authType) => { const handleAuthFlowSuccess = (authType) => {
// console.log(`[BaseLayout] Auth flow success: ${authType}`); // console.log(`[BaseLayout] Auth flow success: ${authType}`);
@@ -151,7 +158,21 @@ const handleWalletAuth = async () => {
} }
} catch (error) { } catch (error) {
// console.error('[BaseLayout] Ошибка при подключении кошелька:', 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 { } finally {
isConnectingWallet.value = false; isConnectingWallet.value = false;
} }

View File

@@ -117,12 +117,14 @@
Загрузка балансов... Загрузка балансов...
</div> </div>
<div v-else-if="!tokenBalances || tokenBalances.length === 0" class="token-no-data"> <div v-else-if="!tokenBalances || tokenBalances.length === 0" class="token-no-data">
Баланс не доступен Баланс не доступен (tokenBalances: {{ tokenBalances }}, length: {{ tokenBalances?.length }})
</div> </div>
<div v-else> <div v-else>
<div class="token-balance-header"> <div class="token-balance-header">
<small class="last-update">Обновлено: {{ formattedLastUpdate }}</small>
<small class="debug-info">Debug: {{ tokenBalances.length }} токенов</small>
</div> </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-name">{{ token.tokenName }}</span>
<span class="token-network">{{ token.network }}</span> <span class="token-network">{{ token.network }}</span>
<span class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span> <span class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span>
@@ -172,9 +174,10 @@ const props = defineProps({
isAuthenticated: Boolean, isAuthenticated: Boolean,
telegramAuth: Object, telegramAuth: Object,
emailAuth: Object, emailAuth: Object,
tokenBalances: Object, tokenBalances: Array,
identities: 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']); 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
watch(() => props.tokenBalances, (newVal, oldVal) => { 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 }); }, { deep: true });
watch(() => props.isLoadingTokens, (newVal, oldVal) => { 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> </script>

View File

@@ -67,7 +67,21 @@
} }
} catch (err) { } catch (err) {
// console.error('Error connecting wallet:', 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 { } finally {
isLoading.value = false; isLoading.value = false;
} }

View 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
};
}

View File

@@ -43,5 +43,27 @@ app.use(ElementPlus);
// console.log('API URL:', axios.defaults.baseURL); // console.log('API URL:', axios.defaults.baseURL);
// console.log('main.js: Starting application with router'); // 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'); app.mount('#app');
// console.log('main.js: Application with router mounted'); // console.log('main.js: Application with router mounted');

View File

@@ -108,6 +108,30 @@ export async function connectWithWallet() {
return verificationResponse.data; return verificationResponse.data;
} catch (error) { } catch (error) {
// console.error('Error connecting wallet:', 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
};
} }
} }

View File

@@ -25,7 +25,21 @@ export async function checkWalletConnection() {
}; };
} catch (error) { } catch (error) {
console.error('Ошибка подключения к кошельку:', 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);
} }
} }

View File

@@ -151,8 +151,16 @@ export const connectWallet = async () => {
// Формируем понятное сообщение об ошибке // Формируем понятное сообщение об ошибке
let errorMessage = 'Произошла ошибка при подключении кошелька.'; 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.'; 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) { } else if (error.response && error.response.data && error.response.data.error) {
errorMessage = error.response.data.error; errorMessage = error.response.data.error;
} else if (error.message) { } else if (error.message) {

View File

@@ -260,7 +260,7 @@
<h5>Добавленные коды ОКВЭД:</h5> <h5>Добавленные коды ОКВЭД:</h5>
<ul class="codes-list"> <ul class="codes-list">
<li v-for="(code, index) in dleSettings.selectedOkved" :key="index" class="code-item"> <li v-for="(code, index) in dleSettings.selectedOkved" :key="index" class="code-item">
<span>{{ getOkvedTitle(code) }}</span> <span>{{ code }}</span>
<button <button
type="button" type="button"
class="btn btn-danger btn-sm" class="btn btn-danger btn-sm"
@@ -325,37 +325,7 @@
<small class="form-help">3-10 символов для токена управления (Governance Token)</small> <small class="form-help">3-10 символов для токена управления (Governance Token)</small>
</div> </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,6 +349,7 @@
<div class="form-row"> <div class="form-row">
<div class="form-group flex-grow"> <div class="form-group flex-grow">
<label class="form-label">Адрес кошелька:</label> <label class="form-label">Адрес кошелька:</label>
<div class="address-input-group">
<input <input
type="text" type="text"
v-model="partner.address" v-model="partner.address"
@@ -386,6 +357,16 @@
placeholder="0x..." placeholder="0x..."
@input="validateEthereumAddress(partner, index)" @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>
<div class="form-group"> <div class="form-group">
<label class="form-label">Количество токенов:</label> <label class="form-label">Количество токенов:</label>
@@ -688,12 +669,7 @@
<strong>🪙 Токен:</strong> {{ dleSettings.tokenSymbol }} <strong>🪙 Токен:</strong> {{ dleSettings.tokenSymbol }}
</div> </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> </div>
@@ -928,7 +904,6 @@ const dleSettings = reactive({
selectedOkved: [], // ОКВЭД - дополнительные коды деятельности selectedOkved: [], // ОКВЭД - дополнительные коды деятельности
name: '', // Имя DLE name: '', // Имя DLE
tokenSymbol: '', // Символ токена tokenSymbol: '', // Символ токена
tokenImage: '', // Картинка токена (base64 или URL)
partners: [{ address: '', amount: 1 }], // Партнеры и их доли токенов partners: [{ address: '', amount: 1 }], // Партнеры и их доли токенов
governanceQuorum: 51, // Кворум для принятия решений (%) governanceQuorum: 51, // Кворум для принятия решений (%)
@@ -1460,7 +1435,7 @@ const loadFormData = () => {
selectedOkved: parsedData.selectedOkved || [], selectedOkved: parsedData.selectedOkved || [],
name: parsedData.name || '', name: parsedData.name || '',
tokenSymbol: parsedData.tokenSymbol || '', tokenSymbol: parsedData.tokenSymbol || '',
tokenImage: parsedData.tokenImage || '',
partners: parsedData.partners || [{ address: '', amount: 1 }], partners: parsedData.partners || [{ address: '', amount: 1 }],
governanceQuorum: parsedData.governanceQuorum || 51, governanceQuorum: parsedData.governanceQuorum || 51,
// Координаты // Координаты
@@ -1541,7 +1516,7 @@ const clearAllData = () => {
dleSettings.selectedOkved = []; dleSettings.selectedOkved = [];
dleSettings.name = ''; dleSettings.name = '';
dleSettings.tokenSymbol = ''; dleSettings.tokenSymbol = '';
dleSettings.tokenImage = ''; // Очищаем картинку токена
dleSettings.partners = [{ address: '', amount: 1 }]; // Сброс к одному пустому партнеру dleSettings.partners = [{ address: '', amount: 1 }]; // Сброс к одному пустому партнеру
dleSettings.governanceQuorum = 51; // Сброс кворума к значению по умолчанию 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(); loadRussianClassifiers();
} }
// Автозаполнение первого партнера подключенным кошельком (если данные не были загружены) // Автозаполнение первого партнера подключенным кошельком
if (!dataLoaded && address.value && dleSettings.partners[0] && !dleSettings.partners[0].address) { if (address.value && dleSettings.partners[0]) {
// Если адрес пустой или это новый пользователь, подставляем адрес кошелька
if (!dleSettings.partners[0].address || !dataLoaded) {
dleSettings.partners[0].address = address.value; dleSettings.partners[0].address = address.value;
console.log('Автоматически подставлен адрес кошелька:', address.value);
}
} }
// Добавляем слушатель события видимости страницы для обновления списка сетей // Добавляем слушатель события видимости страницы для обновления списка сетей
@@ -2417,8 +2315,12 @@ onUnmounted(() => {
// Watcher для автоматического обновления адреса первого партнера при подключении кошелька // Watcher для автоматического обновления адреса первого партнера при подключении кошелька
watch(address, (newAddress) => { watch(address, (newAddress) => {
if (newAddress && dleSettings.partners[0] && !dleSettings.partners[0].address) { if (newAddress && dleSettings.partners[0]) {
// Подставляем адрес, если поле пустое или пользователь только что подключил кошелек
if (!dleSettings.partners[0].address) {
dleSettings.partners[0].address = newAddress; 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(() => { const maskedPrivateKey = computed(() => {
if (!dleSettings.privateKey) return ''; if (!dleSettings.privateKey) return '';
@@ -2498,7 +2410,7 @@ const deploySmartContracts = async () => {
// Основная информация DLE // Основная информация DLE
name: dleSettings.name, name: dleSettings.name,
symbol: dleSettings.tokenSymbol, symbol: dleSettings.tokenSymbol,
tokenImage: dleSettings.tokenImage, // Картинка токена
location: dleSettings.addressData.fullAddress || 'Не указан', location: dleSettings.addressData.fullAddress || 'Не указан',
coordinates: dleSettings.coordinates || '0,0', coordinates: dleSettings.coordinates || '0,0',
jurisdiction: parseInt(dleSettings.jurisdiction) || 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); 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 { .input-icon-wrapper {
position: relative; position: relative;
} }

View File

@@ -44,6 +44,7 @@
<option value="succeeded">Принятые</option> <option value="succeeded">Принятые</option>
<option value="defeated">Отклоненные</option> <option value="defeated">Отклоненные</option>
<option value="executed">Выполненные</option> <option value="executed">Выполненные</option>
<option value="canceled">Отмененные</option>
</select> </select>
<button <button
class="btn btn-sm btn-outline-secondary" class="btn btn-sm btn-outline-secondary"
@@ -660,7 +661,7 @@ async function loadDleData() {
const transformedProposal = { const transformedProposal = {
...proposal, ...proposal,
status: getProposalStatus(proposal), status: getProposalStatus(proposal),
deadline: proposal.deadline || (proposal.startTime + proposal.duration) deadline: proposal.deadline || 0
}; };
console.log('[Frontend] Преобразованное предложение:', transformedProposal); console.log('[Frontend] Преобразованное предложение:', transformedProposal);
return transformedProposal; return transformedProposal;
@@ -776,11 +777,28 @@ function getProposalStatus(proposal) {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const deadline = proposal.deadline || 0; const deadline = proposal.deadline || 0;
// Проверяем отменено ли предложение
if (proposal.canceled) {
return 'canceled';
}
// Проверяем выполнено ли предложение
if (proposal.executed) { if (proposal.executed) {
return '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 quorumPercentage = getQuorumPercentage(proposal);
const requiredQuorum = getRequiredQuorum(); const requiredQuorum = getRequiredQuorum();
const hasReachedQuorum = quorumPercentage >= requiredQuorum; const hasReachedQuorum = quorumPercentage >= requiredQuorum;
@@ -793,7 +811,8 @@ function getProposalStatus(proposal) {
deadlinePassed: deadline > 0 && now >= deadline, deadlinePassed: deadline > 0 && now >= deadline,
quorumPercentage, quorumPercentage,
requiredQuorum, requiredQuorum,
hasReachedQuorum hasReachedQuorum,
isPassed: proposal.isPassed
}); });
// Если кворум достигнут, предложение можно выполнить // Если кворум достигнут, предложение можно выполнить
@@ -815,7 +834,8 @@ function getProposalStatusText(status) {
'active': 'Активно', 'active': 'Активно',
'succeeded': 'Принято', 'succeeded': 'Принято',
'defeated': 'Отклонено', 'defeated': 'Отклонено',
'executed': 'Выполнено' 'executed': 'Выполнено',
'canceled': 'Отменено'
}; };
return statusMap[status] || status; return statusMap[status] || status;
} }
@@ -1719,11 +1739,21 @@ onUnmounted(() => {
color: #0c5460; color: #0c5460;
} }
.proposal-status.executed {
background: #d4edda;
color: #155724;
}
.proposal-status.defeated { .proposal-status.defeated {
background: #f8d7da; background: #f8d7da;
color: #721c24; color: #721c24;
} }
.proposal-status.canceled {
background: #fff3cd;
color: #856404;
}
.proposal-details { .proposal-details {
margin-bottom: 1rem; margin-bottom: 1rem;
} }