diff --git a/infrastructure/blindvote/package.json b/infrastructure/blindvote/package.json index e6ad88d04..90c5007f5 100644 --- a/infrastructure/blindvote/package.json +++ b/infrastructure/blindvote/package.json @@ -21,6 +21,7 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/eventsource": "^1.1.15", "@types/jest": "^29.0.0", "@types/node": "^20.0.0", "jest": "^29.0.0", diff --git a/infrastructure/blindvote/pnpm-lock.yaml b/infrastructure/blindvote/pnpm-lock.yaml deleted file mode 100644 index 3f18c7833..000000000 --- a/infrastructure/blindvote/pnpm-lock.yaml +++ /dev/null @@ -1,3563 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@noble/curves': - specifier: ^1.9.7 - version: 1.9.7 - big-integer: - specifier: ^1.6.51 - version: 1.6.52 - circomlibjs: - specifier: ^0.1.7 - version: 0.1.7 - ffjavascript: - specifier: ^0.2.55 - version: 0.2.63 - noble-bls12-381: - specifier: ^0.7.0 - version: 0.7.2 - noble-hashes: - specifier: ^0.3.1 - version: 0.3.1 - noble-secp256k1: - specifier: ^1.2.14 - version: 1.2.14 - snarkjs: - specifier: ^0.7.0 - version: 0.7.5 - devDependencies: - '@types/jest': - specifier: ^29.0.0 - version: 29.5.14 - '@types/node': - specifier: ^20.0.0 - version: 20.19.9 - jest: - specifier: ^29.0.0 - version: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - ts-jest: - specifier: ^29.0.0 - version: 29.4.0(@babel/core@7.28.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)))(typescript@5.8.3) - ts-node: - specifier: ^10.9.0 - version: 10.9.2(@types/node@20.19.9)(typescript@5.8.3) - typescript: - specifier: ^5.0.0 - version: 5.8.3 - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@ethersproject/abi@5.8.0': - resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - - '@ethersproject/abstract-provider@5.8.0': - resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - - '@ethersproject/abstract-signer@5.8.0': - resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - - '@ethersproject/address@5.8.0': - resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - - '@ethersproject/base64@5.8.0': - resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - - '@ethersproject/basex@5.8.0': - resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} - - '@ethersproject/bignumber@5.8.0': - resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - - '@ethersproject/bytes@5.8.0': - resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - - '@ethersproject/constants@5.8.0': - resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - - '@ethersproject/contracts@5.8.0': - resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} - - '@ethersproject/hash@5.8.0': - resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - - '@ethersproject/hdnode@5.8.0': - resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} - - '@ethersproject/json-wallets@5.8.0': - resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} - - '@ethersproject/keccak256@5.8.0': - resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - - '@ethersproject/logger@5.8.0': - resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - - '@ethersproject/networks@5.8.0': - resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - - '@ethersproject/pbkdf2@5.8.0': - resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} - - '@ethersproject/properties@5.8.0': - resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - - '@ethersproject/providers@5.8.0': - resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} - - '@ethersproject/random@5.8.0': - resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} - - '@ethersproject/rlp@5.8.0': - resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - - '@ethersproject/sha2@5.8.0': - resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} - - '@ethersproject/signing-key@5.8.0': - resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - - '@ethersproject/solidity@5.8.0': - resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} - - '@ethersproject/strings@5.8.0': - resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - - '@ethersproject/transactions@5.8.0': - resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - - '@ethersproject/units@5.8.0': - resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} - - '@ethersproject/wallet@5.8.0': - resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} - - '@ethersproject/web@5.8.0': - resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - - '@ethersproject/wordlists@5.8.0': - resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} - - '@iden3/bigarray@0.0.2': - resolution: {integrity: sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==} - - '@iden3/binfileutils@0.0.12': - resolution: {integrity: sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==} - - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.12': - resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - - '@jridgewell/trace-mapping@0.3.29': - resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.7': - resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} - - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - - '@types/jest@29.5.14': - resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - - '@types/node@20.19.9': - resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - aes-js@3.0.0: - resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - - b4a@1.6.7: - resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} - - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - babel-preset-current-node-syntax@1.1.1: - resolution: {integrity: sha512-23fWKohMTvS5s0wwJKycOe0dBdCwQ6+iiLaNR9zy8P13mtFRFM9qLLX6HJX5DL2pi/FNDf3fCQHM4FIMoHH/7w==} - peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - - bfj@7.1.0: - resolution: {integrity: sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==} - engines: {node: '>= 8.0.0'} - - big-integer@1.6.52: - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} - engines: {node: '>=0.6'} - - blake-hash@2.0.0: - resolution: {integrity: sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==} - engines: {node: '>= 10'} - - blake2b-wasm@2.4.0: - resolution: {integrity: sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==} - - blake2b@2.1.4: - resolution: {integrity: sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==} - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - - bn.js@4.12.2: - resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} - - bn.js@5.2.2: - resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - browserslist@4.25.1: - resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001727: - resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - check-types@11.2.3: - resolution: {integrity: sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - circom_runtime@0.1.28: - resolution: {integrity: sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==} - hasBin: true - - circomlibjs@0.1.7: - resolution: {integrity: sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==} - - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - dedent@1.6.0: - resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - - electron-to-chromium@1.5.192: - resolution: {integrity: sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==} - - elliptic@6.6.1: - resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escodegen@1.14.3: - resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} - engines: {node: '>=4.0'} - hasBin: true - - esprima@1.2.2: - resolution: {integrity: sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==} - engines: {node: '>=0.4.0'} - hasBin: true - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - ethers@5.8.0: - resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastfile@0.0.20: - resolution: {integrity: sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - ffjavascript@0.2.63: - resolution: {integrity: sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==} - - ffjavascript@0.3.0: - resolution: {integrity: sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==} - - ffjavascript@0.3.1: - resolution: {integrity: sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==} - - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - - hoopy@0.1.4: - resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} - engines: {node: '>= 6.0.0'} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - - jake@10.9.2: - resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} - engines: {node: '>=10'} - hasBin: true - - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonpath@1.1.1: - resolution: {integrity: sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.3.0: - resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} - engines: {node: '>= 0.8.0'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - logplease@1.2.15: - resolution: {integrity: sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoassert@2.0.0: - resolution: {integrity: sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - noble-bls12-381@0.7.2: - resolution: {integrity: sha512-Z5isbU6opuWPL3dxsGqO5BdOE8WP1XUM7HFIn/xeE5pATTnml/PEIy4MFQQrktHiitkuJdsCDtzEOnS9eIpC3Q==} - deprecated: Switch to @noble/curves for security updates - - noble-hashes@0.3.1: - resolution: {integrity: sha512-TpYvlZvM8nGB582H9qQdTCLTNPS4TX9r5gkB4iiCWlO/URrdFJKAKwzwwEcNYPhLrcmCvBF1Nfm25GMbFWEplw==} - deprecated: Switch to namespaced @noble/hashes for security and feature updates - - noble-secp256k1@1.2.14: - resolution: {integrity: sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==} - deprecated: Switch to namespaced @noble/secp256k1 for security and feature updates - - node-addon-api@3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.8.3: - resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} - engines: {node: '>= 0.8.0'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - prelude-ls@1.1.2: - resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} - engines: {node: '>= 0.8.0'} - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - r1csfile@0.0.48: - resolution: {integrity: sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - snarkjs@0.7.5: - resolution: {integrity: sha512-h+3c4rXZKLhLuHk4LHydZCk/h5GcNvk5GjVKRRkHmfb6Ntf8gHOA9zea3g656iclRuhqQ3iKDWFgiD9ypLrKiA==} - hasBin: true - - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - static-eval@2.0.2: - resolution: {integrity: sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tryer@1.0.1: - resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==} - - ts-jest@29.4.0: - resolution: {integrity: sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 || ^30.0.0 - '@jest/types': ^29.0.0 || ^30.0.0 - babel-jest: ^29.0.0 || ^30.0.0 - esbuild: '*' - jest: ^29.0.0 || ^30.0.0 - jest-util: ^29.0.0 || ^30.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - type-check@0.3.2: - resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - underscore@1.12.1: - resolution: {integrity: sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - wasmbuilder@0.0.16: - resolution: {integrity: sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==} - - wasmcurves@0.2.2: - resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} - - web-worker@1.2.0: - resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.0': {} - - '@babel/core@7.28.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 - convert-source-map: 2.0.0 - debug: 4.4.1 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.0': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.2': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - - '@babel/parser@7.28.0': - dependencies: - '@babel/types': 7.28.2 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - - '@babel/traverse@7.28.0': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.2': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@bcoe/v8-coverage@0.2.3': {} - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@ethersproject/abi@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/abstract-provider@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - - '@ethersproject/abstract-signer@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/address@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/rlp': 5.8.0 - - '@ethersproject/base64@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - - '@ethersproject/basex@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/bignumber@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - bn.js: 5.2.2 - - '@ethersproject/bytes@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/constants@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - - '@ethersproject/contracts@5.8.0': - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - - '@ethersproject/hash@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/hdnode@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/basex': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/pbkdf2': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/sha2': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/wordlists': 5.8.0 - - '@ethersproject/json-wallets@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/hdnode': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/pbkdf2': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/random': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@ethersproject/transactions': 5.8.0 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - - '@ethersproject/keccak256@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - js-sha3: 0.8.0 - - '@ethersproject/logger@5.8.0': {} - - '@ethersproject/networks@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/pbkdf2@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/sha2': 5.8.0 - - '@ethersproject/properties@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/providers@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/basex': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/random': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/sha2': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - bech32: 1.1.4 - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@ethersproject/random@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/rlp@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/sha2@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - hash.js: 1.1.7 - - '@ethersproject/signing-key@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - bn.js: 5.2.2 - elliptic: 6.6.1 - hash.js: 1.1.7 - - '@ethersproject/solidity@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/sha2': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/strings@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/transactions@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - - '@ethersproject/units@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/wallet@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/hdnode': 5.8.0 - '@ethersproject/json-wallets': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/random': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/wordlists': 5.8.0 - - '@ethersproject/web@5.8.0': - dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/wordlists@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@iden3/bigarray@0.0.2': {} - - '@iden3/binfileutils@0.0.12': - dependencies: - fastfile: 0.0.20 - ffjavascript: 0.3.1 - - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.3': {} - - '@jest/console@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - jest-mock: 29.7.0 - - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 - - '@jest/expect@29.7.0': - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.9 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - '@jest/globals@29.7.0': - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/reporters@29.7.0': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.29 - '@types/node': 20.19.9 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jest/source-map@29.6.3': - dependencies: - '@jridgewell/trace-mapping': 0.3.29 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - '@jest/test-result@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 - - '@jest/test-sequencer@29.7.0': - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 - - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.28.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.29 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - '@jest/types@29.6.3': - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.9 - '@types/yargs': 17.0.33 - chalk: 4.1.2 - - '@jridgewell/gen-mapping@0.3.12': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.4 - '@jridgewell/trace-mapping': 0.3.29 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.4': {} - - '@jridgewell/trace-mapping@0.3.29': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 - - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.8.0': {} - - '@sinclair/typebox@0.27.8': {} - - '@sinonjs/commons@3.0.1': - dependencies: - type-detect: 4.0.8 - - '@sinonjs/fake-timers@10.3.0': - dependencies: - '@sinonjs/commons': 3.0.1 - - '@tsconfig/node10@1.0.11': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.7 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.28.2 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - - '@types/babel__traverse@7.20.7': - dependencies: - '@babel/types': 7.28.2 - - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 20.19.9 - - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@3.0.4': - dependencies: - '@types/istanbul-lib-report': 3.0.3 - - '@types/jest@29.5.14': - dependencies: - expect: 29.7.0 - pretty-format: 29.7.0 - - '@types/node@20.19.9': - dependencies: - undici-types: 6.21.0 - - '@types/stack-utils@2.0.3': {} - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.33': - dependencies: - '@types/yargs-parser': 21.0.3 - - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - - aes-js@3.0.0: {} - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-regex@5.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - arg@4.1.3: {} - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - async@3.2.6: {} - - b4a@1.6.7: {} - - babel-jest@29.7.0(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.0) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.7 - - babel-preset-current-node-syntax@1.1.1(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0) - - babel-preset-jest@29.6.3(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.1(@babel/core@7.28.0) - - balanced-match@1.0.2: {} - - bech32@1.1.4: {} - - bfj@7.1.0: - dependencies: - bluebird: 3.7.2 - check-types: 11.2.3 - hoopy: 0.1.4 - jsonpath: 1.1.1 - tryer: 1.0.1 - - big-integer@1.6.52: {} - - blake-hash@2.0.0: - dependencies: - node-addon-api: 3.2.1 - node-gyp-build: 4.8.4 - readable-stream: 3.6.2 - - blake2b-wasm@2.4.0: - dependencies: - b4a: 1.6.7 - nanoassert: 2.0.0 - - blake2b@2.1.4: - dependencies: - blake2b-wasm: 2.4.0 - nanoassert: 2.0.0 - - bluebird@3.7.2: {} - - bn.js@4.12.2: {} - - bn.js@5.2.2: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - brorand@1.1.0: {} - - browserslist@4.25.1: - dependencies: - caniuse-lite: 1.0.30001727 - electron-to-chromium: 1.5.192 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.1) - - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - - buffer-from@1.1.2: {} - - callsites@3.1.0: {} - - camelcase@5.3.1: {} - - camelcase@6.3.0: {} - - caniuse-lite@1.0.30001727: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - char-regex@1.0.2: {} - - check-types@11.2.3: {} - - ci-info@3.9.0: {} - - circom_runtime@0.1.28: - dependencies: - ffjavascript: 0.3.1 - - circomlibjs@0.1.7: - dependencies: - blake-hash: 2.0.0 - blake2b: 2.1.4 - ethers: 5.8.0 - ffjavascript: 0.2.63 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - cjs-module-lexer@1.4.3: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - co@4.6.0: {} - - collect-v8-coverage@1.0.2: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - - convert-source-map@2.0.0: {} - - create-jest@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-require@1.1.1: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - dedent@1.6.0: {} - - deep-is@0.1.4: {} - - deepmerge@4.3.1: {} - - detect-newline@3.1.0: {} - - diff-sequences@29.6.3: {} - - diff@4.0.2: {} - - ejs@3.1.10: - dependencies: - jake: 10.9.2 - - electron-to-chromium@1.5.192: {} - - elliptic@6.6.1: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - emittery@0.13.1: {} - - emoji-regex@8.0.0: {} - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - - escalade@3.2.0: {} - - escape-string-regexp@2.0.0: {} - - escodegen@1.14.3: - dependencies: - esprima: 4.0.1 - estraverse: 4.3.0 - esutils: 2.0.3 - optionator: 0.8.3 - optionalDependencies: - source-map: 0.6.1 - - esprima@1.2.2: {} - - esprima@4.0.1: {} - - estraverse@4.3.0: {} - - esutils@2.0.3: {} - - ethers@5.8.0: - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/basex': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/contracts': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/hdnode': 5.8.0 - '@ethersproject/json-wallets': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/pbkdf2': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/providers': 5.8.0 - '@ethersproject/random': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/sha2': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - '@ethersproject/solidity': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/units': 5.8.0 - '@ethersproject/wallet': 5.8.0 - '@ethersproject/web': 5.8.0 - '@ethersproject/wordlists': 5.8.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - exit@0.1.2: {} - - expect@29.7.0: - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastfile@0.0.20: {} - - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 - - ffjavascript@0.2.63: - dependencies: - wasmbuilder: 0.0.16 - wasmcurves: 0.2.2 - web-worker: 1.2.0 - - ffjavascript@0.3.0: - dependencies: - wasmbuilder: 0.0.16 - wasmcurves: 0.2.2 - web-worker: 1.2.0 - - ffjavascript@0.3.1: - dependencies: - wasmbuilder: 0.0.16 - wasmcurves: 0.2.2 - web-worker: 1.2.0 - - filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-package-type@0.1.0: {} - - get-stream@6.0.1: {} - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - graceful-fs@4.2.11: {} - - has-flag@4.0.0: {} - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - hoopy@0.1.4: {} - - html-escaper@2.0.2: {} - - human-signals@2.1.0: {} - - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - - imurmurhash@0.1.4: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - is-arrayish@0.2.1: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-fn@2.1.0: {} - - is-number@7.0.0: {} - - is-stream@2.0.1: {} - - isexe@2.0.0: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.28.0 - '@babel/parser': 7.28.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.28.0 - '@babel/parser': 7.28.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jake@10.9.2: - dependencies: - async: 3.2.6 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - - jest-changed-files@29.7.0: - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - jest-circus@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.6.0 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-cli@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-config@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)): - dependencies: - '@babel/core': 7.28.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.19.9 - ts-node: 10.9.2(@types/node@20.19.9)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-docblock@29.7.0: - dependencies: - detect-newline: 3.1.0 - - jest-each@29.7.0: - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 - - jest-environment-node@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - jest-get-type@29.6.3: {} - - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 20.19.9 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - - jest-leak-detector@29.7.0: - dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-message-util@29.7.0: - dependencies: - '@babel/code-frame': 7.27.1 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - jest-util: 29.7.0 - - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 - - jest-regex-util@29.6.3: {} - - jest-resolve-dependencies@29.7.0: - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - jest-resolve@29.7.0: - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.10 - resolve.exports: 2.0.3 - slash: 3.0.0 - - jest-runner@29.7.0: - dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - - jest-runtime@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - chalk: 4.1.2 - cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - jest-snapshot@29.7.0: - dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) - '@babel/types': 7.28.2 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.1(@babel/core@7.28.0) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - jest-util@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - jest-validate@29.7.0: - dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 - - jest-watcher@29.7.0: - dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.9 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 - - jest-worker@29.7.0: - dependencies: - '@types/node': 20.19.9 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - jest@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - js-sha3@0.8.0: {} - - js-tokens@4.0.0: {} - - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - jsesc@3.1.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json5@2.2.3: {} - - jsonpath@1.1.1: - dependencies: - esprima: 1.2.2 - static-eval: 2.0.2 - underscore: 1.12.1 - - kleur@3.0.3: {} - - leven@3.1.0: {} - - levn@0.3.0: - dependencies: - prelude-ls: 1.1.2 - type-check: 0.3.2 - - lines-and-columns@1.2.4: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - lodash.memoize@4.1.2: {} - - logplease@1.2.15: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - make-dir@4.0.0: - dependencies: - semver: 7.7.2 - - make-error@1.3.6: {} - - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - - merge-stream@2.0.0: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mimic-fn@2.1.0: {} - - minimalistic-assert@1.0.1: {} - - minimalistic-crypto-utils@1.0.1: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.2 - - ms@2.1.3: {} - - nanoassert@2.0.0: {} - - natural-compare@1.4.0: {} - - noble-bls12-381@0.7.2: {} - - noble-hashes@0.3.1: {} - - noble-secp256k1@1.2.14: {} - - node-addon-api@3.2.1: {} - - node-gyp-build@4.8.4: {} - - node-int64@0.4.0: {} - - node-releases@2.0.19: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - optionator@0.8.3: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.3.0 - prelude-ls: 1.1.2 - type-check: 0.3.2 - word-wrap: 1.2.5 - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - pirates@4.0.7: {} - - pkg-dir@4.2.0: - dependencies: - find-up: 4.1.0 - - prelude-ls@1.1.2: {} - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - pure-rand@6.1.0: {} - - r1csfile@0.0.48: - dependencies: - '@iden3/bigarray': 0.0.2 - '@iden3/binfileutils': 0.0.12 - fastfile: 0.0.20 - ffjavascript: 0.3.0 - - react-is@18.3.1: {} - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - require-directory@2.1.1: {} - - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - - resolve-from@5.0.0: {} - - resolve.exports@2.0.3: {} - - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - safe-buffer@5.2.1: {} - - scrypt-js@3.0.1: {} - - semver@6.3.1: {} - - semver@7.7.2: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@3.0.7: {} - - sisteransi@1.0.5: {} - - slash@3.0.0: {} - - snarkjs@0.7.5: - dependencies: - '@iden3/binfileutils': 0.0.12 - bfj: 7.1.0 - blake2b-wasm: 2.4.0 - circom_runtime: 0.1.28 - ejs: 3.1.10 - fastfile: 0.0.20 - ffjavascript: 0.3.1 - js-sha3: 0.8.0 - logplease: 1.2.15 - r1csfile: 0.0.48 - - source-map-support@0.5.13: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - sprintf-js@1.0.3: {} - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - - static-eval@2.0.2: - dependencies: - escodegen: 1.14.3 - - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-bom@4.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-json-comments@3.1.1: {} - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - - tmpl@1.0.5: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tryer@1.0.1: {} - - ts-jest@29.4.0(@babel/core@7.28.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)))(typescript@5.8.3): - dependencies: - bs-logger: 0.2.6 - ejs: 3.1.10 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.19.9)(ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.2 - type-fest: 4.41.0 - typescript: 5.8.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.28.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.0) - jest-util: 29.7.0 - - ts-node@10.9.2(@types/node@20.19.9)(typescript@5.8.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.9 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.8.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - - type-check@0.3.2: - dependencies: - prelude-ls: 1.1.2 - - type-detect@4.0.8: {} - - type-fest@0.21.3: {} - - type-fest@4.41.0: {} - - typescript@5.8.3: {} - - underscore@1.12.1: {} - - undici-types@6.21.0: {} - - update-browserslist-db@1.1.3(browserslist@4.25.1): - dependencies: - browserslist: 4.25.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - util-deprecate@1.0.2: {} - - v8-compile-cache-lib@3.0.1: {} - - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.29 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 - - walker@1.0.8: - dependencies: - makeerror: 1.0.12 - - wasmbuilder@0.0.16: {} - - wasmcurves@0.2.2: - dependencies: - wasmbuilder: 0.0.16 - - web-worker@1.2.0: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrappy@1.0.2: {} - - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - ws@8.18.0: {} - - y18n@5.0.8: {} - - yallist@3.1.1: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yn@3.1.1: {} - - yocto-queue@0.1.0: {} diff --git a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte index fa1c6e4ea..c2b66c2a5 100644 --- a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte +++ b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte @@ -2,7 +2,8 @@ import { cn } from "$lib/utils"; import { CupertinoPane } from "cupertino-pane"; import type { Snippet } from "svelte"; -import { swipe } from "svelte-gestures"; +import { useSwipe } from "svelte-gestures"; +import type { SwipeCustomEvent } from "svelte-gestures"; import type { HTMLAttributes } from "svelte/elements"; interface IDrawerProps extends HTMLAttributes { @@ -23,6 +24,24 @@ let { ...restProps }: IDrawerProps = $props(); +const handleDrawerSwipe = (event: SwipeCustomEvent) => { + if (event.detail.direction === ("down" as string)) { + handleSwipe?.(isPaneOpen); + } +}; + +const swipeResult = useSwipe( + handleDrawerSwipe, + () => ({ + timeframe: 300, + minSwipeDistance: 60, + }), + undefined, + true, +); +// biome-ignore lint/suspicious/noExplicitAny: svelte-gestures type definitions are incomplete +const swipe = swipeResult.swipe as any; + // Disabled click outside behavior to prevent white screen issues // const handleClickOutside = () => { // pane?.destroy({ animate: true }); @@ -64,11 +83,7 @@ $effect(() => {
({ - timeframe: 300, - minSwipeDistance: 60, - })} - onswipe={() => handleSwipe?.(isPaneOpen)} + use:swipe bind:this={drawerElem} class={cn(restProps.class)} > diff --git a/infrastructure/w3id/package.json b/infrastructure/w3id/package.json index 5d09a5426..244981346 100644 --- a/infrastructure/w3id/package.json +++ b/infrastructure/w3id/package.json @@ -27,6 +27,7 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@ngneat/falso": "^7.3.0", + "@types/eventsource": "^1.1.15", "@types/node": "^22.13.10", "typescript": "^5.8.2", "vitest": "^3.0.9" diff --git a/platforms/dreamSync/package.json b/platforms/dreamSync/package.json index f83acc8da..ad3116b9c 100644 --- a/platforms/dreamSync/package.json +++ b/platforms/dreamSync/package.json @@ -98,13 +98,5 @@ }, "optionalDependencies": { "bufferutil": "^4.0.8" - }, - "pnpm": { - "overrides": { - "react": "18.3.1", - "react-dom": "18.3.1", - "@types/react": "18.3.11", - "@types/react-dom": "18.3.1" - } } } diff --git a/platforms/eReputation-api/package.json b/platforms/eReputation-api/package.json new file mode 100644 index 000000000..a8f32ff02 --- /dev/null +++ b/platforms/eReputation-api/package.json @@ -0,0 +1,44 @@ +{ + "name": "ereputation-api", + "version": "1.0.0", + "description": "eReputation Platform API", + "main": "src/index.ts", + "scripts": { + "start": "ts-node --project tsconfig.json src/index.ts", + "dev": "nodemon --exec \"npx ts-node\" src/index.ts", + "build": "tsc", + "typeorm": "typeorm-ts-node-commonjs", + "migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts", + "migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts", + "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts" + }, + "dependencies": { + "axios": "^1.6.7", + "bullmq": "^5.3.0", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.2", + "ioredis": "^5.3.2", + "jsonwebtoken": "^9.0.2", + "openai": "^4.20.1", + "pg": "^8.11.3", + "reflect-metadata": "^0.2.1", + "typeorm": "^0.3.24", + "uuid": "^9.0.1", + "web3-adapter": "link:../../infrastructure/web3-adapter" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.11.24", + "@types/pg": "^8.11.2", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^7.0.1", + "@typescript-eslint/parser": "^7.0.1", + "eslint": "^8.56.0", + "nodemon": "^3.0.3", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/platforms/eReputation-api/src/controllers/AuthController.ts b/platforms/eReputation-api/src/controllers/AuthController.ts new file mode 100644 index 000000000..0f97e8456 --- /dev/null +++ b/platforms/eReputation-api/src/controllers/AuthController.ts @@ -0,0 +1,105 @@ +import { Request, Response } from "express"; +import { v4 as uuidv4 } from "uuid"; +import { UserService } from "../services/UserService"; +import { EventEmitter } from "events"; +import { signToken } from "../utils/jwt"; + +export class AuthController { + private userService: UserService; + private eventEmitter: EventEmitter; + + constructor() { + this.userService = new UserService(); + this.eventEmitter = new EventEmitter(); + } + + sseStream = async (req: Request, res: Response) => { + const { id } = req.params; + + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "Access-Control-Allow-Origin": "*", + }); + + const handler = (data: any) => { + res.write(`data: ${JSON.stringify(data)}\n\n`); + }; + + this.eventEmitter.on(id, handler); + + req.on("close", () => { + this.eventEmitter.off(id, handler); + res.end(); + }); + + req.on("error", (error) => { + console.error("SSE Error:", error); + this.eventEmitter.off(id, handler); + res.end(); + }); + }; + + getOffer = async (req: Request, res: Response) => { + const baseUrl = process.env.VITE_EREPUTATION_BASE_URL || "http://localhost:8765"; + const url = new URL( + "/api/auth", + baseUrl, + ).toString(); + const sessionId = uuidv4(); + const offer = `w3ds://auth?redirect=${url}&session=${sessionId}&platform=ereputation`; + res.json({ offer, sessionId }); + }; + + login = async (req: Request, res: Response) => { + try { + const { ename, session, w3id, signature } = req.body; + + if (!ename) { + return res.status(400).json({ error: "ename is required" }); + } + + if (!session) { + return res.status(400).json({ error: "session is required" }); + } + + // Only find existing users - don't create new ones during auth + const user = await this.userService.findUser(ename); + + if (!user) { + // User doesn't exist - they need to be created via webhook first + return res.status(404).json({ + error: "User not found", + message: "User must be created via eVault webhook before authentication" + }); + } + + const token = signToken({ userId: user.id }); + + const data = { + user: { + id: user.id, + ename: user.ename, + name: user.name, + handle: user.handle, + description: user.description, + avatarUrl: user.avatarUrl, + bannerUrl: user.bannerUrl, + isVerified: user.isVerified, + isPrivate: user.isPrivate, + email: user.email, + emailVerified: user.emailVerified, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }, + token, + }; + this.eventEmitter.emit(session, data); + res.status(200).send(); + } catch (error) { + console.error("Error during login:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/CalculationController.ts b/platforms/eReputation-api/src/controllers/CalculationController.ts new file mode 100644 index 000000000..3e9df2cae --- /dev/null +++ b/platforms/eReputation-api/src/controllers/CalculationController.ts @@ -0,0 +1,126 @@ +import { Request, Response } from "express"; +import { CalculationService } from "../services/CalculationService"; +import { authGuard } from "../middleware/auth"; + +export class CalculationController { + private calculationService: CalculationService; + + constructor() { + this.calculationService = new CalculationService(); + } + + calculateReputation = async (req: Request, res: Response) => { + try { + const { targetType, targetId, targetName, userValues } = req.body; + const calculatorId = req.user!.id; + + // Handle self-evaluation: use calculator's ID and name + let finalTargetId = targetId; + let finalTargetName = targetName; + let finalTargetType = targetType; + + if (targetType === "self") { + finalTargetId = calculatorId; + finalTargetName = req.user!.ename || req.user!.name || "Personal Profile"; + finalTargetType = "self"; + } + + if (!finalTargetType || !finalTargetId || !finalTargetName || !userValues) { + return res.status(400).json({ error: "Missing required fields" }); + } + + // Create calculation record + const calculation = await this.calculationService.createCalculation({ + targetType: finalTargetType, + targetId: finalTargetId, + targetName: finalTargetName, + userValues, + calculatorId + }); + + try { + // Calculate reputation synchronously + const result = await this.calculationService.calculateReputation(calculation.id); + + const details = result.calculationDetails ? JSON.parse(result.calculationDetails) : {}; + + res.json({ + score: result.calculatedScore?.toString() || "0", + analysis: details.explanation || "No analysis available", + targetName: result.targetName, + calculationId: result.id + }); + } catch (calcError) { + // If calculation fails, the service already deleted the record + // Just return an error response + console.error("Error calculating reputation:", calcError); + const errorMessage = calcError instanceof Error ? calcError.message : "Failed to calculate reputation"; + res.status(500).json({ error: errorMessage }); + } + } catch (error) { + console.error("Error calculating reputation:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getCalculationResult = async (req: Request, res: Response) => { + try { + const { calculationId } = req.params; + const userId = req.user!.id; + + const calculation = await this.calculationService.getCalculationById(calculationId); + + if (!calculation) { + return res.status(404).json({ error: "Calculation not found" }); + } + + // Check if user is authorized to view this calculation + if (calculation.calculatorId !== userId) { + return res.status(403).json({ error: "Not authorized to view this calculation" }); + } + + const details = calculation.calculationDetails ? JSON.parse(calculation.calculationDetails) : {}; + + res.json({ + id: calculation.id, + targetType: calculation.targetType, + targetName: calculation.targetName, + userValues: calculation.userValues, + calculatedScore: calculation.calculatedScore, + status: calculation.status, + details: details, + createdAt: calculation.createdAt, + updatedAt: calculation.updatedAt + }); + } catch (error) { + console.error("Error getting calculation result:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getUserCalculations = async (req: Request, res: Response) => { + try { + const userId = req.user!.id; + const calculations = await this.calculationService.getUserCalculations(userId); + + res.json({ + calculations: calculations.map(calc => { + const details = calc.calculationDetails ? JSON.parse(calc.calculationDetails) : {}; + return { + id: calc.id, + targetType: calc.targetType, + targetName: calc.targetName, + calculatedScore: calc.calculatedScore, + status: calc.status, + details: details, + createdAt: calc.createdAt, + updatedAt: calc.updatedAt + }; + }) + }); + } catch (error) { + console.error("Error getting user calculations:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/DashboardController.ts b/platforms/eReputation-api/src/controllers/DashboardController.ts new file mode 100644 index 000000000..ba9de466b --- /dev/null +++ b/platforms/eReputation-api/src/controllers/DashboardController.ts @@ -0,0 +1,159 @@ +import { Request, Response } from "express"; +import { ReferenceService } from "../services/ReferenceService"; +import { CalculationService } from "../services/CalculationService"; +import { authGuard } from "../middleware/auth"; + +export class DashboardController { + private referenceService: ReferenceService; + private calculationService: CalculationService; + + constructor() { + this.referenceService = new ReferenceService(); + this.calculationService = new CalculationService(); + } + + getStats = async (req: Request, res: Response) => { + try { + const userId = req.user!.id; + + // Get total references received by this user + const receivedReferences = await this.referenceService.getReferencesForTarget("user", userId); + + res.json({ + totalReferences: receivedReferences.length.toString() + }); + } catch (error) { + console.error("Error getting dashboard stats:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getActivities = async (req: Request, res: Response) => { + try { + const userId = req.user!.id; + const page = parseInt(req.query.page as string) || 1; + const limit = parseInt(req.query.limit as string) || 10; + const filter = req.query.filter as string || 'all'; + const offset = (page - 1) * limit; + + // Get user's sent references + const sentReferences = await this.referenceService.getUserReferences(userId); + + // Get user's calculations + const calculations = await this.calculationService.getUserCalculations(userId); + + // Get references received by this user + const receivedReferences = await this.referenceService.getReferencesForTarget("user", userId); + + // Combine and format activities + const activities: any[] = []; + + // Add sent references (only if filter allows) + if (filter === 'all' || filter === 'sent-references') { + sentReferences.forEach(ref => { + activities.push({ + id: `ref-sent-${ref.id}`, + type: 'reference', + activity: 'Reference Provided', + target: ref.targetName, + targetType: ref.targetType, + date: ref.createdAt, + status: ref.status === 'revoked' ? 'Revoked' : 'Signed', + data: ref + }); + }); + } + + // Add received references (only if filter allows) + if (filter === 'all' || filter === 'received-references') { + receivedReferences.forEach(ref => { + activities.push({ + id: `ref-received-${ref.id}`, + type: 'reference', + activity: 'Reference Received', + target: ref.author.ename || ref.author.name, + targetType: 'user', + date: ref.createdAt, + status: ref.status === 'revoked' ? 'Revoked' : 'Signed', + data: ref + }); + }); + } + + // Add calculations (only if filter allows) + if (filter === 'all' || filter === 'analysis' || filter === 'self-evaluation' || filter === 'other-evaluations') { + calculations.forEach(calc => { + // Determine activity type based on targetType + let activityType: string; + if (calc.targetType === 'self') { + activityType = 'Self eReputation'; + } else if (calc.targetType === 'user') { + activityType = 'User Evaluation'; + } else if (calc.targetType === 'group') { + activityType = 'Group Evaluation'; + } else { + activityType = 'Platform Analysis'; + } + + // Apply specific filters + if (filter === 'self-evaluation' && calc.targetType !== 'self') { + return; // Skip non-self evaluations + } + if (filter === 'other-evaluations' && calc.targetType === 'self') { + return; // Skip self evaluations + } + if (filter === 'analysis' && calc.targetType === 'self') { + return; // Skip self evaluations for analysis filter + } + + // Parse calculation details to get explanation + let explanation = null; + try { + if (calc.calculationDetails) { + const details = JSON.parse(calc.calculationDetails); + explanation = details.explanation || null; + } + } catch (e) { + // If parsing fails, explanation remains null + } + + activities.push({ + id: `calc-${calc.id}`, + type: 'calculation', + activity: activityType, + target: calc.targetName || 'Personal Profile', + targetType: calc.targetType, + date: calc.createdAt, + status: calc.status, + result: calc.calculatedScore ? `Score: ${calc.calculatedScore}/5` : 'Calculating...', + explanation: explanation, + data: calc + }); + }); + } + + // Sort by date (newest first) + activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + // Paginate + const total = activities.length; + const totalPages = Math.ceil(total / limit); + const paginatedActivities = activities.slice(offset, offset + limit); + + res.json({ + activities: paginatedActivities, + pagination: { + page, + limit, + total, + totalPages, + hasNext: page < totalPages, + hasPrev: page > 1 + } + }); + } catch (error) { + console.error("Error getting dashboard activities:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/GroupController.ts b/platforms/eReputation-api/src/controllers/GroupController.ts new file mode 100644 index 000000000..28416e09d --- /dev/null +++ b/platforms/eReputation-api/src/controllers/GroupController.ts @@ -0,0 +1,42 @@ +import { Request, Response } from "express"; +import { GroupService } from "../services/GroupService"; + +export class GroupController { + private groupService: GroupService; + + constructor() { + this.groupService = new GroupService(); + } + + search = async (req: Request, res: Response) => { + try { + const { q, limit } = req.query; + + if (!q || typeof q !== "string") { + return res.status(400).json({ error: "Query parameter 'q' is required" }); + } + + let limitNum = 10; + if (typeof limit === "string") { + const parsed = parseInt(limit, 10); + if (!Number.isNaN(parsed) && parsed > 0 && parsed <= 100) { + limitNum = parsed; + } + } + + const groups = await this.groupService.searchGroups(q, limitNum); + + res.json(groups.map(group => ({ + id: group.id, + name: group.name, + description: group.description, + charter: group.charter, + createdAt: group.createdAt, + updatedAt: group.updatedAt, + }))); + } catch (error) { + console.error("Error searching groups:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/PlatformController.ts b/platforms/eReputation-api/src/controllers/PlatformController.ts new file mode 100644 index 000000000..aeaf19770 --- /dev/null +++ b/platforms/eReputation-api/src/controllers/PlatformController.ts @@ -0,0 +1,58 @@ +import { Request, Response } from "express"; +import { PlatformService } from "../services/PlatformService"; + +export class PlatformController { + private platformService: PlatformService; + + constructor() { + this.platformService = new PlatformService(); + } + + getPlatforms = async (req: Request, res: Response) => { + try { + const platforms = await this.platformService.getActivePlatforms(); + + res.json({ + platforms: platforms.map(platform => ({ + id: platform.id, + name: platform.name, + description: platform.description, + category: platform.category, + logoUrl: platform.logoUrl, + url: platform.url, + type: "platform" + })) + }); + } catch (error) { + console.error("Error getting platforms:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + searchPlatforms = async (req: Request, res: Response) => { + try { + const { q } = req.query; + + if (!q || typeof q !== 'string') { + return res.status(400).json({ error: "Query parameter 'q' is required" }); + } + + const platforms = await this.platformService.searchPlatforms(q); + + res.json({ + platforms: platforms.map(platform => ({ + id: platform.id, + name: platform.name, + description: platform.description, + category: platform.category, + logoUrl: platform.logoUrl, + url: platform.url, + type: "platform" + })) + }); + } catch (error) { + console.error("Error searching platforms:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/ReferenceController.ts b/platforms/eReputation-api/src/controllers/ReferenceController.ts new file mode 100644 index 000000000..90a711d21 --- /dev/null +++ b/platforms/eReputation-api/src/controllers/ReferenceController.ts @@ -0,0 +1,126 @@ +import { Request, Response } from "express"; +import { ReferenceService } from "../services/ReferenceService"; +import { authGuard } from "../middleware/auth"; + +export class ReferenceController { + private referenceService: ReferenceService; + + constructor() { + this.referenceService = new ReferenceService(); + } + + createReference = async (req: Request, res: Response) => { + try { + const { targetType, targetId, targetName, content, referenceType, numericScore } = req.body; + const authorId = req.user!.id; + + if (!targetType || !targetId || !targetName || !content) { + return res.status(400).json({ error: "Missing required fields" }); + } + + if (numericScore && (numericScore < 1 || numericScore > 5)) { + return res.status(400).json({ error: "Numeric score must be between 1 and 5" }); + } + + const reference = await this.referenceService.createReference({ + targetType, + targetId, + targetName, + content, + referenceType: referenceType || "general", + numericScore, + authorId + }); + + res.status(201).json({ + message: "Reference created successfully", + reference: { + id: reference.id, + targetType: reference.targetType, + targetName: reference.targetName, + content: reference.content, + numericScore: reference.numericScore, + status: reference.status, + createdAt: reference.createdAt + } + }); + } catch (error) { + console.error("Error creating reference:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getReferencesForTarget = async (req: Request, res: Response) => { + try { + const { targetType, targetId } = req.params; + + const references = await this.referenceService.getReferencesForTarget(targetType, targetId); + + res.json({ + references: references.map(ref => ({ + id: ref.id, + content: ref.content, + numericScore: ref.numericScore, + referenceType: ref.referenceType, + status: ref.status, + author: { + id: ref.author.id, + ename: ref.author.ename, + name: ref.author.name + }, + createdAt: ref.createdAt + })) + }); + } catch (error) { + console.error("Error getting references:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getUserReferences = async (req: Request, res: Response) => { + try { + const userId = req.user!.id; + const references = await this.referenceService.getUserReferences(userId); + + res.json({ + references: references.map(ref => ({ + id: ref.id, + targetType: ref.targetType, + targetName: ref.targetName, + content: ref.content, + numericScore: ref.numericScore, + referenceType: ref.referenceType, + status: ref.status, + createdAt: ref.createdAt + })) + }); + } catch (error) { + console.error("Error getting user references:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + revokeReference = async (req: Request, res: Response) => { + try { + const { referenceId } = req.params; + const userId = req.user!.id; + + const reference = await this.referenceService.revokeReference(referenceId, userId); + + if (!reference) { + return res.status(404).json({ error: "Reference not found or not authorized" }); + } + + res.json({ + message: "Reference revoked successfully", + reference: { + id: reference.id, + status: reference.status + } + }); + } catch (error) { + console.error("Error revoking reference:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/UserController.ts b/platforms/eReputation-api/src/controllers/UserController.ts new file mode 100644 index 000000000..0ef5a8395 --- /dev/null +++ b/platforms/eReputation-api/src/controllers/UserController.ts @@ -0,0 +1,140 @@ +import { Request, Response } from "express"; +import { UserService } from "../services/UserService"; + +export class UserController { + private userService: UserService; + + constructor() { + this.userService = new UserService(); + } + + currentUser = async (req: Request, res: Response) => { + try { + if (!req.user) { + return res.status(401).json({ error: "Authentication required" }); + } + + const user = await this.userService.getUserById(req.user.id); + if (!user) { + return res.status(404).json({ error: "User not found" }); + } + + res.json({ + id: user.id, + ename: user.ename, + name: user.name, + handle: user.handle, + description: user.description, + avatarUrl: user.avatarUrl, + bannerUrl: user.bannerUrl, + isVerified: user.isVerified, + isPrivate: user.isPrivate, + email: user.email, + emailVerified: user.emailVerified, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }); + } catch (error) { + console.error("Error getting current user:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + getProfileById = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const user = await this.userService.getUserById(id); + + if (!user) { + return res.status(404).json({ error: "User not found" }); + } + + res.json({ + id: user.id, + ename: user.ename, + name: user.name, + handle: user.handle, + description: user.description, + avatarUrl: user.avatarUrl, + bannerUrl: user.bannerUrl, + isVerified: user.isVerified, + isPrivate: user.isPrivate, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }); + } catch (error) { + console.error("Error getting user profile:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + search = async (req: Request, res: Response) => { + try { + const { q, limit } = req.query; + + if (!q || typeof q !== "string") { + return res.status(400).json({ error: "Query parameter 'q' is required" }); + } + + const limitNum = limit ? parseInt(limit as string) : 10; + const users = await this.userService.searchUsers(q, limitNum); + + res.json(users.map(user => ({ + id: user.id, + ename: user.ename, + name: user.name, + handle: user.handle, + description: user.description, + avatarUrl: user.avatarUrl, + bannerUrl: user.bannerUrl, + isVerified: user.isVerified, + isPrivate: user.isPrivate, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }))); + } catch (error) { + console.error("Error searching users:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + + updateProfile = async (req: Request, res: Response) => { + try { + if (!req.user) { + return res.status(401).json({ error: "Authentication required" }); + } + + const { name, handle, description, avatarUrl, bannerUrl, isPrivate } = req.body; + + const updateData = { + name, + handle, + description, + avatarUrl, + bannerUrl, + isPrivate, + }; + + const updatedUser = await this.userService.updateUser(req.user.id, updateData); + + res.json({ + id: updatedUser.id, + ename: updatedUser.ename, + name: updatedUser.name, + handle: updatedUser.handle, + description: updatedUser.description, + avatarUrl: updatedUser.avatarUrl, + bannerUrl: updatedUser.bannerUrl, + isVerified: updatedUser.isVerified, + isPrivate: updatedUser.isPrivate, + email: updatedUser.email, + emailVerified: updatedUser.emailVerified, + createdAt: updatedUser.createdAt, + updatedAt: updatedUser.updatedAt, + }); + } catch (error) { + console.error("Error updating profile:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; +} diff --git a/platforms/eReputation-api/src/controllers/WebhookController.ts b/platforms/eReputation-api/src/controllers/WebhookController.ts new file mode 100644 index 000000000..f788badd7 --- /dev/null +++ b/platforms/eReputation-api/src/controllers/WebhookController.ts @@ -0,0 +1,322 @@ +import { Request, Response } from "express"; +import { UserService } from "../services/UserService"; +import { GroupService } from "../services/GroupService"; +import { VoteService } from "../services/VoteService"; +import { PollService } from "../services/PollService"; +import { JobQueueService } from "../services/JobQueueService"; +import { adapter } from "../web3adapter/watchers/subscriber"; +import { User } from "../database/entities/User"; +import { Group } from "../database/entities/Group"; +import { Poll } from "../database/entities/Poll"; +import { AppDataSource } from "../database/data-source"; +import axios from "axios"; + +export class WebhookController { + userService: UserService; + groupService: GroupService; + voteService: VoteService; + pollService: PollService; + jobQueueService: JobQueueService; + adapter: typeof adapter; + + constructor(jobQueueService: JobQueueService) { + this.userService = new UserService(); + this.groupService = new GroupService(); + this.voteService = new VoteService(); + this.pollService = new PollService(); + this.jobQueueService = jobQueueService; + this.adapter = adapter; + } + + handleWebhook = async (req: Request, res: Response) => { + const globalId = req.body.id; + const schemaId = req.body.schemaId; + + try { + // Forward to ANCHR if configured + if (process.env.ANCHR_URL) { + try { + await axios.post( + new URL("ereputation-api", process.env.ANCHR_URL).toString(), + req.body + ); + } catch (error) { + // Don't fail the webhook if ANCHR forwarding fails + } + } + + const mapping = Object.values(this.adapter.mapping).find( + (m: any) => m.schemaId === schemaId + ) as any; + + if (!mapping) { + throw new Error("No mapping found"); + } + + // Check if this globalId is already locked (being processed) + if (this.adapter.lockedIds.includes(globalId)) { + return res.status(200).send(); + } + + this.adapter.addToLockedIds(globalId); + + const local = await this.adapter.fromGlobal({ + data: req.body.data, + mapping, + }); + + let localId = await this.adapter.mappingDb.getLocalId(globalId); + let finalLocalId = localId; + + if (mapping.tableName === "users") { + if (localId) { + const user = await this.userService.getUserById(localId); + if (!user) throw new Error(); + + // Only update simple properties, not relationships + const updateData: Partial = { + name: req.body.data.displayName, + handle: local.data.username as string | undefined, + description: local.data.bio as string | undefined, + avatarUrl: local.data.avatarUrl as string | undefined, + bannerUrl: local.data.bannerUrl as string | undefined, + isVerified: local.data.isVerified as boolean | undefined, + isPrivate: local.data.isPrivate as boolean | undefined, + email: local.data.email as string | undefined, + emailVerified: local.data.emailVerified as boolean | undefined, + }; + + await this.userService.updateUser(user.id, updateData); + await this.adapter.mappingDb.storeMapping({ + localId: user.id, + globalId: req.body.id, + }); + this.adapter.addToLockedIds(user.id); + this.adapter.addToLockedIds(globalId); + finalLocalId = user.id; + } else { + const user = await this.userService.createBlankUser(req.body.w3id); + + // Update user with webhook data + await this.userService.updateUser(user.id, { + name: req.body.data.displayName, + handle: req.body.data.username, + description: req.body.data.bio, + avatarUrl: req.body.data.avatarUrl, + bannerUrl: req.body.data.bannerUrl, + isVerified: req.body.data.isVerified, + isPrivate: req.body.data.isPrivate, + }); + + await this.adapter.mappingDb.storeMapping({ + localId: user.id, + globalId: req.body.id, + }); + this.adapter.addToLockedIds(user.id); + this.adapter.addToLockedIds(globalId); + finalLocalId = user.id; + } + } else if (mapping.tableName === "groups") { + let participants: User[] = []; + if ( + local.data.participants && + Array.isArray(local.data.participants) + ) { + const participantPromises = local.data.participants.map( + async (ref: string) => { + if (ref && typeof ref === "string") { + const userId = ref.split("(")[1].split(")")[0]; + return await this.userService.getUserById(userId); + } + return null; + } + ); + + participants = ( + await Promise.all(participantPromises) + ).filter((user: User | null): user is User => user !== null); + } + + let adminIds = local?.data?.admins as string[] ?? [] + adminIds = adminIds.map((a) => a.includes("(") ? a.split("(")[1].split(")")[0]: a) + + if (localId) { + const group = await this.groupService.getGroupById(localId); + if (!group) { + return res.status(500).send(); + } + + group.name = local.data.name as string; + group.description = local.data.description as string; + group.owner = local.data.owner as string; + group.admins = adminIds.map(id => ({ id } as User)); + group.participants = participants; + group.charter = local.data.charter as string; + group.ename = local.data.ename as string + + this.adapter.addToLockedIds(localId); + await this.groupService.groupRepository.save(group); + finalLocalId = group.id; + } else { + // Check if a group with the same name and description already exists + // This prevents duplicate group creation from junction table webhooks + const existingGroup = await this.groupService.groupRepository.findOne({ + where: { + name: local.data.name as string, + description: local.data.description as string + } + }); + + if (existingGroup) { + this.adapter.addToLockedIds(existingGroup.id); + await this.adapter.mappingDb.storeMapping({ + localId: existingGroup.id, + globalId: req.body.id, + }); + finalLocalId = existingGroup.id; + } else { + const group = await this.groupService.createGroup( + local.data.name as string, + local.data.description as string, + local.data.owner as string, + adminIds, + participants.map(p => p.id), + local.data.charter as string | undefined, + ); + this.adapter.addToLockedIds(group.id); + await this.adapter.mappingDb.storeMapping({ + localId: group.id, + globalId: req.body.id, + }); + finalLocalId = group.id; + } + } + } else if (mapping.tableName === "votes") { + if (localId) { + const vote = await this.voteService.getVoteById(localId); + if (!vote) { + return res.status(500).send(); + } + + vote.data = local.data.data as any; + vote.voterId = local.data.voterId as string; + await this.voteService.voteRepository.save(vote); + finalLocalId = vote.id; + } else { + // Get userId from voterId or local.data.userId + const userId = local.data.userId as string; + const voterId = local.data.voterId as string; + const pollId = local.data.pollId as string; + const voteData = local.data.data as any; + + if (!userId || !voterId || !pollId || !voteData) { + return res.status(400).send(); + } + + const vote = await this.voteService.createVote( + pollId, + userId, + voterId, + voteData + ); + this.adapter.addToLockedIds(vote.id); + await this.adapter.mappingDb.storeMapping({ + localId: vote.id, + globalId: req.body.id, + }); + finalLocalId = vote.id; + } + } else if (mapping.tableName === "polls") { + const pollRepository = AppDataSource.getRepository(Poll); + + // Get groupId from group reference if present + let groupId: string | null = null; + if (local.data.group) { + if (typeof local.data.group === "string" && local.data.group.includes("(")) { + groupId = local.data.group.split("(")[1].split(")")[0]; + } else if (typeof local.data.group === "object" && local.data.group !== null && "id" in local.data.group) { + groupId = (local.data.group as { id: string }).id; + } + } else if (local.data.groupId) { + groupId = local.data.groupId as string; + } + + if (localId) { + // Update existing poll + const poll = await pollRepository.findOne({ + where: { id: localId } + }); + + if (poll) { + poll.title = local.data.title as string; + poll.mode = local.data.mode as "normal" | "point" | "rank"; + poll.visibility = local.data.visibility as "public" | "private"; + poll.votingWeight = (local.data.votingWeight || "1p1v") as "1p1v" | "ereputation"; + poll.options = Array.isArray(local.data.options) + ? local.data.options + : (local.data.options as string).split(","); + poll.deadline = local.data.deadline ? new Date(local.data.deadline as string) : null; + poll.groupId = groupId; + + await pollRepository.save(poll); + finalLocalId = poll.id; + + // Enqueue reputation calculation job if needed + if (this.voteService.isEReputationWeighted(poll) && poll.groupId) { + try { + await this.jobQueueService.enqueuePollReputationJob( + poll.id, + poll.groupId, + globalId // Use globalId as eventId for idempotency + ); + } catch (error) { + console.error(`Failed to enqueue reputation job for poll ${poll.id}:`, error); + } + } + } + } else { + // Create new poll + const poll = pollRepository.create({ + title: local.data.title as string, + mode: local.data.mode as "normal" | "point" | "rank", + visibility: local.data.visibility as "public" | "private", + votingWeight: (local.data.votingWeight || "1p1v") as "1p1v" | "ereputation", + options: Array.isArray(local.data.options) + ? local.data.options + : (local.data.options as string).split(","), + deadline: local.data.deadline ? new Date(local.data.deadline as string) : null, + groupId: groupId + }); + + const savedPoll = await pollRepository.save(poll); + + this.adapter.addToLockedIds(savedPoll.id); + await this.adapter.mappingDb.storeMapping({ + localId: savedPoll.id, + globalId: req.body.id, + }); + finalLocalId = savedPoll.id; + + // Enqueue reputation calculation job if needed + if (this.voteService.isEReputationWeighted(savedPoll) && savedPoll.groupId) { + try { + await this.jobQueueService.enqueuePollReputationJob( + savedPoll.id, + savedPoll.groupId, + globalId // Use globalId as eventId for idempotency + ); + } catch (error) { + console.error(`Failed to enqueue reputation job for poll ${savedPoll.id}:`, error); + } + } + } + } + + res.status(200).send(); + } catch (e) { + console.error("Webhook error:", e); + res.status(500).send(); + } + }; + +} diff --git a/platforms/eReputation-api/src/database/data-source.ts b/platforms/eReputation-api/src/database/data-source.ts new file mode 100644 index 000000000..df98d562c --- /dev/null +++ b/platforms/eReputation-api/src/database/data-source.ts @@ -0,0 +1,29 @@ +import "reflect-metadata"; +import path from "node:path"; +import { config } from "dotenv"; +import { DataSource, type DataSourceOptions } from "typeorm"; +import { User } from "./entities/User"; +import { Group } from "./entities/Group"; +import { Reference } from "./entities/Reference"; +import { Calculation } from "./entities/Calculation"; +import { Vote } from "./entities/Vote"; +import { Poll } from "./entities/Poll"; +import { VoteReputationResult } from "./entities/VoteReputationResult"; +import { Message } from "./entities/Message"; +import { PostgresSubscriber } from "../web3adapter/watchers/subscriber"; + +// Use absolute path for better CLI compatibility +const envPath = path.resolve(__dirname, "../../../../.env"); +config({ path: envPath }); + +export const dataSourceOptions: DataSourceOptions = { + type: "postgres", + url: process.env.EREPUTATION_DATABASE_URL, + synchronize: false, // Auto-sync in development + entities: [User, Group, Reference, Calculation, Vote, Poll, VoteReputationResult, Message], + migrations: [path.join(__dirname, "migrations", "*.ts")], + logging: process.env.NODE_ENV === "development", + subscribers: [PostgresSubscriber], +}; + +export const AppDataSource = new DataSource(dataSourceOptions); \ No newline at end of file diff --git a/platforms/eReputation-api/src/database/entities/Calculation.ts b/platforms/eReputation-api/src/database/entities/Calculation.ts new file mode 100644 index 000000000..808dedc88 --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Calculation.ts @@ -0,0 +1,42 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; +import { User } from "./User"; + +@Entity("calculations") +export class Calculation { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column() + targetType!: string; // "user", "group", "platform" + + @Column() + targetId!: string; + + @Column() + targetName!: string; + + @Column("text") + userValues!: string; // What the user values + + @Column("float") + calculatedScore!: number; // Final calculated score (1-5) + + @Column("text", { nullable: true }) + calculationDetails!: string; // JSON string with calculation breakdown + + @Column() + calculatorId!: string; // Who calculated this + + @ManyToOne(() => User) + @JoinColumn({ name: "calculatorId" }) + calculator!: User; + + @Column() + status!: string; // "complete", "processing", "error" + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/platforms/eReputation-api/src/database/entities/Group.ts b/platforms/eReputation-api/src/database/entities/Group.ts new file mode 100644 index 000000000..bacc96e0f --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Group.ts @@ -0,0 +1,81 @@ +import { + Entity, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, + Column, + ManyToMany, + OneToMany, + JoinTable, +} from "typeorm"; +import { User } from "./User"; +import { Message } from "./Message"; + +@Entity() +export class Group { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column({ nullable: true }) + name!: string; + + @Column({ nullable: true }) + description!: string; + + @Column({ nullable: true }) + owner!: string; + + @Column({ type: "text", nullable: true }) + charter!: string; // Markdown content for the group charter + + @Column({ default: false }) + isPrivate!: boolean; + + @Column({ default: "public" }) + visibility!: "public" | "private" | "restricted"; + + @ManyToMany(() => User) + @JoinTable({ + name: "group_members", + joinColumn: { name: "group_id", referencedColumnName: "id" }, + inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } + }) + members!: User[]; + + @ManyToMany(() => User) + @JoinTable({ + name: "group_admins", + joinColumn: { name: "group_id", referencedColumnName: "id" }, + inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } + }) + admins!: User[]; + + @ManyToMany(() => User) + @JoinTable({ + name: "group_participants", + joinColumn: { name: "group_id", referencedColumnName: "id" }, + inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } + }) + participants!: User[]; + + @Column({ nullable: true}) + ename!: string + + @Column({ nullable: true }) + avatarUrl!: string; + + @Column({ nullable: true }) + bannerUrl!: string; + + @Column({ type: "json", nullable: true }) + originalMatchParticipants!: string[]; // Store user IDs from the original match + + @OneToMany(() => Message, (message) => message.group) + messages!: Message[]; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/platforms/eReputation-api/src/database/entities/Message.ts b/platforms/eReputation-api/src/database/entities/Message.ts new file mode 100644 index 000000000..18793ae44 --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Message.ts @@ -0,0 +1,41 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, +} from "typeorm"; +import { User } from "./User"; +import { Group } from "./Group"; + +@Entity("messages") +export class Message { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @ManyToOne(() => User, { nullable: true }) + sender?: User; // Nullable for system messages + + @Column("text") + text!: string; + + @ManyToOne(() => Group, (group) => group.messages) + group!: Group; + + @Column({ default: false }) + isSystemMessage!: boolean; // Flag to identify system messages + + @Column("uuid", { nullable: true }) + voteId?: string; // ID of the vote/poll this system message relates to + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; + + @Column({ default: false }) + isArchived!: boolean; +} + diff --git a/platforms/eReputation-api/src/database/entities/Poll.ts b/platforms/eReputation-api/src/database/entities/Poll.ts new file mode 100644 index 000000000..63cdcee0d --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Poll.ts @@ -0,0 +1,62 @@ +import { + Column, + CreateDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from "typeorm"; +import { Vote } from "./Vote"; + +@Entity("polls") +export class Poll { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column("varchar", { length: 255 }) + title!: string; + + @Column("enum", { + enum: ["normal", "point", "rank"], + default: "normal", + }) + mode!: "normal" | "point" | "rank"; + + @Column("enum", { + enum: ["public", "private"], + default: "public", + }) + visibility!: "public" | "private"; + + @Column("enum", { + enum: ["1p1v", "ereputation"], + default: "1p1v", + }) + votingWeight!: "1p1v" | "ereputation"; + + @Column("simple-array") + options!: string[]; // stored as comma-separated values + + @Column({ type: "timestamp", nullable: true }) + deadline!: Date | null; + + @Column({ type: "boolean", default: false }) + deadlineMessageSent!: boolean; + + @Column("uuid", { nullable: true }) + groupId!: string | null; // Group this poll belongs to + + @OneToMany( + () => Vote, + (vote) => vote.poll, + ) + votes!: Vote[]; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} + + diff --git a/platforms/eReputation-api/src/database/entities/Reference.ts b/platforms/eReputation-api/src/database/entities/Reference.ts new file mode 100644 index 000000000..944521234 --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Reference.ts @@ -0,0 +1,42 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; +import { User } from "./User"; + +@Entity("references") +export class Reference { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column() + targetType!: string; // "user", "group", "platform" + + @Column() + targetId!: string; + + @Column() + targetName!: string; + + @Column("text") + content!: string; + + @Column() + referenceType!: string; // "general", "professional", etc. + + @Column("int", { nullable: true }) + numericScore?: number; // Optional 1-5 score + + @Column() + authorId!: string; + + @ManyToOne(() => User) + @JoinColumn({ name: "authorId" }) + author!: User; + + @Column({ nullable: true }) + status!: string; // "signed", "revoked" + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/platforms/eReputation-api/src/database/entities/User.ts b/platforms/eReputation-api/src/database/entities/User.ts new file mode 100644 index 000000000..1445e81e9 --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/User.ts @@ -0,0 +1,76 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + OneToMany, + ManyToMany, + JoinTable, +} from "typeorm"; +import { Vote } from "./Vote"; +import { Poll } from "./Poll"; + +@Entity("users") +export class User { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column({ nullable: true }) + handle!: string; + + @Column({ nullable: true }) + name!: string; + + @Column({ nullable: true }) + description!: string; + + @Column({ nullable: true }) + avatarUrl!: string; + + @Column({ nullable: true }) + bannerUrl!: string; + + @Column({ nullable: true }) + ename!: string; + + @Column({ default: false }) + isVerified!: boolean; + + @Column({ default: false }) + isPrivate!: boolean; + + @Column("varchar", { name: "email", length: 255, nullable: true }) + email!: string; + + @Column("boolean", { name: "emailVerified", default: false }) + emailVerified!: boolean; + + @ManyToMany(() => User) + @JoinTable({ + name: "user_followers", + joinColumn: { name: "user_id", referencedColumnName: "id" }, + inverseJoinColumn: { name: "follower_id", referencedColumnName: "id" }, + }) + followers!: User[]; + + @ManyToMany(() => User) + @JoinTable({ + name: "user_following", + joinColumn: { name: "user_id", referencedColumnName: "id" }, + inverseJoinColumn: { name: "following_id", referencedColumnName: "id" }, + }) + following!: User[]; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; + + @Column({ default: false }) + isArchived!: boolean; + + @OneToMany(() => Vote, (vote) => vote.user) + votes!: Vote[]; +} diff --git a/platforms/eReputation-api/src/database/entities/Vote.ts b/platforms/eReputation-api/src/database/entities/Vote.ts new file mode 100644 index 000000000..d5947d448 --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/Vote.ts @@ -0,0 +1,87 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from "typeorm"; +import { Poll } from "./Poll"; +import { User } from "./User"; + +export type NormalVoteData = string[]; + +export type PointVoteData = { + option: string; + points: number; +}[]; + +export type RankVoteData = { + option: string; + points: number; // 1 = top pick, 2 = next, etc. +}[]; + +export type VoteData = NormalVoteData | PointVoteData | RankVoteData; + +export type BlindVoteData = { + mode: "blind"; + commitment: string; + proof: string; + revealed: boolean; + actualVote?: number; + randomness?: string; + revealedAt?: Date; +}; + +export type VoteDataByMode = + | { mode: "normal"; data: NormalVoteData } + | { mode: "point"; data: PointVoteData } + | { mode: "rank"; data: RankVoteData } + | BlindVoteData; + +@Entity("votes") +export class Vote { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @ManyToOne( + () => Poll, + (poll) => poll.votes, + { onDelete: "CASCADE" }, + ) + @JoinColumn({ name: "pollId" }) + poll!: Poll; + + @Column("uuid") + pollId!: string; + + @ManyToOne(() => User, (user) => user.votes) + @JoinColumn({ name: "userId" }) + user!: User; + + @Column("uuid") + userId!: string; + + // This can be user ID, session ID, or anonymous identifier + @Column("varchar", { length: 255 }) + voterId!: string; + + /** + * For "normal" mode: array of chosen options (usually 1) + * For "point" mode: { option: string, points: number }[] + * For "rank" mode: ordered array of option strings + * + * Stored as JSON for flexibility + */ + @Column("jsonb") + data!: VoteDataByMode; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} + + diff --git a/platforms/eReputation-api/src/database/entities/VoteReputationResult.ts b/platforms/eReputation-api/src/database/entities/VoteReputationResult.ts new file mode 100644 index 000000000..0ea05fa4d --- /dev/null +++ b/platforms/eReputation-api/src/database/entities/VoteReputationResult.ts @@ -0,0 +1,52 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from "typeorm"; +import { Poll } from "./Poll"; +import { Group } from "./Group"; + +export interface MemberReputation { + ename: string; + score: number; + justification: string; +} + +@Entity("vote_reputation_results") +export class VoteReputationResult { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @ManyToOne(() => Poll) + @JoinColumn({ name: "pollId" }) + poll!: Poll; + + @Column("uuid") + pollId!: string; + + @ManyToOne(() => Group) + @JoinColumn({ name: "groupId" }) + group!: Group; + + @Column("uuid") + groupId!: string; + + /** + * Array of reputation scores for each group member + * Format: [{ ename: string, score: number, justification: string }] + */ + @Column("jsonb") + results!: MemberReputation[]; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} + + diff --git a/platforms/eReputation-api/src/database/migrations/1761316350100-migration.ts b/platforms/eReputation-api/src/database/migrations/1761316350100-migration.ts new file mode 100644 index 000000000..b4414e2a3 --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1761316350100-migration.ts @@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1761316350100 implements MigrationInterface { + name = 'Migration1761316350100' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "handle" character varying, "name" character varying, "description" character varying, "avatarUrl" character varying, "bannerUrl" character varying, "ename" character varying, "isVerified" boolean NOT NULL DEFAULT false, "isPrivate" boolean NOT NULL DEFAULT false, "email" character varying(255), "emailVerified" boolean NOT NULL DEFAULT false, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "group" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying, "description" character varying, "owner" character varying, "charter" text, "isPrivate" boolean NOT NULL DEFAULT false, "visibility" character varying NOT NULL DEFAULT 'public', "ename" character varying, "avatarUrl" character varying, "bannerUrl" character varying, "originalMatchParticipants" json, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_256aa0fda9b1de1a73ee0b7106b" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "user_followers" ("user_id" uuid NOT NULL, "follower_id" uuid NOT NULL, CONSTRAINT "PK_d7b47e785d7dbc74b2f22f30045" PRIMARY KEY ("user_id", "follower_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_a59d62cda8101214445e295cdc" ON "user_followers" ("user_id") `); + await queryRunner.query(`CREATE INDEX "IDX_da722d93356ae3119d6be40d98" ON "user_followers" ("follower_id") `); + await queryRunner.query(`CREATE TABLE "user_following" ("user_id" uuid NOT NULL, "following_id" uuid NOT NULL, CONSTRAINT "PK_5d7e9a83ee6f9b806d569068a30" PRIMARY KEY ("user_id", "following_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_a28a2c27629ac06a41720d01c3" ON "user_following" ("user_id") `); + await queryRunner.query(`CREATE INDEX "IDX_94e1183284db3e697031eb7775" ON "user_following" ("following_id") `); + await queryRunner.query(`CREATE TABLE "group_members" ("group_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_f5939ee0ad233ad35e03f5c65c1" PRIMARY KEY ("group_id", "user_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_2c840df5db52dc6b4a1b0b69c6" ON "group_members" ("group_id") `); + await queryRunner.query(`CREATE INDEX "IDX_20a555b299f75843aa53ff8b0e" ON "group_members" ("user_id") `); + await queryRunner.query(`CREATE TABLE "group_admins" ("group_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_a63ab4ea34529a63cdd55eed88d" PRIMARY KEY ("group_id", "user_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_0ecd81bfecc31d4f804ece20ef" ON "group_admins" ("group_id") `); + await queryRunner.query(`CREATE INDEX "IDX_29bb650b1c5b1639dfb089f39a" ON "group_admins" ("user_id") `); + await queryRunner.query(`CREATE TABLE "group_participants" ("group_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_92021b85af6470d6b405e12f312" PRIMARY KEY ("group_id", "user_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_e61f897ae7a7df4b56595adaae" ON "group_participants" ("group_id") `); + await queryRunner.query(`CREATE INDEX "IDX_bb1d0ab0d82e0a62fa55b7e841" ON "group_participants" ("user_id") `); + await queryRunner.query(`ALTER TABLE "user_followers" ADD CONSTRAINT "FK_a59d62cda8101214445e295cdc8" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "user_followers" ADD CONSTRAINT "FK_da722d93356ae3119d6be40d988" FOREIGN KEY ("follower_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "user_following" ADD CONSTRAINT "FK_a28a2c27629ac06a41720d01c30" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "user_following" ADD CONSTRAINT "FK_94e1183284db3e697031eb7775d" FOREIGN KEY ("following_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_members" ADD CONSTRAINT "FK_2c840df5db52dc6b4a1b0b69c6e" FOREIGN KEY ("group_id") REFERENCES "group"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_members" ADD CONSTRAINT "FK_20a555b299f75843aa53ff8b0ee" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_admins" ADD CONSTRAINT "FK_0ecd81bfecc31d4f804ece20efc" FOREIGN KEY ("group_id") REFERENCES "group"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_admins" ADD CONSTRAINT "FK_29bb650b1c5b1639dfb089f39a7" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_participants" ADD CONSTRAINT "FK_e61f897ae7a7df4b56595adaae7" FOREIGN KEY ("group_id") REFERENCES "group"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "group_participants" ADD CONSTRAINT "FK_bb1d0ab0d82e0a62fa55b7e8411" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "group_participants" DROP CONSTRAINT "FK_bb1d0ab0d82e0a62fa55b7e8411"`); + await queryRunner.query(`ALTER TABLE "group_participants" DROP CONSTRAINT "FK_e61f897ae7a7df4b56595adaae7"`); + await queryRunner.query(`ALTER TABLE "group_admins" DROP CONSTRAINT "FK_29bb650b1c5b1639dfb089f39a7"`); + await queryRunner.query(`ALTER TABLE "group_admins" DROP CONSTRAINT "FK_0ecd81bfecc31d4f804ece20efc"`); + await queryRunner.query(`ALTER TABLE "group_members" DROP CONSTRAINT "FK_20a555b299f75843aa53ff8b0ee"`); + await queryRunner.query(`ALTER TABLE "group_members" DROP CONSTRAINT "FK_2c840df5db52dc6b4a1b0b69c6e"`); + await queryRunner.query(`ALTER TABLE "user_following" DROP CONSTRAINT "FK_94e1183284db3e697031eb7775d"`); + await queryRunner.query(`ALTER TABLE "user_following" DROP CONSTRAINT "FK_a28a2c27629ac06a41720d01c30"`); + await queryRunner.query(`ALTER TABLE "user_followers" DROP CONSTRAINT "FK_da722d93356ae3119d6be40d988"`); + await queryRunner.query(`ALTER TABLE "user_followers" DROP CONSTRAINT "FK_a59d62cda8101214445e295cdc8"`); + await queryRunner.query(`DROP INDEX "public"."IDX_bb1d0ab0d82e0a62fa55b7e841"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e61f897ae7a7df4b56595adaae"`); + await queryRunner.query(`DROP TABLE "group_participants"`); + await queryRunner.query(`DROP INDEX "public"."IDX_29bb650b1c5b1639dfb089f39a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_0ecd81bfecc31d4f804ece20ef"`); + await queryRunner.query(`DROP TABLE "group_admins"`); + await queryRunner.query(`DROP INDEX "public"."IDX_20a555b299f75843aa53ff8b0e"`); + await queryRunner.query(`DROP INDEX "public"."IDX_2c840df5db52dc6b4a1b0b69c6"`); + await queryRunner.query(`DROP TABLE "group_members"`); + await queryRunner.query(`DROP INDEX "public"."IDX_94e1183284db3e697031eb7775"`); + await queryRunner.query(`DROP INDEX "public"."IDX_a28a2c27629ac06a41720d01c3"`); + await queryRunner.query(`DROP TABLE "user_following"`); + await queryRunner.query(`DROP INDEX "public"."IDX_da722d93356ae3119d6be40d98"`); + await queryRunner.query(`DROP INDEX "public"."IDX_a59d62cda8101214445e295cdc"`); + await queryRunner.query(`DROP TABLE "user_followers"`); + await queryRunner.query(`DROP TABLE "group"`); + await queryRunner.query(`DROP TABLE "users"`); + } + +} diff --git a/platforms/eReputation-api/src/database/migrations/1761324658339-migration.ts b/platforms/eReputation-api/src/database/migrations/1761324658339-migration.ts new file mode 100644 index 000000000..10eeb80e1 --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1761324658339-migration.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1761324658339 implements MigrationInterface { + name = 'Migration1761324658339' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "references" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "targetType" character varying NOT NULL, "targetId" character varying NOT NULL, "targetName" character varying NOT NULL, "content" text NOT NULL, "referenceType" character varying NOT NULL, "numericScore" integer, "authorId" uuid NOT NULL, "status" character varying, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_795ec632ca1153bf5ec99d656e5" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "calculations" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "targetType" character varying NOT NULL, "targetId" character varying NOT NULL, "targetName" character varying NOT NULL, "userValues" text NOT NULL, "calculatedScore" double precision NOT NULL, "calculationDetails" text, "calculatorId" uuid NOT NULL, "status" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_a57a12855a44935db91c2533b71" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "references" ADD CONSTRAINT "FK_98fc39160edc395b34d1960ed87" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "calculations" ADD CONSTRAINT "FK_9bc689175cd305ff8403d64b4a6" FOREIGN KEY ("calculatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "calculations" DROP CONSTRAINT "FK_9bc689175cd305ff8403d64b4a6"`); + await queryRunner.query(`ALTER TABLE "references" DROP CONSTRAINT "FK_98fc39160edc395b34d1960ed87"`); + await queryRunner.query(`DROP TABLE "calculations"`); + await queryRunner.query(`DROP TABLE "references"`); + } + +} diff --git a/platforms/eReputation-api/src/database/migrations/1763549545131-migration.ts b/platforms/eReputation-api/src/database/migrations/1763549545131-migration.ts new file mode 100644 index 000000000..1a06e1682 --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1763549545131-migration.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1763549545131 implements MigrationInterface { + name = 'Migration1763549545131' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "public"."polls_mode_enum" AS ENUM('normal', 'point', 'rank')`); + await queryRunner.query(`CREATE TYPE "public"."polls_visibility_enum" AS ENUM('public', 'private')`); + await queryRunner.query(`CREATE TABLE "polls" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "title" character varying(255) NOT NULL, "mode" "public"."polls_mode_enum" NOT NULL DEFAULT 'normal', "visibility" "public"."polls_visibility_enum" NOT NULL DEFAULT 'public', "options" text NOT NULL, "deadline" TIMESTAMP, "deadlineMessageSent" boolean NOT NULL DEFAULT false, "creatorId" uuid NOT NULL, "groupId" uuid, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_b9bbb8fc7b142553c518ddffbb6" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "votes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "pollId" uuid NOT NULL, "userId" uuid NOT NULL, "voterId" character varying(255) NOT NULL, "data" jsonb NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_f3d9fd4a0af865152c3f59db8ff" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "polls" ADD CONSTRAINT "FK_57e3240e3361bf5e1400ba0191d" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "votes" ADD CONSTRAINT "FK_2e40638d2d3b898da1af363837c" FOREIGN KEY ("pollId") REFERENCES "polls"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "votes" ADD CONSTRAINT "FK_5169384e31d0989699a318f3ca4" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "votes" DROP CONSTRAINT "FK_5169384e31d0989699a318f3ca4"`); + await queryRunner.query(`ALTER TABLE "votes" DROP CONSTRAINT "FK_2e40638d2d3b898da1af363837c"`); + await queryRunner.query(`ALTER TABLE "polls" DROP CONSTRAINT "FK_57e3240e3361bf5e1400ba0191d"`); + await queryRunner.query(`DROP TABLE "votes"`); + await queryRunner.query(`DROP TABLE "polls"`); + await queryRunner.query(`DROP TYPE "public"."polls_visibility_enum"`); + await queryRunner.query(`DROP TYPE "public"."polls_mode_enum"`); + } + +} diff --git a/platforms/eReputation-api/src/database/migrations/1763622721610-migration.ts b/platforms/eReputation-api/src/database/migrations/1763622721610-migration.ts new file mode 100644 index 000000000..c7ec09c62 --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1763622721610-migration.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1763622721610 implements MigrationInterface { + name = 'Migration1763622721610' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "vote_reputation_results" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "pollId" uuid NOT NULL, "groupId" uuid NOT NULL, "results" jsonb NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_cbdd7662c8c8bdc281086fba0ed" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "public"."polls_votingweight_enum" AS ENUM('1p1v', 'ereputation')`); + await queryRunner.query(`ALTER TABLE "polls" ADD "votingWeight" "public"."polls_votingweight_enum" NOT NULL DEFAULT '1p1v'`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ADD CONSTRAINT "FK_f3dd3537bf6ab2e12be9a9ec7f5" FOREIGN KEY ("pollId") REFERENCES "polls"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ADD CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "vote_reputation_results" DROP CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5"`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" DROP CONSTRAINT "FK_f3dd3537bf6ab2e12be9a9ec7f5"`); + await queryRunner.query(`ALTER TABLE "polls" DROP COLUMN "votingWeight"`); + await queryRunner.query(`DROP TYPE "public"."polls_votingweight_enum"`); + await queryRunner.query(`DROP TABLE "vote_reputation_results"`); + } + +} diff --git a/platforms/eReputation-api/src/database/migrations/1763623302938-migration.ts b/platforms/eReputation-api/src/database/migrations/1763623302938-migration.ts new file mode 100644 index 000000000..212e01cfd --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1763623302938-migration.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1763623302938 implements MigrationInterface { + name = 'Migration1763623302938' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" DROP CONSTRAINT "FK_57e3240e3361bf5e1400ba0191d"`); + await queryRunner.query(`ALTER TABLE "polls" DROP COLUMN "creatorId"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" ADD "creatorId" uuid NOT NULL`); + await queryRunner.query(`ALTER TABLE "polls" ADD CONSTRAINT "FK_57e3240e3361bf5e1400ba0191d" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + +} diff --git a/platforms/eReputation-api/src/database/migrations/1763629617081-migration.ts b/platforms/eReputation-api/src/database/migrations/1763629617081-migration.ts new file mode 100644 index 000000000..5034c37e7 --- /dev/null +++ b/platforms/eReputation-api/src/database/migrations/1763629617081-migration.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1763629617081 implements MigrationInterface { + name = 'Migration1763629617081' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "messages" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "text" text NOT NULL, "isSystemMessage" boolean NOT NULL DEFAULT false, "voteId" uuid, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "senderId" uuid, "groupId" uuid, CONSTRAINT "PK_18325f38ae6de43878487eff986" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "messages" ADD CONSTRAINT "FK_2db9cf2b3ca111742793f6c37ce" FOREIGN KEY ("senderId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "messages" ADD CONSTRAINT "FK_438f09ab5b4bbcd27683eac2a5e" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_438f09ab5b4bbcd27683eac2a5e"`); + await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_2db9cf2b3ca111742793f6c37ce"`); + await queryRunner.query(`DROP TABLE "messages"`); + } + +} diff --git a/platforms/eReputation-api/src/index.ts b/platforms/eReputation-api/src/index.ts new file mode 100644 index 000000000..359688874 --- /dev/null +++ b/platforms/eReputation-api/src/index.ts @@ -0,0 +1,122 @@ +import "reflect-metadata"; +import path from "node:path"; +import cors from "cors"; +import { config } from "dotenv"; +import express from "express"; +import "./types/express"; +import { AppDataSource } from "./database/data-source"; +import { UserController } from "./controllers/UserController"; +import { AuthController } from "./controllers/AuthController"; +import { WebhookController } from "./controllers/WebhookController"; +import { ReferenceController } from "./controllers/ReferenceController"; +import { CalculationController } from "./controllers/CalculationController"; +import { PlatformController } from "./controllers/PlatformController"; +import { GroupController } from "./controllers/GroupController"; +import { DashboardController } from "./controllers/DashboardController"; +import { authMiddleware, authGuard } from "./middleware/auth"; +import { adapter } from "./web3adapter/watchers/subscriber"; +import { JobQueueService } from "./services/JobQueueService"; +import { PollReputationWorker } from "./workers/PollReputationWorker"; + +config({ path: path.resolve(__dirname, "../../../.env") }); + +const app = express(); +const port = process.env.PORT || 8765; + +// Initialize database connection and adapter +AppDataSource.initialize() + .then(async () => { + console.log("Database connection established"); + console.log("Web3 adapter initialized"); + }) + .catch((error: unknown) => { + console.error("Error during initialization:", error); + process.exit(1); + }); + +// Middleware +app.use( + cors({ + origin: "*", + methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], + allowedHeaders: [ + "Content-Type", + "Authorization", + "X-Webhook-Signature", + "X-Webhook-Timestamp", + ], + credentials: true, + }), +); +app.use(express.json({ limit: "50mb" })); +app.use(express.urlencoded({ limit: "50mb", extended: true })); + +// Initialize job queue and worker +const jobQueueService = new JobQueueService(); +const pollReputationWorker = new PollReputationWorker(jobQueueService); + +// Controllers +const userController = new UserController(); +const authController = new AuthController(); +const webhookController = new WebhookController(jobQueueService); +const referenceController = new ReferenceController(); +const calculationController = new CalculationController(); +const platformController = new PlatformController(); +const groupController = new GroupController(); +const dashboardController = new DashboardController(); + +// Health check endpoint +app.get("/api/health", (req, res) => { + res.json({ + status: "ok", + timestamp: new Date().toISOString(), + services: { + database: AppDataSource.isInitialized ? "connected" : "disconnected", + web3adapter: "ready" + } + }); +}); + +// Public routes (no auth required) +app.get("/api/auth/offer", authController.getOffer); +app.post("/api/auth", authController.login); +app.get("/api/auth/sessions/:id", authController.sseStream); + +// Webhook route (no auth required) +app.post("/api/webhook", webhookController.handleWebhook); + +// Platform routes (public) +app.get("/api/platforms", platformController.getPlatforms); +app.get("/api/platforms/search", platformController.searchPlatforms); + +// Protected routes (auth required) +app.use(authMiddleware); // Apply auth middleware to all routes below + +// User routes +app.get("/api/users/me", authGuard, userController.currentUser); +app.get("/api/users/search", userController.search); +app.get("/api/users/:id", authGuard, userController.getProfileById); +app.patch("/api/users", authGuard, userController.updateProfile); + +// Group routes +app.get("/api/groups/search", groupController.search); + +// Dashboard routes +app.get("/api/dashboard/stats", authGuard, dashboardController.getStats); +app.get("/api/dashboard/activities", authGuard, dashboardController.getActivities); + +// Reference routes +app.post("/api/references", authGuard, referenceController.createReference); +app.get("/api/references/target/:targetType/:targetId", referenceController.getReferencesForTarget); +app.get("/api/references/my", authGuard, referenceController.getUserReferences); +app.patch("/api/references/:referenceId/revoke", authGuard, referenceController.revokeReference); + +// Calculation routes +app.post("/api/reputation/calculate", authGuard, calculationController.calculateReputation); +app.get("/api/reputation/calculations/:calculationId", authGuard, calculationController.getCalculationResult); +app.get("/api/reputation/calculations/my", authGuard, calculationController.getUserCalculations); + +// Start server +app.listen(port, () => { + console.log(`eReputation API server running on port ${port}`); +}); diff --git a/platforms/eReputation-api/src/middleware/auth.ts b/platforms/eReputation-api/src/middleware/auth.ts new file mode 100644 index 000000000..bfb1426f7 --- /dev/null +++ b/platforms/eReputation-api/src/middleware/auth.ts @@ -0,0 +1,41 @@ +import type { NextFunction, Request, Response } from "express"; +import { AppDataSource } from "../database/data-source"; +import { User } from "../database/entities/User"; +import { verifyToken, AuthTokenPayload } from "../utils/jwt"; + +export const authMiddleware = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + const authHeader = req.headers.authorization; + + if (!authHeader?.startsWith("Bearer ")) { + return next(); + } + + const token = authHeader.split(" ")[1]; + const decoded: AuthTokenPayload = verifyToken(token); + + const userRepository = AppDataSource.getRepository(User); + const user = await userRepository.findOneBy({ id: decoded.userId }); + + if (!user) { + return res.status(401).json({ error: "User not found" }); + } + + req.user = user; + next(); + } catch (error) { + console.error("Auth middleware error:", error); + res.status(401).json({ error: "Invalid token" }); + } +}; + +export const authGuard = (req: Request, res: Response, next: NextFunction) => { + if (!req.user) { + return res.status(401).json({ error: "Authentication required" }); + } + next(); +}; diff --git a/platforms/eReputation-api/src/services/CalculationService.ts b/platforms/eReputation-api/src/services/CalculationService.ts new file mode 100644 index 000000000..0d7758114 --- /dev/null +++ b/platforms/eReputation-api/src/services/CalculationService.ts @@ -0,0 +1,184 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Calculation } from "../database/entities/Calculation"; +import { Reference } from "../database/entities/Reference"; +import OpenAI from "openai"; + +export class CalculationService { + calculationRepository: Repository; + referenceRepository: Repository; + private openai: OpenAI; + + constructor() { + this.calculationRepository = AppDataSource.getRepository(Calculation); + this.referenceRepository = AppDataSource.getRepository(Reference); + + if (!process.env.OPENAI_API_KEY) { + throw new Error("OPENAI_API_KEY environment variable is required"); + } + + this.openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + } + + async createCalculation(data: { + targetType: string; + targetId: string; + targetName: string; + userValues: string; + calculatorId: string; + }): Promise { + const calculation = this.calculationRepository.create({ + ...data, + status: "processing", + calculatedScore: 0 + }); + return await this.calculationRepository.save(calculation); + } + + async calculateReputation(calculationId: string): Promise { + const calculation = await this.calculationRepository.findOne({ + where: { id: calculationId } + }); + + if (!calculation) { + throw new Error("Calculation not found"); + } + + try { + // Get all references for this target + // For self-evaluation, references are stored with targetType "user", not "self" + const referenceTargetType = calculation.targetType === "self" ? "user" : calculation.targetType; + const references = await this.referenceRepository.find({ + where: { + targetType: referenceTargetType, + targetId: calculation.targetId, + status: "signed" // Only include signed references + }, + relations: ["author"] + }); + + if (references.length === 0) { + calculation.calculatedScore = 0; + calculation.status = "complete"; + calculation.calculationDetails = JSON.stringify({ + message: "No references found for this target", + referencesCount: 0 + }); + return await this.calculationRepository.save(calculation); + } + + // Prepare data for OpenAI + const referencesData = references.map(ref => ({ + content: ref.content, + numericScore: ref.numericScore, + author: ref.author.ename || ref.author.name || "Anonymous" + })); + + const prompt = this.buildPrompt(calculation.userValues, referencesData, calculation.targetName); + + const response = await this.openai.chat.completions.create({ + model: "gpt-4", + messages: [ + { + role: "system", + content: "You are an expert reputation analyst. You analyze references and calculate reputation scores based on user values. Always respond with a valid JSON object containing a score (1-5) and explanation." + }, + { + role: "user", + content: prompt + } + ], + temperature: 0.3, + max_tokens: 1000 + }); + + const aiResponseContent = response.choices[0].message.content; + if (!aiResponseContent) { + throw new Error("AI returned empty response"); + } + + let result; + try { + result = JSON.parse(aiResponseContent); + } catch (parseError) { + throw new Error(`Failed to parse AI response: ${parseError instanceof Error ? parseError.message : 'Invalid JSON'}`); + } + + // Validate that we got a proper score from AI + if (!result.score || typeof result.score !== 'number') { + throw new Error("Invalid AI response: missing or invalid score"); + } + + calculation.calculatedScore = Math.max(1, Math.min(5, result.score || 0)); + calculation.status = "complete"; + calculation.calculationDetails = JSON.stringify({ + explanation: result.explanation || "No explanation provided", + referencesCount: references.length, + referencesAnalyzed: referencesData.length, + userValues: calculation.userValues + }); + + return await this.calculationRepository.save(calculation); + + } catch (error) { + console.error("Error calculating reputation:", error); + // Delete the calculation if there was an error getting the AI score + // Don't save failed calculations to the database + await this.calculationRepository.remove(calculation); + throw error; // Re-throw so controller can handle it + } + } + + private buildPrompt(userValues: string, references: any[], targetName: string): string { + return ` +You are analyzing the reputation of "${targetName}" based on the following user values and references. + +USER VALUES (what the evaluator cares about): +${userValues} + +REFERENCES (what others have said about ${targetName}): +${references.map(ref => ` +Reference from ${ref.author} (Score: ${ref.numericScore}/5): +"${ref.content}" +`).join('\n')} + +TASK: +Based on the user's values and the references provided, calculate a reputation score from 1-5 for ${targetName}. + +IMPORTANT: The score must be between 1 and 5 (inclusive). Use the full range: +- 1 = Very poor reputation +- 2 = Poor reputation +- 3 = Average reputation +- 4 = Good reputation +- 5 = Excellent reputation + +Consider: +1. How well the references align with what the user values +2. The quality and consistency of the references +3. The numeric scores given by the reference authors +4. Whether the references address the user's specific values + +Respond with a JSON object in this exact format: +{ + "score": , + "explanation": "" +} + `.trim(); + } + + async getCalculationById(id: string): Promise { + return await this.calculationRepository.findOne({ + where: { id }, + relations: ["calculator"] + }); + } + + async getUserCalculations(calculatorId: string): Promise { + return await this.calculationRepository.find({ + where: { calculatorId }, + order: { createdAt: "DESC" } + }); + } +} diff --git a/platforms/eReputation-api/src/services/GroupService.ts b/platforms/eReputation-api/src/services/GroupService.ts new file mode 100644 index 000000000..b3c12175b --- /dev/null +++ b/platforms/eReputation-api/src/services/GroupService.ts @@ -0,0 +1,78 @@ +import { Repository, In } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Group } from "../database/entities/Group"; +import { User } from "../database/entities/User"; + +export class GroupService { + groupRepository: Repository; + userRepository: Repository; + + constructor() { + this.groupRepository = AppDataSource.getRepository(Group); + this.userRepository = AppDataSource.getRepository(User); + } + + async getGroupById(id: string): Promise { + return await this.groupRepository.findOne({ + where: { id }, + relations: ["members", "admins", "participants"] + }); + } + + async createGroup( + name: string, + description: string, + owner: string, + adminIds: string[] = [], + participantIds: string[] = [], + charter?: string + ): Promise { + const group = this.groupRepository.create({ + name, + description, + owner, + charter, + }); + + // Add admins + if (adminIds.length > 0) { + const admins = await this.userRepository.findBy({ id: In(adminIds) }); + group.admins = admins; + } + + // Add participants + if (participantIds.length > 0) { + const participants = await this.userRepository.findBy({ id: In(participantIds) }); + group.participants = participants; + } + + return await this.groupRepository.save(group); + } + + async updateGroup(id: string, updateData: Partial): Promise { + await this.groupRepository.update(id, updateData); + const updatedGroup = await this.groupRepository.findOneBy({ id }); + if (!updatedGroup) { + throw new Error("Group not found after update"); + } + return updatedGroup; + } + + async getUserGroups(userId: string): Promise { + return await this.groupRepository + .createQueryBuilder("group") + .leftJoinAndSelect("group.members", "members") + .leftJoinAndSelect("group.admins", "admins") + .leftJoinAndSelect("group.participants", "participants") + .where("members.id = :userId OR admins.id = :userId OR participants.id = :userId", { userId }) + .getMany(); + } + + async searchGroups(query: string, limit: number = 10): Promise { + return await this.groupRepository + .createQueryBuilder("group") + .where("group.name ILIKE :query OR group.description ILIKE :query", { query: `%${query}%` }) + .limit(limit) + .getMany(); + } +} diff --git a/platforms/eReputation-api/src/services/JobQueueService.ts b/platforms/eReputation-api/src/services/JobQueueService.ts new file mode 100644 index 000000000..6dba43faf --- /dev/null +++ b/platforms/eReputation-api/src/services/JobQueueService.ts @@ -0,0 +1,100 @@ +import { Queue, QueueOptions } from "bullmq"; +import Redis from "ioredis"; + +export interface PollReputationJobData { + pollId: string; + eventId: string; // For idempotency + groupId: string; +} + +export class JobQueueService { + private queue: Queue; + private redis: Redis; + + constructor() { + // Create Redis connection + this.redis = new Redis({ + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379", 10), + maxRetriesPerRequest: null, + }); + + // Create BullMQ queue + const queueOptions: QueueOptions = { + connection: { + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379", 10), + }, + defaultJobOptions: { + attempts: 3, + backoff: { + type: "exponential", + delay: 2000, + }, + removeOnComplete: { + age: 3600, // Keep completed jobs for 1 hour + count: 1000, + }, + removeOnFail: { + age: 86400, // Keep failed jobs for 24 hours + }, + }, + }; + + this.queue = new Queue("poll-reputation-calculation", queueOptions); + } + + /** + * Enqueue a poll reputation calculation job with deduplication + */ + async enqueuePollReputationJob( + pollId: string, + groupId: string, + eventId: string + ): Promise { + try { + // Use pollId as the job ID for deduplication (same poll = same job) + const jobId = `poll-reputation:${pollId}`; + + // Check if job already exists or was recently processed + const existingJob = await this.queue.getJob(jobId); + if (existingJob) { + const state = await existingJob.getState(); + if (state === "active" || state === "waiting" || state === "delayed") { + // Job already queued, skip + return; + } + } + + // Add job with deduplication + await this.queue.add( + "calculate-poll-reputation", + { + pollId, + groupId, + eventId, + }, + { + jobId, // Use pollId as job ID for deduplication + removeOnComplete: true, + removeOnFail: false, + } + ); + } catch (error) { + throw error; + } + } + + /** + * Close the queue connection + */ + async close(): Promise { + await this.queue.close(); + await this.redis.quit(); + } + + getQueue(): Queue { + return this.queue; + } +} + diff --git a/platforms/eReputation-api/src/services/MessageService.ts b/platforms/eReputation-api/src/services/MessageService.ts new file mode 100644 index 000000000..ea18785d7 --- /dev/null +++ b/platforms/eReputation-api/src/services/MessageService.ts @@ -0,0 +1,77 @@ +import { AppDataSource } from "../database/data-source"; +import { Message } from "../database/entities/Message"; +import { User } from "../database/entities/User"; +import { Group } from "../database/entities/Group"; + +export class MessageService { + public messageRepository = AppDataSource.getRepository(Message); + private userRepository = AppDataSource.getRepository(User); + private groupRepository = AppDataSource.getRepository(Group); + + /** + * Create a regular message from a user + */ + async createMessage(messageData: { + text: string; + senderId: string; + groupId: string; + }): Promise { + const sender = await this.userRepository.findOne({ where: { id: messageData.senderId } }); + const group = await this.groupRepository.findOne({ where: { id: messageData.groupId } }); + + if (!sender || !group) { + throw new Error("Sender or group not found"); + } + + const message = this.messageRepository.create({ + text: messageData.text, + sender, + group, + isSystemMessage: false, + }); + + return await this.messageRepository.save(message); + } + + /** + * Create a system message (no sender, automatically synced to eVault) + */ + async createSystemMessage(messageData: { + text: string; + groupId: string; + voteId?: string; + }): Promise { + const group = await this.groupRepository.findOne({ where: { id: messageData.groupId } }); + + if (!group) { + throw new Error("Group not found"); + } + + // Add system message prefix to identify it + const systemText = `$$system-message$$ ${messageData.text}`; + + const message = this.messageRepository.create({ + text: systemText, + sender: undefined, // No sender for system messages + group, + isSystemMessage: true, + voteId: messageData.voteId, + }); + + return await this.messageRepository.save(message); + } + + /** + * Get system messages for a group + */ + async getGroupSystemMessages(groupId: string): Promise { + return await this.messageRepository.find({ + where: { + group: { id: groupId }, + isSystemMessage: true + }, + order: { createdAt: "DESC" } + }); + } +} + diff --git a/platforms/eReputation-api/src/services/PlatformService.ts b/platforms/eReputation-api/src/services/PlatformService.ts new file mode 100644 index 000000000..c6b323efb --- /dev/null +++ b/platforms/eReputation-api/src/services/PlatformService.ts @@ -0,0 +1,157 @@ +import axios from "axios"; + +export interface Platform { + id: string; + name: string; + description: string; + category: string; + logoUrl?: string; + url?: string; + appStoreUrl?: string; + playStoreUrl?: string; +} + +export class PlatformService { + private marketplaceData: Platform[] = [ + { + id: "eid-wallet", + name: "eID for W3DS", + description: "Secure digital identity wallet for W3DS. Maintain sovereign control over your digital identity.", + category: "Identity", + logoUrl: "/eid-w3ds.png", + appStoreUrl: "https://apps.apple.com/in/app/eid-for-w3ds/id6747748667", + playStoreUrl: "https://play.google.com/store/apps/details?id=foundation.metastate.eid_wallet" + }, + { + id: "blabsy", + name: "Blabsy", + description: "Micro blogging first style application for sharing thoughts across the W3DS ecosystem.", + category: "Social", + logoUrl: "/blabsy.svg", + url: "http://localhost:4444" + }, + { + id: "pictique", + name: "Pictique", + description: "Photo sharing first style application for sharing moments across the W3DS ecosystem.", + category: "Social", + logoUrl: "/pictique.svg", + url: "http://localhost:1111" + }, + { + id: "evoting", + name: "eVoting", + description: "Secure, transparent, and verifiable electronic voting platform with cryptographic guarantees.", + category: "Governance", + logoUrl: "/evoting.png", + url: "http://localhost:7777" + }, + { + id: "group-charter", + name: "Charter Manager", + description: "Define rules, manage memberships, and ensure transparent governance for your communities.", + category: "Governance", + logoUrl: "/charter.png", + url: "http://localhost:5555" + }, + { + id: "dreamsync", + name: "DreamSync", + description: "Individual discovery platform, find people of interest across the W3DS ecosystem.", + category: "Wellness", + logoUrl: undefined, + url: "https://dreamsync.w3ds.metastate.foundation" + } + ]; + + async getActivePlatforms(): Promise { + try { + const registryUrl = process.env.PUBLIC_REGISTRY_URL || "http://localhost:3000"; + const response = await axios.get(`${registryUrl}/platforms`); + + if (response.data && Array.isArray(response.data)) { + // Map registry URLs to marketplace data + return response.data.map((url: string) => { + const platformId = this.extractPlatformIdFromUrl(url); + const marketplacePlatform = this.marketplaceData.find(p => p.id === platformId); + + if (marketplacePlatform) { + return { + ...marketplacePlatform, + url: url // Use the actual registry URL + }; + } + + // Fallback for unknown platforms + return { + id: platformId, + name: platformId.charAt(0).toUpperCase() + platformId.slice(1), + description: `Platform at ${url}`, + category: "Unknown", + url: url + }; + }); + } + + // Fallback to marketplace data if registry is not available + console.warn("Registry not available, using marketplace data"); + return this.marketplaceData; + + } catch (error) { + console.error("Error fetching platforms from registry:", error); + // Fallback to marketplace data + return this.marketplaceData; + } + } + + async searchPlatforms(query: string): Promise { + const platforms = await this.getActivePlatforms(); + const lowercaseQuery = query.toLowerCase(); + + return platforms.filter(platform => + platform.name.toLowerCase().includes(lowercaseQuery) || + platform.description.toLowerCase().includes(lowercaseQuery) || + platform.category.toLowerCase().includes(lowercaseQuery) + ); + } + + private extractPlatformIdFromUrl(url: string): string { + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname; + + // Handle localhost URLs for testing (including local network IPs) + if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.')) { + const port = urlObj.port; + // Map localhost ports to platform IDs + switch (port) { + case '4444': return 'blabsy'; + case '1111': return 'pictique'; + case '5555': return 'group-charter'; + case '7777': return 'evoting'; + case '8765': return 'ereputation'; + default: return `platform-${port}`; + } + } + + // Extract from production URLs + if (hostname.includes('blabsy')) return 'blabsy'; + if (hostname.includes('pictique')) return 'pictique'; + if (hostname.includes('evoting')) return 'evoting'; + if (hostname.includes('charter')) return 'group-charter'; + if (hostname.includes('dreamsync')) return 'dreamsync'; + if (hostname.includes('ereputation')) return 'ereputation'; + + // Fallback: use subdomain or path + const parts = hostname.split('.'); + if (parts.length > 0) { + return parts[0]; + } + + return 'unknown-platform'; + } catch (error) { + console.error("Error extracting platform ID from URL:", url, error); + return 'unknown-platform'; + } + } +} diff --git a/platforms/eReputation-api/src/services/PollService.ts b/platforms/eReputation-api/src/services/PollService.ts new file mode 100644 index 000000000..b47327750 --- /dev/null +++ b/platforms/eReputation-api/src/services/PollService.ts @@ -0,0 +1,60 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Poll } from "../database/entities/Poll"; + +export class PollService { + pollRepository: Repository; + + constructor() { + this.pollRepository = AppDataSource.getRepository(Poll); + } + + async getPollById(id: string): Promise { + return await this.pollRepository.findOne({ + where: { id }, + relations: ["votes"] + }); + } + + async createPoll( + title: string, + mode: "normal" | "point" | "rank", + visibility: "public" | "private", + options: string[], + groupId?: string | null, + deadline?: Date | null + ): Promise { + const poll = this.pollRepository.create({ + title, + mode, + visibility, + options, + groupId: groupId || null, + deadline: deadline || null + }); + + return await this.pollRepository.save(poll); + } + + async updatePoll(id: string, updateData: Partial): Promise { + await this.pollRepository.update(id, updateData); + const updatedPoll = await this.pollRepository.findOne({ where: { id } }); + if (!updatedPoll) { + throw new Error("Poll not found after update"); + } + return updatedPoll; + } + + async deletePoll(id: string): Promise { + await this.pollRepository.delete(id); + } + + async getPollsByGroup(groupId: string): Promise { + return await this.pollRepository.find({ + where: { groupId }, + relations: ["votes"], + order: { createdAt: "DESC" } + }); + } +} + diff --git a/platforms/eReputation-api/src/services/ReferenceService.ts b/platforms/eReputation-api/src/services/ReferenceService.ts new file mode 100644 index 000000000..ff328691d --- /dev/null +++ b/platforms/eReputation-api/src/services/ReferenceService.ts @@ -0,0 +1,55 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Reference } from "../database/entities/Reference"; + +export class ReferenceService { + referenceRepository: Repository; + + constructor() { + this.referenceRepository = AppDataSource.getRepository(Reference); + } + + async createReference(data: { + targetType: string; + targetId: string; + targetName: string; + content: string; + referenceType: string; + numericScore?: number; + authorId: string; + }): Promise { + const reference = this.referenceRepository.create({ + ...data, + status: "signed" + }); + return await this.referenceRepository.save(reference); + } + + async getReferencesForTarget(targetType: string, targetId: string): Promise { + return await this.referenceRepository.find({ + where: { targetType, targetId }, + relations: ["author"], + order: { createdAt: "DESC" } + }); + } + + async getUserReferences(authorId: string): Promise { + return await this.referenceRepository.find({ + where: { authorId }, + order: { createdAt: "DESC" } + }); + } + + async revokeReference(referenceId: string, authorId: string): Promise { + const reference = await this.referenceRepository.findOne({ + where: { id: referenceId, authorId } + }); + + if (!reference) { + return null; + } + + reference.status = "revoked"; + return await this.referenceRepository.save(reference); + } +} diff --git a/platforms/eReputation-api/src/services/UserService.ts b/platforms/eReputation-api/src/services/UserService.ts new file mode 100644 index 000000000..898ff5e24 --- /dev/null +++ b/platforms/eReputation-api/src/services/UserService.ts @@ -0,0 +1,66 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { User } from "../database/entities/User"; + +export class UserService { + userRepository: Repository; + + constructor() { + this.userRepository = AppDataSource.getRepository(User); + } + + async findUser(ename: string): Promise { + // Only find user, don't create - users should only be created via webhooks + return this.getUserByEname(ename); + } + + async getUserByEname(ename: string): Promise { + // Strip @ prefix if present for database lookup + const cleanEname = this.stripEnamePrefix(ename); + return this.userRepository.findOne({ + where: { ename: cleanEname }, + }); + } + + async getUserById(id: string): Promise { + return await this.userRepository.findOne({ + where: { id }, + relations: ["followers", "following"] + }); + } + + async createBlankUser(w3id: string): Promise { + // Strip @ prefix if present before storing + const cleanEname = this.stripEnamePrefix(w3id); + const user = this.userRepository.create({ + ename: cleanEname, + }); + return await this.userRepository.save(user); + } + + async updateUser(id: string, updateData: Partial): Promise { + await this.userRepository.update(id, updateData); + const updatedUser = await this.userRepository.findOneBy({ id }); + if (!updatedUser) { + throw new Error("User not found after update"); + } + return updatedUser; + } + + async searchUsers(query: string, limit: number = 10): Promise { + return await this.userRepository + .createQueryBuilder("user") + .where("user.name ILIKE :query OR user.handle ILIKE :query", { query: `%${query}%` }) + .limit(limit) + .getMany(); + } + + /** + * Strips the @ prefix from ename if present + * @param ename - The ename with or without @ prefix + * @returns The ename without @ prefix + */ + private stripEnamePrefix(ename: string): string { + return ename.startsWith('@') ? ename.slice(1) : ename; + } +} diff --git a/platforms/eReputation-api/src/services/VoteService.ts b/platforms/eReputation-api/src/services/VoteService.ts new file mode 100644 index 000000000..10030e9f0 --- /dev/null +++ b/platforms/eReputation-api/src/services/VoteService.ts @@ -0,0 +1,83 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Vote, VoteDataByMode } from "../database/entities/Vote"; +import { Poll } from "../database/entities/Poll"; +import { User } from "../database/entities/User"; + +export class VoteService { + voteRepository: Repository; + private pollRepository: Repository; + private userRepository: Repository; + + constructor() { + this.voteRepository = AppDataSource.getRepository(Vote); + this.pollRepository = AppDataSource.getRepository(Poll); + this.userRepository = AppDataSource.getRepository(User); + } + + /** + * Check if a poll is eReputation-weighted + */ + isEReputationWeighted(poll: Poll): boolean { + if (!poll.groupId) { + return false; // Must be a group poll + } + + // Check votingWeight column instead of title + return poll.votingWeight === "ereputation"; + } + + async getVoteById(id: string): Promise { + return await this.voteRepository.findOne({ + where: { id }, + relations: ["poll", "user"] + }); + } + + async getVotesByPoll(pollId: string): Promise { + return await this.voteRepository.find({ + where: { pollId }, + relations: ["user"] + }); + } + + async createVote( + pollId: string, + userId: string, + voterId: string, + data: VoteDataByMode + ): Promise { + const poll = await this.pollRepository.findOne({ where: { id: pollId } }); + if (!poll) { + throw new Error("Poll not found"); + } + + const user = await this.userRepository.findOne({ where: { id: userId } }); + if (!user) { + throw new Error("User not found"); + } + + const vote = this.voteRepository.create({ + pollId, + userId, + voterId, + data + }); + + return await this.voteRepository.save(vote); + } + + async updateVote(id: string, data: VoteDataByMode): Promise { + const vote = await this.voteRepository.findOne({ where: { id } }); + if (!vote) { + throw new Error("Vote not found"); + } + + vote.data = data; + return await this.voteRepository.save(vote); + } + + async deleteVote(id: string): Promise { + await this.voteRepository.delete(id); + } +} diff --git a/platforms/eReputation-api/src/services/VotingReputationService.ts b/platforms/eReputation-api/src/services/VotingReputationService.ts new file mode 100644 index 000000000..0b1292d8b --- /dev/null +++ b/platforms/eReputation-api/src/services/VotingReputationService.ts @@ -0,0 +1,508 @@ +import { Repository } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { Group } from "../database/entities/Group"; +import { Reference } from "../database/entities/Reference"; +import { VoteReputationResult, MemberReputation } from "../database/entities/VoteReputationResult"; +import OpenAI from "openai"; + +export class VotingReputationService { + private groupRepository: Repository; + private referenceRepository: Repository; + private voteReputationResultRepository: Repository; + private openai: OpenAI; + + constructor() { + this.groupRepository = AppDataSource.getRepository(Group); + this.referenceRepository = AppDataSource.getRepository(Reference); + this.voteReputationResultRepository = AppDataSource.getRepository(VoteReputationResult); + + if (!process.env.OPENAI_API_KEY) { + throw new Error("OPENAI_API_KEY environment variable is required"); + } + + this.openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + } + + /** + * Calculate eReputation for all group members based on charter rules + */ + async calculateGroupMemberReputations( + groupId: string, + charter: string + ): Promise { + // Get group with all members + const group = await this.groupRepository.findOne({ + where: { id: groupId }, + relations: ["participants", "admins", "members"] + }); + + if (!group) { + throw new Error("Group not found"); + } + + // Combine all members (participants + admins + members, removing duplicates) + const allMembers = new Map(); + + if (group.participants) { + group.participants.forEach(member => { + allMembers.set(member.id, member); + }); + } + + if (group.admins) { + group.admins.forEach(admin => { + allMembers.set(admin.id, admin); + }); + } + + if (group.members) { + group.members.forEach(member => { + allMembers.set(member.id, member); + }); + } + + const members = Array.from(allMembers.values()); + + // Filter out members without ename (they can't be used for eReputation voting) + const membersWithEname = members.filter(member => member.ename); + + if (membersWithEname.length === 0) { + console.log(`⚠️ No members with ename found in group ${groupId}`); + if (members.length > 0) { + console.log(` Found ${members.length} members but none have ename set`); + } + return []; + } + + if (membersWithEname.length < members.length) { + console.log(`⚠️ Filtered out ${members.length - membersWithEname.length} members without ename`); + } + + console.log(`📋 Starting eReputation calculation for ${membersWithEname.length} group members (with ename):`); + membersWithEname.forEach((member, index) => { + console.log(` ${index + 1}. ${member.name || "Unknown"} (ename: ${member.ename}, ID: ${member.id})`); + }); + + // Fetch all references for all members in parallel + console.log(`\n🔄 Fetching references for all ${membersWithEname.length} members...`); + const memberReferencesMap = new Map>(); + + const referencePromises = membersWithEname.map(async (member) => { + const references = await this.referenceRepository.find({ + where: { + targetType: "user", + targetId: member.id, + status: "signed" + }, + relations: ["author"] + }); + + const referencesData = references.map(ref => ({ + content: ref.content, + numericScore: ref.numericScore, + author: ref.author.ename || ref.author.name || "Anonymous" + })); + + memberReferencesMap.set(member.id, referencesData); + return { memberId: member.id, count: references.length }; + }); + + const referenceResults = await Promise.all(referencePromises); + referenceResults.forEach(({ memberId, count }) => { + const member = membersWithEname.find(m => m.id === memberId); + const memberName = member ? (member.name || "Unknown") : "Unknown"; + const memberEname = member ? member.ename : "Unknown"; + console.log(` → ${memberName} (${memberEname}): ${count} references`); + }); + + // Calculate all reputations in a single OpenAI call + console.log(`\n🔄 Calling OpenAI API once for all ${membersWithEname.length} members...`); + const results = await this.calculateAllMemberReputations(membersWithEname, charter, memberReferencesMap); + + console.log(`\n✅ Completed eReputation calculations:`); + console.log(` - Total members processed: ${membersWithEname.length}`); + console.log(` - Successful calculations: ${results.length}`); + console.log(` - Failed calculations: ${membersWithEname.length - results.length}`); + console.log(`\n📊 eReputation Results for each person:`); + results.forEach((result, index) => { + const member = members.find(m => m.ename === result.ename); + const memberName = member ? (member.name || "Unknown") : "Unknown"; + console.log(`\n ${index + 1}. ${memberName} (ename: ${result.ename}):`); + console.log(` 📊 eReputation Score: ${result.score}/5`); + console.log(` 💬 Justification: "${result.justification}"`); + }); + + return results; + } + + /** + * Calculate reputation for all members in a single OpenAI call + */ + private async calculateAllMemberReputations( + members: Array<{ id: string; ename?: string; name?: string }>, + charter: string, + memberReferencesMap: Map> + ): Promise { + try { + // Build CSV-like format with all members and their references + // Use ename (all members should have ename at this point) + const membersData = members.map(member => { + if (!member.ename) { + throw new Error(`Member ${member.id} (${member.name || "Unknown"}) has no ename`); + } + const memberName = member.name || "Unknown"; + const references = memberReferencesMap.get(member.id) || []; + return { + ename: member.ename, + userName: memberName, + userId: member.id, // Keep for reference lookup + references + }; + }); + + // Build prompt for AI + const prompt = this.buildBulkVotingPrompt(charter, membersData); + + console.log(` → Sending bulk request to OpenAI with ${members.length} members...`); + + // Call OpenAI once for all members + const response = await this.openai.chat.completions.create({ + model: "gpt-4", + messages: [ + { + role: "system", + content: "You are an expert reputation analyst for voting systems. Analyze the group charter and references to calculate reputation scores for voting purposes. Always respond with valid JSON containing an array of results, each with ename (user's ename identifier), score (1-5), and a one-sentence justification." + }, + { + role: "user", + content: prompt + } + ], + temperature: 0.3, + max_tokens: 2000 + }); + + const aiResponseContent = response.choices[0].message.content; + if (!aiResponseContent) { + throw new Error("AI returned empty response"); + } + + console.log(` → Received AI response from OpenAI:`); + console.log(` Raw response length: ${aiResponseContent.length} characters`); + console.log(` First 300 chars: ${aiResponseContent.substring(0, 300)}${aiResponseContent.length > 300 ? '...' : ''}`); + console.log(` → Parsing AI response...`); + + let result; + try { + result = JSON.parse(aiResponseContent); + console.log(` → Successfully parsed JSON response`); + console.log(` Results array length: ${Array.isArray(result) ? result.length : 'not an array'}`); + } catch (parseError) { + console.error(` ❌ Failed to parse AI response as JSON:`, parseError); + throw new Error(`Failed to parse AI response: ${parseError instanceof Error ? parseError.message : 'Invalid JSON'}`); + } + + // Validate response is an array + if (!Array.isArray(result)) { + console.error(` ❌ Invalid AI response - not an array:`, result); + throw new Error("Invalid AI response: expected array of results"); + } + + // Validate and process each result + const validResults: MemberReputation[] = []; + result.forEach((item: any, index: number) => { + // The AI should return ename, but accept userId for backward compatibility + let ename = item.ename; + if (!ename && item.userId) { + // Fallback: find member by userId and get their ename + const member = members.find(m => m.id === item.userId); + ename = member?.ename || null; + } + + // Explicit validation: ensure ename is a non-empty string + if (typeof ename !== 'string' || ename.trim() === '') { + console.error(` ❌ Invalid result at index ${index}: ename is not a non-empty string`, item); + return; + } + + // Explicit validation: ensure score is a finite number + if (typeof item.score !== 'number' || !Number.isFinite(item.score)) { + console.error(` ❌ Invalid result at index ${index}: score is not a finite number`, item); + return; + } + + // Explicit validation: ensure justification is a string + if (typeof item.justification !== 'string') { + console.error(` ❌ Invalid result at index ${index}: justification is not a string`, item); + return; + } + + const score = Math.max(1, Math.min(5, Math.round(item.score))); + const member = members.find(m => m.ename === ename); + const memberName = member ? (member.name || "Unknown") : "Unknown"; + + validResults.push({ + ename: ename, + score, + justification: item.justification.trim() + }); + + console.log(` ✅ Processed result for ${memberName} (ename: ${ename}):`); + console.log(` - Raw score: ${item.score}`); + console.log(` - Final score: ${score}/5`); + console.log(` - Justification: "${item.justification.trim()}"`); + }); + + console.log(` ✅ Successfully processed ${validResults.length}/${members.length} results from OpenAI`); + return validResults; + } catch (error) { + console.error(` ❌ ERROR calculating bulk reputations:`, error); + return []; + } + } + + /** + * Calculate reputation for a single member (deprecated - use calculateAllMemberReputations) + * Note: This method is deprecated and should not be used. It's kept for reference only. + */ + private async calculateMemberReputation( + userId: string, + userName: string, + charter: string, + userEname?: string + ): Promise { + try { + console.log(` → Fetching references for ${userName}...`); + + // Get all signed references for this user + const references = await this.referenceRepository.find({ + where: { + targetType: "user", + targetId: userId, + status: "signed" + }, + relations: ["author"] + }); + + console.log(` → Found ${references.length} signed references`); + + // Prepare references data + const referencesData = references.map(ref => ({ + content: ref.content, + numericScore: ref.numericScore, + author: ref.author.ename || ref.author.name || "Anonymous" + })); + + // Build prompt for AI + const prompt = this.buildVotingPrompt(charter, userName, referencesData); + + console.log(` → Calling OpenAI API for eReputation calculation...`); + + // Call OpenAI + const response = await this.openai.chat.completions.create({ + model: "gpt-4", + messages: [ + { + role: "system", + content: "You are an expert reputation analyst for voting systems. Analyze the group charter and references to calculate a reputation score for voting purposes. Always respond with valid JSON containing a score (1-5) and a one-sentence justification." + }, + { + role: "user", + content: prompt + } + ], + temperature: 0.3, + max_tokens: 500 + }); + + const aiResponseContent = response.choices[0].message.content; + if (!aiResponseContent) { + throw new Error("AI returned empty response"); + } + + console.log(` → Received AI response from OpenAI:`); + console.log(` Raw response: ${aiResponseContent.substring(0, 200)}${aiResponseContent.length > 200 ? '...' : ''}`); + console.log(` → Parsing AI response...`); + + let result; + try { + result = JSON.parse(aiResponseContent); + console.log(` → Successfully parsed JSON response:`, { + score: result.score, + hasJustification: !!result.justification, + justificationLength: result.justification?.length || 0 + }); + } catch (parseError) { + console.error(` ❌ Failed to parse AI response as JSON:`, parseError); + throw new Error(`Failed to parse AI response: ${parseError instanceof Error ? parseError.message : 'Invalid JSON'}`); + } + + // Validate response + if (!result.score || typeof result.score !== 'number') { + console.error(` ❌ Invalid AI response - missing or invalid score:`, result); + throw new Error("Invalid AI response: missing or invalid score"); + } + + if (!result.justification || typeof result.justification !== 'string') { + console.error(` ❌ Invalid AI response - missing or invalid justification:`, result); + throw new Error("Invalid AI response: missing or invalid justification"); + } + + // Ensure score is between 1 and 5 + const rawScore = result.score; + const score = Math.max(1, Math.min(5, Math.round(result.score))); + + // This method is deprecated - ename should be provided + if (!userEname) { + throw new Error("calculateMemberReputation requires ename (deprecated method)"); + } + + const memberResult: MemberReputation = { + ename: userEname, + score, + justification: result.justification.trim() + }; + + console.log(` ✅ Processed OpenAI result for ${userName}:`); + console.log(` - Raw score from AI: ${rawScore}`); + console.log(` - Final score (rounded/clamped): ${score}/5`); + console.log(` - Justification: "${memberResult.justification}"`); + console.log(` ✅ Completed: ${userName} - Score: ${score}/5`); + console.log(` Justification: ${memberResult.justification}`); + + return memberResult; + } catch (error) { + console.error(` ❌ ERROR calculating reputation for ${userName} (${userId}):`, error); + return null; + } + } + + /** + * Build prompt for AI to calculate voting reputation for all members at once + */ + private buildBulkVotingPrompt( + charter: string, + membersData: Array<{ ename: string; userName: string; userId: string; references: Array<{ content: string; numericScore?: number; author: string }> }> + ): string { + const membersCSV = membersData.map(member => { + const refsText = member.references.length > 0 + ? member.references.map(ref => + ` - From ${ref.author}${ref.numericScore ? ` (Score: ${ref.numericScore}/5)` : ''}: "${ref.content}"` + ).join('\n') + : ' - No references found'; + + return `USER: ${member.userName} (ename: ${member.ename}) +REFERENCES: +${refsText}`; + }).join('\n\n'); + + return ` +You are analyzing the reputation of multiple users for voting purposes within a group. + +GROUP CHARTER: +${charter} + +USERS AND THEIR REFERENCES: +${membersCSV} + +TASK: +Based on the group charter and the references provided, calculate a reputation score from 1-5 for EACH user that will be used for weighted voting. + +IMPORTANT: +- Each score must be between 1 and 5 (inclusive) +- Consider how well the references align with the group's charter and values +- Focus on voting-relevant reputation factors mentioned in the charter +- Provide a ONE SENTENCE justification explaining each score + +Respond with a JSON array in this exact format: +[ + { + "ename": "", + "score": , + "justification": "" + }, + ... +] + `.trim(); + } + + /** + * Build prompt for AI to calculate voting reputation (deprecated - use buildBulkVotingPrompt) + */ + private buildVotingPrompt( + charter: string, + userName: string, + references: Array<{ content: string; numericScore?: number; author: string }> + ): string { + return ` +You are analyzing the reputation of "${userName}" for voting purposes within a group. + +GROUP CHARTER: +${charter} + +REFERENCES (what others have said about ${userName}): +${references.length > 0 + ? references.map(ref => ` +Reference from ${ref.author}${ref.numericScore ? ` (Score: ${ref.numericScore}/5)` : ''}: +"${ref.content}" +`).join('\n') + : 'No references found for this user.' +} + +TASK: +Based on the group charter and the references provided, calculate a reputation score from 1-5 for ${userName} that will be used for weighted voting. + +IMPORTANT: +- The score must be between 1 and 5 (inclusive) +- Consider how well the references align with the group's charter and values +- Focus on voting-relevant reputation factors mentioned in the charter +- Provide a ONE SENTENCE justification explaining the score + +Respond with a JSON object in this exact format: +{ + "score": , + "justification": "" +} + `.trim(); + } + + /** + * Save reputation results for a poll + */ + async saveReputationResults( + pollId: string, + groupId: string, + results: MemberReputation[] + ): Promise { + // Check if result already exists + let result = await this.voteReputationResultRepository.findOne({ + where: { pollId } + }); + + if (result) { + result.results = results; + return await this.voteReputationResultRepository.save(result); + } else { + result = this.voteReputationResultRepository.create({ + pollId, + groupId, + results + }); + return await this.voteReputationResultRepository.save(result); + } + } + + /** + * Get reputation results for a poll + */ + async getReputationResults(pollId: string): Promise { + return await this.voteReputationResultRepository.findOne({ + where: { pollId }, + relations: ["poll", "group"] + }); + } +} + + diff --git a/platforms/eReputation-api/src/types/express.ts b/platforms/eReputation-api/src/types/express.ts new file mode 100644 index 000000000..fd43b899b --- /dev/null +++ b/platforms/eReputation-api/src/types/express.ts @@ -0,0 +1,12 @@ +import { User } from "../database/entities/User"; + +declare global { + namespace Express { + interface Request { + user?: User; + } + } +} + +// Export empty object to make this a module +export {}; diff --git a/platforms/eReputation-api/src/utils/jwt.ts b/platforms/eReputation-api/src/utils/jwt.ts new file mode 100644 index 000000000..3e1cce7ed --- /dev/null +++ b/platforms/eReputation-api/src/utils/jwt.ts @@ -0,0 +1,29 @@ +import jwt, { JwtPayload } from "jsonwebtoken"; + +// Fail fast if JWT_SECRET is missing +if (!process.env.JWT_SECRET) { + throw new Error("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration."); +} + +const JWT_SECRET = process.env.JWT_SECRET; + +export interface AuthTokenPayload { + userId: string; +} + +export const signToken = (payload: AuthTokenPayload): string => { + return jwt.sign(payload, JWT_SECRET, { expiresIn: "7d" }); +}; + +export const verifyToken = (token: string): AuthTokenPayload => { + const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload & AuthTokenPayload; + + // Validate that the decoded token has the required userId field + if (!decoded.userId || typeof decoded.userId !== 'string') { + throw new Error("Invalid token: missing or invalid userId"); + } + + return { + userId: decoded.userId + }; +}; diff --git a/platforms/eReputation-api/src/web3adapter/index.ts b/platforms/eReputation-api/src/web3adapter/index.ts new file mode 100644 index 000000000..9414ffa48 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/index.ts @@ -0,0 +1 @@ +export { adapter } from "./watchers/subscriber"; diff --git a/platforms/eReputation-api/src/web3adapter/mappings/group.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/group.mapping.json new file mode 100644 index 000000000..4790736e5 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/group.mapping.json @@ -0,0 +1,24 @@ +{ + "tableName": "groups", + "schemaId": "550e8400-e29b-41d4-a716-446655440003", + "ownerEnamePath": "users(participants[].ename)", + "ownedJunctionTables": ["group_participants"], + "localToUniversalMap": { + "name": "name", + "description": "description", + "owner": "owner", + "admins": "users(admins[].id),admins", + "charter": "charter", + "ename": "ename", + "participants": "users(participants[].id),participantIds", + "members": "users(members[].id),memberIds", + "originalMatchParticipants": "originalMatchParticipants", + "isPrivate": "isPrivate", + "visibility": "visibility", + "avatarUrl": "avatarUrl", + "bannerUrl": "bannerUrl", + "createdAt": "createdAt", + "updatedAt": "updatedAt" + }, + "readOnly": false +} diff --git a/platforms/eReputation-api/src/web3adapter/mappings/message.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/message.mapping.json new file mode 100644 index 000000000..ac51a0215 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/message.mapping.json @@ -0,0 +1,16 @@ +{ + "tableName": "messages", + "schemaId": "550e8400-e29b-41d4-a716-446655440004", + "ownerEnamePath": "groups(group.ename)||users(group.members[].ename)", + "ownedJunctionTables": [], + "localToUniversalMap": { + "text": "content", + "sender": "users(sender.id),senderId", + "group": "groups(group.id),chatId", + "isSystemMessage": "isSystemMessage", + "createdAt": "createdAt", + "updatedAt": "updatedAt", + "isArchived": "isArchived" + } +} + diff --git a/platforms/eReputation-api/src/web3adapter/mappings/poll.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/poll.mapping.json new file mode 100644 index 000000000..6f617d78e --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/poll.mapping.json @@ -0,0 +1,18 @@ +{ + "tableName": "polls", + "schemaId": "660e8400-e29b-41d4-a716-446655440100", + "ownerEnamePath": "groups(group.ename)", + "localToUniversalMap": { + "title": "title", + "mode": "mode", + "visibility": "visibility", + "votingWeight": "votingWeight", + "options": "options", + "deadline": "deadline", + "creatorId": "creatorId", + "group": "groups(group.id),group", + "createdAt": "createdAt", + "updatedAt": "updatedAt" + }, + "readOnly": false +} \ No newline at end of file diff --git a/platforms/eReputation-api/src/web3adapter/mappings/user.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/user.mapping.json new file mode 100644 index 000000000..ee718b6a9 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/user.mapping.json @@ -0,0 +1,22 @@ +{ + "tableName": "users", + "schemaId": "550e8400-e29b-41d4-a716-446655440000", + "ownerEnamePath": "ename", + "ownedJunctionTables": ["user_followers", "user_following"], + "localToUniversalMap": { + "handle": "username", + "name": "displayName", + "description": "bio", + "avatarUrl": "avatarUrl", + "bannerUrl": "bannerUrl", + "ename": "ename", + "isVerified": "isVerified", + "isPrivate": "isPrivate", + "createdAt": "createdAt", + "updatedAt": "updatedAt", + "isArchived": "isArchived", + "followers": "followers", + "following": "following" + }, + "readOnly": true +} diff --git a/platforms/eReputation-api/src/web3adapter/mappings/vote-reputation-result.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/vote-reputation-result.mapping.json new file mode 100644 index 000000000..70ca6768d --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/vote-reputation-result.mapping.json @@ -0,0 +1,13 @@ +{ + "tableName": "vote_reputation_results", + "schemaId": "660e8400-e29b-41d4-a716-446655440102", + "ownerEnamePath": "groups(poll.group.ename)", + "localToUniversalMap": { + "pollId": "polls(poll.id),pollId", + "groupId": "groups(group.id),groupId", + "results": "results", + "createdAt": "createdAt", + "updatedAt": "updatedAt" + }, + "readOnly": false +} \ No newline at end of file diff --git a/platforms/eReputation-api/src/web3adapter/mappings/vote.mapping.json b/platforms/eReputation-api/src/web3adapter/mappings/vote.mapping.json new file mode 100644 index 000000000..7f5312c68 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/mappings/vote.mapping.json @@ -0,0 +1,15 @@ +{ + "tableName": "votes", + "schemaId": "550e8400-e29b-41d4-a716-446655440005", + "localToUniversalMap": { + "id": "id", + "pollId": "pollId", + "userId": "userId", + "voterId": "voterId", + "data": "data", + "createdAt": "createdAt", + "updatedAt": "updatedAt" + }, + "readOnly": false +} + diff --git a/platforms/eReputation-api/src/web3adapter/watchers/subscriber.ts b/platforms/eReputation-api/src/web3adapter/watchers/subscriber.ts new file mode 100644 index 000000000..745d67f87 --- /dev/null +++ b/platforms/eReputation-api/src/web3adapter/watchers/subscriber.ts @@ -0,0 +1,403 @@ +import { + EventSubscriber, + EntitySubscriberInterface, + InsertEvent, + UpdateEvent, + RemoveEvent, + ObjectLiteral, +} from "typeorm"; +import { Web3Adapter } from "web3-adapter"; +import path from "path"; +import dotenv from "dotenv"; +import { AppDataSource } from "../../database/data-source"; + +dotenv.config({ path: path.resolve(__dirname, "../../../../../.env") }); + +export const adapter = new Web3Adapter({ + schemasPath: path.resolve(__dirname, "../mappings/"), + dbPath: path.resolve(process.env.EREPUTATION_MAPPING_DB_PATH as string), + registryUrl: process.env.PUBLIC_REGISTRY_URL as string, + platform: process.env.VITE_EREPUTATION_BASE_URL as string, +}); + +// Map of junction tables to their parent entities +const JUNCTION_TABLE_MAP = { + user_followers: { entity: "User", idField: "user_id" }, + user_following: { entity: "User", idField: "user_id" }, + group_participants: { entity: "Group", idField: "group_id" }, +}; + +@EventSubscriber() +export class PostgresSubscriber implements EntitySubscriberInterface { + static { + console.log("🔧 PostgresSubscriber class is being loaded"); + } + private adapter: Web3Adapter; + private junctionTableDebounceMap: Map = new Map(); + + constructor() { + console.log("🚀 PostgresSubscriber constructor called - subscriber is being instantiated"); + this.adapter = adapter; + } + + /** + * Called before entity insertion. + */ + beforeInsert(event: InsertEvent) { + + } + + async enrichEntity(entity: any, tableName: string, tableTarget: any) { + try { + const enrichedEntity = { ...entity }; + + // Handle author enrichment (for backward compatibility) + if (entity.author) { + const author = await AppDataSource.getRepository( + "User" + ).findOne({ where: { id: entity.author.id } }); + enrichedEntity.author = author; + } + + return this.entityToPlain(enrichedEntity); + } catch (error) { + console.error("Error loading relations:", error); + return this.entityToPlain(entity); + } + } + + /** + * Called after entity insertion. + */ + async afterInsert(event: InsertEvent) { + let entity = event.entity; + if (entity) { + entity = (await this.enrichEntity( + entity, + event.metadata.tableName, + event.metadata.target + )) as ObjectLiteral; + } + + this.handleChange( + // @ts-ignore + entity ?? event.entityId, + event.metadata.tableName.endsWith("s") + ? event.metadata.tableName + : event.metadata.tableName + "s" + ); + } + + /** + * Called before entity update. + */ + beforeUpdate(event: UpdateEvent) { + // Handle any pre-update processing if needed + } + + /** + * Called after entity update. + */ + async afterUpdate(event: UpdateEvent) { + // For updates, we need to reload the full entity since event.entity only contains changed fields + let entity = event.entity; + + // Try different ways to get the entity ID + let entityId = event.entity?.id || event.databaseEntity?.id; + + if (!entityId && event.entity) { + // If we have the entity but no ID, try to extract it from the entity object + const entityKeys = Object.keys(event.entity); + + // Look for common ID field names + entityId = event.entity.id || event.entity.Id || event.entity.ID || event.entity._id; + } + + if (entityId) { + // Reload the full entity from the database + const repository = AppDataSource.getRepository(event.metadata.target); + const entityName = typeof event.metadata.target === 'function' + ? event.metadata.target.name + : event.metadata.target; + + const fullEntity = await repository.findOne({ + where: { id: entityId }, + relations: this.getRelationsForEntity(entityName) + }); + + if (fullEntity) { + entity = (await this.enrichEntity( + fullEntity, + event.metadata.tableName, + event.metadata.target + )) as ObjectLiteral; + } + } + + this.handleChange( + // @ts-ignore + entity ?? event.entityId, + event.metadata.tableName.endsWith("s") + ? event.metadata.tableName + : event.metadata.tableName + "s" + ); + } + + /** + * Called before entity removal. + */ + beforeRemove(event: RemoveEvent) { + // Handle any pre-remove processing if needed + } + + /** + * Called after entity removal. + */ + async afterRemove(event: RemoveEvent) { + this.handleChange( + // @ts-ignore + event.entityId, + event.metadata.tableName.endsWith("s") + ? event.metadata.tableName + : event.metadata.tableName + "s" + ); + } + + /** + * Handle entity changes and send to web3adapter + */ + private async handleChange(entity: any, tableName: string): Promise { + console.log(`🔍 handleChange called for: ${tableName}, entityId: ${entity?.id}`); + + // For Message entities, only process if they are system messages + if (tableName === "messages") { + const isSystemMessage = entity.text && entity.text.includes('$$system-message$$'); + + if (!isSystemMessage) { + console.log(`⏭️ Skipping non-system message: ${entity.id}`); + return; + } + } + + // Handle junction table changes + // @ts-ignore + const junctionInfo = JUNCTION_TABLE_MAP[tableName]; + if (junctionInfo) { + console.log(`🔗 Processing junction table change for: ${tableName}`); + await this.handleJunctionTableChange(entity, junctionInfo); + return; + } + + // Handle regular entity changes with debouncing for groups + const data = this.entityToPlain(entity); + if (!data.id) return; + + // Add debouncing for group entities to prevent duplicate webhooks + if (tableName === "groups") { + const debounceKey = `group:${data.id}`; + console.log(`🔍 Group debounce key: ${debounceKey}`); + + // Clear existing timeout for this group + if (this.junctionTableDebounceMap.has(debounceKey)) { + console.log(`🔍 Clearing existing group timeout for: ${debounceKey}`); + clearTimeout(this.junctionTableDebounceMap.get(debounceKey)!); + } + + // Set new timeout + const timeoutId = setTimeout(async () => { + try { + console.log(`🔍 Executing debounced group webhook for: ${debounceKey}`); + await this.sendGroupWebhook(data); + this.junctionTableDebounceMap.delete(debounceKey); + console.log(`🔍 Completed group webhook for: ${debounceKey}`); + } catch (error) { + console.error("Error in group timeout:", error); + this.junctionTableDebounceMap.delete(debounceKey); + } + }, 3_000); + + // Store the timeout ID + this.junctionTableDebounceMap.set(debounceKey, timeoutId); + return; + } + + try { + setTimeout(async () => { + let globalId = await this.adapter.mappingDb.getGlobalId( + entity.id + ); + globalId = globalId ?? ""; + + if (this.adapter.lockedIds.includes(globalId)) { + return; + } + + // Check if this entity was recently created by a webhook + if (this.adapter.lockedIds.includes(entity.id)) { + return; + } + + const envelope = await this.adapter.handleChange({ + data, + tableName: tableName.toLowerCase(), + }); + }, 3_000); + } catch (error) { + console.error(`Error processing change for ${tableName}:`, error); + } + } + + /** + * Handle changes in junction tables by converting them to parent entity changes + */ + private async handleJunctionTableChange( + entity: any, + junctionInfo: { entity: string; idField: string } + ): Promise { + try { + const parentId = entity[junctionInfo.idField]; + if (!parentId) { + console.error("No parent ID found in junction table change"); + return; + } + + const repository = AppDataSource.getRepository(junctionInfo.entity); + const parentEntity = await repository.findOne({ + where: { id: parentId }, + relations: this.getRelationsForEntity(junctionInfo.entity), + }); + + if (!parentEntity) { + console.error(`Parent entity not found: ${parentId}`); + return; + } + + // Use debouncing to prevent multiple webhook packets for the same group + const debounceKey = `${junctionInfo.entity}:${parentId}`; + + console.log(`🔗 Junction table debounce key: ${debounceKey}`); + + // Clear existing timeout for this group + if (this.junctionTableDebounceMap.has(debounceKey)) { + console.log(`🔗 Clearing existing timeout for: ${debounceKey}`); + clearTimeout(this.junctionTableDebounceMap.get(debounceKey)!); + } + + // Set new timeout + const timeoutId = setTimeout(async () => { + try { + console.log(`🔗 Executing debounced webhook for: ${debounceKey}`); + let globalId = await this.adapter.mappingDb.getGlobalId( + entity.id + ); + globalId = globalId ?? ""; + + if (this.adapter.lockedIds.includes(globalId)) { + console.log(`🔗 GlobalId ${globalId} is locked, skipping`); + return; + } + + const tableName = `${junctionInfo.entity.toLowerCase()}s`; + console.log(`🔗 Sending webhook packet for group: ${parentId}, tableName: ${tableName}`); + await this.adapter.handleChange({ + data: this.entityToPlain(parentEntity), + tableName, + }); + + // Remove from debounce map after processing + this.junctionTableDebounceMap.delete(debounceKey); + console.log(`🔗 Completed webhook for: ${debounceKey}`); + } catch (error) { + console.error("Error in junction table timeout:", error); + this.junctionTableDebounceMap.delete(debounceKey); + } + }, 3_000); + + // Store the timeout ID for potential cancellation + this.junctionTableDebounceMap.set(debounceKey, timeoutId); + } catch (error) { + console.error("Error handling junction table change:", error); + } + } + + /** + * Send webhook for group entity + */ + private async sendGroupWebhook(data: any): Promise { + try { + let globalId = await this.adapter.mappingDb.getGlobalId(data.id); + globalId = globalId ?? ""; + + if (this.adapter.lockedIds.includes(globalId)) { + console.log(`🔍 Group globalId ${globalId} is locked, skipping`); + return; + } + + console.log(`🔍 Sending group webhook for: ${data.id}, tableName: groups`); + await this.adapter.handleChange({ + data, + tableName: "groups", + }); + } catch (error) { + console.error("Error sending group webhook:", error); + } + } + + /** + * Get the relations that should be loaded for each entity type + */ + private getRelationsForEntity(entityName: string): string[] { + switch (entityName) { + case "User": + return ["followers", "following"]; + case "Group": + return ["participants", "admins", "members"]; + default: + return []; + } + } + + /** + * Convert TypeORM entity to plain object + */ + private entityToPlain(entity: any): any { + if (!entity) return {}; + + // If it's already a plain object, return it + if (typeof entity !== "object" || entity === null) { + return entity; + } + + // Handle Date objects + if (entity instanceof Date) { + return entity.toISOString(); + } + + // Handle arrays + if (Array.isArray(entity)) { + return entity.map((item) => this.entityToPlain(item)); + } + + // Convert entity to plain object + const plain: Record = {}; + for (const [key, value] of Object.entries(entity)) { + // Skip private properties and methods + if (key.startsWith("_")) continue; + + // Handle nested objects and arrays + if (value && typeof value === "object") { + if (Array.isArray(value)) { + plain[key] = value.map((item) => this.entityToPlain(item)); + } else if (value instanceof Date) { + plain[key] = value.toISOString(); + } else { + plain[key] = this.entityToPlain(value); + } + } else { + plain[key] = value; + } + } + + return plain; + } +} diff --git a/platforms/eReputation-api/src/workers/PollReputationWorker.ts b/platforms/eReputation-api/src/workers/PollReputationWorker.ts new file mode 100644 index 000000000..ba7dc4adb --- /dev/null +++ b/platforms/eReputation-api/src/workers/PollReputationWorker.ts @@ -0,0 +1,219 @@ +import { Worker, Job } from "bullmq"; +import { PollReputationJobData, JobQueueService } from "../services/JobQueueService"; +import { VotingReputationService } from "../services/VotingReputationService"; +import { MessageService } from "../services/MessageService"; +import { GroupService } from "../services/GroupService"; +import { PollService } from "../services/PollService"; +import { AppDataSource } from "../database/data-source"; +import { Poll } from "../database/entities/Poll"; +import { VoteReputationResult } from "../database/entities/VoteReputationResult"; +import { Group } from "../database/entities/Group"; +import { UserService } from "../services/UserService"; + +export class PollReputationWorker { + private worker: Worker; + private votingReputationService: VotingReputationService; + private messageService: MessageService; + private groupService: GroupService; + private pollService: PollService; + private userService: UserService; + + constructor(queueService: JobQueueService) { + this.votingReputationService = new VotingReputationService(); + this.messageService = new MessageService(); + this.groupService = new GroupService(); + this.pollService = new PollService(); + this.userService = new UserService(); + + this.worker = new Worker( + "poll-reputation-calculation", + async (job: Job) => { + await this.processJob(job); + }, + { + connection: { + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379", 10), + }, + concurrency: 1, // Process one job at a time to avoid conflicts + limiter: { + max: 5, + duration: 10000, // Max 5 jobs per 10 seconds + }, + } + ); + + this.worker.on("failed", (job, err) => { + console.error(`Job ${job?.id} failed:`, err); + }); + } + + private async processJob(job: Job): Promise { + const { pollId, groupId, eventId } = job.data; + + try { + // Load poll + const poll = await this.pollService.getPollById(pollId); + if (!poll) { + throw new Error(`Poll not found: ${pollId}`); + } + + if (!poll.groupId) { + throw new Error("Poll has no groupId, cannot calculate eReputation"); + } + + // Get group with charter + const group = await this.groupService.getGroupById(poll.groupId); + if (!group) { + throw new Error("Group not found for poll"); + } + + if (!group.charter) { + throw new Error("Group has no charter, cannot calculate eReputation"); + } + + // Calculate reputations for all group members + const reputationResults = await this.votingReputationService.calculateGroupMemberReputations( + poll.groupId, + group.charter + ); + + // Save results + const voteReputationResult = await this.votingReputationService.saveReputationResults( + poll.id, + poll.groupId, + reputationResults + ); + + // Transmit results via web3adapter + await this.transmitReputationResults(voteReputationResult, poll, group); + + // Create system message with eReputation results + await this.createReputationMessage(poll, reputationResults); + } catch (error) { + console.error(`Error processing poll reputation job for poll ${pollId}:`, error); + throw error; // Re-throw to mark job as failed + } + } + + private async transmitReputationResults( + result: VoteReputationResult, + poll: Poll, + group: Group + ): Promise { + const voteReputationResultRepository = AppDataSource.getRepository(VoteReputationResult); + const pollRepository = AppDataSource.getRepository(Poll); + const groupRepository = AppDataSource.getRepository(Group); + + // Reload result with relations + const reloadedResult = await voteReputationResultRepository.findOne({ + where: { id: result.id }, + relations: ["poll", "group"] + }); + + if (!reloadedResult) { + throw new Error(`Result not found: ${result.id}`); + } + + let pollEntity: Poll | null = reloadedResult.poll || null; + let groupEntity: Group | null = reloadedResult.group || null; + + if (!pollEntity && reloadedResult.pollId) { + pollEntity = await pollRepository.findOne({ + where: { id: reloadedResult.pollId } + }); + } + + if (!groupEntity && reloadedResult.groupId) { + groupEntity = await groupRepository.findOne({ + where: { id: reloadedResult.groupId }, + select: ["id", "ename", "name"] + }); + } + + if (!pollEntity) { + throw new Error(`Poll not found: ${reloadedResult.pollId}`); + } + + if (!groupEntity) { + throw new Error(`Group not found: ${reloadedResult.groupId}`); + } + + if (!groupEntity.ename) { + throw new Error(`Group ${groupEntity.id} has no ename! This will cause ownerEnamePath to fail.`); + } + + // Convert entity to plain object for web3adapter + const data: any = { + id: reloadedResult.id, + pollId: reloadedResult.pollId, + groupId: reloadedResult.groupId, + results: JSON.stringify(reloadedResult.results), + createdAt: reloadedResult.createdAt, + updatedAt: reloadedResult.updatedAt + }; + + // Add poll with group for ownerEnamePath resolution + data.poll = { + id: pollEntity.id, + groupId: pollEntity.groupId, + group: { + id: groupEntity.id, + ename: groupEntity.ename, + name: groupEntity.name + } + }; + + if (!data.groupId) { + data.groupId = groupEntity.id; + } + + // Use web3adapter to sync to eVault + const { adapter } = await import("../web3adapter/watchers/subscriber"); + await adapter.handleChange({ + data, + tableName: "vote_reputation_results" + }); + } + + private async createReputationMessage( + poll: Poll, + reputationResults: Array<{ ename: string; score: number; justification: string }> + ): Promise { + if (!poll.groupId) return; + + try { + const messageLines: string[] = []; + messageLines.push(`eReputation scores calculated for poll: "${poll.title}"`); + messageLines.push(``); + + for (const result of reputationResults) { + const user = await this.userService.getUserByEname(result.ename); + const userName = user?.name || "Unknown"; + const userEname = result.ename; + const score = result.score; + const justification = result.justification; + + messageLines.push(`${userName} (@${userEname}): ${score}/5`); + messageLines.push(` ${justification}`); + messageLines.push(``); + } + + const messageText = messageLines.join('\n'); + + await this.messageService.createSystemMessage({ + text: messageText, + groupId: poll.groupId, + voteId: poll.id + }); + } catch (error) { + console.error(`Failed to create system message for poll ${poll.id}:`, error); + // Don't throw - message creation failure shouldn't fail the job + } + } + + async close(): Promise { + await this.worker.close(); + } +} + diff --git a/platforms/eReputation-api/tsconfig.json b/platforms/eReputation-api/tsconfig.json new file mode 100644 index 000000000..7ef05f0f4 --- /dev/null +++ b/platforms/eReputation-api/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "commonjs", + "moduleResolution": "node", + "rootDir": "./src", + "baseUrl": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/platforms/eReputation/client/src/App.tsx b/platforms/eReputation/client/src/App.tsx index 2f8a1fcb3..ae3ddbd45 100644 --- a/platforms/eReputation/client/src/App.tsx +++ b/platforms/eReputation/client/src/App.tsx @@ -3,10 +3,10 @@ import { queryClient } from "./lib/queryClient"; import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; +import { AuthProvider } from "@/lib/auth-context"; import { useAuth } from "@/hooks/useAuth"; import AuthPage from "@/pages/auth-page"; import Dashboard from "@/pages/dashboard"; -import References from "@/pages/references"; import NotFound from "@/pages/not-found"; function Router() { @@ -20,7 +20,6 @@ function Router() { return ( - ); @@ -29,12 +28,14 @@ function Router() { function App() { return ( - - - - + + + + + + ); } -export default App; +export default App; \ No newline at end of file diff --git a/platforms/eReputation/client/src/components/auth/login-screen.tsx b/platforms/eReputation/client/src/components/auth/login-screen.tsx new file mode 100644 index 000000000..183c015ec --- /dev/null +++ b/platforms/eReputation/client/src/components/auth/login-screen.tsx @@ -0,0 +1,165 @@ +import React, { useEffect, useState } from "react"; +import { QRCodeSVG } from "qrcode.react"; +import { useAuth } from "@/hooks/useAuth"; +import { apiClient } from "@/lib/apiClient"; +import { isMobileDevice, getDeepLinkUrl, getAppStoreLink } from "@/lib/utils/mobile-detection"; +import { Star } from "lucide-react"; + +export function LoginScreen() { + const { login } = useAuth(); + const [qrCode, setQrCode] = useState(""); + const [sessionId, setSessionId] = useState(""); + const [isConnecting, setIsConnecting] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const getAuthOffer = async () => { + try { + console.log("🔍 Getting auth offer from:", apiClient.defaults.baseURL); + const response = await apiClient.get("/api/auth/offer"); + console.log("✅ Auth offer response:", response.data); + setQrCode(response.data.offer); + setSessionId(response.data.sessionId); + setIsLoading(false); + } catch (error: unknown) { + console.error("❌ Failed to get auth offer:", error); + if (error && typeof error === 'object' && 'response' in error) { + const axiosError = error as { response?: { data?: unknown; status?: number } }; + console.error("❌ Error details:", axiosError.response?.data); + console.error("❌ Error status:", axiosError.response?.status); + } + setIsLoading(false); + } + }; + + getAuthOffer(); + }, []); + + useEffect(() => { + if (!sessionId) return; + + const eventSource = new EventSource( + `${import.meta.env.VITE_EREPUTATION_BASE_URL || "http://localhost:8765"}/api/auth/sessions/${sessionId}` + ); + + eventSource.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + if (data.user && data.token) { + setIsConnecting(true); + // Store the token and user ID directly + localStorage.setItem("ereputation_token", data.token); + localStorage.setItem("ereputation_user_id", data.user.id); + // Redirect to home page + window.location.href = "/"; + } + } catch (error) { + console.error("Error parsing SSE data:", error); + } + }; + + eventSource.onerror = (error) => { + console.error("SSE Error:", error); + eventSource.close(); + }; + + return () => { + eventSource.close(); + }; + }, [sessionId, login]); + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (isConnecting) { + return ( +
+
+
+

Authenticating...

+
+
+ ); + } + + return ( +
+
+
+
+ +
+

eReputation

+
+

+ Manage your reputation in the MetaState +

+
+ +
+
+

+ Scan the QR code using your eID App to login +

+
+ + {qrCode && ( +
+ {isMobileDevice() ? ( +
+ + Login with eID Wallet + +
+ Click the button to open your eID wallet app +
+
+ ) : ( +
+ +
+ )} +
+ )} + +
+

+ The {isMobileDevice() ? "button" : "code"} is valid for 60 seconds + Please refresh the page if it expires +

+
+ +
+ You are entering eReputation - a social reputation platform built on the Web 3.0 Data Space (W3DS) + architecture. This system is designed around the principle + of data-platform separation, where all your personal content + is stored in your own sovereign eVault, not on centralised + servers. +
+ + + W3DS Logo + +
+
+ ); +} \ No newline at end of file diff --git a/platforms/eReputation/client/src/components/auth/protected-route.tsx b/platforms/eReputation/client/src/components/auth/protected-route.tsx new file mode 100644 index 000000000..563f9847e --- /dev/null +++ b/platforms/eReputation/client/src/components/auth/protected-route.tsx @@ -0,0 +1,32 @@ +import React, { useEffect } from "react"; +import { useLocation } from "wouter"; +import { useAuth } from "@/hooks/useAuth"; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +export function ProtectedRoute({ children }: ProtectedRouteProps) { + const { isAuthenticated, isLoading } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!isLoading && !isAuthenticated) { + setLocation("/auth"); + } + }, [isAuthenticated, isLoading, setLocation]); + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (!isAuthenticated) { + return null; + } + + return <>{children}; +} diff --git a/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx b/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx index 2e2eacf22..5792fb36e 100644 --- a/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx +++ b/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query"; import { useDebouncedCallback } from 'use-debounce'; -import { apiRequest } from "@/lib/queryClient"; +import { apiClient } from "@/lib/apiClient"; import { useToast } from "@/hooks/use-toast"; import { isUnauthorizedError } from "@/lib/authUtils"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; @@ -10,6 +10,7 @@ import { Progress } from "@/components/ui/progress"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; import ViewReputationModal from "./view-reputation-modal"; @@ -19,27 +20,27 @@ interface OtherCalculationModalProps { } const TARGET_TYPES = [ - { - value: "user", - label: "User", + { + value: "user", + label: "User", icon: ( ) }, - { - value: "group", - label: "Group", + { + value: "group", + label: "Group", icon: ( ) }, - { - value: "platform", - label: "Platform", + { + value: "platform", + label: "Platform", icon: ( @@ -101,6 +102,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu const [progress, setProgress] = useState(0); const [showViewModal, setShowViewModal] = useState(false); const [reputationResult, setReputationResult] = useState(null); + const [userValues, setUserValues] = useState(""); const { toast } = useToast(); const queryClient = useQueryClient(); @@ -116,37 +118,47 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu } }, [isCalculating, currentStep]); - const debouncedSearch = useDebouncedCallback((query: string) => { - if (query.length >= 2) { - refetch(); - } - }, 300); - const { data: searchResults = [], refetch } = useQuery({ - queryKey: ['/api/search', targetType, searchQuery], - queryFn: () => { + const { data: searchResults = [], refetch, isLoading: isSearching } = useQuery({ + queryKey: ['search', targetType, searchQuery], + queryFn: async () => { if (!targetType || searchQuery.length < 2) return []; - const endpoint = `/api/search/${targetType}s?q=${encodeURIComponent(searchQuery)}`; - return fetch(endpoint, { credentials: "include" }).then(res => res.json()); + + if (targetType === 'platform') { + // Search platforms using the new platform endpoint + const response = await apiClient.get(`/api/platforms/search?q=${encodeURIComponent(searchQuery)}`); + return response.data.platforms || response.data || []; + } else if (targetType === 'user') { + // Search users using existing endpoint + const response = await apiClient.get(`/api/users/search?q=${encodeURIComponent(searchQuery)}`); + // Ensure we return an array, handle different response structures + return Array.isArray(response.data) ? response.data : []; + } else if (targetType === 'group') { + // Search groups using new endpoint + const response = await apiClient.get(`/api/groups/search?q=${encodeURIComponent(searchQuery)}`); + return response.data; + } + return []; }, - enabled: false, + enabled: targetType !== "" && searchQuery.length >= 2, }); const calculateMutation = useMutation({ mutationFn: async () => { - const response = await apiRequest("POST", "/api/reputation/calculate", { + const response = await apiClient.post("/api/reputation/calculate", { targetType: targetType, targetId: selectedTarget?.id || '', - targetName: selectedTarget?.name || selectedTarget?.title || 'Unknown', + targetName: selectedTarget?.name || selectedTarget?.ename || selectedTarget?.handle || selectedTarget?.title || 'Unknown', + userValues: userValues.trim(), variables: ALL_VARIABLES }); - return response.json(); + return response.data; }, onError: (error) => { setIsCalculating(false); setCurrentStep(0); setProgress(0); - + if (isUnauthorizedError(error)) { toast({ title: "Unauthorized", @@ -174,7 +186,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu setReputationResult(calculateMutation.data); setIsCalculating(false); setShowViewModal(true); - + // Update dashboard queries queryClient.invalidateQueries({ queryKey: ["/api/dashboard/stats"] }); queryClient.invalidateQueries({ queryKey: ["/api/dashboard/activities"] }); @@ -190,14 +202,11 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu setCurrentStep(0); setProgress(0); setReputationResult(null); + setUserValues(""); }; const handleSearchChange = (value: string) => { setSearchQuery(value); - // Trigger search if query is long enough - if (value.length >= 2) { - debouncedSearch(value); - } // Don't automatically set selected target, let user pick from results if (!value.trim()) { setSelectedTarget(null); @@ -206,7 +215,8 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu const handleSelectTarget = (target: any) => { setSelectedTarget(target); - setSearchQuery(target.name); + // Use name, ename, or handle as fallback + setSearchQuery(target.name || target.ename || target.handle || 'Unknown'); }; const handleStartCalculation = () => { @@ -228,6 +238,15 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu return; } + if (!userValues.trim()) { + toast({ + title: "Missing Values", + description: "Please describe what qualities you value in people", + variant: "destructive", + }); + return; + } + setIsCalculating(true); setCurrentStep(0); setProgress(0); @@ -256,7 +275,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu
- +
{/* Progress Bar or Ready State */} @@ -264,9 +283,9 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu // Calculating state - show progress
-

Calculating {selectedTarget?.name || 'Target'}'s eReputation

+

Calculating {(selectedTarget?.name || selectedTarget?.ename || selectedTarget?.handle || 'Target')}'s eReputation

- {currentStep < ANALYSIS_STEPS.length + {currentStep < ANALYSIS_STEPS.length ? ANALYSIS_STEPS[currentStep].label : "Calculation complete!" } @@ -321,11 +340,11 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu

- +

Ready to Calculate

- We'll analyze {selectedTarget.name}'s eReputation across multiple post-platforms including likes, dislikes, and engagement metrics. + We'll analyze {(selectedTarget.name || selectedTarget.ename || selectedTarget.handle || 'Target')}'s eReputation across multiple post-platforms including likes, dislikes, and engagement metrics.

@@ -346,7 +365,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu
- +

Select Target to Evaluate

@@ -373,11 +392,10 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu {TARGET_TYPES.map((type) => (

@@ -389,7 +407,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu
- + {/* Search Target */}
- )} - - {/* Manual Entry Option */} - {searchQuery.length >= 2 && !selectedTarget && ( -
- + )) + ) : ( +
+ No {targetType}s found for "{searchQuery}"
- + )}
)}
- + {/* Selected Target Display */} {selectedTarget && (
@@ -464,7 +477,7 @@ export default function OtherCalculationModal({ open, onOpenChange }: OtherCalcu - {selectedTarget.name} + {selectedTarget.name || selectedTarget.ename || selectedTarget.handle || 'Unknown'}
)} + + {/* User Values Input */} +
+ +