diff --git a/package.json b/package.json index 47156b65..087d9b05 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,10 @@ "@icons-pack/react-simple-icons": "^13.7.0", "@lobehub/icons": "^2.9.0", "@modelcontextprotocol/sdk": "^1.13.2", - "@nuwa-ai/cap-kit": "^0.3.8", + "@nuwa-ai/cap-kit": "^0.4.4", "@nuwa-ai/identity-kit": "^0.4.0", - "@nuwa-ai/identity-kit-web": "^0.3.5", + "@nuwa-ai/identity-kit-web": "^0.4.0", + "@nuwa-ai/payment-kit": "^0.4.4", "@openrouter/ai-sdk-provider": "^0.7.2", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f7fd71a..a72c96da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,14 +54,17 @@ importers: specifier: ^1.13.2 version: 1.13.2 '@nuwa-ai/cap-kit': - specifier: ^0.3.8 - version: 0.3.8(react@19.1.0)(typescript@5.8.3) + specifier: ^0.4.4 + version: 0.4.4(react@19.1.0)(typescript@5.8.3) '@nuwa-ai/identity-kit': specifier: ^0.4.0 version: 0.4.0(typescript@5.8.3) '@nuwa-ai/identity-kit-web': - specifier: ^0.3.5 - version: 0.3.5(react@19.1.0)(typescript@5.8.3) + specifier: ^0.4.0 + version: 0.4.0(react@19.1.0)(typescript@5.8.3) + '@nuwa-ai/payment-kit': + specifier: ^0.4.4 + version: 0.4.4(express@5.1.0)(typescript@5.8.3) '@openrouter/ai-sdk-provider': specifier: ^0.7.2 version: 0.7.2(ai@4.3.16(react@19.1.0)(zod@3.25.67))(zod@3.25.67) @@ -513,10 +516,6 @@ packages: resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} @@ -534,8 +533,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -556,8 +555,8 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.3': - resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} engines: {node: '>=6.9.0'} '@babel/parser@7.28.0': @@ -565,11 +564,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/plugin-transform-runtime@7.28.0': resolution: {integrity: sha512-dGopk9nZrtCs2+nfIem25UuHyt5moSJamArzIoh9/vezUQPmYDOzjaHDCkAzuGJibCIkPup8rMT2+wYB6S73cA==} engines: {node: '>=6.9.0'} @@ -588,10 +582,6 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.3': - resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.0': resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} engines: {node: '>=6.9.0'} @@ -1328,10 +1318,6 @@ packages: resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.6': - resolution: {integrity: sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==} - engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -1364,26 +1350,31 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nuwa-ai/cap-kit@0.3.8': - resolution: {integrity: sha512-moaBuYJr0j71t6kjF74I0jRuAkbqicIK4oulDcYkv4YI2b2DKtVpm4+EGzpdvxoVGFw+dO23GhIPv19pGG5itw==} + '@nuwa-ai/cap-kit@0.4.4': + resolution: {integrity: sha512-oQhMhsMklFlDafYxqsjoZmlGrMWXb9EXUgYkHho9MO0OYmRyKH1nz2O64zTtE2jC1l03zEj5PfKV1HydJA27eA==} - '@nuwa-ai/identity-kit-web@0.3.5': - resolution: {integrity: sha512-egjSXMiGfDxzPW8l496PRGF2b0MGN+KbTY8m1oRmcnffSWei7A3gvrzHBvKyeHTB93mPbzYck7ca1/09Wclp4A==} + '@nuwa-ai/identity-kit-web@0.4.0': + resolution: {integrity: sha512-8v95z8BN6duFGsiULgFdTsRATrUkBQ8mPNBQMZTW2DSiaZzoY7HKe4xn7xOe1kBV+DzJGAXHtsFWAJDhYpKfdg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: react: optional: true - '@nuwa-ai/identity-kit@0.3.4': - resolution: {integrity: sha512-Lty7wuKmEuriLmGd15EM1IRPvDTuHvm/Px/BXb3i1O/l2sB6ZlYxybL++KU694I0tY2TZznuc3tNJUJfkYyIfA==} - - '@nuwa-ai/identity-kit@0.3.6': - resolution: {integrity: sha512-eVhgOQja2YiXVuBnCb16xrdF5uHcmBZWPZdBLKU97aaQ4R5pIvPbE26MbgHy2s4F+ZC2rthpJcd5zthGZGXgqQ==} - '@nuwa-ai/identity-kit@0.4.0': resolution: {integrity: sha512-LEULnr4ptnB7DV0+mHdqEM10fzK/DC1jNPpHukB0/kBameQ7p2JnKf8l3JRQ8nVV/x9ObPzyexDfzA6+T4IacQ==} + '@nuwa-ai/payment-kit@0.4.4': + resolution: {integrity: sha512-GgaRaFfMVrHVwggrvJrWvyCX3ISAj5jV6JUzVBeTnNNP9/aJBkTHnK4aRkB8QFvJDMoD8xpOdJrd9Qkri3wkIg==} + peerDependencies: + express: '>=4' + pg: '>=8' + peerDependenciesMeta: + express: + optional: true + pg: + optional: true + '@openrouter/ai-sdk-provider@0.7.2': resolution: {integrity: sha512-Fry2mV7uGGJRmP9JntTZRc8ElESIk7AJNTacLbF6Syoeb5k8d7HPGkcK9rTXDlqBb8HgU1hOKtz23HojesTmnw==} engines: {node: '>=18'} @@ -3161,6 +3152,9 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/pg@8.15.5': + resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -5230,6 +5224,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lossless-json@4.1.1: + resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -5643,6 +5640,10 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -5797,6 +5798,17 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5914,6 +5926,22 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + preact@10.24.2: resolution: {integrity: sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==} @@ -7846,13 +7874,13 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.0 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.3 - '@babel/parser': 7.28.3 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.28.2 + '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.3 + '@babel/traverse': 7.28.0 '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 @@ -7870,14 +7898,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - '@babel/generator@7.28.3': - dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': dependencies: '@babel/compat-data': 7.28.0 @@ -7906,12 +7926,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.3 + '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color @@ -7923,7 +7943,7 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.3': + '@babel/helpers@7.28.2': dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.2 @@ -7932,10 +7952,6 @@ snapshots: dependencies: '@babel/types': 7.28.0 - '@babel/parser@7.28.3': - dependencies: - '@babel/types': 7.28.2 - '@babel/plugin-transform-runtime@7.28.0(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -7968,18 +7984,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.28.3': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.3 - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - '@babel/types@7.28.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -8976,10 +8980,6 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 - '@noble/curves@1.9.6': - dependencies: - '@noble/hashes': 1.8.0 - '@noble/hashes@1.4.0': {} '@noble/hashes@1.5.0': {} @@ -9002,12 +9002,12 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nuwa-ai/cap-kit@0.3.8(react@19.1.0)(typescript@5.8.3)': + '@nuwa-ai/cap-kit@0.4.4(react@19.1.0)(typescript@5.8.3)': dependencies: '@modelcontextprotocol/sdk': 1.13.2 - '@noble/curves': 1.9.6 + '@noble/curves': 1.9.2 '@noble/hashes': 1.8.0 - '@nuwa-ai/identity-kit': 0.3.6(typescript@5.8.3) + '@nuwa-ai/identity-kit': 0.4.0(typescript@5.8.3) '@roochnetwork/rooch-sdk': 0.3.6(typescript@5.8.3) ai: 4.3.16(react@19.1.0)(zod@3.25.67) js-yaml: 4.1.0 @@ -9018,16 +9018,16 @@ snapshots: - supports-color - typescript - '@nuwa-ai/identity-kit-web@0.3.5(react@19.1.0)(typescript@5.8.3)': + '@nuwa-ai/identity-kit-web@0.4.0(react@19.1.0)(typescript@5.8.3)': dependencies: - '@nuwa-ai/identity-kit': 0.3.4(typescript@5.8.3) + '@nuwa-ai/identity-kit': 0.4.0(typescript@5.8.3) optionalDependencies: react: 19.1.0 transitivePeerDependencies: - supports-color - typescript - '@nuwa-ai/identity-kit@0.3.4(typescript@5.8.3)': + '@nuwa-ai/identity-kit@0.4.0(typescript@5.8.3)': dependencies: '@noble/curves': 1.9.2 '@noble/hashes': 1.8.0 @@ -9037,22 +9037,18 @@ snapshots: - supports-color - typescript - '@nuwa-ai/identity-kit@0.3.6(typescript@5.8.3)': + '@nuwa-ai/payment-kit@0.4.4(express@5.1.0)(typescript@5.8.3)': dependencies: - '@noble/curves': 1.9.6 - '@noble/hashes': 1.8.0 + '@nuwa-ai/identity-kit': 0.4.0(typescript@5.8.3) '@roochnetwork/rooch-sdk': 0.3.6(typescript@5.8.3) - multiformats: 9.9.0 - transitivePeerDependencies: - - supports-color - - typescript - - '@nuwa-ai/identity-kit@0.4.0(typescript@5.8.3)': - dependencies: - '@noble/curves': 1.9.6 - '@noble/hashes': 1.8.0 - '@roochnetwork/rooch-sdk': 0.3.6(typescript@5.8.3) - multiformats: 9.9.0 + '@types/js-yaml': 4.0.9 + '@types/pg': 8.15.5 + js-yaml: 4.1.0 + lossless-json: 4.1.1 + on-headers: 1.1.0 + zod: 3.25.67 + optionalDependencies: + express: 5.1.0 transitivePeerDependencies: - supports-color - typescript @@ -11606,6 +11602,12 @@ snapshots: '@types/parse-json@4.0.2': {} + '@types/pg@8.15.5': + dependencies: + '@types/node': 24.0.3 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + '@types/prismjs@1.26.5': {} '@types/react-dom@19.1.6(@types/react@19.1.8)': @@ -14723,6 +14725,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lossless-json@4.1.1: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -15407,6 +15411,8 @@ snapshots: dependencies: ee-first: 1.1.1 + on-headers@1.1.0: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -15620,6 +15626,18 @@ snapshots: pathe@2.0.3: {} + pg-int8@1.0.1: {} + + pg-protocol@1.10.3: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -15730,6 +15748,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + preact@10.24.2: {} preact@10.27.0: {} diff --git a/src/features/chat/services/providers/index.ts b/src/features/chat/services/providers/index.ts index 6611d07c..7670f3a1 100644 --- a/src/features/chat/services/providers/index.ts +++ b/src/features/chat/services/providers/index.ts @@ -1,12 +1,14 @@ import { createOpenAI } from '@ai-sdk/openai'; -import { createAuthorizedFetch } from '@/shared/services/authorized-fetch'; import { createOpenRouter } from './openrouter-provider'; +import { createPaymentFetch } from '@/shared/services/payment-fetch'; +import { LLM_GATEWAY_BASE_URL } from '@/shared/config/llm-gateway'; // Settings of Nuwa LLM Gateway +const baseURL = LLM_GATEWAY_BASE_URL; const providerSettings = { apiKey: 'NOT-USED', // specify a fake api key to avoid provider errors - baseURL: 'https://test-llm.nuwa.dev/api/v1', - fetch: createAuthorizedFetch(), + baseURL, + fetch: createPaymentFetch(baseURL), }; const openrouter = createOpenRouter(providerSettings); diff --git a/src/features/wallet/components/balance-card.tsx b/src/features/wallet/components/balance-card.tsx index 72957094..51a2d178 100644 --- a/src/features/wallet/components/balance-card.tsx +++ b/src/features/wallet/components/balance-card.tsx @@ -8,8 +8,7 @@ import { CardTitle, } from '@/shared/components/ui/card'; import { useDevMode } from '@/shared/hooks/use-dev-mode'; -import { useNuwaToUsdRate } from '../hooks/use-nuwa-to-usd-rate'; -import { useWallet } from '../hooks/use-wallet'; +import { usePaymentHubRgas } from '@/shared/hooks/usePaymentHub'; import { TestnetFaucetDialog } from './testnet-faucet-dialog'; interface BalanceCardProps { @@ -17,13 +16,12 @@ interface BalanceCardProps { } export function BalanceCard({ onTopUp }: BalanceCardProps) { - const { balance } = useWallet(); - const nuwaToUsdRate = useNuwaToUsdRate(); + const { amount, usd } = usePaymentHubRgas(); const isDevMode = useDevMode(); const [showFaucetDialog, setShowFaucetDialog] = useState(false); - const nuwaValue = balance.toLocaleString(); - const usdValue = (balance / nuwaToUsdRate).toFixed(6); + const rgasValue = amount; + const usdValue = usd; return ( <> @@ -39,8 +37,8 @@ export function BalanceCard({ onTopUp }: BalanceCardProps) {
- Balance -

Testnet

+ PaymentHub Balance +

RGas on Testnet

@@ -68,6 +66,7 @@ export function BalanceCard({ onTopUp }: BalanceCardProps) { USD
+
{rgasValue} RGas
{/*
diff --git a/src/features/wallet/components/sidebar-wallet-card.tsx b/src/features/wallet/components/sidebar-wallet-card.tsx index bbd3581b..305917d7 100644 --- a/src/features/wallet/components/sidebar-wallet-card.tsx +++ b/src/features/wallet/components/sidebar-wallet-card.tsx @@ -5,8 +5,7 @@ import { useCopyToClipboard } from 'usehooks-ts'; import { useAuth } from '@/features/auth/hooks/use-auth'; import { Card, CardContent } from '@/shared/components/ui/card'; import { cn } from '@/shared/utils'; -import { useNuwaToUsdRate } from '../hooks/use-nuwa-to-usd-rate'; -import { useWallet } from '../hooks/use-wallet'; +import { usePaymentHubRgas } from '@/shared/hooks/usePaymentHub'; interface SidebarWalletCardProps { className?: string; @@ -15,11 +14,10 @@ interface SidebarWalletCardProps { export function SidebarWalletCard({ className }: SidebarWalletCardProps) { const navigate = useNavigate(); const { did, isConnected } = useAuth(); - const { balance } = useWallet(); - const nuwaToUsdRate = useNuwaToUsdRate(); + const { usd, loading, error } = usePaymentHubRgas(); const [_, copyToClipboard] = useCopyToClipboard(); - const usdValue = (balance / nuwaToUsdRate).toFixed(2); + const usdValue = loading ? '…' : error ? '-' : usd; const handleClick = () => { navigate('/wallet'); diff --git a/src/features/wallet/components/transaction-history.tsx b/src/features/wallet/components/transaction-history.tsx index 4351e879..1726cc60 100644 --- a/src/features/wallet/components/transaction-history.tsx +++ b/src/features/wallet/components/transaction-history.tsx @@ -4,80 +4,78 @@ import { CardHeader, CardTitle, } from '@/shared/components/ui/card'; -import { useNuwaToUsdRate } from '../hooks/use-nuwa-to-usd-rate'; -import { useWallet } from '../hooks/use-wallet'; -import type { Transaction } from '../types'; - -function TransactionRow({ transaction }: { transaction: Transaction }) { - const nuwaToUsdRate = useNuwaToUsdRate(); - const amountColor = - transaction.type === 'deposit' ? 'text-green-600' : 'text-red-600'; - const amountPrefix = transaction.type === 'deposit' ? '+' : '-'; - - const formatDate = (timestamp: string) => { - return new Date(timestamp).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - }); - }; - - const getStatusBadge = (status: Transaction['status']) => { - const baseClasses = - 'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium'; - - switch (status) { - case 'completed': - return `${baseClasses} bg-green-100 text-green-800`; - case 'confirming': - return `${baseClasses} bg-yellow-100 text-yellow-800`; - default: - return `${baseClasses} bg-gray-100 text-gray-800`; - } - }; - - const usdValue = transaction.amount.toFixed(6).toLocaleString(); - const nuwaValue = (transaction.amount * nuwaToUsdRate).toFixed(6); +import { useTransactions } from '@/shared/hooks/useTransactions'; +import { formatAmount } from '@nuwa-ai/payment-kit'; +function TransactionRow({ + operation, + status, + statusCode, + cost, + costUsd, + paidAt, + stream, +}: { + operation: string; + status: string; + statusCode?: number; + cost?: string; + costUsd?: string; + paidAt?: string; + stream?: boolean; +}) { return (
- {transaction.label} - - {transaction.status} - + {operation} + {stream && stream}
-

- {formatDate(transaction.timestamp.toString())} -

+

{paidAt ? new Date(paidAt).toLocaleString() : ''}

-
-
{`${amountPrefix}$${usdValue} USD`}
-
- {`${amountPrefix}${nuwaValue} $NUWA`} -
+
+
{status}{statusCode ? ` (${statusCode})` : ''}
+ {cost && ( +
{costUsd ? `${cost} (${costUsd} USD)` : cost}
+ )}
); } export function TransactionHistory() { - const { transactions } = useWallet(); + const { items, loading, error, reload } = useTransactions(50); return ( - - Recent Transactions + + Payment Transactions + - {transactions.length === 0 ? ( -

- No transactions found -

+ {loading ?

Loading...

: error ? ( +

{error}

+ ) : items.length === 0 ? ( +

No payment transactions

) : (
- {transactions.map((transaction) => ( - + {items.map((tx) => ( + { + const v = tx.payment?.costUsd as unknown; + if (typeof v === 'bigint') return `$${formatAmount(v, 12)}`; + if (v !== undefined && v !== null) { + return `$${formatAmount(BigInt(String(v)), 12)}`; + } + return undefined; + })()} + paidAt={tx.payment?.paidAt} + stream={tx.stream} + /> ))}
)} diff --git a/src/shared/config/llm-gateway.ts b/src/shared/config/llm-gateway.ts new file mode 100644 index 00000000..8eac61fd --- /dev/null +++ b/src/shared/config/llm-gateway.ts @@ -0,0 +1,6 @@ +// Centralized LLM Gateway configuration for reuse across features + +// TODO: consider moving to environment-configurable source +export const LLM_GATEWAY_BASE_URL = 'https://llm-gateway-payment-test.up.railway.app/api/v1'; + + diff --git a/src/shared/hooks/usePaymentHub.ts b/src/shared/hooks/usePaymentHub.ts new file mode 100644 index 00000000..f94c6ae5 --- /dev/null +++ b/src/shared/hooks/usePaymentHub.ts @@ -0,0 +1,43 @@ +import { useCallback, useEffect, useState } from 'react'; +import { getPaymentHubClient } from '@/shared/services/payment-clients'; + +function formatBigIntWithDecimals(value: bigint, decimals: number, fractionDigits: number): string { + const negative = value < 0n; + const v = negative ? -value : value; + const base = 10n ** BigInt(decimals); + const integer = v / base; + let fraction = (v % base).toString().padStart(decimals, '0'); + if (fractionDigits >= 0) fraction = fraction.slice(0, Math.min(decimals, fractionDigits)); + const fracPart = fractionDigits > 0 ? `.${fraction}` : ''; + return `${negative ? '-' : ''}${integer.toString()}${fracPart}`; +} + +export function usePaymentHubRgas(defaultAssetId = '0x3::gas_coin::RGas') { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [amount, setAmount] = useState('0'); + const [usd, setUsd] = useState('0'); + + const refetch = useCallback(async () => { + setLoading(true); + try { + const hub = await getPaymentHubClient(defaultAssetId); + const res = await hub.getBalanceWithUsd({ assetId: defaultAssetId }); + setAmount(formatBigIntWithDecimals(res.balance, 8, 8)); + setUsd(formatBigIntWithDecimals(res.balancePicoUSD, 12, 2)); + setError(null); + } catch (e: any) { + setError(e?.message || String(e)); + } finally { + setLoading(false); + } + }, [defaultAssetId]); + + useEffect(() => { + refetch(); + }, [refetch]); + + return { loading, error, amount, usd, refetch }; +} + + diff --git a/src/shared/hooks/useTransactions.ts b/src/shared/hooks/useTransactions.ts new file mode 100644 index 00000000..53e883d3 --- /dev/null +++ b/src/shared/hooks/useTransactions.ts @@ -0,0 +1,50 @@ +import { useCallback, useEffect, useState } from 'react'; +import { getHttpClient } from '@/shared/services/payment-clients'; +import type { TransactionRecord, TransactionStore } from '@nuwa-ai/payment-kit'; + +export function useTransactions(limit = 50) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [items, setItems] = useState([]); + const [store, setStore] = useState(null); + + const load = useCallback(async () => { + setLoading(true); + try { + const client = await getHttpClient(); + const txStore = client.getTransactionStore(); + setStore(txStore); + const res = await txStore.list({}, { limit }); + setItems(res.items); + setError(null); + } catch (e: any) { + setError(e?.message || String(e)); + } finally { + setLoading(false); + } + }, [limit]); + + useEffect(() => { + let unsub: (() => void) | undefined; + (async () => { + await load(); + try { + if (store && store.subscribe) { + unsub = store.subscribe(() => { + // naive: just reload on any change + load(); + }); + } + } catch {} + })(); + return () => { + if (unsub) { + try { unsub(); } catch {} + } + }; + }, [load, store]); + + return { loading, error, items, reload: load }; +} + + diff --git a/src/shared/services/payment-clients.ts b/src/shared/services/payment-clients.ts new file mode 100644 index 00000000..6cea4e56 --- /dev/null +++ b/src/shared/services/payment-clients.ts @@ -0,0 +1,42 @@ +import { IdentityKitWeb } from '@nuwa-ai/identity-kit-web'; +import { createHttpClient, RoochPaymentChannelContract, PaymentHubClient } from '@nuwa-ai/payment-kit'; +import type { PaymentChannelHttpClient } from '@nuwa-ai/payment-kit'; +import { LLM_GATEWAY_BASE_URL } from '@/shared/config/llm-gateway'; + +let httpClientPromise: Promise | null = null; +let hubClientPromise: Promise | null = null; + +async function getIdentityEnvAndSigner() { + const sdk = await IdentityKitWeb.init({ storage: 'local' }); + const env = sdk.getIdentityEnv(); + const signer = env.keyManager; + return { sdk, env, signer }; +} + +export async function getHttpClient(): Promise { + if (!httpClientPromise) { + httpClientPromise = (async () => { + const { env } = await getIdentityEnvAndSigner(); + return createHttpClient({ baseUrl: LLM_GATEWAY_BASE_URL, env }); + })(); + } + return httpClientPromise; +} + +export async function getPaymentHubClient(defaultAssetId?: string): Promise { + if (!hubClientPromise) { + hubClientPromise = (async () => { + const { env, signer } = await getIdentityEnvAndSigner(); + const chain = (env as any).chainConfig || undefined; + const contract = new RoochPaymentChannelContract({ + rpcUrl: chain?.rpcUrl, + network: chain?.network, + debug: !!chain?.debug, + }); + return new PaymentHubClient({ contract, signer, defaultAssetId: defaultAssetId || '0x3::gas_coin::RGas' }); + })(); + } + return hubClientPromise; +} + + diff --git a/src/shared/services/payment-fetch.ts b/src/shared/services/payment-fetch.ts new file mode 100644 index 00000000..7c3612c3 --- /dev/null +++ b/src/shared/services/payment-fetch.ts @@ -0,0 +1,18 @@ +import { getHttpClient } from '@/shared/services/payment-clients'; + +/** + * Create a fetch-compatible function backed by Payment Kit. + * It automatically handles payment-channel headers and streaming settlement. + */ +export function createPaymentFetch(baseUrl: string, _options?: { maxAmount?: bigint }) { + return async function paymentFetch(input: RequestInfo | URL, init?: RequestInit): Promise { + const targetUrl = new URL(typeof input === 'string' ? input : (input as any).url ?? input.toString()); + const methodFromInit = (init?.method ?? 'POST').toUpperCase() as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + + const client = await getHttpClient(); + const handle = await client.requestWithPayment(methodFromInit, targetUrl.toString(), init); + return handle.response; + }; +} + +