feat: Integrate Ethereum wallet support with wagmi and react-query

Kim committed May 13, 2026 at 19:00 UTC 8694dc61b11ae3b1c712ccf322526f95ff47fb6d
5 files changed +472 -47
frontend/package-lock.json
+401 -3
@@ -16,6 +16,7 @@
16 "@radix-ui/react-slot": "^1.2.4",
17 "@radix-ui/react-tooltip": "^1.2.8",
18 "@ssgoi/react": "^2.5.5",
19 + "@tanstack/react-query": "^5.100.10",
20 "class-variance-authority": "^0.7.1",
21 "clsx": "^2.1.1",
22 "cmdk": "^1.1.1",
@@ -23,7 +24,9 @@
24 "react": "^19.2.1",
25 "react-dom": "^19.2.1",
26 "react-router-dom": "^7.10.1",
26 - "tailwind-merge": "^3.4.0"
27 + "tailwind-merge": "^3.4.0",
28 + "viem": "^2.48.11",
29 + "wagmi": "^3.6.14"
30 },
31 "devDependencies": {
32 "@tailwindcss/vite": "^4.1.17",
@@ -52,6 +55,12 @@
55 "dev": true,
56 "license": "MIT"
57 },
58 + "node_modules/@adraffy/ens-normalize": {
59 + "version": "1.11.1",
60 + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz",
61 + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==",
62 + "license": "MIT"
63 + },
64 "node_modules/@asamuzakjp/css-color": {
65 "version": "5.0.1",
66 "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz",
@@ -1370,6 +1379,45 @@
1379 "@jridgewell/sourcemap-codec": "^1.4.14"
1380 }
1381 },
1382 + "node_modules/@noble/ciphers": {
1383 + "version": "1.3.0",
1384 + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
1385 + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
1386 + "license": "MIT",
1387 + "engines": {
1388 + "node": "^14.21.3 || >=16"
1389 + },
1390 + "funding": {
1391 + "url": "https://paulmillr.com/funding/"
1392 + }
1393 + },
1394 + "node_modules/@noble/curves": {
1395 + "version": "1.9.1",
1396 + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
1397 + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==",
1398 + "license": "MIT",
1399 + "dependencies": {
1400 + "@noble/hashes": "1.8.0"
1401 + },
1402 + "engines": {
1403 + "node": "^14.21.3 || >=16"
1404 + },
1405 + "funding": {
1406 + "url": "https://paulmillr.com/funding/"
1407 + }
1408 + },
1409 + "node_modules/@noble/hashes": {
1410 + "version": "1.8.0",
1411 + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1412 + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1413 + "license": "MIT",
1414 + "engines": {
1415 + "node": "^14.21.3 || >=16"
1416 + },
1417 + "funding": {
1418 + "url": "https://paulmillr.com/funding/"
1419 + }
1420 + },
1421 "node_modules/@radix-ui/number": {
1422 "version": "1.1.1",
1423 "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -2502,6 +2550,42 @@
2550 "win32"
2551 ]
2552 },
2553 + "node_modules/@scure/base": {
2554 + "version": "1.2.6",
2555 + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
2556 + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
2557 + "license": "MIT",
2558 + "funding": {
2559 + "url": "https://paulmillr.com/funding/"
2560 + }
2561 + },
2562 + "node_modules/@scure/bip32": {
2563 + "version": "1.7.0",
2564 + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
2565 + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==",
2566 + "license": "MIT",
2567 + "dependencies": {
2568 + "@noble/curves": "~1.9.0",
2569 + "@noble/hashes": "~1.8.0",
2570 + "@scure/base": "~1.2.5"
2571 + },
2572 + "funding": {
2573 + "url": "https://paulmillr.com/funding/"
2574 + }
2575 + },
2576 + "node_modules/@scure/bip39": {
2577 + "version": "1.6.0",
2578 + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz",
2579 + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==",
2580 + "license": "MIT",
2581 + "dependencies": {
2582 + "@noble/hashes": "~1.8.0",
2583 + "@scure/base": "~1.2.5"
2584 + },
2585 + "funding": {
2586 + "url": "https://paulmillr.com/funding/"
2587 + }
2588 + },
2589 "node_modules/@ssgoi/core": {
2590 "version": "2.5.6",
2591 "resolved": "https://registry.npmjs.org/@ssgoi/core/-/core-2.5.6.tgz",
@@ -2806,6 +2890,32 @@
2890 "vite": "^5.2.0 || ^6 || ^7"
2891 }
2892 },
2893 + "node_modules/@tanstack/query-core": {
2894 + "version": "5.100.10",
2895 + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz",
2896 + "integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==",
2897 + "license": "MIT",
2898 + "funding": {
2899 + "type": "github",
2900 + "url": "https://github.com/sponsors/tannerlinsley"
2901 + }
2902 + },
2903 + "node_modules/@tanstack/react-query": {
2904 + "version": "5.100.10",
2905 + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz",
2906 + "integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==",
2907 + "license": "MIT",
2908 + "dependencies": {
2909 + "@tanstack/query-core": "5.100.10"
2910 + },
2911 + "funding": {
2912 + "type": "github",
2913 + "url": "https://github.com/sponsors/tannerlinsley"
2914 + },
2915 + "peerDependencies": {
2916 + "react": "^18 || ^19"
2917 + }
2918 + },
2919 "node_modules/@testing-library/dom": {
2920 "version": "10.4.1",
2921 "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -3335,6 +3445,109 @@
3445 "url": "https://opencollective.com/vitest"
3446 }
3447 },
3448 + "node_modules/@wagmi/connectors": {
3449 + "version": "8.0.13",
3450 + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-8.0.13.tgz",
3451 + "integrity": "sha512-uRWutZygoX5K1Vk4svKeMxmyeTEjdZn8f/+EDKQEQaFMvy6yMVlLpy5DKFK0NRjtTIinRrqVccDqJQYCcC62hw==",
3452 + "license": "MIT",
3453 + "funding": {
3454 + "url": "https://github.com/sponsors/wevm"
3455 + },
3456 + "peerDependencies": {
3457 + "@base-org/account": "^2.5.1",
3458 + "@coinbase/wallet-sdk": "^4.3.6",
3459 + "@metamask/connect-evm": "^1.0.0",
3460 + "@safe-global/safe-apps-provider": "~0.18.6",
3461 + "@safe-global/safe-apps-sdk": "^9.1.0",
3462 + "@wagmi/core": "3.4.11",
3463 + "@walletconnect/ethereum-provider": "^2.21.1",
3464 + "accounts": "~0.10",
3465 + "porto": "~0.2.35",
3466 + "typescript": ">=5.7.3",
3467 + "viem": "2.x"
3468 + },
3469 + "peerDependenciesMeta": {
3470 + "@base-org/account": {
3471 + "optional": true
3472 + },
3473 + "@coinbase/wallet-sdk": {
3474 + "optional": true
3475 + },
3476 + "@metamask/connect-evm": {
3477 + "optional": true
3478 + },
3479 + "@safe-global/safe-apps-provider": {
3480 + "optional": true
3481 + },
3482 + "@safe-global/safe-apps-sdk": {
3483 + "optional": true
3484 + },
3485 + "@walletconnect/ethereum-provider": {
3486 + "optional": true
3487 + },
3488 + "accounts": {
3489 + "optional": true
3490 + },
3491 + "porto": {
3492 + "optional": true
3493 + },
3494 + "typescript": {
3495 + "optional": true
3496 + }
3497 + }
3498 + },
3499 + "node_modules/@wagmi/core": {
3500 + "version": "3.4.11",
3501 + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-3.4.11.tgz",
3502 + "integrity": "sha512-dmrZr0fKxCmG3pyWO9//oYtE5cgWRrgaQjC0+jX6IiuufE4MSW7gSL2MKdK8bvT3jg4Ra8cdRIJpveHJSHKdNw==",
3503 + "license": "MIT",
3504 + "dependencies": {
3505 + "eventemitter3": "5.0.1",
3506 + "mipd": "0.0.7",
3507 + "zustand": "5.0.0"
3508 + },
3509 + "funding": {
3510 + "url": "https://github.com/sponsors/wevm"
3511 + },
3512 + "peerDependencies": {
3513 + "@tanstack/query-core": ">=5.0.0",
3514 + "accounts": "~0.8.1",
3515 + "typescript": ">=5.7.3",
3516 + "viem": "2.x"
3517 + },
3518 + "peerDependenciesMeta": {
3519 + "@tanstack/query-core": {
3520 + "optional": true
3521 + },
3522 + "accounts": {
3523 + "optional": true
3524 + },
3525 + "typescript": {
3526 + "optional": true
3527 + }
3528 + }
3529 + },
3530 + "node_modules/abitype": {
3531 + "version": "1.2.3",
3532 + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz",
3533 + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==",
3534 + "license": "MIT",
3535 + "funding": {
3536 + "url": "https://github.com/sponsors/wevm"
3537 + },
3538 + "peerDependencies": {
3539 + "typescript": ">=5.0.4",
3540 + "zod": "^3.22.0 || ^4.0.0"
3541 + },
3542 + "peerDependenciesMeta": {
3543 + "typescript": {
3544 + "optional": true
3545 + },
3546 + "zod": {
3547 + "optional": true
3548 + }
3549 + }
3550 + },
3551 "node_modules/acorn": {
3552 "version": "8.16.0",
3553 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -4194,6 +4407,12 @@
4407 "node": ">=0.10.0"
4408 }
4409 },
4410 + "node_modules/eventemitter3": {
4411 + "version": "5.0.1",
4412 + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
4413 + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
4414 + "license": "MIT"
4415 + },
4416 "node_modules/expect-type": {
4417 "version": "1.3.0",
4418 "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
@@ -4503,6 +4722,21 @@
4722 "dev": true,
4723 "license": "ISC"
4724 },
4725 + "node_modules/isows": {
4726 + "version": "1.0.7",
4727 + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz",
4728 + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==",
4729 + "funding": [
4730 + {
4731 + "type": "github",
4732 + "url": "https://github.com/sponsors/wevm"
4733 + }
4734 + ],
4735 + "license": "MIT",
4736 + "peerDependencies": {
4737 + "ws": "*"
4738 + }
4739 + },
4740 "node_modules/jiti": {
4741 "version": "2.6.1",
4742 "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -4992,6 +5226,26 @@
5226 "url": "https://github.com/sponsors/isaacs"
5227 }
5228 },
5229 + "node_modules/mipd": {
5230 + "version": "0.0.7",
5231 + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz",
5232 + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==",
5233 + "funding": [
5234 + {
5235 + "type": "github",
5236 + "url": "https://github.com/sponsors/wagmi-dev"
5237 + }
5238 + ],
5239 + "license": "MIT",
5240 + "peerDependencies": {
5241 + "typescript": ">=5.0.4"
5242 + },
5243 + "peerDependenciesMeta": {
5244 + "typescript": {
5245 + "optional": true
5246 + }
5247 + }
5248 + },
5249 "node_modules/ms": {
5250 "version": "2.1.3",
5251 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5061,6 +5315,36 @@
5315 "node": ">= 0.8.0"
5316 }
5317 },
5318 + "node_modules/ox": {
5319 + "version": "0.14.20",
5320 + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.20.tgz",
5321 + "integrity": "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==",
5322 + "funding": [
5323 + {
5324 + "type": "github",
5325 + "url": "https://github.com/sponsors/wevm"
5326 + }
5327 + ],
5328 + "license": "MIT",
5329 + "dependencies": {
5330 + "@adraffy/ens-normalize": "^1.11.0",
5331 + "@noble/ciphers": "^1.3.0",
5332 + "@noble/curves": "1.9.1",
5333 + "@noble/hashes": "^1.8.0",
5334 + "@scure/bip32": "^1.7.0",
5335 + "@scure/bip39": "^1.6.0",
5336 + "abitype": "^1.2.3",
5337 + "eventemitter3": "5.0.1"
5338 + },
5339 + "peerDependencies": {
5340 + "typescript": ">=5.4.0"
5341 + },
5342 + "peerDependenciesMeta": {
5343 + "typescript": {
5344 + "optional": true
5345 + }
5346 + }
5347 + },
5348 "node_modules/p-limit": {
5349 "version": "3.1.0",
5350 "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -5738,7 +6022,7 @@
6022 "version": "5.9.3",
6023 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
6024 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
5741 - "dev": true,
6025 + "devOptional": true,
6026 "license": "Apache-2.0",
6027 "bin": {
6028 "tsc": "bin/tsc",
@@ -5849,6 +6133,45 @@
6133 }
6134 }
6135 },
6136 + "node_modules/use-sync-external-store": {
6137 + "version": "1.4.0",
6138 + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
6139 + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
6140 + "license": "MIT",
6141 + "peerDependencies": {
6142 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
6143 + }
6144 + },
6145 + "node_modules/viem": {
6146 + "version": "2.48.11",
6147 + "resolved": "https://registry.npmjs.org/viem/-/viem-2.48.11.tgz",
6148 + "integrity": "sha512-+WZ5E0dBS6GtKb+1wEk5DeYRRRW42+pFnXCo67Ydodf42sBwO+hu3wnQy66lc4MKmHz+llPVdbyehYr9oTE2iw==",
6149 + "funding": [
6150 + {
6151 + "type": "github",
6152 + "url": "https://github.com/sponsors/wevm"
6153 + }
6154 + ],
6155 + "license": "MIT",
6156 + "dependencies": {
6157 + "@noble/curves": "1.9.1",
6158 + "@noble/hashes": "1.8.0",
6159 + "@scure/bip32": "1.7.0",
6160 + "@scure/bip39": "1.6.0",
6161 + "abitype": "1.2.3",
6162 + "isows": "1.0.7",
6163 + "ox": "0.14.20",
6164 + "ws": "8.18.3"
6165 + },
6166 + "peerDependencies": {
6167 + "typescript": ">=5.0.4"
6168 + },
6169 + "peerDependenciesMeta": {
6170 + "typescript": {
6171 + "optional": true
6172 + }
6173 + }
6174 + },
6175 "node_modules/vite": {
6176 "version": "7.3.1",
6177 "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
@@ -6015,6 +6338,31 @@
6338 "node": ">=18"
6339 }
6340 },
6341 + "node_modules/wagmi": {
6342 + "version": "3.6.14",
6343 + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.6.14.tgz",
6344 + "integrity": "sha512-kHRaaD9DeFaUN6WcVMEKApTwVwImt0fGINh04Z4WYXLJhkb0rBxG4js+3t8EzXsV/veec5dV90j7jfGwjGSsqQ==",
6345 + "license": "MIT",
6346 + "dependencies": {
6347 + "@wagmi/connectors": "8.0.13",
6348 + "@wagmi/core": "3.4.11",
6349 + "use-sync-external-store": "1.4.0"
6350 + },
6351 + "funding": {
6352 + "url": "https://github.com/sponsors/wevm"
6353 + },
6354 + "peerDependencies": {
6355 + "@tanstack/react-query": ">=5.0.0",
6356 + "react": ">=18",
6357 + "typescript": ">=5.7.3",
6358 + "viem": "2.x"
6359 + },
6360 + "peerDependenciesMeta": {
6361 + "typescript": {
6362 + "optional": true
6363 + }
6364 + }
6365 + },
6366 "node_modules/webidl-conversions": {
6367 "version": "8.0.1",
6368 "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
@@ -6093,6 +6441,27 @@
6441 "node": ">=0.10.0"
6442 }
6443 },
6444 + "node_modules/ws": {
6445 + "version": "8.18.3",
6446 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
6447 + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
6448 + "license": "MIT",
6449 + "engines": {
6450 + "node": ">=10.0.0"
6451 + },
6452 + "peerDependencies": {
6453 + "bufferutil": "^4.0.1",
6454 + "utf-8-validate": ">=5.0.2"
6455 + },
6456 + "peerDependenciesMeta": {
6457 + "bufferutil": {
6458 + "optional": true
6459 + },
6460 + "utf-8-validate": {
6461 + "optional": true
6462 + }
6463 + }
6464 + },
6465 "node_modules/xml-name-validator": {
6466 "version": "5.0.0",
6467 "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -6134,7 +6503,7 @@
6503 "version": "4.3.6",
6504 "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
6505 "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
6137 - "dev": true,
6506 + "devOptional": true,
6507 "license": "MIT",
6508 "funding": {
6509 "url": "https://github.com/sponsors/colinhacks"
@@ -6152,6 +6521,35 @@
6521 "peerDependencies": {
6522 "zod": "^3.25.0 || ^4.0.0"
6523 }
6524 + },
6525 + "node_modules/zustand": {
6526 + "version": "5.0.0",
6527 + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz",
6528 + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==",
6529 + "license": "MIT",
6530 + "engines": {
6531 + "node": ">=12.20.0"
6532 + },
6533 + "peerDependencies": {
6534 + "@types/react": ">=18.0.0",
6535 + "immer": ">=9.0.6",
6536 + "react": ">=18.0.0",
6537 + "use-sync-external-store": ">=1.2.0"
6538 + },
6539 + "peerDependenciesMeta": {
6540 + "@types/react": {
6541 + "optional": true
6542 + },
6543 + "immer": {
6544 + "optional": true
6545 + },
6546 + "react": {
6547 + "optional": true
6548 + },
6549 + "use-sync-external-store": {
6550 + "optional": true
6551 + }
6552 + }
6553 }
6554 }
6555 }
frontend/package.json
+4 -1
@@ -25,6 +25,7 @@
25 "@radix-ui/react-slot": "^1.2.4",
26 "@radix-ui/react-tooltip": "^1.2.8",
27 "@ssgoi/react": "^2.5.5",
28 + "@tanstack/react-query": "^5.100.10",
29 "class-variance-authority": "^0.7.1",
30 "clsx": "^2.1.1",
31 "cmdk": "^1.1.1",
@@ -32,7 +33,9 @@
33 "react": "^19.2.1",
34 "react-dom": "^19.2.1",
35 "react-router-dom": "^7.10.1",
35 - "tailwind-merge": "^3.4.0"
36 + "tailwind-merge": "^3.4.0",
37 + "viem": "^2.48.11",
38 + "wagmi": "^3.6.14"
39 },
40 "devDependencies": {
41 "@tailwindcss/vite": "^4.1.17",
frontend/src/hooks/useAuth.ts
+29 -25
@@ -1,4 +1,11 @@
1 import { useEffect, useState } from "react";
2 +import {
3 + useAccount,
4 + useConnect,
5 + useConnectors,
6 + useDisconnect,
7 + useSignMessage,
8 +} from "wagmi";
9 import { API_PATHS } from "@/lib/apiPaths";
10 import { APIClientError, apiClient } from "@/lib/apiClient";
11
@@ -31,16 +38,6 @@ interface WalletAuthLoginPayload {
38 type AuthTarget = "admin" | "agent" | "auto";
39 type ResolvedAuthTarget = "admin" | "agent";
40
34 -type EthereumProvider = {
35 - request<T>(args: { method: string; params?: unknown[] }): Promise<T>;
36 -};
37 -
38 -declare global {
39 - interface Window {
40 - ethereum?: EthereumProvider;
41 - }
42 -}
43 -
41 const authPaths = {
42 admin: {
43 challenge: API_PATHS.admin.authChallenge,
@@ -95,11 +92,12 @@ async function fetchAuthState(target: AuthTarget, preferred?: ResolvedAuthTarget
92 return emptyAuthState(target === "admin" || target === "agent" ? target : "");
93 }
94
98 -function ethereumProvider(): EthereumProvider | undefined {
99 - return window.ethereum;
100 -}
101 -
95 export function useAuth(target: AuthTarget = "admin") {
96 + const { address: connectedAddress, isConnected } = useAccount();
97 + const connectors = useConnectors();
98 + const { connectAsync } = useConnect();
99 + const { disconnectAsync } = useDisconnect();
100 + const { signMessageAsync } = useSignMessage();
101 const [authState, setAuthState] = useState<AuthState>({
102 isAuthenticated: false,
103 isLoading: true,
@@ -119,16 +117,17 @@ export function useAuth(target: AuthTarget = "admin") {
117
118 const login = async (): Promise<LoginResult> => {
119 try {
122 - const provider = ethereumProvider();
123 - if (!provider) {
124 - return { success: false, error: "Wallet provider is unavailable." };
120 + let address = connectedAddress;
121 + if (!isConnected || !address) {
122 + const connector = connectors[0];
123 + if (!connector) {
124 + return { success: false, error: "Wallet connector is unavailable." };
125 + }
126 + const connected = await connectAsync({ connector });
127 + address = connected.accounts[0];
128 }
126 - const accounts = await provider.request<string[]>({
127 - method: "eth_requestAccounts",
128 - });
129 - const address = accounts?.[0]?.trim();
129 if (!address) {
131 - return { success: false, error: "Wallet account is unavailable." };
130 + return { success: false, error: "Wallet provider is unavailable." };
131 }
132 let authTarget = authState.authTarget;
133 if (!authTarget) {
@@ -144,9 +143,9 @@ export function useAuth(target: AuthTarget = "admin") {
143 authPaths[authTarget].challenge,
144 { address }
145 );
147 - const signature = await provider.request<string>({
148 - method: "personal_sign",
149 - params: [challenge.siwe_message, address],
146 + const signature = await signMessageAsync({
147 + account: address,
148 + message: challenge.siwe_message,
149 });
150 const data = await apiClient.post<WalletAuthLoginPayload>(
151 authPaths[authTarget].login,
@@ -189,6 +188,11 @@ export function useAuth(target: AuthTarget = "admin") {
188 }
189 }
190 setAuthState((prev) => ({ ...prev, isAuthenticated: false, walletAddress: "" }));
191 + try {
192 + await disconnectAsync();
193 + } catch {
194 + // Some wallet connectors cannot be disconnected programmatically.
195 + }
196 };
197
198 return {
frontend/src/lib/wagmi.ts new
+11
@@ -0,0 +1,11 @@
1 +import { createConfig, http } from "wagmi";
2 +import { mainnet } from "wagmi/chains";
3 +import { injected } from "wagmi/connectors";
4 +
5 +export const wagmiConfig = createConfig({
6 + chains: [mainnet],
7 + connectors: [injected()],
8 + transports: {
9 + [mainnet.id]: http(),
10 + },
11 +});
frontend/src/main.tsx
+27 -18
@@ -1,30 +1,39 @@
1 import { Ssgoi } from "@ssgoi/react";
2 import { hero } from "@ssgoi/react/view-transitions";
3 +import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4 import ReactDOM from "react-dom/client";
5 import { BrowserRouter } from "react-router-dom";
6 +import { WagmiProvider } from "wagmi";
7 import { ThemeProvider } from "@/components/ThemeProvider";
8 +import { wagmiConfig } from "@/lib/wagmi";
9 import App from "./App.tsx";
10 import "./index.css";
11
12 +const queryClient = new QueryClient();
13 +
14 ReactDOM.createRoot(document.getElementById("root")!).render(
15 <ThemeProvider>
11 - <Ssgoi
12 - config={{
13 - transitions: [
14 - {
15 - from: "/",
16 - to: "/server/*",
17 - transition: hero(),
18 - symmetric: true,
19 - },
20 - ],
21 - }}
22 - >
23 - <div style={{ position: "relative", minHeight: "100vh" }}>
24 - <BrowserRouter>
25 - <App />
26 - </BrowserRouter>
27 - </div>
28 - </Ssgoi>
16 + <WagmiProvider config={wagmiConfig}>
17 + <QueryClientProvider client={queryClient}>
18 + <Ssgoi
19 + config={{
20 + transitions: [
21 + {
22 + from: "/",
23 + to: "/server/*",
24 + transition: hero(),
25 + symmetric: true,
26 + },
27 + ],
28 + }}
29 + >
30 + <div style={{ position: "relative", minHeight: "100vh" }}>
31 + <BrowserRouter>
32 + <App />
33 + </BrowserRouter>
34 + </div>
35 + </Ssgoi>
36 + </QueryClientProvider>
37 + </WagmiProvider>
38 </ThemeProvider>
39 );