diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a699ac..339ae34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,3 +147,43 @@ jobs: name: build-output path: frontend/dist retention-days: 7 + + bundle-budget: + name: Bundle analysis & size budget + needs: [lint, test] + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build with rollup visualizer + run: pnpm run build:analyze + + - name: Enforce size budget (size-limit) + run: pnpm run size + + - name: Upload bundle analysis (treemap) + uses: actions/upload-artifact@v4 + with: + name: bundle-stats + path: frontend/dist/stats.html + retention-days: 14 diff --git a/README.md b/README.md index 1ec6cc3..5796fee 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,13 @@ pnpm run test:watch # Run tests in watch mode > **⚠️ Important:** Always use `pnpm` (not `npm` or `yarn`) to maintain consistency. > Using different package managers creates lock file conflicts and dependency mismatches. +### Soroban contracts (proxy / upgrade safety) + +Smart contracts live in [navin-contracts](https://github.com/Navin-xmr/navin-contracts). This frontend does not embed Rust contracts, but any future Soroban client code should validate **contract StrKeys** (`C…`) and, for upgradeable or proxy deployments, restrict calls to IDs listed in environment configuration. + +- Utilities: `frontend/src/utils/contractSafety.ts` (`validateSorobanContractId`, `checkSorobanContractForProxySafety`). +- Optional allowlist: set `VITE_SOROBAN_CONTRACT_ALLOWLIST` in `.env` (see `frontend/.env.example`) to a comma-separated list of trusted contract StrKeys. + --- ## Contributing diff --git a/frontend/.env.example b/frontend/.env.example index 300684c..c85de50 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1 +1,5 @@ VITE_API_BASE_URL=http://localhost:3000/api + +# Optional: comma-separated Soroban contract StrKeys the app is allowed to use (proxy / upgrade safety). +# When set, client code should reject any other contract id before invoking Soroban. +# VITE_SOROBAN_CONTRACT_ALLOWLIST=CDEMO...,COTHER... diff --git a/frontend/.size-limit.json b/frontend/.size-limit.json new file mode 100644 index 0000000..2c571ac --- /dev/null +++ b/frontend/.size-limit.json @@ -0,0 +1,8 @@ +[ + { + "name": "All production JS/CSS (gzip)", + "path": ["dist/assets/*.js", "dist/assets/*.css"], + "limit": "2400 kB", + "gzip": true + } +] diff --git a/frontend/README.md b/frontend/README.md index 18bc70e..396148f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -7,6 +7,11 @@ Currently, two official plugins are available: - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +## Bundle size & analysis + +- **Budget (CI):** [`size-limit`](https://github.com/ai/size-limit) reads `dist/assets/*.{js,css}` after a production build; limits live in [`.size-limit.json`](./.size-limit.json). +- **Treemap:** `pnpm run build:analyze` sets `ANALYZE=true`, runs `rollup-plugin-visualizer`, and writes `dist/stats.html`. The GitHub Actions **Bundle analysis & size budget** job uploads that file as the `bundle-stats` artifact. + ## React Compiler The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). diff --git a/frontend/package.json b/frontend/package.json index 5c69554..16db5bf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "build:analyze": "cross-env ANALYZE=true tsc -b && cross-env ANALYZE=true vite build", + "size": "size-limit", "lint": "eslint .", "preview": "vite preview", "test": "vitest run", @@ -13,6 +15,7 @@ }, "dependencies": { "@stellar/freighter-api": "^6.0.1", + "@stellar/stellar-base": "^14.1.0", "@tailwindcss/vite": "^4.2.1", "axios": "^1.7.0", "lucide-react": "^0.575.0", @@ -23,6 +26,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@size-limit/file": "^12.0.1", "@tailwindcss/postcss": "^4.2.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -34,12 +38,15 @@ "@typescript-eslint/parser": "^8.0.0", "@vitejs/plugin-react": "^5.1.1", "autoprefixer": "^10.4.24", + "cross-env": "^10.1.0", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "jsdom": "^28.1.0", "postcss": "^8.5.6", + "rollup-plugin-visualizer": "^7.0.1", + "size-limit": "^12.0.1", "tailwindcss": "^4.2.1", "typescript": "^5.6.0", "vite": "^7.3.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index f16cdcf..ab72160 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@stellar/freighter-api': specifier: ^6.0.1 version: 6.0.1 + '@stellar/stellar-base': + specifier: ^14.1.0 + version: 14.1.0 '@tailwindcss/vite': specifier: ^4.2.1 version: 4.2.1(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)) @@ -36,6 +39,9 @@ importers: '@eslint/js': specifier: ^9.39.1 version: 9.39.3 + '@size-limit/file': + specifier: ^12.0.1 + version: 12.0.1(size-limit@12.0.1(jiti@2.6.1)) '@tailwindcss/postcss': specifier: ^4.2.1 version: 4.2.1 @@ -69,6 +75,9 @@ importers: autoprefixer: specifier: ^10.4.24 version: 10.4.24(postcss@8.5.6) + cross-env: + specifier: ^10.1.0 + version: 10.1.0 eslint: specifier: ^9.39.1 version: 9.39.3(jiti@2.6.1) @@ -83,10 +92,16 @@ importers: version: 16.5.0 jsdom: specifier: ^28.1.0 - version: 28.1.0 + version: 28.1.0(@noble/hashes@1.8.0) postcss: specifier: ^8.5.6 version: 8.5.6 + rollup-plugin-visualizer: + specifier: ^7.0.1 + version: 7.0.1(rollup@4.59.0) + size-limit: + specifier: ^12.0.1 + version: 12.0.1(jiti@2.6.1) tailwindcss: specifier: ^4.2.1 version: 4.2.1 @@ -98,7 +113,7 @@ importers: version: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1) vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@22.19.11)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1) + version: 4.0.18(@types/node@22.19.11)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@1.8.0))(lightningcss@1.31.1) packages: @@ -244,6 +259,9 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -479,6 +497,14 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@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} + '@reduxjs/toolkit@2.11.2': resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: @@ -618,6 +644,12 @@ packages: cpu: [x64] os: [win32] + '@size-limit/file@12.0.1': + resolution: {integrity: sha512-Kvbnz46iV7WeHaANf1HmWjXBVMU2KkCU+0xJ78FzIjZwlVKKEqy+QCZprdBMfIWrzrvYeqP4cfuzKG8z6xVivg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + size-limit: 12.0.1 + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -627,6 +659,13 @@ packages: '@stellar/freighter-api@6.0.1': resolution: {integrity: sha512-eqwakEqSg+zoLuPpSbKyrX0pG8DQFzL/J5GtbfuMCmJI+h+oiC9pQ5C6QLc80xopZQKdGt8dUAFCmDMNdAG95w==} + '@stellar/js-xdr@3.1.2': + resolution: {integrity: sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==} + + '@stellar/stellar-base@14.1.0': + resolution: {integrity: sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==} + engines: {node: '>=20.0.0'} + '@tailwindcss/node@4.2.1': resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} @@ -932,6 +971,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -940,6 +983,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -964,6 +1011,10 @@ packages: peerDependencies: postcss: ^8.1.0 + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} @@ -974,6 +1025,10 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base32.js@0.1.0: + resolution: {integrity: sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==} + engines: {node: '>=0.12.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -985,6 +1040,9 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1000,10 +1058,26 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes-iec@3.1.1: + resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1019,6 +1093,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1044,6 +1122,11 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1128,6 +1211,22 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1153,6 +1252,9 @@ packages: electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + enhanced-resolve@5.19.0: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} @@ -1305,6 +1407,10 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -1324,6 +1430,14 @@ packages: 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-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1355,6 +1469,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1414,10 +1531,22 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1426,9 +1555,29 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1548,6 +1697,10 @@ packages: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1608,6 +1761,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1617,6 +1773,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1654,6 +1814,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1661,6 +1825,10 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1755,11 +1923,31 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + rollup-plugin-visualizer@7.0.1: + resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} + engines: {node: '>=22'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -1784,6 +1972,15 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1795,16 +1992,38 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + size-limit@12.0.1: + resolution: {integrity: sha512-vuFj+6lDOoBJQu6OLhcMQv7jnbXjuoEn4WsQHlSLOV/8EFfOka/tfjtLQ/rZig5Gagi3R0GnU/0kd4EY/y2etg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + jiti: ^2.0.0 + peerDependenciesMeta: + jiti: + optional: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -1852,6 +2071,10 @@ packages: resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} hasBin: true + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -1870,6 +2093,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1989,6 +2216,10 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2003,6 +2234,14 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -2010,9 +2249,21 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + 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@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2192,6 +2443,8 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.27.3': optional: true @@ -2316,7 +2569,9 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.14.1': {} + '@exodus/bytes@1.14.1(@noble/hashes@1.8.0)': + optionalDependencies: + '@noble/hashes': 1.8.0 '@humanfs/core@0.19.1': {} @@ -2348,6 +2603,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': dependencies: '@standard-schema/spec': 1.1.0 @@ -2437,6 +2698,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true + '@size-limit/file@12.0.1(size-limit@12.0.1(jiti@2.6.1))': + dependencies: + size-limit: 12.0.1(jiti@2.6.1) + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -2446,6 +2711,17 @@ snapshots: buffer: 6.0.3 semver: 7.7.1 + '@stellar/js-xdr@3.1.2': {} + + '@stellar/stellar-base@14.1.0': + dependencies: + '@noble/curves': 1.9.7 + '@stellar/js-xdr': 3.1.2 + base32.js: 0.1.0 + bignumber.js: 9.3.1 + buffer: 6.0.3 + sha.js: 2.4.12 + '@tailwindcss/node@4.2.1': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2787,12 +3063,16 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + argparse@2.0.1: {} aria-query@5.3.0: @@ -2814,6 +3094,10 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + axios@1.13.5: dependencies: follow-redirects: 1.15.11 @@ -2826,6 +3110,8 @@ snapshots: balanced-match@4.0.4: {} + base32.js@0.1.0: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.10.0: {} @@ -2834,6 +3120,8 @@ snapshots: dependencies: require-from-string: 2.0.2 + bignumber.js@9.3.1: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2856,11 +3144,29 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes-iec@3.1.1: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001774: {} @@ -2872,6 +3178,12 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + clsx@2.1.1: {} color-convert@2.0.1: @@ -2890,6 +3202,11 @@ snapshots: cookie@1.1.1: {} + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2950,10 +3267,10 @@ snapshots: d3-timer@3.0.1: {} - data-urls@7.0.0: + data-urls@7.0.0(@noble/hashes@1.8.0): dependencies: whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 + whatwg-url: 16.0.1(@noble/hashes@1.8.0) transitivePeerDependencies: - '@noble/hashes' @@ -2967,6 +3284,21 @@ snapshots: deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@3.0.0: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -2985,6 +3317,8 @@ snapshots: electron-to-chromium@1.5.302: {} + emoji-regex@10.6.0: {} + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 @@ -3165,6 +3499,10 @@ snapshots: follow-redirects@1.15.11: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -3182,6 +3520,10 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3214,6 +3556,10 @@ snapshots: has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -3230,9 +3576,9 @@ snapshots: dependencies: hermes-estree: 0.25.1 - html-encoding-sniffer@6.0.0: + html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0): dependencies: - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) transitivePeerDependencies: - '@noble/hashes' @@ -3269,16 +3615,38 @@ snapshots: indent-string@4.0.0: {} + inherits@2.0.4: {} + internmap@2.0.3: {} + is-callable@1.2.7: {} + + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-potential-custom-element-name@1.0.1: {} + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} jiti@2.6.1: {} @@ -3289,16 +3657,16 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@28.1.0: + jsdom@28.1.0(@noble/hashes@1.8.0): dependencies: '@acemir/cssom': 0.9.31 '@asamuzakjp/dom-selector': 6.8.1 '@bramus/specificity': 2.4.2 - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) cssstyle: 6.1.0 - data-urls: 7.0.0 + data-urls: 7.0.0(@noble/hashes@1.8.0) decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@1.8.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 @@ -3310,7 +3678,7 @@ snapshots: w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 + whatwg-url: 16.0.1(@noble/hashes@1.8.0) xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -3384,6 +3752,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 + lilconfig@3.1.3: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3430,12 +3800,25 @@ snapshots: nanoid@3.3.11: {} + nanospinner@1.2.2: + dependencies: + picocolors: 1.1.1 + natural-compare@1.4.0: {} node-releases@2.0.27: {} obug@2.1.1: {} + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3471,6 +3854,8 @@ snapshots: picomatch@4.0.3: {} + possible-typed-array-names@1.1.0: {} + postcss-value-parser@4.2.0: {} postcss@8.5.6: @@ -3479,6 +3864,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + powershell-utils@0.1.0: {} + prelude-ls@1.2.1: {} pretty-format@27.5.1: @@ -3564,6 +3951,15 @@ snapshots: resolve-from@4.0.0: {} + rollup-plugin-visualizer@7.0.1(rollup@4.59.0): + dependencies: + open: 11.0.0 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 18.0.0 + optionalDependencies: + rollup: 4.59.0 + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -3595,6 +3991,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 + run-applescript@7.1.0: {} + + safe-buffer@5.2.1: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -3609,6 +4009,21 @@ snapshots: set-cookie-parser@2.7.2: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3617,12 +4032,34 @@ snapshots: siginfo@2.0.0: {} + size-limit@12.0.1(jiti@2.6.1): + dependencies: + bytes-iec: 3.1.1 + lilconfig: 3.1.3 + nanospinner: 1.2.2 + picocolors: 1.1.1 + tinyglobby: 0.2.15 + optionalDependencies: + jiti: 2.6.1 + source-map-js@1.2.1: {} + source-map@0.7.6: {} + stackback@0.0.2: {} std-env@3.10.0: {} + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -3658,6 +4095,12 @@ snapshots: dependencies: tldts-core: 7.0.23 + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + tough-cookie@6.0.0: dependencies: tldts: 7.0.23 @@ -3674,6 +4117,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + typescript@5.9.3: {} undici-types@6.21.0: {} @@ -3725,7 +4174,7 @@ snapshots: jiti: 2.6.1 lightningcss: 1.31.1 - vitest@4.0.18(@types/node@22.19.11)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1): + vitest@4.0.18(@types/node@22.19.11)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@1.8.0))(lightningcss@1.31.1): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)) @@ -3749,7 +4198,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.11 - jsdom: 28.1.0 + jsdom: 28.1.0(@noble/hashes@1.8.0) transitivePeerDependencies: - jiti - less @@ -3771,14 +4220,24 @@ snapshots: whatwg-mimetype@5.0.0: {} - whatwg-url@16.0.1: + whatwg-url@16.0.1(@noble/hashes@1.8.0): dependencies: - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: - '@noble/hashes' + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3790,12 +4249,36 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.3.6): diff --git a/frontend/src/pages/Payments/PaymentDetailModal/PaymentDetailModal.tsx b/frontend/src/pages/Payments/PaymentDetailModal/PaymentDetailModal.tsx index 7509950..94cc0a7 100644 --- a/frontend/src/pages/Payments/PaymentDetailModal/PaymentDetailModal.tsx +++ b/frontend/src/pages/Payments/PaymentDetailModal/PaymentDetailModal.tsx @@ -1,6 +1,6 @@ // frontend/src/pages/Payments/PaymentDetailModal/PaymentDetailModal.tsx import React, { useEffect } from 'react'; -import { X, ExternalLink, ShieldCheck, Clock, MapPin } from 'lucide-react'; +import { X, ExternalLink, ShieldCheck, MapPin } from 'lucide-react'; import './PaymentDetailModal.css'; interface PaymentDetailModalProps { diff --git a/frontend/src/pages/Payments/PaymentHistory/PaymentHistory.tsx b/frontend/src/pages/Payments/PaymentHistory/PaymentHistory.tsx index f260c2a..753f025 100644 --- a/frontend/src/pages/Payments/PaymentHistory/PaymentHistory.tsx +++ b/frontend/src/pages/Payments/PaymentHistory/PaymentHistory.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { Link } from "react-router-dom"; import { ChevronDown, ChevronLeft, ChevronRight, ExternalLink, ArrowUpDown } from "lucide-react"; +import PaymentDetailModal from "../PaymentDetailModal/PaymentDetailModal"; type PaymentStatus = "Pending" | "Escrowed" | "Released" | "Failed"; @@ -17,6 +18,8 @@ const statusClasses: Record = { const PaymentHistory: React.FC = () => { const [isLoading] = useState(false); + const [selectedPayment, setSelectedPayment] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); const [filterStatus, setFilterStatus] = useState("All"); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); const [currentPage, setCurrentPage] = useState(1); diff --git a/frontend/src/utils/contractSafety.test.ts b/frontend/src/utils/contractSafety.test.ts new file mode 100644 index 0000000..b2b95a0 --- /dev/null +++ b/frontend/src/utils/contractSafety.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect } from "vitest"; +import { + parseContractAllowlist, + validateSorobanContractId, + assertContractOnAllowlist, + checkSorobanContractForProxySafety, +} from "./contractSafety"; + +/** Deterministic valid Soroban contract StrKeys (32-byte payloads encoded with StrKey). */ +const CONTRACT_A = + "CAAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQC526"; +const CONTRACT_B = + "CABAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAFNSZ"; + +describe("parseContractAllowlist", () => { + it("returns empty array for undefined/blank", () => { + expect(parseContractAllowlist(undefined)).toEqual([]); + expect(parseContractAllowlist("")).toEqual([]); + expect(parseContractAllowlist(" ")).toEqual([]); + }); + + it("splits on comma and trims entries", () => { + expect( + parseContractAllowlist(` ${CONTRACT_A} , ${CONTRACT_B} `), + ).toEqual([CONTRACT_A, CONTRACT_B]); + }); +}); + +describe("validateSorobanContractId", () => { + it("rejects empty and non-contract StrKeys", () => { + expect(validateSorobanContractId("")).toEqual({ + ok: false, + reason: "empty_contract_id", + }); + expect(validateSorobanContractId(null)).toEqual({ + ok: false, + reason: "empty_contract_id", + }); + expect( + validateSorobanContractId( + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", + ), + ).toEqual({ + ok: false, + reason: "invalid_contract_strkey", + }); + }); + + it("accepts a valid contract StrKey", () => { + expect(validateSorobanContractId(CONTRACT_A)).toEqual({ ok: true }); + }); +}); + +describe("assertContractOnAllowlist", () => { + it("when allowlist is empty, only validates StrKey", () => { + expect(assertContractOnAllowlist(CONTRACT_A, [])).toEqual({ + ok: true, + }); + }); + + it("when allowlist is set, rejects unknown ids", () => { + expect( + assertContractOnAllowlist(CONTRACT_A, [CONTRACT_B]), + ).toEqual({ + ok: false, + reason: "contract_not_on_allowlist", + }); + }); + + it("when allowlist is set, accepts listed ids", () => { + expect( + assertContractOnAllowlist(CONTRACT_A, [CONTRACT_A, CONTRACT_B]), + ).toEqual({ ok: true }); + }); +}); + +describe("checkSorobanContractForProxySafety", () => { + it("uses explicit allowlist when provided", () => { + expect( + checkSorobanContractForProxySafety(CONTRACT_A, [CONTRACT_A]), + ).toEqual({ ok: true }); + expect( + checkSorobanContractForProxySafety(CONTRACT_A, [CONTRACT_B]), + ).toEqual({ + ok: false, + reason: "contract_not_on_allowlist", + }); + }); +}); diff --git a/frontend/src/utils/contractSafety.ts b/frontend/src/utils/contractSafety.ts new file mode 100644 index 0000000..4eb0c91 --- /dev/null +++ b/frontend/src/utils/contractSafety.ts @@ -0,0 +1,89 @@ +import { StrKey } from "@stellar/stellar-base"; + +export type ContractSafetyFailureReason = + | "empty_contract_id" + | "invalid_contract_strkey" + | "contract_not_on_allowlist"; + +export type ContractSafetyResult = + | { ok: true } + | { ok: false; reason: ContractSafetyFailureReason }; + +/** + * Parses a comma-separated list of Soroban contract StrKeys from config. + * Whitespace around entries is trimmed; empty segments are dropped. + */ +export function parseContractAllowlist(raw: string | undefined): string[] { + if (!raw?.trim()) { + return []; + } + return raw + .split(",") + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); +} + +/** + * Allowlist from `VITE_SOROBAN_CONTRACT_ALLOWLIST` (build-time env). + * When empty, callers may still validate StrKey shape only. + */ +export function getContractAllowlistFromEnv(): string[] { + const raw = import.meta.env.VITE_SOROBAN_CONTRACT_ALLOWLIST as + | string + | undefined; + return parseContractAllowlist(raw); +} + +/** + * Validates that `contractId` is a well-formed Soroban contract StrKey (`C…`). + */ +export function validateSorobanContractId( + contractId: string | undefined | null, +): ContractSafetyResult { + if (contractId == null || !String(contractId).trim()) { + return { ok: false, reason: "empty_contract_id" }; + } + const trimmed = String(contractId).trim(); + if (!StrKey.isValidContract(trimmed)) { + return { ok: false, reason: "invalid_contract_strkey" }; + } + return { ok: true }; +} + +/** + * Proxy / upgradeable setups use a stable proxy address while implementation may change. + * The UI should only treat contract IDs as trusted when they match deployment config. + * + * When `allowlist` is non-empty, `contractId` must be present in the list (exact StrKey match). + * When `allowlist` is empty, only StrKey validity is enforced (no allowlist restriction). + */ +export function assertContractOnAllowlist( + contractId: string | undefined | null, + allowlist: string[], +): ContractSafetyResult { + const shape = validateSorobanContractId(contractId); + if (!shape.ok) { + return shape; + } + if (allowlist.length === 0) { + return { ok: true }; + } + const trimmed = String(contractId).trim(); + const allowed = new Set(allowlist.map((a) => a.trim())); + if (!allowed.has(trimmed)) { + return { ok: false, reason: "contract_not_on_allowlist" }; + } + return { ok: true }; +} + +/** + * Validates StrKey shape and optional allowlist using env (`VITE_SOROBAN_CONTRACT_ALLOWLIST`) + * unless `allowlist` is passed explicitly (e.g. tests). + */ +export function checkSorobanContractForProxySafety( + contractId: string | undefined | null, + allowlist?: string[], +): ContractSafetyResult { + const list = allowlist ?? getContractAllowlistFromEnv(); + return assertContractOnAllowlist(contractId, list); +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 11f02fe..bee4339 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1 +1,10 @@ /// + +interface ImportMetaEnv { + /** Comma-separated Soroban contract StrKeys permitted for client-side calls (proxy safety). */ + readonly VITE_SOROBAN_CONTRACT_ALLOWLIST?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 917eac4..6909045 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,14 +1,30 @@ /// /// +import process from "node:process"; import { configDefaults, defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import { resolve } from "path"; import { fileURLToPath } from "url"; +import { visualizer } from "rollup-plugin-visualizer"; // import tailwindcss from '@tailwindcss/vite' const __dirname = fileURLToPath(new URL(".", import.meta.url)); // https://vite.dev/config/ +const analyze = process.env.ANALYZE === "true"; export default defineConfig({ - plugins: [react()], // tailwindcss() removed temporarily for CI + plugins: [ + react(), // tailwindcss() removed temporarily for CI + ...(analyze + ? [ + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: true, + open: false, + template: "treemap", + }), + ] + : []), + ], resolve: { alias: { "@": resolve(__dirname, "./src"), diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2afe732..ab71efc 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,16 +1,33 @@ /// /// +import process from "node:process"; import { configDefaults, defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import { resolve } from "path"; import { fileURLToPath } from "url"; +import { visualizer } from "rollup-plugin-visualizer"; // import tailwindcss from '@tailwindcss/vite' const __dirname = fileURLToPath(new URL(".", import.meta.url)); // https://vite.dev/config/ +const analyze = process.env.ANALYZE === "true"; + export default defineConfig({ - plugins: [react()], // tailwindcss() removed temporarily for CI + plugins: [ + react(), // tailwindcss() removed temporarily for CI + ...(analyze + ? [ + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: true, + open: false, + template: "treemap", + }), + ] + : []), + ], resolve: { alias: { "@": resolve(__dirname, "./src"),