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
);