diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d06f930 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +version: 2 +updates: + # Frontend dependencies + - package-ecosystem: "npm" + directory: "/frontend" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "frontend" + reviewers: + - "Shaurya2k06" + commit-message: + prefix: "chore(frontend)" + include: "scope" + groups: + react: + patterns: + - "react*" + - "@types/react*" + vite: + patterns: + - "vite*" + - "@vitejs/*" + solana: + patterns: + - "@solana/*" + - "@coral-xyz/*" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "chore(ci)" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..fe251f9 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,43 @@ +name: Deploy Frontend to Vercel + +on: + push: + branches: + - main + paths: + - 'frontend/**' + - '.github/workflows/deploy.yml' + workflow_dispatch: + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel Environment Information + working-directory: ./frontend + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + working-directory: ./frontend + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts to Vercel + working-directory: ./frontend + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..16bef4a --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,60 @@ +name: PR Checks + +on: + pull_request: + branches: + - main + - develop + +jobs: + lint-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm ci + + - name: Run linter + working-directory: ./frontend + run: npm run lint --if-present || echo "No lint script found" + + - name: Type check + working-directory: ./frontend + run: npm run type-check --if-present || npx tsc --noEmit + + - name: Run tests + working-directory: ./frontend + run: npm test --if-present || echo "No test script found" + + - name: Build project + working-directory: ./frontend + run: npm run build + + code-quality: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm ci + + - name: Check code formatting + working-directory: ./frontend + run: npx prettier --check "src/**/*.{ts,tsx}" || echo "No prettier config found" diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..4afdacd --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,2 @@ +src/components/OnboardingFlow.old.tsx +src/components/OnboardingFlow.tsx.backup diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5882b8d..7bd8ba7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -35,6 +35,7 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.3", + "baseline-browser-mapping": "^2.9.18", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -1298,19 +1299,19 @@ } }, "node_modules/@ethereumjs/common": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-10.0.0.tgz", - "integrity": "sha512-qb0M1DGdXzMAf3O6Zg5Wr5UDjoxBmplLPbQyC6DQ0LfgVDBRdqn0Pk+/hHm4q0McE22Of0MxbV4hhiDTkSgKag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-10.1.0.tgz", + "integrity": "sha512-zIHCy0i2LFmMDp+QkENyoPGxcoD3QzeNVhx6/vE4nJk4uWGNXzO8xJ2UC4gtGW4UJTAOXja8Z1yZMVeRc2/+Ew==", "license": "MIT", "dependencies": { - "@ethereumjs/util": "^10.0.0", + "@ethereumjs/util": "^10.1.0", "eventemitter3": "^5.0.1" } }, "node_modules/@ethereumjs/common/node_modules/@ethereumjs/rlp": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.0.0.tgz", - "integrity": "sha512-h2SK6RxFBfN5ZGykbw8LTNNLckSXZeuUZ6xqnmtF22CzZbHflFMcIOyfVGdvyCVQqIoSbGMHtvyxMCWnOyB9RA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.0.tgz", + "integrity": "sha512-r67BJbwilammAqYI4B5okA66cNdTlFzeWxPNJOolKV52ZS/flo0tUBf4x4gxWXBgh48OgsdFV1Qp5pRoSe8IhQ==", "license": "MPL-2.0", "bin": { "rlp": "bin/rlp.cjs" @@ -1320,12 +1321,12 @@ } }, "node_modules/@ethereumjs/common/node_modules/@ethereumjs/util": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.0.0.tgz", - "integrity": "sha512-lO23alM4uQsv8dp6/yEm4Xw4328+wIRjSeuBO1mRTToUWRcByEMTk87yzBpXgpixpgHrl+9LTn9KB2vvKKtOQQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.1.0.tgz", + "integrity": "sha512-GGTCkRu1kWXbz2JoUnIYtJBOoA9T5akzsYa91Bh+DZQ3Cj4qXj3hkNU0Rx6wZlbcmkmhQfrjZfVt52eJO/y2nA==", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/rlp": "^10.0.0", + "@ethereumjs/rlp": "^10.1.0", "ethereum-cryptography": "^3.2.0" }, "engines": { @@ -1391,9 +1392,9 @@ } }, "node_modules/@ethereumjs/common/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/@ethereumjs/rlp": { @@ -1409,14 +1410,14 @@ } }, "node_modules/@ethereumjs/tx": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-10.0.0.tgz", - "integrity": "sha512-DApm04kp2nbvaOuHy2Rkcz1ZeJkTVgW6oCuNnQf9bRtGc+LsvLrdULE3LoGtBItEoNEcgXLJqrV0foooWFX6jw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-10.1.0.tgz", + "integrity": "sha512-svG6pyzUZDpunafszf2BaolA6Izuvo8ZTIETIegpKxAXYudV1hmzPQDdSI+d8nHCFyQfEFbQ6tfUq95lNArmmg==", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/common": "^10.0.0", - "@ethereumjs/rlp": "^10.0.0", - "@ethereumjs/util": "^10.0.0", + "@ethereumjs/common": "^10.1.0", + "@ethereumjs/rlp": "^10.1.0", + "@ethereumjs/util": "^10.1.0", "ethereum-cryptography": "^3.2.0" }, "engines": { @@ -1424,9 +1425,9 @@ } }, "node_modules/@ethereumjs/tx/node_modules/@ethereumjs/rlp": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.0.0.tgz", - "integrity": "sha512-h2SK6RxFBfN5ZGykbw8LTNNLckSXZeuUZ6xqnmtF22CzZbHflFMcIOyfVGdvyCVQqIoSbGMHtvyxMCWnOyB9RA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.0.tgz", + "integrity": "sha512-r67BJbwilammAqYI4B5okA66cNdTlFzeWxPNJOolKV52ZS/flo0tUBf4x4gxWXBgh48OgsdFV1Qp5pRoSe8IhQ==", "license": "MPL-2.0", "bin": { "rlp": "bin/rlp.cjs" @@ -1436,12 +1437,12 @@ } }, "node_modules/@ethereumjs/tx/node_modules/@ethereumjs/util": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.0.0.tgz", - "integrity": "sha512-lO23alM4uQsv8dp6/yEm4Xw4328+wIRjSeuBO1mRTToUWRcByEMTk87yzBpXgpixpgHrl+9LTn9KB2vvKKtOQQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.1.0.tgz", + "integrity": "sha512-GGTCkRu1kWXbz2JoUnIYtJBOoA9T5akzsYa91Bh+DZQ3Cj4qXj3hkNU0Rx6wZlbcmkmhQfrjZfVt52eJO/y2nA==", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/rlp": "^10.0.0", + "@ethereumjs/rlp": "^10.1.0", "ethereum-cryptography": "^3.2.0" }, "engines": { @@ -2478,9 +2479,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -7806,34 +7807,32 @@ "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.1.0.tgz", - "integrity": "sha512-90EArG+eCCEzDGj3OJNoCtwpWDwxjv+rs/RNPhvg4bulpjN/CSRj+Ys/SalRcfM4/WRC5/qAfjzmJBAuquWhkA==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-14.0.4.tgz", + "integrity": "sha512-UbNW6zbdOBXJwLAV2mMak0bIC9nw3IZVlQXkv2w2dk1jgCbJjy3oRVC943zeGE5JAm0Z9PHxrIjmkpGhayY7kw==", "license": "Apache-2.0", "dependencies": { + "@noble/curves": "^1.9.6", "@stellar/js-xdr": "^3.1.2", "base32.js": "^0.1.0", - "bignumber.js": "^9.1.2", + "bignumber.js": "^9.3.1", "buffer": "^6.0.3", - "sha.js": "^2.3.6", - "tweetnacl": "^1.0.3" + "sha.js": "^2.4.12" }, "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "sodium-native": "^4.3.3" + "node": ">=20.0.0" } }, "node_modules/@stellar/stellar-sdk": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.3.0.tgz", - "integrity": "sha512-8+GHcZLp+mdin8gSjcgfb/Lb6sSMYRX6Nf/0LcSJxvjLQR0XHpjGzOiRbYb2jSXo51EnA6kAV5j+4Pzh5OUKUg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.2.0.tgz", + "integrity": "sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==", + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@stellar/stellar-base": "^13.1.0", - "axios": "^1.8.4", - "bignumber.js": "^9.3.0", + "@stellar/stellar-base": "^14.0.1", + "axios": "^1.12.2", + "bignumber.js": "^9.3.1", "eventsource": "^2.0.2", "feaxios": "^0.0.23", "randombytes": "^2.1.0", @@ -7841,7 +7840,7 @@ "urijs": "^1.19.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@swc/helpers": { @@ -8288,34 +8287,22 @@ } }, "node_modules/@trezor/analytics": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@trezor/analytics/-/analytics-1.4.3.tgz", - "integrity": "sha512-0o7gp7nfip8yjhAwP3R/Hcy5S8RfmZmYwpCcN0PbydWa5U5VMQ+T/iB/OpbpeV+8j13bf6i7++38nTCUNas0GA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/analytics/-/analytics-1.5.0.tgz", + "integrity": "sha512-evILW5XJEmfPlf0TY1duOLtGJ47pdGeSKVE3P75ODEUsRNxtPVqlkOUBPmYpCxPnzS8XDmkatT8lf9/DF0G6nA==", "license": "See LICENSE.md in repo root", "dependencies": { - "@trezor/env-utils": "1.4.3", - "@trezor/utils": "9.4.3" - }, - "peerDependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@trezor/analytics/node_modules/@trezor/utils": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.4.3.tgz", - "integrity": "sha512-QkLHpGTF3W3wNGj6OCdcMog7MhAAdlUmpjjmMjMqE0JSoi1Yjr0m7k7xN0iIHSqcgrhYteZDeJZAGlAf/b7gqw==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "bignumber.js": "^9.3.1" + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link/-/blockchain-link-2.5.4.tgz", - "integrity": "sha512-3Xki/2Vmr1/rKa5LF+Eb2/Qd5N9LqwyRL76+ycqe1KwqV7xK3XGMsqTH9FUUReRvGxrzAFonbOgADAJczhx81w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link/-/blockchain-link-2.6.1.tgz", + "integrity": "sha512-SPwxkihOMI0o79BOy0RkfgVL2meuJhIe1yWHCeR8uoqf5KGblUyeXxvNCy6w8ckJ9LRpM1+bZhsUODuNs3083Q==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@solana-program/compute-budget": "^0.8.0", @@ -8324,48 +8311,48 @@ "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", "@solana/rpc-types": "^2.3.0", - "@stellar/stellar-sdk": "^13.3.0", - "@trezor/blockchain-link-types": "1.4.4", - "@trezor/blockchain-link-utils": "1.4.4", - "@trezor/env-utils": "1.4.3", - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4", - "@trezor/websocket-client": "1.2.4", + "@stellar/stellar-sdk": "14.2.0", + "@trezor/blockchain-link-types": "1.5.0", + "@trezor/blockchain-link-utils": "1.5.1", + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", + "@trezor/websocket-client": "1.3.0", "@types/web": "^0.0.197", "crypto-browserify": "3.12.0", "socks-proxy-agent": "8.0.5", "stream-browserify": "^3.0.0", - "xrpl": "^4.4.0" + "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link-types": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.4.4.tgz", - "integrity": "sha512-B38LH4aniZ7gKbpSdMRiA4YriauoYPHgjhKEd+ybR0ca+liNlPAvMwSHJyMhL1eDIYEs16oOjTgT53WjRRZbMg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.5.0.tgz", + "integrity": "sha512-wD6FKKxNr89MTWYL+NikRkBcWXhiWNFR0AuDHW6GHmlCEHhKu/hAvQtcER8X5jt/Wd0hSKNZqtHBXJ1ZkpJ6rg==", "license": "See LICENSE.md in repo root", "dependencies": { - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4" + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link-utils": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.4.4.tgz", - "integrity": "sha512-8BZD6h5gs3ETPOG2Ri+GOyH44D38YQVeQj8n7oVOVQi21zx93JOpdL3fWS9RytkfmvB84WwVzYoHGlTs3T80Gw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.5.1.tgz", + "integrity": "sha512-2tDGLEj5jzydjsJQONGTWVmCDDy6FTZ4ytr1/2gE6anyYEJU8MbaR+liTt3UvcP5jwZTNutwYLvZixRfrb8JpA==", "license": "See LICENSE.md in repo root", "dependencies": { "@mobily/ts-belt": "^3.13.1", - "@stellar/stellar-sdk": "^13.3.0", - "@trezor/env-utils": "1.4.3", - "@trezor/protobuf": "1.4.4", - "@trezor/utils": "9.4.4", - "xrpl": "^4.4.0" + "@stellar/stellar-sdk": "14.2.0", + "@trezor/env-utils": "1.5.0", + "@trezor/protobuf": "1.5.1", + "@trezor/utils": "9.5.0", + "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" @@ -8394,13 +8381,13 @@ } }, "node_modules/@trezor/connect": { - "version": "9.6.4", - "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.6.4.tgz", - "integrity": "sha512-/N3hhOFIIhufvihCx92wvxd15Wy9XAJOSbTiV8rYG2N9uBvzejctNO2+LpwCRl/cBKle9rsp4S7C/zz++iDuOg==", + "version": "9.7.1", + "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.7.1.tgz", + "integrity": "sha512-W2ym0bs4FVmXByEr9gANBp+bRErzNcmqqqYzSJLOVkawxikqYXag2aCpdiXU3LlZbFbhFhIsT/fpDLfwiLRySA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@ethereumjs/common": "^10.0.0", - "@ethereumjs/tx": "^10.0.0", + "@ethereumjs/common": "^10.1.0", + "@ethereumjs/tx": "^10.1.0", "@fivebinaries/coin-selection": "3.0.0", "@mobily/ts-belt": "^3.13.1", "@noble/hashes": "^1.6.1", @@ -8410,21 +8397,22 @@ "@solana-program/token": "^0.5.1", "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", - "@trezor/blockchain-link": "2.5.4", - "@trezor/blockchain-link-types": "1.4.4", - "@trezor/blockchain-link-utils": "1.4.4", - "@trezor/connect-analytics": "1.3.6", - "@trezor/connect-common": "0.4.4", - "@trezor/crypto-utils": "1.1.5", - "@trezor/device-utils": "1.1.4", - "@trezor/env-utils": "^1.4.3", - "@trezor/protobuf": "1.4.4", - "@trezor/protocol": "1.2.10", - "@trezor/schema-utils": "1.3.4", - "@trezor/transport": "1.5.4", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4", + "@trezor/blockchain-link": "2.6.1", + "@trezor/blockchain-link-types": "1.5.0", + "@trezor/blockchain-link-utils": "1.5.1", + "@trezor/connect-analytics": "1.4.0", + "@trezor/connect-common": "0.5.0", + "@trezor/crypto-utils": "1.2.0", + "@trezor/device-authenticity": "1.1.1", + "@trezor/device-utils": "1.2.0", + "@trezor/env-utils": "^1.5.0", + "@trezor/protobuf": "1.5.1", + "@trezor/protocol": "1.3.0", + "@trezor/schema-utils": "1.4.0", + "@trezor/transport": "1.6.1", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", "blakejs": "^1.2.1", "bs58": "^6.0.0", "bs58check": "^4.0.0", @@ -8437,41 +8425,41 @@ } }, "node_modules/@trezor/connect-analytics": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@trezor/connect-analytics/-/connect-analytics-1.3.6.tgz", - "integrity": "sha512-Skya46inItcjaahaqpeSsQmB2Xle70f/l+6eTTJYxKQdpMtuW5LRsRRiyMAQTp5RBycL2ngnsVtY+/83Bt5lUw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/connect-analytics/-/connect-analytics-1.4.0.tgz", + "integrity": "sha512-hy2J2oeIhRC/e1bOWXo5dsVMVnDwO2UKnxhR6FD8PINR3jgM6PWAXc6k33WJsBcyiTzwMP7/xPysLcgNJH5o4w==", "license": "See LICENSE.md in repo root", "dependencies": { - "@trezor/analytics": "1.4.3" + "@trezor/analytics": "1.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/connect-common": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.4.4.tgz", - "integrity": "sha512-xG2CoPjgcldtO6HU0ZjNCvFdQ4hpl56qzU1VEF1/A1BL2zj2TwLGLmyr4E878go1mmfksNGY5a1tqnzAZ7pRLw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.5.0.tgz", + "integrity": "sha512-WE71iaFcWmfQxDCiTUNynj2DccRgUiLBJ+g3nrqCBJqEYzu+cD6eZ5k/OLtZ3hfh5gyB5EQwXdGvRT07iNdxAA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@trezor/env-utils": "1.4.3", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4" + "@trezor/env-utils": "1.5.0", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/connect-web": { - "version": "9.6.4", - "resolved": "https://registry.npmjs.org/@trezor/connect-web/-/connect-web-9.6.4.tgz", - "integrity": "sha512-Dip0+B2NKso8noux0mrsyy/Sz3Y0ZEn4Lq7CqM0hif4iu38FIPaUOWaR9BtDLj5TDYDi/sXHHkP+90gPobwigA==", + "version": "9.7.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-web/-/connect-web-9.7.1.tgz", + "integrity": "sha512-L9rv8R4bOwXoZlbWr8NnsdR2bYjehNY+eEog7fhOPzcRqW30+P06Ari1ZzOMeg469s4GlwvkVA34wqSGxLeelQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@trezor/connect": "9.6.4", - "@trezor/connect-common": "0.4.4", - "@trezor/utils": "9.4.4", - "@trezor/websocket-client": "1.2.4" + "@trezor/connect": "9.7.1", + "@trezor/connect-common": "0.5.0", + "@trezor/utils": "9.5.0", + "@trezor/websocket-client": "1.3.0" }, "peerDependencies": { "tslib": "^2.6.2" @@ -8512,24 +8500,64 @@ } }, "node_modules/@trezor/crypto-utils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@trezor/crypto-utils/-/crypto-utils-1.1.5.tgz", - "integrity": "sha512-Bp3L9MvzYy1OhPcNJIPIPu7kAH1lQyI1ZMuGnIo53nLDcU+t7cWO8z8xpyGW1BAnQ9wn+xaqrycLRf76I0TBtA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/crypto-utils/-/crypto-utils-1.2.0.tgz", + "integrity": "sha512-9i1NrfW1IE6JO910ut7xrx4u5LxE++GETbpJhWLj4P5xpuGDDSDLEn/MXaYisls2DpE897aOrGPaa1qyt8V6tw==", "license": "SEE LICENSE IN LICENSE.md", "peerDependencies": { "tslib": "^2.6.2" } }, + "node_modules/@trezor/device-authenticity": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@trezor/device-authenticity/-/device-authenticity-1.1.1.tgz", + "integrity": "sha512-WlYbQgc5l0pWUVP9GkMp+Oj3rVAqMKsWF0HyxujoymNjEB7rLTl2hXs+GFjlz7VnldaSslECc6EBex/eQiNOnA==", + "license": "See LICENSE.md in repo root", + "dependencies": { + "@noble/curves": "^2.0.1", + "@trezor/crypto-utils": "1.2.0", + "@trezor/protobuf": "1.5.1", + "@trezor/schema-utils": "1.4.0", + "@trezor/utils": "9.5.0" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@trezor/device-utils": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.1.4.tgz", - "integrity": "sha512-hFC0nVnWVFaWx0IfCsoHGvBrh5SKsnTHwrX5pvBotwOw51lzTDMd43CkA7nHRybkhcc2JgX1Qq2UbYdwgEWhPg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.2.0.tgz", + "integrity": "sha512-Aqp7pIooFTx21zRUtTI6i1AS4d9Lrx7cclvksh2nJQF9WJvbzuCXshEGkLoOsHwhQrCl3IXfbGuMdA12yDenPA==", "license": "See LICENSE.md in repo root" }, "node_modules/@trezor/env-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@trezor/env-utils/-/env-utils-1.4.3.tgz", - "integrity": "sha512-sWC828NRNQi5vc9W4M9rHOJDeI9XlsgnzZaML/lHju7WhlZCmSq5BOntZQvD8d1W0fSwLMLdlcBKBr/gQkvFZQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/env-utils/-/env-utils-1.5.0.tgz", + "integrity": "sha512-u1TN7dMQ5Qhpbae08Z4JJmI9fQrbbJ4yj8eIAsuzMQn6vb+Sg9vbntl+IDsZ1G9WeI73uHTLu1wWMmAgiujH8w==", "license": "See LICENSE.md in repo root", "dependencies": { "ua-parser-js": "^2.0.4" @@ -8553,12 +8581,12 @@ } }, "node_modules/@trezor/protobuf": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.4.4.tgz", - "integrity": "sha512-+DwcXkio4qlMkPu6KxnEfhXv5PHTkKh2n6Fo88i5zishUHpYD3NhCS0pouzti8PIPyJE73HQ9MoisG44KQjbtg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.1.tgz", + "integrity": "sha512-nAkaCCAqLpErBd+IuKeG5MpbyLR/2RMgCw18TWc80m1Ws/XgQirhHY9Jbk6gLImTXb9GTrxP0+MDSahzd94rSA==", "license": "See LICENSE.md in repo root", "dependencies": { - "@trezor/schema-utils": "1.3.4", + "@trezor/schema-utils": "1.4.0", "long": "5.2.5", "protobufjs": "7.4.0" }, @@ -8567,18 +8595,18 @@ } }, "node_modules/@trezor/protocol": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-1.2.10.tgz", - "integrity": "sha512-Ek5bHu2s4OAWOaJU5ksd1kcpe/STyLWOtUVTq6Vn4oMT3++qtrjWRQx/aTN/UaTfNoZlKvFXCC/STGlgBv9CKQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-1.3.0.tgz", + "integrity": "sha512-rmrxbDrdgxTouBPbZcSeqU7ba/e5WVT1dxvxxEntHqRdTiDl7d3VK+BErCrlyol8EH5YCqEF3/rXt0crSOfoFw==", "license": "See LICENSE.md in repo root", "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/schema-utils": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.3.4.tgz", - "integrity": "sha512-guP5TKjQEWe6c5HGx+7rhM0SAdEL5gylpkvk9XmJXjZDnl1Ew81nmLHUs2ghf8Od3pKBe4qjBIMBHUQNaOqWUg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.4.0.tgz", + "integrity": "sha512-K7upSeh7VDrORaIC4KAxYVW93XNlohmUnH5if/5GKYmTdQSRp1nBkO6Jm+Z4hzIthdnz/1aLgnbeN3bDxWLRxA==", "license": "See LICENSE.md in repo root", "dependencies": { "@sinclair/typebox": "^0.33.7", @@ -8589,15 +8617,15 @@ } }, "node_modules/@trezor/transport": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.5.4.tgz", - "integrity": "sha512-3vGn2IEofbzhKMyLjzmTCwVTE5Wj0gkncLCNc66DU95IEW5WlwNGt/nXSJCg9TMBHK6qtlbY1HOBFuUzEW2Q7w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.6.1.tgz", + "integrity": "sha512-RQNQingZ1TOVKSJu3Av9bmQovsu9n1NkcAYJ64+ZfapORfl/AzmZizRflhxU3FlIujQJK1gbIaW79+L54g7a8w==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@trezor/protobuf": "1.4.4", - "@trezor/protocol": "1.2.10", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4", + "@trezor/protobuf": "1.5.1", + "@trezor/protocol": "1.3.0", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", "cross-fetch": "^4.0.0", "usb": "^2.15.0" }, @@ -8615,15 +8643,15 @@ } }, "node_modules/@trezor/type-utils": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.1.9.tgz", - "integrity": "sha512-/Ug5pmVEpT5OVrf007kEvDj+zOdedHV0QcToUHG/WpVAKH9IsOssOAYIfRr8lDDgT+mDHuArZk/bYa1qvVz8Hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.2.0.tgz", + "integrity": "sha512-+E2QntxkyQuYfQQyl8RvT01tq2i5Dp/LFUOXuizF+KVOqsZBjBY43j5hewcCO3+MokD7deDiPyekbUEN5/iVlw==", "license": "See LICENSE.md in repo root" }, "node_modules/@trezor/utils": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.4.4.tgz", - "integrity": "sha512-08ciafbBqhApn58q3KkewdLQ3dCA71MsK/BOUfD43EB2GpB420zzky7REilXhOONc3giD0fBbTG3Zdt3HNL0/Q==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.5.0.tgz", + "integrity": "sha512-kdyMyDbxzvOZmwBNvTjAK+C/kzyOz8T4oUbFvq+KaXn5mBFf1uf8rq5X2HkxgdYRPArtHS3PxLKsfkNCdhCYtQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "bignumber.js": "^9.3.1" @@ -8633,12 +8661,12 @@ } }, "node_modules/@trezor/utxo-lib": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@trezor/utxo-lib/-/utxo-lib-2.4.4.tgz", - "integrity": "sha512-rccdH3+iqvBL/Nkso/wGCdIXAQY+M/ubLIf/i/hBbcpRH6JoOg8oyaoaHzegsYNE6yHKnykTOZWz5Q4MTG02Bw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utxo-lib/-/utxo-lib-2.5.0.tgz", + "integrity": "sha512-Fa2cZh0037oX6AHNLfpFIj65UR/OoX0ZJTocFuQASe77/1PjZHysf6BvvGfmzuFToKfrAQ+DM/1Sx+P/vnyNmA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@trezor/utils": "9.4.4", + "@trezor/utils": "9.5.0", "bech32": "^2.0.0", "bip66": "^2.0.0", "bitcoin-ops": "^1.4.1", @@ -8686,12 +8714,12 @@ } }, "node_modules/@trezor/websocket-client": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-1.2.4.tgz", - "integrity": "sha512-UgU31gFX8gY0abeI5DjRVnH4RfbXqHcOb019ogkR51KlfjkiWXTvUWKRLLqwslWiUIMEAI3ZFeXQds84b7Uw/Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-1.3.0.tgz", + "integrity": "sha512-9KQSaVc3NtmM6rFFj1e+9bM0C5mVKVidbnxlfzuBJu7G2YMRdIdLPcAXhvmRZjs40uzDuBeApK+p547kODz2ug==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@trezor/utils": "9.4.4", + "@trezor/utils": "9.5.0", "ws": "^8.18.0" }, "peerDependencies": { @@ -10464,81 +10492,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/bare-addon-resolve": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.9.5.tgz", - "integrity": "sha512-XdqrG73zLK9LDfblOJwoAxmJ+7YdfRW4ex46+f4L+wPhk7H7LDrRMAbBw8s8jkxeEFpUenyB7QHnv0ErAWd3Yg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-module-resolve": "^1.10.0", - "bare-semver": "^1.0.0" - }, - "peerDependencies": { - "bare-url": "*" - }, - "peerDependenciesMeta": { - "bare-url": { - "optional": true - } - } - }, - "node_modules/bare-module-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.11.2.tgz", - "integrity": "sha512-HIBu9WacMejg3Dz4X1v6lJjp7ECnwpujvuLub+8I7JJLRwJaGxWMzGYvieOoS9R1n5iRByvTmLtIdPbwjfRgiQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-semver": "^1.0.0" - }, - "peerDependencies": { - "bare-url": "*" - }, - "peerDependenciesMeta": { - "bare-url": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-semver": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", - "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/bare-url": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.1.tgz", - "integrity": "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, "node_modules/base-x": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", @@ -10587,9 +10540,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.21", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", - "integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==", + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -11036,21 +10989,6 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "license": "MIT" }, - "node_modules/bufferutil": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", - "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -11462,12 +11400,16 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cookie-es": { @@ -13304,9 +13246,9 @@ "license": "MIT" }, "node_modules/h3": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", - "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", @@ -13314,9 +13256,9 @@ "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.2", + "node-mock-http": "^1.0.4", "radix3": "^1.1.2", - "ufo": "^1.6.1", + "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, @@ -13627,9 +13569,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -14337,9 +14279,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -14478,12 +14420,12 @@ } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -14905,9 +14847,9 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash.isequal": { @@ -15583,9 +15525,9 @@ "license": "(Apache-2.0 AND MIT)" }, "node_modules/nan": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", - "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz", + "integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==", "license": "MIT" }, "node_modules/nanoassert": { @@ -15661,9 +15603,9 @@ "license": "MIT" }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -15687,9 +15629,9 @@ "license": "MIT" }, "node_modules/node-mock-http": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", - "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", "license": "MIT" }, "node_modules/node-releases": { @@ -16516,9 +16458,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -16918,9 +16860,9 @@ } }, "node_modules/react-router": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", - "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -16940,12 +16882,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", - "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", "license": "MIT", "dependencies": { - "react-router": "7.9.5" + "react-router": "7.13.0" }, "engines": { "node": ">=20.0.0" @@ -16999,20 +16941,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, - "node_modules/require-addon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.1.0.tgz", - "integrity": "sha512-KbXAD5q2+v1GJnkzd8zzbOxchTkStSyJZ9QwoCq3QwEXAaIlG3wDYRZGzVD357jmwaGY7hr5VaoEAL0BkF0Kvg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-addon-resolve": "^1.3.0", - "bare-url": "^2.1.0" - }, - "engines": { - "bare": ">=1.10.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -17170,9 +17098,9 @@ } }, "node_modules/ripple-binary-codec": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-2.5.1.tgz", - "integrity": "sha512-rzN4GTorLRH0bQD7Tccgn6Eq4aunMhZaUTXDEUJ+3xrjo0m1Av5AY1Doc/jsCIaxPIAnyoVg5rWlmI93U7pGdg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-2.6.0.tgz", + "integrity": "sha512-OJBRxjjalO7SrIwydHhcC9wOFLoeKcawoqSEfGZilAtXROYTWHx5kTly2VcUMmMMSEYIh1+yEstBtLBObNjeKQ==", "license": "ISC", "dependencies": { "@xrplf/isomorphic": "^1.0.1", @@ -17861,16 +17789,6 @@ "node": ">= 14" } }, - "node_modules/sodium-native": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", - "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", - "license": "MIT", - "optional": true, - "dependencies": { - "require-addon": "^1.1.0" - } - }, "node_modules/sonic-boom": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", @@ -18473,12 +18391,6 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -18589,9 +18501,9 @@ "license": "MIT" }, "node_modules/ua-parser-js": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.6.tgz", - "integrity": "sha512-EmaxXfltJaDW75SokrY4/lXMrVyXomE/0FpIIqP2Ctic93gK7rlme55Cwkz8l3YZ6gqf94fCU7AnIkidd/KXPg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.8.tgz", + "integrity": "sha512-BdnBM5waFormdrOFBU+cA90R689V0tWUWlIG2i30UXxElHjuCu5+dOV2Etw3547jcQ/yaLtPm9wrqIuOY2bSJg==", "funding": [ { "type": "opencollective", @@ -18620,9 +18532,9 @@ } }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, "node_modules/uint8array-tools": { @@ -18858,9 +18770,9 @@ "license": "MIT" }, "node_modules/usb": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/usb/-/usb-2.16.0.tgz", - "integrity": "sha512-jD88fvzDViMDH5KmmNJgzMBDj/95bDTt6+kBNaNxP4G98xUTnDMiLUY2CYmToba6JAFhM9VkcaQuxCNRLGR7zg==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.17.0.tgz", + "integrity": "sha512-UuFgrlglgDn5ll6d5l7kl3nDb2Yx43qLUGcDq+7UNLZLtbNug0HZBb2Xodhgx2JZB1LqvU+dOGqLEeYUeZqsHg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -18881,21 +18793,6 @@ "node": "^18 || ^20 || >= 21" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -19468,9 +19365,9 @@ } }, "node_modules/xrpl": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/xrpl/-/xrpl-4.4.2.tgz", - "integrity": "sha512-lMQeTBhn7XR7yCsldQLT3al6i6OZgmINkXiqTdVgYOlbml6XrxOGj2bsuT9FVxeBpVjIPtbmGVVTMF+3RpRJ3A==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/xrpl/-/xrpl-4.4.3.tgz", + "integrity": "sha512-vi2OjuNkiaP8nv1j+nqHp8GZwwEjO6Y8+j/OuVMg6M4LwXEwyHdIj33dlg7cyY1Lw5+jb9HqFOQvABhaywVbTQ==", "license": "ISC", "dependencies": { "@scure/bip32": "^1.3.1", @@ -19479,6 +19376,7 @@ "@xrplf/secret-numbers": "^2.0.0", "bignumber.js": "^9.0.0", "eventemitter3": "^5.0.1", + "fast-json-stable-stringify": "^2.1.0", "ripple-address-codec": "^5.0.0", "ripple-binary-codec": "^2.5.0", "ripple-keypairs": "^2.0.0" @@ -19488,9 +19386,9 @@ } }, "node_modules/xrpl/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/xtend": { diff --git a/frontend/package.json b/frontend/package.json index 858eb20..0cbaa37 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,6 +37,7 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.3", + "baseline-browser-mapping": "^2.9.18", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/frontend/public/fonts/Advercase.otf b/frontend/public/fonts/Advercase.otf new file mode 100644 index 0000000..99b0fb1 Binary files /dev/null and b/frontend/public/fonts/Advercase.otf differ diff --git a/frontend/src/App.css b/frontend/src/App.css index 08f506a..db953e5 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,142 +1 @@ -@import "tailwindcss"; -@import "tw-animate-css"; - -@custom-variant dark (&:is(.dark *)); - -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} - -/* QR Scanner scanning animation */ -@keyframes scan { - 0% { - top: 0; - opacity: 0; - } - 25% { - opacity: 1; - } - 75% { - opacity: 1; - } - 100% { - top: 100%; - opacity: 0; - } -} - -.animate-scan { - animation: scan 2s ease-in-out infinite; -} \ No newline at end of file +/* Cleared to avoid conflicts with index.css design system */ \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0c946cb..197c3dd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,17 @@ import { useMemo } from 'react'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'; import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets'; import { clusterApiUrl } from '@solana/web3.js'; import { SolsticeProvider } from './contexts/SolsticeContext'; -import { Dashboard } from './components/Dashboard'; -import { Header } from './components/Header'; -import Home from './pages/Home'; +import { Layout } from './components/Layout'; +import { OnboardingFlow } from './components/OnboardingFlow'; +import { ProtectedRoute } from './components/ProtectedRoute'; +import { IdentityStatusPage } from './pages/IdentityStatusPage'; +import { QRScannerPage } from './pages/QRScannerPage'; +import { ChallengeScannerPage } from './pages/ChallengeScannerPage'; +import { ProofsPage } from './pages/ProofsPage'; import './App.css'; import '@solana/wallet-adapter-react-ui/styles.css'; @@ -31,18 +35,45 @@ function App() { - } /> - -
-
- -
- - } - /> + {/* Onboarding Route - First time users */} + } /> + + {/* Main App Routes - Protected, requires onboarding */} + }> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + diff --git a/frontend/src/components/ChallengeScanner.tsx b/frontend/src/components/ChallengeScanner.tsx index bf701ce..c0c89fc 100644 --- a/frontend/src/components/ChallengeScanner.tsx +++ b/frontend/src/components/ChallengeScanner.tsx @@ -27,7 +27,7 @@ export function ChallengeScanner() { const [error, setError] = useState(null); const [cameraError, setCameraError] = useState(null); const [pasteError, setPasteError] = useState(null); - + const fileInputRef = useRef(null); const videoRef = useRef(null); const canvasRef = useRef(null); @@ -48,7 +48,7 @@ export function ChallengeScanner() { canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0); - + const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height); if (imageData) { const code = jsQR(imageData.data, imageData.width, imageData.height); @@ -74,21 +74,21 @@ export function ChallengeScanner() { setError(null); const clipboardItems = await navigator.clipboard.read(); - + for (const item of clipboardItems) { if (item.types.includes('image/png') || item.types.includes('image/jpeg')) { const blob = await item.getType(item.types.find(t => t.startsWith('image/'))!); - + const img = new Image(); const url = URL.createObjectURL(blob); - + img.onload = async () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0); - + const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height); if (imageData) { const code = jsQR(imageData.data, imageData.width, imageData.height); @@ -100,12 +100,12 @@ export function ChallengeScanner() { } URL.revokeObjectURL(url); }; - + img.src = url; return; } } - + setPasteError('No image found in clipboard. Please copy an image first.'); } catch (error: any) { console.error('Error pasting image:', error); @@ -125,7 +125,7 @@ export function ChallengeScanner() { if (videoRef.current) { videoRef.current.srcObject = stream; streamRef.current = stream; - + videoRef.current.onloadedmetadata = () => { videoRef.current?.play(); scanQRFromVideo(); @@ -134,7 +134,7 @@ export function ChallengeScanner() { } catch (error: any) { console.error('Camera error:', error); setCameraError( - error.name === 'NotAllowedError' + error.name === 'NotAllowedError' ? 'Camera permission denied. Please allow camera access and try again.' : 'Failed to access camera.' ); @@ -171,7 +171,7 @@ export function ChallengeScanner() { if (imageData) { const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { - console.log('✅ Challenge QR code detected from camera!'); + console.log(' Challenge QR code detected from camera!'); stopCamera(); handleChallengeQRData(code.data); return; @@ -192,7 +192,7 @@ export function ChallengeScanner() { try { setError(null); console.log('Processing challenge QR code...'); - + // Parse challenge from QR code let parsed; try { @@ -256,21 +256,21 @@ export function ChallengeScanner() { if (challenge.callbackUrl) { console.log('📤 Submitting proof to:', challenge.callbackUrl); console.log('📦 Proof response:', proofResponse); - + // Extract challenge ID from callback URL for verification const urlMatch = challenge.callbackUrl.match(/challenges\/([^/]+)\//); const expectedChallengeId = urlMatch ? urlMatch[1] : null; console.log('🎯 Expected challenge ID from URL:', expectedChallengeId); - + if (expectedChallengeId && proofResponse.challengeId !== expectedChallengeId) { console.warn('⚠️ Challenge ID mismatch detected!'); console.warn(' From QR:', proofResponse.challengeId); console.warn(' From URL:', expectedChallengeId); // Use the challenge ID from the URL proofResponse.challengeId = expectedChallengeId; - console.log('✅ Corrected challenge ID to match URL'); + console.log(' Corrected challenge ID to match URL'); } - + const response = await fetch(challenge.callbackUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -279,22 +279,22 @@ export function ChallengeScanner() { if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); - console.error('❌ Backend error:', errorData); + console.error(' Backend error:', errorData); throw new Error(errorData.error || 'Failed to submit proof to app'); } - - console.log('✅ Proof accepted by backend'); + + console.log(' Proof accepted by backend'); } setSuccess(true); - console.log('✅ Proof submitted successfully!'); - + console.log(' Proof submitted successfully!'); + // Refresh identity status after successful proof submission if (wallet.publicKey) { await fetchIdentity(wallet.publicKey.toString()); console.log('🔄 Identity status refreshed'); } - + // Reset after 3 seconds setTimeout(() => { setChallenge(null); @@ -358,28 +358,28 @@ export function ChallengeScanner() { return (
-
-

Verification Challenge

- +
+

Verification Challenge

+
- -

{challenge.appName}

+ +

{challenge.appName}

- -

{getProofTypeDescription(challenge.proofType, challenge.params)}

+ +

{getProofTypeDescription(challenge.proofType, challenge.params)}

- -

{challenge.challengeId.substring(0, 16)}...

+ +

{challenge.challengeId.substring(0, 16)}...

{!isExpired && (
- +

{minutes}:{seconds.toString().padStart(2, '0')}

@@ -397,13 +397,12 @@ export function ChallengeScanner() { @@ -435,8 +434,8 @@ export function ChallengeScanner() { return (
-

Scan Verification Challenge

-

+

Scan Verification Challenge

+

Scan a challenge QR code from a third-party app that requires verification

@@ -458,7 +457,7 @@ export function ChallengeScanner() { <> - - - -
- - {/* Tab Content */} -
- {activeTab === 'scan' && } - {activeTab === 'challenge' && } - {activeTab === 'verify' && } - {activeTab === 'status' && } -
-
-
- ); -} diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 9d13868..3878902 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -3,20 +3,20 @@ import { Shield } from 'lucide-react'; export function Header() { return ( -
+
-
- +
+
-

Solstice Protocol

-

Zero-Knowledge Identity Verification

+

Solstice Protocol

+

Zero-Knowledge Identity Verification

- - + +
diff --git a/frontend/src/components/IdentityStatus.tsx b/frontend/src/components/IdentityStatus.tsx index 4f16ccd..ccb22ac 100644 --- a/frontend/src/components/IdentityStatus.tsx +++ b/frontend/src/components/IdentityStatus.tsx @@ -20,21 +20,21 @@ export function IdentityStatus({ identity, loading, expanded = false }: Identity if (loading) { return ( -
-
-
+
+
+
); } if (!identity) { return ( -
+
- +
-

No Identity Registered

-

Scan your Aadhaar QR code to get started

+

No Identity Registered

+

Scan your Aadhaar QR code to get started

@@ -44,7 +44,7 @@ export function IdentityStatus({ identity, loading, expanded = false }: Identity return ( <> {/* Main Status Card */} -
+
{identity.isVerified ? ( @@ -52,17 +52,17 @@ export function IdentityStatus({ identity, loading, expanded = false }: Identity )}
-

+

{identity.isVerified ? 'Verified Identity' : 'Pending Verification'}

-

+

{identity.isVerified ? 'Ready for authentication' : 'Complete attribute verification'}

- + {identity.verificationTimestamp && ( -

+

Verified: {new Date(identity.verificationTimestamp * 1000).toLocaleDateString()}

)} @@ -87,27 +87,27 @@ export function IdentityStatus({ identity, loading, expanded = false }: Identity {/* Expanded Details */} {expanded && ( -
-

Identity Details

- +
+

Identity Details

+
- {identity.verificationTimestamp && ( - )}
-
-

Verification Breakdown

+
+

Verification Breakdown

@@ -122,16 +122,16 @@ export function IdentityStatus({ identity, loading, expanded = false }: Identity function AttributeCard({ title, verified, description }: { title: string; verified: boolean; description: string }) { return ( -
+
{verified ? ( ) : ( - + )} -

{title}

+

{title}

-

{description}

+

{description}

); } @@ -139,17 +139,16 @@ function AttributeCard({ title, verified, description }: { title: string; verifi function DetailItem({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) { return (
-

{label}

-

{value}

+

{label}

+

{value}

); } function VerificationBadge({ label, verified }: { label: string; verified: boolean }) { return ( - + {label}: {verified ? '' : ''} ); diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..76e121e --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import { Sidebar, MobileMenuButton } from './Sidebar'; +import { Header } from './Header'; + +export function Layout() { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + return ( +
+ {/* Mobile Menu Button - positioned absolutely/fixed by the component itself */} + setIsSidebarOpen(true)} /> + + {/* Layout Container */} +
+ {/* Sidebar Navigation */} + setIsSidebarOpen(false)} + /> + + {/* Main Content Area */} +
+ + {/* Background Elements (Subtle) */} +
+
+
+
+ + {/* Header - Sticky within the main content area */} +
+
+
+ + {/* Page Content (Nested Routes) */} +
+ +
+ + {/* Footer - Internal App Footer */} +
+
+
+

Solstice Protocol App

+
+ Privacy + Terms +
+
+
+
+
+
+
+ ); +} diff --git a/frontend/src/components/OnboardingFlow.old.tsx b/frontend/src/components/OnboardingFlow.old.tsx new file mode 100644 index 0000000..67b42a5 --- /dev/null +++ b/frontend/src/components/OnboardingFlow.old.tsx @@ -0,0 +1,527 @@ +import { useState, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useWallet } from '@solana/wallet-adapter-react'; +import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; +import { useSolstice } from '../contexts/SolsticeContext'; +import { CheckCircle, Loader, Shield, QrCode, ChevronRight, AlertCircle, Camera, Upload, X } from 'lucide-react'; +import jsQR from 'jsqr'; +import { generateAllProofs, storeProofs } from '../lib/proofGenerator'; +import { parseAadhaarQR } from '../lib/aadhaarParser'; +import { + isOnboardingComplete, + recordOnboardingCompletion, + linkAadhaarToWallet, + verifyAadhaarWalletLink +} from '../lib/onboarding'; + +type OnboardingStep = 'wallet' | 'verify' | 'complete'; + +export function OnboardingFlow() { + const navigate = useNavigate(); + const wallet = useWallet(); + const { identity, fetchIdentity, registerIdentity, parseQRCode } = useSolstice(); + const [currentStep, setCurrentStep] = useState('wallet'); + const [scanning, setScanning] = useState(false); + const [processing, setProcessing] = useState(false); + const [error, setError] = useState(null); + const [aadhaarData, setAadhaarData] = useState(null); + const [scanMethod, setScanMethod] = useState<'camera' | 'upload' | null>(null); + const videoRef = useRef(null); + const canvasRef = useRef(null); + const fileInputRef = useRef(null); + const streamRef = useRef(null); + const animationFrameRef = useRef(null); + + // Check if user has already completed onboarding + useEffect(() => { + const checkOnboarding = async () => { + if (wallet.publicKey) { + const onboardedLocally = isOnboardingComplete(wallet.publicKey.toString()); + await fetchIdentity(wallet.publicKey.toString()); + } + }; + + checkOnboarding(); + }, [wallet.publicKey, fetchIdentity]); + + // Auto-advance steps + useEffect(() => { + if (wallet.connected && wallet.publicKey && currentStep === 'wallet') { + const onboarded = isOnboardingComplete(wallet.publicKey.toString()); + if (onboarded && identity) { + navigate('/status'); + } else { + setCurrentStep('verify'); + } + } + }, [wallet.connected, wallet.publicKey, currentStep, identity, navigate]); + + const handleQRScan = async (qrData: string) => { + if (processing) return; + + setProcessing(true); + setError(null); + setScanMethod(null); + setScanning(false); + + try { + console.log('📱 Processing Aadhaar QR code...'); + + const parsedData = await parseAadhaarQR(qrData); + + if (!parsedData) { + throw new Error('Invalid Aadhaar QR code'); + } + + setAadhaarData(parsedData); + console.log(' Aadhaar data parsed'); + + const nationality = 'IN'; + const aadhaarHash = qrData.substring(0, 12); + const nonce = qrData.substring(qrData.length - 32); + + const proofInputs = { + dateOfBirth: parsedData.dateOfBirth.split('/').reverse().join(''), + nationality, + aadhaarNumber: aadhaarHash, + nonce + }; + + console.log('⚙️ Generating ZK proofs...'); + const proofResults = await generateAllProofs(proofInputs); + + if (proofResults.errors.length > 0) { + throw new Error(`Proof generation errors: ${proofResults.errors.join(', ')}`); + } + + const proofsToStore = { + age: proofResults.ageProof || undefined, + nationality: proofResults.nationalityProof || undefined, + uniqueness: proofResults.uniquenessProof || undefined + }; + + console.log(' All proofs generated'); + + await storeProofs(wallet.publicKey!.toString(), proofsToStore); + console.log('💾 Proofs stored locally'); + + console.log(' Generating commitment...'); + const { commitment } = await parseQRCode(qrData); + + console.log('⛓️ Registering identity on-chain...'); + const merkleRoot = 'mock-merkle-root'; + const success = await registerIdentity(commitment, merkleRoot); + + if (success) { + console.log(' Registration complete!'); + + recordOnboardingCompletion(wallet.publicKey!.toString()); + linkAadhaarToWallet(aadhaarHash, wallet.publicKey!.toString()); + + setCurrentStep('complete'); + } else { + throw new Error('Failed to register identity'); + } + } catch (err: any) { + console.error('Error during onboarding:', err); + setError(err.message || 'Failed to process QR code'); + setScanning(false); + } finally { + setProcessing(false); + } + }; + + const startCamera = async () => { + try { + setError(null); + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' } + }); + + if (videoRef.current) { + videoRef.current.srcObject = stream; + streamRef.current = stream; + await videoRef.current.play(); + scanFromCamera(); + } + } catch (err: any) { + setError('Camera access denied. Please allow camera access or upload an image.'); + console.error('Camera error:', err); + } + }; + + const scanFromCamera = () => { + if (!videoRef.current || !canvasRef.current) return; + + const canvas = canvasRef.current; + const video = videoRef.current; + const context = canvas.getContext('2d'); + + if (!context) return; + + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + + const scan = () => { + if (video.readyState === video.HAVE_ENOUGH_DATA) { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + stopCamera(); + handleQRScan(code.data); + return; + } + } + + animationFrameRef.current = requestAnimationFrame(scan); + }; + + scan(); + }; + + const stopCamera = () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = null; + } + }; + + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setError(null); + + try { + const reader = new FileReader(); + reader.onload = async (event) => { + const img = new Image(); + img.onload = async () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + + if (!context) { + setError('Failed to process image'); + return; + } + + context.drawImage(img, 0, 0); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + await handleQRScan(code.data); + } else { + setError('No QR code found in image. Please try again with a clearer image.'); + } + }; + img.src = event.target?.result as string; + }; + reader.readAsDataURL(file); + } catch (err: any) { + setError('Failed to read file. Please try again.'); + } + }; + + useEffect(() => { + return () => { + stopCamera(); + }; + }, []); + + useEffect(() => { + if (scanMethod === 'camera') { + startCamera(); + } + return () => { + if (scanMethod === 'camera') { + stopCamera(); + } + }; + }, [scanMethod]); + + const handleComplete = () => { + navigate('/status'); + }; + + return ( +
+
+ {/* Progress Indicator */} +
+
+ {(['wallet', 'verify', 'complete'] as const).map((step, index) => ( +
+
+ {index + 1} +
+ {index < 2 && ( +
+ )} +
+ ))} +
+
+ Connect Wallet + Verify Identity + Complete +
+
+ + {/* Main Content Card (Styled like eth-delhi) */} +
+ {currentStep === 'wallet' && ( +
+
+
+ +
+

+ Welcome to Solstice +

+

+ Connect your Solana wallet to begin your identity verification journey +

+
+ +
+ {!wallet.connected ? ( + + ) : ( +
+ + Wallet Connected +
+ )} +
+
+ )} + + {currentStep === 'verify' && ( +
+
+
+ +
+

+ Verify Your Identity +

+

+ Scan your Aadhaar QR code to create your privacy-preserving identity +

+
+ + {error && ( +
+ +

{error}

+
+ )} + + {processing ? ( +
+ +

Processing your identity...

+

This may take a moment

+
+ ) : scanning ? ( +
+ {!scanMethod ? ( +
+

+ Choose Scan Method +

+ + + + + + + + +
+ ) : scanMethod === 'camera' ? ( +
+
+
+
+
+ +
+ + +
+
+ ) : ( +
+ +

Processing image...

+
+ )} +
+ ) : ( +
+ +
+ )} +
+ )} + + {currentStep === 'complete' && ( +
+
+
+ +
+

+ Identity Verified! +

+

+ Your privacy-preserving identity has been successfully created +

+
+ +
+
+ + Zero-knowledge proofs generated +
+
+ + Identity registered on Solana +
+
+ + Wallet permanently linked +
+
+ + +
+ )} +
+ + {/* Footer */} +
+

Your privacy is protected. All data is processed securely.

+
+
+
+ ); +} diff --git a/frontend/src/components/OnboardingFlow.tsx b/frontend/src/components/OnboardingFlow.tsx new file mode 100644 index 0000000..1aa736b --- /dev/null +++ b/frontend/src/components/OnboardingFlow.tsx @@ -0,0 +1,445 @@ +import { useState, useRef, useEffect } from 'react'; +import { useWallet } from '@solana/wallet-adapter-react'; +import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; +import { useNavigate } from 'react-router-dom'; +import jsQR from 'jsqr'; +import { useSolstice } from '../contexts/SolsticeContext'; +import { generateAllProofs, storeProofs } from '../lib/proofGenerator'; +import { parseAadhaarQR } from '../lib/aadhaarParser'; +import { generateIdentityCommitment } from '../lib/crypto'; +import { + isOnboardingComplete, + recordOnboardingCompletion, + linkAadhaarToWallet +} from '../lib/onboarding'; + +type OnboardingStep = 'welcome' | 'wallet' | 'scan-method' | 'camera' | 'upload' | 'processing' | 'complete'; + +export function OnboardingFlow() { + const wallet = useWallet(); + const navigate = useNavigate(); + const { fetchIdentity, registerIdentity } = useSolstice(); + const [currentStep, setCurrentStep] = useState('welcome'); + const [error, setError] = useState(null); + const videoRef = useRef(null); + const canvasRef = useRef(null); + const fileInputRef = useRef(null); + const streamRef = useRef(null); + + // Progress dots based on major steps + const getStepIndex = (): number => { + switch (currentStep) { + case 'welcome': return 0; + case 'wallet': return 1; + case 'scan-method': + case 'camera': + case 'upload': return 2; + case 'processing': return 3; + case 'complete': return 4; + default: return 0; + } + }; + + const stopCamera = () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + }; + + const startCamera = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' } + }); + if (videoRef.current) { + videoRef.current.srcObject = stream; + videoRef.current.play(); + streamRef.current = stream; + scanQRCode(); + } + } catch { + setError('Unable to access camera. Please check permissions.'); + } + }; + + const scanQRCode = () => { + const video = videoRef.current; + const canvas = canvasRef.current; + + if (!video || !canvas) return; + + const context = canvas.getContext('2d'); + if (!context) return; + + const scan = () => { + if (video.readyState === video.HAVE_ENOUGH_DATA && currentStep === 'camera') { + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + context.drawImage(video, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + handleQRScan(code.data); + return; + } + } + requestAnimationFrame(scan); + }; + scan(); + }; + + const handleQRScan = async (qrData: string) => { + try { + stopCamera(); + setCurrentStep('processing'); + setError(null); + + if (!wallet.publicKey) { + setError('Please connect your wallet first'); + setCurrentStep('wallet'); + return; + } + + console.log('📱 Processing Aadhaar QR code...'); + + const parsedData = await parseAadhaarQR(qrData); + + if (!parsedData) { + throw new Error('Invalid Aadhaar QR code'); + } + + console.log('✅ Aadhaar data parsed'); + + const nationality = 'IN'; + const aadhaarHash = qrData.substring(0, 12); + const nonce = qrData.substring(qrData.length - 32); + + const proofInputs = { + dateOfBirth: parsedData.dateOfBirth.split('/').reverse().join(''), + nationality, + aadhaarNumber: aadhaarHash, + nonce + }; + + console.log('⚙️ Generating ZK proofs...'); + const proofResults = await generateAllProofs(proofInputs); + + if (proofResults.errors.length > 0) { + throw new Error(`Proof generation errors: ${proofResults.errors.join(', ')}`); + } + + const proofsToStore = { + age: proofResults.ageProof || undefined, + nationality: proofResults.nationalityProof || undefined, + uniqueness: proofResults.uniquenessProof || undefined + }; + + console.log('✅ All proofs generated'); + + await storeProofs(wallet.publicKey.toString(), proofsToStore); + console.log('💾 Proofs stored locally'); + + console.log('🔐 Generating commitment...'); + const commitment = await generateIdentityCommitment({ + name: parsedData.name, + dateOfBirth: parsedData.dateOfBirth, + gender: parsedData.gender, + address: parsedData.address + }); + + console.log('⛓️ Registering identity on-chain...'); + const merkleRoot = 'mock-merkle-root'; + const success = await registerIdentity(commitment, merkleRoot); + + if (success) { + console.log('✅ Registration complete!'); + + recordOnboardingCompletion(wallet.publicKey.toString()); + linkAadhaarToWallet(aadhaarHash, wallet.publicKey.toString()); + + setCurrentStep('complete'); + } else { + throw new Error('Failed to register identity'); + } + } catch (err: unknown) { + const error = err as Error; + console.error('Error during onboarding:', error); + setError(error.message || 'Failed to process QR code'); + setCurrentStep('scan-method'); + } + }; + + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setCurrentStep('upload'); + setError(null); + + try { + const reader = new FileReader(); + reader.onload = async (event) => { + const img = new Image(); + img.onload = async () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + + if (!context) { + setError('Failed to process image'); + setCurrentStep('scan-method'); + return; + } + + context.drawImage(img, 0, 0); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + await handleQRScan(code.data); + } else { + setError('No QR code found in image'); + setCurrentStep('scan-method'); + } + }; + img.src = event.target?.result as string; + }; + reader.readAsDataURL(file); + } catch { + setError('Failed to read file'); + setCurrentStep('scan-method'); + } + }; + + useEffect(() => { + if (currentStep === 'camera') { + startCamera(); + } + return () => { + stopCamera(); + }; + }, [currentStep]); + + useEffect(() => { + if (wallet.connected && currentStep === 'wallet') { + setTimeout(() => setCurrentStep('scan-method'), 800); + } + }, [wallet.connected, currentStep]); + + // Check if user has already completed onboarding + useEffect(() => { + const checkOnboarding = async () => { + if (wallet.publicKey) { + const onboardedLocally = isOnboardingComplete(wallet.publicKey.toString()); + if (onboardedLocally) { + navigate('/status'); + } + await fetchIdentity(wallet.publicKey.toString()); + } + }; + + checkOnboarding(); + }, [wallet.publicKey, fetchIdentity, navigate]); + + const ProgressDots = () => ( +
+ {[0, 1, 2, 3, 4].map((index) => ( +
+ ))} +
+ ); + + return ( +
+
+ + + {/* Welcome Step */} + {currentStep === 'welcome' && ( +
+

+ Welcome to Solstice +

+

+ Privacy-preserving identity verification on Solana +

+ +
+ )} + + {/* Wallet Connection Step */} + {currentStep === 'wallet' && ( +
+

+ Connect your wallet +

+

+ Link your Solana wallet to your identity +

+
+ {!wallet.connected ? ( + + ) : ( +
+ + + Wallet connected + +
+ )} +
+
+ )} + + {/* Scan Method Selection */} + {currentStep === 'scan-method' && ( +
+

+ Scan your Aadhaar QR code +

+

+ Choose your preferred scanning method +

+ + {error && ( +
+ {error} +
+ )} + +
+ + + +
+
+ )} + + {/* Camera Scanning */} + {currentStep === 'camera' && ( +
+
+

+ Scanning +

+

Position QR code in frame

+
+ +
+
+ + +
+ )} + + {/* Processing */} + {(currentStep === 'processing' || currentStep === 'upload') && ( +
+
+

+ Processing your identity +

+

+ Creating zero-knowledge proofs... +

+
+ )} + + {/* Complete */} + {currentStep === 'complete' && ( +
+
+ ✓ +
+

+ Identity verified +

+

+ Your privacy-preserving identity is ready +

+ +
+
+ + Zero-knowledge proofs generated +
+
+ + Identity registered on Solana +
+
+ + Wallet permanently linked +
+
+ + +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/OnboardingFlow.tsx.backup b/frontend/src/components/OnboardingFlow.tsx.backup new file mode 100644 index 0000000..67b42a5 --- /dev/null +++ b/frontend/src/components/OnboardingFlow.tsx.backup @@ -0,0 +1,527 @@ +import { useState, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useWallet } from '@solana/wallet-adapter-react'; +import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; +import { useSolstice } from '../contexts/SolsticeContext'; +import { CheckCircle, Loader, Shield, QrCode, ChevronRight, AlertCircle, Camera, Upload, X } from 'lucide-react'; +import jsQR from 'jsqr'; +import { generateAllProofs, storeProofs } from '../lib/proofGenerator'; +import { parseAadhaarQR } from '../lib/aadhaarParser'; +import { + isOnboardingComplete, + recordOnboardingCompletion, + linkAadhaarToWallet, + verifyAadhaarWalletLink +} from '../lib/onboarding'; + +type OnboardingStep = 'wallet' | 'verify' | 'complete'; + +export function OnboardingFlow() { + const navigate = useNavigate(); + const wallet = useWallet(); + const { identity, fetchIdentity, registerIdentity, parseQRCode } = useSolstice(); + const [currentStep, setCurrentStep] = useState('wallet'); + const [scanning, setScanning] = useState(false); + const [processing, setProcessing] = useState(false); + const [error, setError] = useState(null); + const [aadhaarData, setAadhaarData] = useState(null); + const [scanMethod, setScanMethod] = useState<'camera' | 'upload' | null>(null); + const videoRef = useRef(null); + const canvasRef = useRef(null); + const fileInputRef = useRef(null); + const streamRef = useRef(null); + const animationFrameRef = useRef(null); + + // Check if user has already completed onboarding + useEffect(() => { + const checkOnboarding = async () => { + if (wallet.publicKey) { + const onboardedLocally = isOnboardingComplete(wallet.publicKey.toString()); + await fetchIdentity(wallet.publicKey.toString()); + } + }; + + checkOnboarding(); + }, [wallet.publicKey, fetchIdentity]); + + // Auto-advance steps + useEffect(() => { + if (wallet.connected && wallet.publicKey && currentStep === 'wallet') { + const onboarded = isOnboardingComplete(wallet.publicKey.toString()); + if (onboarded && identity) { + navigate('/status'); + } else { + setCurrentStep('verify'); + } + } + }, [wallet.connected, wallet.publicKey, currentStep, identity, navigate]); + + const handleQRScan = async (qrData: string) => { + if (processing) return; + + setProcessing(true); + setError(null); + setScanMethod(null); + setScanning(false); + + try { + console.log('📱 Processing Aadhaar QR code...'); + + const parsedData = await parseAadhaarQR(qrData); + + if (!parsedData) { + throw new Error('Invalid Aadhaar QR code'); + } + + setAadhaarData(parsedData); + console.log(' Aadhaar data parsed'); + + const nationality = 'IN'; + const aadhaarHash = qrData.substring(0, 12); + const nonce = qrData.substring(qrData.length - 32); + + const proofInputs = { + dateOfBirth: parsedData.dateOfBirth.split('/').reverse().join(''), + nationality, + aadhaarNumber: aadhaarHash, + nonce + }; + + console.log('⚙️ Generating ZK proofs...'); + const proofResults = await generateAllProofs(proofInputs); + + if (proofResults.errors.length > 0) { + throw new Error(`Proof generation errors: ${proofResults.errors.join(', ')}`); + } + + const proofsToStore = { + age: proofResults.ageProof || undefined, + nationality: proofResults.nationalityProof || undefined, + uniqueness: proofResults.uniquenessProof || undefined + }; + + console.log(' All proofs generated'); + + await storeProofs(wallet.publicKey!.toString(), proofsToStore); + console.log('💾 Proofs stored locally'); + + console.log(' Generating commitment...'); + const { commitment } = await parseQRCode(qrData); + + console.log('⛓️ Registering identity on-chain...'); + const merkleRoot = 'mock-merkle-root'; + const success = await registerIdentity(commitment, merkleRoot); + + if (success) { + console.log(' Registration complete!'); + + recordOnboardingCompletion(wallet.publicKey!.toString()); + linkAadhaarToWallet(aadhaarHash, wallet.publicKey!.toString()); + + setCurrentStep('complete'); + } else { + throw new Error('Failed to register identity'); + } + } catch (err: any) { + console.error('Error during onboarding:', err); + setError(err.message || 'Failed to process QR code'); + setScanning(false); + } finally { + setProcessing(false); + } + }; + + const startCamera = async () => { + try { + setError(null); + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' } + }); + + if (videoRef.current) { + videoRef.current.srcObject = stream; + streamRef.current = stream; + await videoRef.current.play(); + scanFromCamera(); + } + } catch (err: any) { + setError('Camera access denied. Please allow camera access or upload an image.'); + console.error('Camera error:', err); + } + }; + + const scanFromCamera = () => { + if (!videoRef.current || !canvasRef.current) return; + + const canvas = canvasRef.current; + const video = videoRef.current; + const context = canvas.getContext('2d'); + + if (!context) return; + + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + + const scan = () => { + if (video.readyState === video.HAVE_ENOUGH_DATA) { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + stopCamera(); + handleQRScan(code.data); + return; + } + } + + animationFrameRef.current = requestAnimationFrame(scan); + }; + + scan(); + }; + + const stopCamera = () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = null; + } + }; + + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setError(null); + + try { + const reader = new FileReader(); + reader.onload = async (event) => { + const img = new Image(); + img.onload = async () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + + if (!context) { + setError('Failed to process image'); + return; + } + + context.drawImage(img, 0, 0); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + + if (code && code.data) { + await handleQRScan(code.data); + } else { + setError('No QR code found in image. Please try again with a clearer image.'); + } + }; + img.src = event.target?.result as string; + }; + reader.readAsDataURL(file); + } catch (err: any) { + setError('Failed to read file. Please try again.'); + } + }; + + useEffect(() => { + return () => { + stopCamera(); + }; + }, []); + + useEffect(() => { + if (scanMethod === 'camera') { + startCamera(); + } + return () => { + if (scanMethod === 'camera') { + stopCamera(); + } + }; + }, [scanMethod]); + + const handleComplete = () => { + navigate('/status'); + }; + + return ( +
+
+ {/* Progress Indicator */} +
+
+ {(['wallet', 'verify', 'complete'] as const).map((step, index) => ( +
+
+ {index + 1} +
+ {index < 2 && ( +
+ )} +
+ ))} +
+
+ Connect Wallet + Verify Identity + Complete +
+
+ + {/* Main Content Card (Styled like eth-delhi) */} +
+ {currentStep === 'wallet' && ( +
+
+
+ +
+

+ Welcome to Solstice +

+

+ Connect your Solana wallet to begin your identity verification journey +

+
+ +
+ {!wallet.connected ? ( + + ) : ( +
+ + Wallet Connected +
+ )} +
+
+ )} + + {currentStep === 'verify' && ( +
+
+
+ +
+

+ Verify Your Identity +

+

+ Scan your Aadhaar QR code to create your privacy-preserving identity +

+
+ + {error && ( +
+ +

{error}

+
+ )} + + {processing ? ( +
+ +

Processing your identity...

+

This may take a moment

+
+ ) : scanning ? ( +
+ {!scanMethod ? ( +
+

+ Choose Scan Method +

+ + + + + + + + +
+ ) : scanMethod === 'camera' ? ( +
+
+
+
+
+ +
+ + +
+
+ ) : ( +
+ +

Processing image...

+
+ )} +
+ ) : ( +
+ +
+ )} +
+ )} + + {currentStep === 'complete' && ( +
+
+
+ +
+

+ Identity Verified! +

+

+ Your privacy-preserving identity has been successfully created +

+
+ +
+
+ + Zero-knowledge proofs generated +
+
+ + Identity registered on Solana +
+
+ + Wallet permanently linked +
+
+ + +
+ )} +
+ + {/* Footer */} +
+

Your privacy is protected. All data is processed securely.

+
+
+
+ ); +} diff --git a/frontend/src/components/ProofsDashboard.tsx b/frontend/src/components/ProofsDashboard.tsx index edfaa4a..636bf9c 100644 --- a/frontend/src/components/ProofsDashboard.tsx +++ b/frontend/src/components/ProofsDashboard.tsx @@ -21,11 +21,11 @@ export function ProofsDashboard() { const loadProofs = async () => { try { setLoading(true); - + // Load proofs from IndexedDB const storedProofs = await getStoredProofs(); setProofs(storedProofs); - + } catch (error) { console.error('Error loading proofs:', error); } finally { @@ -36,29 +36,29 @@ export function ProofsDashboard() { const getStoredProofs = async (): Promise => { return new Promise((resolve) => { const request = indexedDB.open('SolsticeProofs', 1); - + request.onupgradeneeded = (event: any) => { const db = event.target.result; if (!db.objectStoreNames.contains('proofs')) { db.createObjectStore('proofs', { keyPath: 'type' }); } }; - + request.onsuccess = (event: any) => { const db = event.target.result; const transaction = db.transaction(['proofs'], 'readonly'); const store = transaction.objectStore('proofs'); const getAllRequest = store.getAll(); - + getAllRequest.onsuccess = () => { resolve(getAllRequest.result || []); }; - + getAllRequest.onerror = () => { resolve([]); }; }; - + request.onerror = () => { resolve([]); }; @@ -128,7 +128,7 @@ export function ProofsDashboard() { if (loading) { return (
- +
); } @@ -136,9 +136,9 @@ export function ProofsDashboard() { if (proofs.length === 0) { return (
- -

No Proofs Generated Yet

-

+ +

No Proofs Generated Yet

+

Upload your Aadhaar QR code in the "Scan QR Code" tab to automatically generate your identity proofs.

@@ -148,8 +148,8 @@ export function ProofsDashboard() { return (
-

My Identity Proofs

-

+

My Identity Proofs

+

Your zero-knowledge proofs allow you to verify attributes without revealing personal data. Share these proofs with services that need identity verification.

@@ -159,14 +159,14 @@ export function ProofsDashboard() { {proofs.map((proof) => (
{/* Header */}
{getProofIcon(proof.type)}
-

+

{getProofTitle(proof.type)}

@@ -187,12 +187,12 @@ export function ProofsDashboard() {
{/* Description */} -

+

{getProofDescription(proof.type)}

{/* Metadata */} -
+
Generated: {new Date(proof.generatedAt).toLocaleDateString()}
@@ -207,14 +207,14 @@ export function ProofsDashboard() {
{/* Scanning indicator */} -
+
Scanning...
{/* Instructions */} -
-

+

+

📸 Position the QR code within the frame

-

+

Scanning automatically (~60 times per second)

{cameraError && ( -
+

{cameraError}

@@ -460,45 +460,46 @@ export function QRScanner() { )} {step === 'registered' && ( -
+
-

Identity Registered!

+

Identity Registered!

Your identity commitment has been registered on Solana.

- + {generatingProofs && ( -
- -

Generating ZK Proofs...

-

+

+ +

Generating ZK Proofs...

+

This may take a few seconds. All computation happens in your browser for privacy.

)} - + {!generatingProofs && ( -
+

- ZK proofs generated and stored locally
+ ZK proofs generated and stored locally
You can now verify attributes on any dApp instantly!

)} - + - -

+ +

Your identity is now registered. One person, one identity.

)} +
); } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx new file mode 100644 index 0000000..1f5cd73 --- /dev/null +++ b/frontend/src/components/Sidebar.tsx @@ -0,0 +1,148 @@ +import { useState, useEffect } from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { X, Shield, CheckCircle, User, Zap } from 'lucide-react'; + +interface SidebarProps { + isOpen: boolean; + onClose: () => void; +} + +export function Sidebar({ isOpen, onClose }: SidebarProps) { + const location = useLocation(); + const [isMobile, setIsMobile] = useState(false); + + // Check if current screen is mobile + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 1024); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); + + // Close sidebar when route changes on mobile + useEffect(() => { + if (isMobile) { + onClose(); + } + }, [location.pathname, isMobile, onClose]); + + const navItems = [ + { id: 'status', label: 'Identity Status', path: '/status', icon: User }, + { id: 'challenge', label: 'Scan Challenge', path: '/challenge', icon: Zap }, + { id: 'proofs', label: 'My Proofs', path: '/proofs', icon: CheckCircle }, + ]; + + const isActive = (path: string) => { + // Exact match or sub-path match if we had nested routes + return location.pathname === path; + }; + + return ( + <> + {/* Mobile Overlay */} + {isMobile && isOpen && ( +