diff --git a/frontend/package.json b/frontend/package.json index 259e4ef..75ec777 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,6 @@ "@web3modal/wagmi": "^3.0.0-beta.4", "chroma-js": "^2.4.2", "encoding": "^0.1", - "ethers": "^5.7.2", "graphql": "^16", "js-sha256": "^0.10", "lokijs": "^1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 9cbe72a..dd9a15d 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -53,9 +53,6 @@ dependencies: encoding: specifier: ^0.1 version: 0.1.13 - ethers: - specifier: ^5.7.2 - version: 5.7.2 graphql: specifier: ^16 version: 16.8.1 @@ -1068,321 +1065,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@ethersproject/abi@5.7.0: - resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} - dependencies: - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - dev: false - - /@ethersproject/abstract-provider@5.7.0: - resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/properties': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/web': 5.7.1 - dev: false - - /@ethersproject/abstract-signer@5.7.0: - resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - dev: false - - /@ethersproject/address@5.7.0: - resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/rlp': 5.7.0 - dev: false - - /@ethersproject/base64@5.7.0: - resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} - dependencies: - '@ethersproject/bytes': 5.7.0 - dev: false - - /@ethersproject/basex@5.7.0: - resolution: {integrity: sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/properties': 5.7.0 - dev: false - - /@ethersproject/bignumber@5.7.0: - resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - bn.js: 5.2.1 - dev: false - - /@ethersproject/bytes@5.7.0: - resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} - dependencies: - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/constants@5.7.0: - resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} - dependencies: - '@ethersproject/bignumber': 5.7.0 - dev: false - - /@ethersproject/contracts@5.7.0: - resolution: {integrity: sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==} - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/transactions': 5.7.0 - dev: false - - /@ethersproject/hash@5.7.0: - resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - dev: false - - /@ethersproject/hdnode@5.7.0: - resolution: {integrity: sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==} - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/wordlists': 5.7.0 - dev: false - - /@ethersproject/json-wallets@5.7.0: - resolution: {integrity: sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==} - dependencies: - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - dev: false - - /@ethersproject/keccak256@5.7.0: - resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} - dependencies: - '@ethersproject/bytes': 5.7.0 - js-sha3: 0.8.0 - dev: false - - /@ethersproject/logger@5.7.0: - resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} - dev: false - - /@ethersproject/networks@5.7.1: - resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} - dependencies: - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/pbkdf2@5.7.0: - resolution: {integrity: sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/sha2': 5.7.0 - dev: false - - /@ethersproject/properties@5.7.0: - resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} - dependencies: - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/providers@5.7.2: - resolution: {integrity: sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==} - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/web': 5.7.1 - bech32: 1.1.4 - ws: 7.4.6 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /@ethersproject/random@5.7.0: - resolution: {integrity: sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/rlp@5.7.0: - resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/sha2@5.7.0: - resolution: {integrity: sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - hash.js: 1.1.7 - dev: false - - /@ethersproject/signing-key@5.7.0: - resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - bn.js: 5.2.1 - elliptic: 6.5.4 - hash.js: 1.1.7 - dev: false - - /@ethersproject/solidity@5.7.0: - resolution: {integrity: sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==} - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/strings': 5.7.0 - dev: false - - /@ethersproject/strings@5.7.0: - resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/transactions@5.7.0: - resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} - dependencies: - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - dev: false - - /@ethersproject/units@5.7.0: - resolution: {integrity: sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==} - dependencies: - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/logger': 5.7.0 - dev: false - - /@ethersproject/wallet@5.7.0: - resolution: {integrity: sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==} - dependencies: - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/json-wallets': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/random': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/wordlists': 5.7.0 - dev: false - - /@ethersproject/web@5.7.1: - resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} - dependencies: - '@ethersproject/base64': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - dev: false - - /@ethersproject/wordlists@5.7.0: - resolution: {integrity: sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/strings': 5.7.0 - dev: false - /@floating-ui/core@1.5.0: resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} dependencies: @@ -3555,10 +3237,6 @@ packages: hasBin: true dev: true - /aes-js@3.0.0: - resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} - dev: false - /aes-js@3.1.2: resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==} @@ -3830,10 +3508,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - /bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - dev: false - /bigint-buffer@1.1.5: resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} engines: {node: '>= 10.0.0'} @@ -3870,10 +3544,6 @@ packages: readable-stream: 3.6.2 dev: true - /bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} - dev: false - /bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} @@ -3902,10 +3572,6 @@ packages: dependencies: fill-range: 7.0.1 - /brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - dev: false - /browserslist@4.22.1: resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4411,18 +4077,6 @@ packages: resolution: {integrity: sha512-j9IcGmfkyN5MBH/0Xzg45GDHasXsnwEJDM6Xnr9H7GlGUni+JH4q6xp6Nk7NV5LjTmoEFBqhILIqg1McJrv6uA==} dev: false - /elliptic@6.5.4: - resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} - dependencies: - bn.js: 4.12.0 - 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 - dev: false - /embla-carousel-react@7.1.0(react@18.2.0): resolution: {integrity: sha512-tbYRPRZSDNd2QLNqYDcArAakGIxtUbhS7tkP0dGXktXHGgcX+3ji3VrOUTOftBiujZrMV8kRxtrRUe/1soloIQ==} peerDependencies: @@ -4910,44 +4564,6 @@ packages: dependencies: fast-safe-stringify: 2.1.1 - /ethers@5.7.2: - resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/abstract-provider': 5.7.0 - '@ethersproject/abstract-signer': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/base64': 5.7.0 - '@ethersproject/basex': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/constants': 5.7.0 - '@ethersproject/contracts': 5.7.0 - '@ethersproject/hash': 5.7.0 - '@ethersproject/hdnode': 5.7.0 - '@ethersproject/json-wallets': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/logger': 5.7.0 - '@ethersproject/networks': 5.7.1 - '@ethersproject/pbkdf2': 5.7.0 - '@ethersproject/properties': 5.7.0 - '@ethersproject/providers': 5.7.2 - '@ethersproject/random': 5.7.0 - '@ethersproject/rlp': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/signing-key': 5.7.0 - '@ethersproject/solidity': 5.7.0 - '@ethersproject/strings': 5.7.0 - '@ethersproject/transactions': 5.7.0 - '@ethersproject/units': 5.7.0 - '@ethersproject/wallet': 5.7.0 - '@ethersproject/web': 5.7.1 - '@ethersproject/wordlists': 5.7.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -5469,14 +5085,6 @@ packages: /hey-listen@1.0.8: resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} - /hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - dev: false - /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: @@ -5931,10 +5539,6 @@ packages: resolution: {integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==} dev: false - /js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - dev: false - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6288,10 +5892,6 @@ packages: /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - /minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - dev: false - /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -7304,10 +6904,6 @@ packages: dependencies: loose-envify: 1.4.0 - /scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - dev: false - /scuid@1.1.0: resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==} dev: false @@ -8173,19 +7769,6 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /ws@7.4.6: - resolution: {integrity: sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - /ws@7.5.9: resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} engines: {node: '>=8.3.0'} diff --git a/frontend/src/app/bounty/[bountyId]/exploit/page.tsx b/frontend/src/app/bounty/[bountyId]/exploit/page.tsx index 2836f4a..7028950 100644 --- a/frontend/src/app/bounty/[bountyId]/exploit/page.tsx +++ b/frontend/src/app/bounty/[bountyId]/exploit/page.tsx @@ -23,8 +23,8 @@ import { SendExploit } from "../../../../model/inputs"; import { usePrepareSendExploit } from "../../../../hooks/bug-buster"; import { useInputBoxAddInput } from "../../../../hooks/contracts"; -import { BountyParams, InvalidBountyId } from "../utils.tsx"; -import { GetBounty } from "../../../../model/reader"; +import { BountyParams } from "../utils.tsx"; +import { useBounty } from "../../../../model/reader"; import { FileDrop } from "../../../../components/filedrop"; interface FileDropTextParams { @@ -122,22 +122,25 @@ const SendExploitPage: FC = ({ params: { bountyId } }) => { hash: data?.hash, }); - if (isNaN(bountyIndex)) { - return ; + const bountyResult = useBounty(bountyIndex); + + switch (bountyResult.kind) { + case "loading": + return
Loading bounty info...
; + case "error": + return
{bountyResult.message}
; } - const result = GetBounty(bountyIndex); + const bounty = bountyResult.response; return (
Submit bounty exploit - {result.kind == "success" && ( - - {result.response.name} - - )} + + {bounty.name} + = ({ params: { bountyId } }) => { minRows={10} maxRows={40} multiline={true} - styles={{ input: { fontFamily: "monospace" } }} + styles={{ + input: { fontFamily: "monospace" }, + }} placeholder="Exploit code" onChange={(e) => setExploitFile(btoa(e.target.value)) diff --git a/frontend/src/app/bounty/[bountyId]/page.tsx b/frontend/src/app/bounty/[bountyId]/page.tsx index 400e4b4..5dc8fbb 100644 --- a/frontend/src/app/bounty/[bountyId]/page.tsx +++ b/frontend/src/app/bounty/[bountyId]/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { FC } from "react"; +import { FC, useEffect, useState } from "react"; import { Button, @@ -14,12 +14,18 @@ import { import { formatEther } from "viem"; -import { GetBounty } from "../../../model/reader"; -import { Exploit, getBountyTotalPrize, AppBounty } from "../../../model/state"; +import { + parseHexAsJson, + useBounty, + useInput, + isInput, +} from "../../../model/reader"; +import { getBountyTotalPrize, AppBounty } from "../../../model/state"; +import { SendExploit } from "../../../model/inputs"; import { usePrepareWithdrawSponsorship } from "../../../hooks/bug-buster"; import { useInputBoxAddInput } from "../../../hooks/contracts"; -import { BountyParams, InvalidBountyId } from "./utils"; +import { BountyParams } from "./utils"; import { useBlockTimestamp } from "../../../hooks/block"; import { BountyStatus } from "../../../model/bountyStatus"; import { getBountyStatus } from "../../../utils/bounty"; @@ -105,14 +111,12 @@ const BountyBox: FC<{ ); }; -const ExploitCodeBox: FC<{ - exploit: Exploit | undefined; -}> = ({ exploit }) => { - if (!!exploit) { +const ExploitCodeBox: FC<{ exploitCode?: string }> = ({ exploitCode }) => { + if (exploitCode !== undefined) { return ( Exploit Code - {exploit.code} + {exploitCode} ); } @@ -148,29 +152,68 @@ const ParticipantsBox: FC<{ ); }; +const isSendExploit = (payload: any): payload is SendExploit => { + return payload !== undefined && "exploit" in payload; +}; + const BountyInfoPage: FC = ({ params: { bountyId } }) => { const bountyIndex = Number(bountyId); - if (isNaN(bountyIndex)) { - return ; - } - const result = GetBounty(bountyIndex); - switch (result.kind) { + + const [exploitInputIndex, setExploitInputIndex] = useState(); + const [exploitCode, setExploitCode] = useState(); + + const bountyResult = useBounty(bountyIndex); + const inputResult = useInput(exploitInputIndex); + + useEffect(() => { + if (bountyResult.kind == "success") { + const bounty = bountyResult.response; + const exploit = bounty.exploit; + if (exploit !== null) { + setExploitInputIndex(exploit.inputIndex); + } + } + }, [bountyResult]); + + useEffect(() => { + if (inputResult.kind == "success") { + const input = parseHexAsJson(inputResult.response.payload); + if (isInput(input)) { + const payload = input.payload; + if (isSendExploit(payload)) { + setExploitCode(atob(payload.exploit)); + } + } + } + }, [inputResult]); + + switch (bountyResult.kind) { case "loading": return
Loading bounty info...
; case "error": - return
{result.message}
; - case "success": - const bounty = result.response; - return ( -
- - - - - -
- ); + return
{bountyResult.message}
; + } + + if (exploitInputIndex !== undefined) { + switch (inputResult.kind) { + case "loading": + return
Loading exploit input...
; + case "error": + return
{inputResult.message}
; + } } + + const bounty = bountyResult.response; + + return ( +
+ + + + + +
+ ); }; export default BountyInfoPage; diff --git a/frontend/src/app/bounty/[bountyId]/sponsor/page.tsx b/frontend/src/app/bounty/[bountyId]/sponsor/page.tsx index 179257b..9096add 100644 --- a/frontend/src/app/bounty/[bountyId]/sponsor/page.tsx +++ b/frontend/src/app/bounty/[bountyId]/sponsor/page.tsx @@ -18,8 +18,8 @@ import { usePrepareAddSponsorship } from "../../../../hooks/bug-buster"; import { useEtherPortalDepositEther } from "../../../../hooks/contracts"; import { useWaitForTransaction } from "wagmi"; -import { BountyParams, InvalidBountyId } from "../utils.tsx"; -import { GetBounty } from "../../../../model/reader"; +import { BountyParams } from "../utils.tsx"; +import { useBounty } from "../../../../model/reader"; const toWei = (input: string | number) => { if (typeof input == "number") { @@ -51,26 +51,29 @@ const AddSponsorshipPage: FC = ({ params: { bountyId } }) => { hash: data?.hash, }); - if (isNaN(bountyIndex)) { - return ; - } - function wrapSetter(setter: any) { return (e: any) => setter(e.target.value); } - const result = GetBounty(bountyIndex); + const bountyResult = useBounty(bountyIndex); + + switch (bountyResult.kind) { + case "loading": + return
Loading bounty info...
; + case "error": + return
{bountyResult.message}
; + } + + const bounty = bountyResult.response; return (
Sponsor bounty - {result.kind == "success" && ( - - {result.response.name} - - )} + + {bounty.name} + { - return ( - -
Invalid bounty ID
-
- ); -}; diff --git a/frontend/src/app/bounty/create/page.tsx b/frontend/src/app/bounty/create/page.tsx index ff2b4cc..8b8d716 100644 --- a/frontend/src/app/bounty/create/page.tsx +++ b/frontend/src/app/bounty/create/page.tsx @@ -50,13 +50,17 @@ const FileDropText: FC = ({ filename }) => { const CreateBountyPage: FC = () => { const theme = useMantineTheme(); - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [imgLink, setImgLink] = useState(""); - const [filename, setFilename] = useState(); - + const [name, setName] = useState(); + const [description, setDescription] = useState(); + const [imgLink, setImgLink] = useState(); + const [filename, setFilename] = useState(); const [minDeadline, setMinDeadline] = useState(); + const [deadline, setDeadline] = useState(); + const [codeZipBinary, setCodeZipBinary] = useState(); + const [codeZipPath, setCodeZipPath] = useState(); + const blockTimestamp = useBlockTimestamp(); + useEffect(() => { if (blockTimestamp === undefined) { setMinDeadline(undefined); @@ -66,35 +70,30 @@ const CreateBountyPage: FC = () => { setMinDeadline(blockDate); } }, [blockTimestamp]); - const [deadline, setDeadline] = useState(null); - - const [appFile, setAppFile] = useState(null); - const [appPath, setAppPath] = useState(null); - - const readFile = (f: FileWithPath | null) => { - if (f) { - f.arrayBuffer().then((buf) => { - //setAppFile(bytesToHex(new Uint8Array(buf))); - setAppFile( - btoa( - Array.from(new Uint8Array(buf)) - .map((b) => String.fromCharCode(b)) - .join(""), - ), - ); - setFilename(f.name); + + const readFile = (fileWithPath: FileWithPath) => { + // prettier-ignore + fileWithPath + .arrayBuffer() + .then((buf) => { + // prettier-ignore + const str = Array.from(new Uint8Array(buf)) + .map((b) => String.fromCharCode(b)) + .join(""); + + setCodeZipBinary(btoa(str)); + setFilename(fileWithPath.name); }); - } }; - const bounty = { - name, - description, + const bounty: CreateAppBounty = { + name: name ?? "", + description: description ?? "", imgLink, - deadline: deadline ? deadline.getTime() / 1000 : null, - codeZipBinary: appFile, - codeZipPath: appPath, - } as CreateAppBounty; + deadline: (deadline ?? new Date()).getTime() / 1000, + codeZipBinary, + codeZipPath, + }; const config = usePrepareCreateBounty(bounty); @@ -103,10 +102,6 @@ const CreateBountyPage: FC = () => { hash: data?.hash, }); - function submit() { - if (write) write(); - } - return (
@@ -142,7 +137,11 @@ const CreateBountyPage: FC = () => { label="Deadline" value={deadline} minDate={minDeadline} - onChange={(e) => setDeadline(e)} + onChange={(e) => { + if (e !== null) { + setDeadline(e); + } + }} /> @@ -153,7 +152,12 @@ const CreateBountyPage: FC = () => { readFile(files[0])} + onDrop={(files) => { + const file = files.at(0); + if (file !== undefined) { + readFile(file); + } + }} accept={{ "application/octet-stream": [".tar.xz"], }} @@ -165,9 +169,9 @@ const CreateBountyPage: FC = () => { setAppPath(e.target.value)} + onChange={(e) => setCodeZipPath(e.target.value)} /> @@ -176,15 +180,17 @@ const CreateBountyPage: FC = () => { diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 032fc91..4e91ca5 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -14,7 +14,7 @@ import { Group, } from "@mantine/core"; import Link from "next/link"; -import { GetLatestState } from "../model/reader"; +import { useLatestState } from "../model/reader"; import { AppBounty } from "../model/state"; import { BountyStatusBadgeGroup } from "../components/bountyStatus"; import { HasConnectedAccount } from "../components/hasConnectedAccount"; @@ -57,30 +57,32 @@ const Bounty: FC<{ }; const BountyList: FC = () => { - const result = GetLatestState(); + const stateResult = useLatestState(); const blockTimestamp = useBlockTimestamp(); - switch (result.kind) { + + switch (stateResult.kind) { case "loading": return
Loading list of bounties...
; case "error": - return
{result.message}
; - case "success": - const state = result.response; - return ( - - {state.bounties?.map((bounty, index) => { - return ( - - ); - })} - - ); + return
{stateResult.message}
; } + + const state = stateResult.response; + + return ( + + {state.bounties.map((bounty, index) => { + return ( + + ); + })} + + ); }; const Home: FC = () => { diff --git a/frontend/src/app/voucher/page.tsx b/frontend/src/app/voucher/page.tsx index 7920442..0bb89a0 100644 --- a/frontend/src/app/voucher/page.tsx +++ b/frontend/src/app/voucher/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { FC, useEffect, useState } from "react"; +import { FC } from "react"; import { Badge, Button, @@ -11,129 +11,137 @@ import { Stack, Text, } from "@mantine/core"; -import { GetVouchers } from "../../model/reader"; -import { Voucher } from "../../model/state"; -import { decodePayload, getVouchersByUser } from "../../utils/voucher"; +import { useVouchers } from "../../model/reader"; +import { Voucher } from "../../utils/voucher"; +import { decodeVoucher, filterVouchersByReceiver } from "../../utils/voucher"; import { useAccount, useContractRead, useContractWrite } from "wagmi"; import { getDAppAddress } from "../../utils/address"; -import { - getExecuteVoucherABI, - getWasVoucherExecutedABI, -} from "../../utils/voucher"; +import { voucherExecutionAbi, dummyProof } from "../../utils/voucher"; +import { Address, formatEther } from "viem"; const WithdrawButton: FC<{ voucher: Voucher }> = ({ voucher }) => { - const [isExecuted, setIsExecuted] = useState(false); - const [isWaitingProof, setIsWaitingProof] = useState(true); - - const { data, isLoading } = useContractRead({ + const { data: wasExecuted, error: wasExecutedError } = useContractRead({ address: getDAppAddress(), - abi: getWasVoucherExecutedABI(), + abi: voucherExecutionAbi, functionName: "wasVoucherExecuted", - args: [voucher.input.index, voucher.index], + args: [BigInt(voucher.input.index), BigInt(voucher.index)], }); - const { write } = useContractWrite({ + const proof = voucher.proof ?? dummyProof; + const { validity } = proof; + const { inputIndexWithinEpoch, outputIndexWithinInput } = validity; + + const { write: executeVoucher } = useContractWrite({ address: getDAppAddress(), - abi: getExecuteVoucherABI(), + abi: voucherExecutionAbi, functionName: "executeVoucher", - args: [voucher.destination, voucher.payload, voucher.proof], + args: [ + voucher.destination, + voucher.payload, + { + ...proof, + validity: { + ...validity, + inputIndexWithinEpoch: BigInt(inputIndexWithinEpoch), + outputIndexWithinInput: BigInt(outputIndexWithinInput), + }, + }, + ], }); - useEffect(() => { - if (!!voucher.proof) { - setIsWaitingProof(false); - - if (!isLoading && data == true) { - setIsExecuted(true); - } else { - setIsExecuted(false); - } - } else { - setIsWaitingProof(true); - setIsExecuted(false); - } - }, [voucher.proof, data, isLoading]); - - if (isWaitingProof) { - return Waiting proof...; - } else { - if (isExecuted) { - return Executed!; - } else { - return ; - } + if (wasExecutedError !== null) { + return {wasExecutedError.message}; } + + if (wasExecuted === undefined) { + return Checking execution status...; + } + + if (wasExecuted) { + return Executed!; + } + + if (voucher.proof === null) { + return Waiting for proof...; + } + + if (executeVoucher === undefined) { + return Preparing transaction...; + } + + return ; }; const VoucherInfo: FC<{ voucher: Voucher }> = ({ voucher }) => { - const { voucherAmount } = decodePayload(voucher.payload) || { - voucherAmount: null, - }; + const { value } = decodeVoucher(voucher); return (
- {voucherAmount} ETH + {formatEther(value)} ETH - Voucher:{voucher.index} - / - Input:{voucher.input.index} + + Input {voucher.input.index} / Voucher {voucher.index} +
); }; -const VoucherList: FC<{ allVouchers: Voucher[]; connectedAccount: string }> = ({ - allVouchers, - connectedAccount, +const VoucherList: FC<{ vouchers: Voucher[]; account: Address }> = ({ + vouchers, + account, }) => { - const accountVouchers = getVouchersByUser(allVouchers, connectedAccount); + const vouchersForAccount = filterVouchersByReceiver(vouchers, account); + + if (vouchersForAccount.length == 0) { + return ( +
+ No vouchers available for {account}! +
+ ); + } + return (
- {accountVouchers.length > 0 ? ( - Available vouchers: - ) : ( - No vouchers available! - )} + Available vouchers:
- {accountVouchers.length > 0 && - accountVouchers.map((voucher) => { - const key = voucher.index + voucher.input.index; - return ; - })} + {vouchersForAccount.map((voucher) => { + const key = `voucher_${voucher.input.index}_${voucher.index}`; + return ; + })}
); }; +const ErrorMessage: FC<{ message: string }> = ({ message }) => { + return
{message}
; +}; + const VoucherHome: FC = () => { - const { address } = useAccount(); - const result = GetVouchers(); - switch (result.kind) { + const { address: account } = useAccount(); + const vouchersResult = useVouchers(); + + switch (vouchersResult.kind) { case "loading": - return
Checking for vouchers...
; + return ; case "error": - return
{result.message}
; - case "success": - const allVouchers = result.response; - { - if (allVouchers.length > 0) { - return ( - - ); - } else { - return
No vouchers available!
; - } - } + return ; + } + + const vouchers = vouchersResult.response; + + if (vouchers.length == 0) { + return ; } + + return ; }; export default VoucherHome; diff --git a/frontend/src/hooks/bug-buster.tsx b/frontend/src/hooks/bug-buster.tsx index 9d036d3..e8daea7 100644 --- a/frontend/src/hooks/bug-buster.tsx +++ b/frontend/src/hooks/bug-buster.tsx @@ -36,7 +36,10 @@ function usePrepareBugBusterETHDeposit(input: Input, valueInWei: bigint) { } export function usePrepareCreateBounty(bounty: CreateAppBounty) { - return usePrepareBugBusterInput({ kind: "CreateAppBounty", payload: bounty }); + return usePrepareBugBusterInput({ + kind: "CreateAppBounty", + payload: bounty, + }); } export function usePrepareAddSponsorship( diff --git a/frontend/src/model/inputs.ts b/frontend/src/model/inputs.ts index 3db32d9..ae06f22 100644 --- a/frontend/src/model/inputs.ts +++ b/frontend/src/model/inputs.ts @@ -1,3 +1,7 @@ +import { Hex } from "viem"; + +import { CompletionStatus } from "./__generated__/graphql"; + export interface CreateAppBounty { name: string; description: string; @@ -34,3 +38,8 @@ export type Input = | TaggedInput<"AddSponsorship", AddSponsorship> | TaggedInput<"SendExploit", SendExploit> | TaggedInput<"WithdrawSponsorship", WithdrawSponsorship>; + +export interface InputInfo { + payload: Hex; + status: CompletionStatus; +} diff --git a/frontend/src/model/reader.ts b/frontend/src/model/reader.ts index 8269a87..b9cc771 100644 --- a/frontend/src/model/reader.ts +++ b/frontend/src/model/reader.ts @@ -1,8 +1,11 @@ +import { useState, useEffect } from "react"; import { useQuery } from "@apollo/client"; import { gql } from "./__generated__/gql"; -import { CompletionStatus } from "./__generated__/graphql"; -import { BugBusterState, AppBounty, Voucher } from "./state"; -import { TaggedInput, SendExploit } from "./inputs"; +import { GetVouchersQuery } from "./__generated__/graphql"; +import { BugBusterState, AppBounty } from "./state"; +import { Validity, Proof, Voucher } from "../utils/voucher"; +import { InputInfo } from "./inputs"; +import { Hex, isHex, hexToBytes, isAddress, isHash } from "viem"; type ReaderLoadingResult = { kind: "loading"; @@ -35,18 +38,11 @@ const GET_LAST_REPORTS = gql(/* GraphQL */ ` } `); -const GET_INPUT_STATUS = gql(/* GraphQL */ ` - query getInputStatus($inputIndex: Int!) { - input(index: $inputIndex) { - status - } - } -`); - -const GET_INPUT_PAYLOAD = gql(/* GraphQL */ ` - query getInputPayload($inputIndex: Int!) { +const GET_INPUT = gql(/* GraphQL */ ` + query getInput($inputIndex: Int!) { input(index: $inputIndex) { payload + status } } `); @@ -81,126 +77,190 @@ const GET_VOUCHERS = gql(/* GraphQL */ ` } `); -// Get the latest bug less state from the GraphQL API polling the API every 500 ms. -function GetLatestState(): ReaderResult { - const { data, loading, error } = useQuery(GET_LAST_REPORTS, { - pollInterval: 500, // ms - }); - if (loading) return { kind: "loading" }; - if (error) return { kind: "error", message: error.message }; - let reportEdge = data?.reports.edges.findLast((edge) => - // starts with {"bounties": - edge.node.payload.startsWith("0x7b22626f756e74696573223a"), - ); - let payload = reportEdge?.node.payload; - let stateBytes = fromHexString(payload?.substring(2)); // remove '0x' - let stateJson = null; - if (stateBytes !== undefined) { - let stateText = new TextDecoder().decode(stateBytes); - stateJson = JSON.parse(stateText) as BugBusterState; - } else { - stateJson = { bounties: [] }; +const POLL_INTERVAL = 2000; // ms + +// Create a stateful variable for a reader result +function useReaderResult() { + return useState>({ kind: "loading" }); +} + +export const parseHexAsJson = (hex: Hex) => { + const bytes = hexToBytes(hex); + const buffer = Buffer.from(bytes); + const str = buffer.toString("utf-8"); + try { + return JSON.parse(str); + } catch (e) {} +}; + +export const parseStringAsJson = (str: string) => { + if (isHex(str)) { + return parseHexAsJson(str); } - return { kind: "success", response: stateJson }; +}; + +function isBugBusterState(state: any): state is BugBusterState { + return state !== undefined && "bounties" in state; } -// Get the details for the given bounty including the exploit code. -function GetBounty(bountyIndex: number): ReaderResult { - const reportsQuery = useQuery(GET_LAST_REPORTS, { - pollInterval: 500, // ms +export const useLatestState = () => { + const [result, setResult] = useReaderResult(); + const { data, loading, error } = useQuery(GET_LAST_REPORTS, { + pollInterval: POLL_INTERVAL, }); - let reportEdge = reportsQuery.data?.reports.edges.findLast((edge) => - // starts with {"bounties": - edge.node.payload.startsWith("0x7b22626f756e74696573223a"), + + useEffect(() => { + if (loading) { + setResult({ kind: "loading" }); + } else if (error !== undefined) { + setResult({ kind: "error", message: error.message }); + } else if (data !== undefined) { + const lastEdge = data.reports.edges.findLast((edge) => { + const state = parseStringAsJson(edge.node.payload); + return isBugBusterState(state); + }); + if (lastEdge === undefined) { + const initialState: BugBusterState = { bounties: [] }; + setResult({ kind: "success", response: initialState }); + } else { + const state = parseStringAsJson(lastEdge.node.payload); + if (isBugBusterState(state)) { + setResult({ kind: "success", response: state }); + } + } + } + }, [data, loading, error, setResult]); + + return result; +}; + +export const useBounty = (bountyIndex: number) => { + const [result, setResult] = useReaderResult(); + const stateResult = useLatestState(); + + useEffect(() => { + if (isNaN(bountyIndex)) { + setResult({ + kind: "error", + message: "Bounty index is not a number", + }); + } else { + if (stateResult.kind == "success") { + const state = stateResult.response; + const bounty = state.bounties.at(bountyIndex); + if (bounty !== undefined) { + setResult({ kind: "success", response: bounty }); + } else { + setResult({ kind: "error", message: "Bounty not found" }); + } + } else { + setResult(stateResult); + } + } + }, [bountyIndex, stateResult, setResult]); + + return result; +}; + +export const isInput = ( + input: any, +): input is { kind: string; payload: any } => { + return ( + input !== undefined && + "kind" in input && + typeof input.kind === "string" && + "payload" in input ); - let payload = reportEdge?.node.payload; - let stateBytes = fromHexString(payload?.substring(2)); // remove '0x' - let stateJson = null; - if (stateBytes !== undefined) { - let stateText = new TextDecoder().decode(stateBytes); - stateJson = JSON.parse(stateText) as BugBusterState; - } - let bounty = stateJson?.bounties.at(bountyIndex); - let exploit = bounty?.exploit; - const exploitQuery = useQuery(GET_INPUT_PAYLOAD, { - skip: !exploit?.inputIndex, - variables: { - inputIndex: exploit?.inputIndex as number, // this is fine because of skip - }, +}; + +export const useInput = (inputIndex?: number) => { + const [result, setResult] = useReaderResult(); + const { data, loading, error } = useQuery(GET_INPUT, { + pollInterval: POLL_INTERVAL, + variables: inputIndex !== undefined ? { inputIndex } : undefined, }); - let SendExploitInputBytes = fromHexString( - exploitQuery.data?.input.payload?.substring(2), // remove '0x' - ); - if (exploit && SendExploitInputBytes) { - let SendExploitInputText = new TextDecoder().decode( - SendExploitInputBytes, - ); - let SendExploitInput = JSON.parse(SendExploitInputText) as TaggedInput< - "SendExploit", - SendExploit - >; - exploit.code = atob(SendExploitInput.payload.exploit); - } - if (reportsQuery.loading) return { kind: "loading" }; - if (reportsQuery.error) - return { kind: "error", message: reportsQuery.error.message }; + useEffect(() => { + if (loading) { + setResult({ kind: "loading" }); + } else if (error !== undefined) { + setResult({ kind: "error", message: error.message }); + } else if (data !== undefined) { + const { status, payload } = data.input; + if (isHex(payload)) { + setResult({ kind: "success", response: { status, payload } }); + } else { + setResult({ + kind: "error", + message: "Malformed input payload", + }); + } + } + }, [data, loading, error, setResult]); - if (exploitQuery.loading) return { kind: "loading" }; - if (exploitQuery.error) - return { kind: "error", message: exploitQuery.error.message }; + return result; +}; - if (bounty === undefined) { - return { kind: "error", message: "bounty not found" }; - } +type VoucherFromQuery = GetVouchersQuery["vouchers"]["edges"][number]["node"]; +type ProofFromQuery = NonNullable; +type ValidityFromQuery = ProofFromQuery["validity"]; - return { kind: "success", response: bounty }; -} +const isValidity = (validity: ValidityFromQuery): validity is Validity => { + const { + inputIndexWithinEpoch, + outputIndexWithinInput, + outputHashesRootHash, + vouchersEpochRootHash, + noticesEpochRootHash, + machineStateHash, + outputHashInOutputHashesSiblings, + outputHashesInEpochSiblings, + } = validity; -// Get whether the given input is ready. -function IsInputReady(inputIndex: number): ReaderResult { - const { data, loading, error } = useQuery(GET_INPUT_STATUS, { - pollInterval: 500, // ms - variables: { - inputIndex, - }, - }); - if (loading) return { kind: "loading" }; - if (error) { - if (error.message === "input not found") - return { kind: "success", response: false }; - return { kind: "error", message: error.message }; - } - const ready = data?.input.status == CompletionStatus.Accepted; - return { kind: "success", response: ready }; -} + return ( + !isNaN(inputIndexWithinEpoch) && + !isNaN(outputIndexWithinInput) && + isHash(outputHashesRootHash) && + isHash(vouchersEpochRootHash) && + isHash(noticesEpochRootHash) && + isHash(machineStateHash) && + outputHashInOutputHashesSiblings.every(isHash) && + outputHashesInEpochSiblings.every(isHash) + ); +}; -// Get whether the given input is ready. -function GetVouchers(): ReaderResult { - const { data, loading, error } = useQuery(GET_VOUCHERS, { - pollInterval: 1000, // ms - TODO Check this and other poll intervals to avoid consuming all API request from RPC provider - }); - if (loading) return { kind: "loading" }; - if (error) { - return { kind: "error", message: error.message }; - } +const isProof = (proof: ProofFromQuery): proof is Proof => { + const { context, validity } = proof; + return isHex(context) && isValidity(validity); +}; - const vouchers: Voucher[] = data?.vouchers.edges?.map( - (edge) => edge.node, - ) as Voucher[]; +const isVoucher = (voucher: VoucherFromQuery): voucher is Voucher => { + const { destination, payload, proof } = voucher; + return ( + isAddress(destination) && isHex(payload) && (!proof || isProof(proof)) + ); +}; - return { kind: "success", response: vouchers }; -} +export const useVouchers = () => { + const [result, setResult] = useReaderResult(); + const { data, loading, error } = useQuery(GET_VOUCHERS, { + pollInterval: POLL_INTERVAL, + }); -function fromHexString(hexString: string | undefined): Uint8Array | undefined { - if (hexString === undefined) { - return undefined; - } - let match = hexString.match(/.{1,2}/g); - if (match === null) { - return undefined; - } - return Uint8Array.from(match.map((byte) => parseInt(byte, 16))); -} + useEffect(() => { + if (loading) { + setResult({ kind: "loading" }); + } else if (error !== undefined) { + setResult({ kind: "error", message: error.message }); + } else if (data !== undefined) { + const vouchers = data.vouchers.edges.map((edge) => edge.node); + if (vouchers.every(isVoucher)) { + setResult({ kind: "success", response: vouchers }); + } else { + setResult({ kind: "error", message: "Malformed vouchers" }); + } + } + }, [data, loading, error, setResult]); -export { GetLatestState, GetBounty, IsInputReady, GetVouchers }; + return result; +}; diff --git a/frontend/src/model/state.ts b/frontend/src/model/state.ts index 2f18649..6a78a14 100644 --- a/frontend/src/model/state.ts +++ b/frontend/src/model/state.ts @@ -1,3 +1,5 @@ +import { Address, Hex } from "viem"; + export interface BugBusterState { bounties: AppBounty[]; } @@ -7,8 +9,8 @@ export interface AppBounty { imgLink?: string; description: string; deadline: number; - sponsorships?: Sponsorship[]; - exploit?: Exploit; + sponsorships: Sponsorship[] | null; + exploit: Exploit | null; withdrawn: boolean; } @@ -24,7 +26,7 @@ export const getBountyTotalPrize = (bounty: AppBounty) => { }; export interface Profile { - address: string; + address: Address; name: string; imgLink?: string; } @@ -32,30 +34,9 @@ export interface Profile { export interface Exploit { hacker: Profile; inputIndex: number; - code: string; } export interface Sponsorship { sponsor: Profile; - value: string; // number encoded as hex string -} - -export interface Voucher { - index: number; - input: { index: number }; - destination: string; - payload: string; - proof?: { - context: string; - validity: { - inputIndexWithinEpoch: number; - outputIndexWithinInput: number; - outputHashesRootHash: string; - vouchersEpochRootHash: string; - noticesEpochRootHash: string; - machineStateHash: string; - outputHashInOutputHashesSiblings: string[]; - outputHashesInEpochSiblings: string[]; - }; - }; + value: Hex; } diff --git a/frontend/src/utils/voucher.tsx b/frontend/src/utils/voucher.tsx index 640d7ed..3b5eb76 100644 --- a/frontend/src/utils/voucher.tsx +++ b/frontend/src/utils/voucher.tsx @@ -1,164 +1,84 @@ -import { ethers } from "ethers"; -import { Voucher } from "../model/state"; +import { + parseAbi, + decodeFunctionData, + Address, + isAddressEqual, + Hash, + Hex, +} from "viem"; -const ETHER_TRANSFER_SELECTOR = "0x522f6815"; +export interface Validity { + inputIndexWithinEpoch: number; + outputIndexWithinInput: number; + outputHashesRootHash: Hash; + vouchersEpochRootHash: Hash; + noticesEpochRootHash: Hash; + machineStateHash: Hash; + outputHashInOutputHashesSiblings: Hash[]; + outputHashesInEpochSiblings: Hash[]; +} -export function decodePayload(payload: string) { - if (!payload) return null; +export interface Proof { + context: Hex; + validity: Validity; +} - let voucherDestination; - let voucherAmount; +const ZERO_HASH: Hash = + "0x0000000000000000000000000000000000000000000000000000000000000000"; - const decoder = new ethers.utils.AbiCoder(); - const functionSelector = decoder.decode(["bytes4"], payload)[0]; - let payloadWithoutSelector = ethers.utils.hexDataSlice(payload, 4); - try { - if (functionSelector == ETHER_TRANSFER_SELECTOR) { - //ether transfer; - const transferParams = decoder.decode( - ["address", "uint256"], - payloadWithoutSelector, - ); - voucherDestination = transferParams[0]; - voucherAmount = ethers.utils.formatEther(transferParams[1]); - } else { - throw new Error( - "Unknown function selector. Only ether transfer is supported.", - ); - } - } catch (error) { - throw new Error("Error decoding payload: " + error); - } +export const dummyProof: Proof = { + context: "0x", + validity: { + inputIndexWithinEpoch: 0, + outputIndexWithinInput: 0, + outputHashesRootHash: ZERO_HASH, + vouchersEpochRootHash: ZERO_HASH, + noticesEpochRootHash: ZERO_HASH, + machineStateHash: ZERO_HASH, + outputHashInOutputHashesSiblings: [], + outputHashesInEpochSiblings: [], + }, +}; - return { voucherDestination, voucherAmount }; +export interface Voucher { + index: number; + input: { index: number }; + destination: Address; + payload: Hex; + proof?: Proof; } -export function getVouchersByUser(vouchers: Voucher[], account: string) { - return vouchers.filter((voucher) => { - const decodedPayload = decodePayload(voucher.payload); - const voucherDestination = decodedPayload - ? decodedPayload.voucherDestination - : null; - return ( - voucherDestination && - voucherDestination.toLowerCase() === account.toLowerCase() - ); +const withdrawEtherAbi = parseAbi([ + "function withdrawEther(address receiver, uint256 value)", +]); + +export const voucherExecutionAbi = parseAbi([ + "struct OutputValidityProof { uint64 inputIndexWithinEpoch; uint64 outputIndexWithinInput; bytes32 outputHashesRootHash; bytes32 vouchersEpochRootHash; bytes32 noticesEpochRootHash; bytes32 machineStateHash; bytes32[] outputHashInOutputHashesSiblings; bytes32[] outputHashesInEpochSiblings; }", + "struct Proof { OutputValidityProof validity; bytes context; }", + "function wasVoucherExecuted(uint256 inputIndex, uint256 outputIndexWithinInput) external view returns (bool)", + "function executeVoucher(address _destination, bytes calldata _payload, Proof calldata _proof) external returns (bool)", +]); + +export function decodeVoucher(voucher: Voucher) { + const { args } = decodeFunctionData({ + abi: withdrawEtherAbi, + data: voucher.payload, }); -} -export function getExecuteVoucherABI() { - return [ - { - inputs: [ - { - internalType: "address", - name: "_destination", - type: "address", - }, - { - internalType: "bytes", - name: "_payload", - type: "bytes", - }, - { - components: [ - { - components: [ - { - internalType: "uint64", - name: "inputIndexWithinEpoch", - type: "uint64", - }, - { - internalType: "uint64", - name: "outputIndexWithinInput", - type: "uint64", - }, - { - internalType: "bytes32", - name: "outputHashesRootHash", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "vouchersEpochRootHash", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "noticesEpochRootHash", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "machineStateHash", - type: "bytes32", - }, - { - internalType: "bytes32[]", - name: "outputHashInOutputHashesSiblings", - type: "bytes32[]", - }, - { - internalType: "bytes32[]", - name: "outputHashesInEpochSiblings", - type: "bytes32[]", - }, - ], - internalType: "struct OutputValidityProof", - name: "validity", - type: "tuple", - }, - { - internalType: "bytes", - name: "context", - type: "bytes", - }, - ], - internalType: "struct Proof", - name: "_proof", - type: "tuple", - }, - ], - name: "executeVoucher", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - ]; + const [receiver, value] = args; + + return { + receiver, + value, + }; } -export function getWasVoucherExecutedABI() { - return [ - { - inputs: [ - { - internalType: "uint256", - name: "_inputIndex", - type: "uint256", - }, - { - internalType: "uint256", - name: "_outputIndexWithinInput", - type: "uint256", - }, - ], - name: "wasVoucherExecuted", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - ]; +export function filterVouchersByReceiver( + vouchers: Voucher[], + account: Address, +) { + return vouchers.filter((voucher) => { + const { receiver } = decodeVoucher(voucher); + return isAddressEqual(receiver, account); + }); }