diff --git a/config/wallet-config.json b/config/wallet-config.json index 33311666e8e..aae4567b5f7 100644 --- a/config/wallet-config.json +++ b/config/wallet-config.json @@ -100,6 +100,6 @@ "mainnetApiUrl": "https://api2.ordinalsbot.com", "signetApiUrl": "https://signet.ordinalsbot.com" }, - "recoverUninscribedTaprootUtxosFeatureEnabled": false, + "recoverUninscribedTaprootUtxosFeatureEnabled": true, "runesEnabled": true } diff --git a/package.json b/package.json index 32e0d4efad1..901a5bd2fd9 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,7 @@ "@btckit/types": "0.0.19", "@chromatic-com/storybook": "1.2.23", "@leather-wallet/prettier-config": "0.0.5", - "@leather-wallet/types": "0.0.20", + "@leather-wallet/rpc": "0.3.0", "@ls-lint/ls-lint": "2.2.3", "@mdx-js/loader": "3.0.0", "@pandacss/dev": "0.32.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85dc9c79d9a..55fe9dfa4b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -382,9 +382,9 @@ devDependencies: '@leather-wallet/prettier-config': specifier: 0.0.5 version: 0.0.5 - '@leather-wallet/types': - specifier: 0.0.20 - version: 0.0.20(@swc/core@1.4.8)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.4.4) + '@leather-wallet/rpc': + specifier: 0.3.0 + version: 0.3.0 '@ls-lint/ls-lint': specifier: 2.2.3 version: 2.2.3 @@ -3125,15 +3125,6 @@ packages: resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} dev: true - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/aix-ppc64@0.20.2: resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} @@ -3142,15 +3133,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.20.2: resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} @@ -3159,15 +3141,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.20.2: resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} @@ -3176,15 +3149,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.20.2: resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} @@ -3193,15 +3157,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.20.2: resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} @@ -3210,15 +3165,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.20.2: resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} @@ -3227,15 +3173,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.20.2: resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} @@ -3244,15 +3181,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.20.2: resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} @@ -3261,15 +3189,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.20.2: resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} @@ -3278,15 +3197,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.20.2: resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} @@ -3295,15 +3205,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.20.2: resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} @@ -3312,15 +3213,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.20.2: resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} @@ -3329,15 +3221,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.20.2: resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} @@ -3346,15 +3229,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.20.2: resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} @@ -3363,15 +3237,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.20.2: resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} @@ -3380,15 +3245,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.20.2: resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} @@ -3397,15 +3253,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.20.2: resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} @@ -3414,15 +3261,6 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.20.2: resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} @@ -3431,15 +3269,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.20.2: resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} @@ -3448,15 +3277,6 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.20.2: resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} @@ -3465,15 +3285,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.20.2: resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} @@ -3482,15 +3293,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.20.2: resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} @@ -3499,15 +3301,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.20.2: resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -3797,7 +3590,6 @@ packages: resolution: {integrity: sha512-6uwhdJxEhIpCI/29TPQIXAzfcz4niVIIujkGuwRFSyUiT/E8IEvjM30A3p9pMtEPCzO2UnpVERGLiLSIk48fgg==} dependencies: bignumber.js: 9.1.2 - dev: false /@leather-wallet/models@0.4.5: resolution: {integrity: sha512-H0NhxYy1C2Byrj1wswL9Zf6bWOejvgVuwgVDT6xh7zqvoPotiAtrfEe3hiBdSeUtUsTh70ha1ZMuGwf/rph67w==} @@ -3855,25 +3647,11 @@ packages: resolution: {integrity: sha512-jRexusk82Bax0CMIC9T2ApvnxA5rvd1yWjRyOxeImHdfQUK140MMij/Za8OcC8TYcFGMZbm3DEK1Xh9/NY6oIg==} dependencies: '@leather-wallet/models': 0.4.0 - dev: false /@leather-wallet/tokens@0.0.14: resolution: {integrity: sha512-xCg+aIcn8DexmQhfvxYM85IGP2Q1JNfUBq80ZwV4horKw18MxxTdX5FeEthTrO8quRZu7up+IW6m/l3wElnDsA==} dev: false - /@leather-wallet/types@0.0.20(@swc/core@1.4.8)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.4.4): - resolution: {integrity: sha512-cypcCgzjfjzFJ4B+3fIlqZuBVFPGDTxlZJDzpkCVJSRBhVRX6z/YRjN7I6VMoCxaN/gBU9KHU7gzD5MTaoznSg==} - dependencies: - tsup: 8.0.2(@swc/core@1.4.8)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.4.4) - transitivePeerDependencies: - - '@microsoft/api-extractor' - - '@swc/core' - - postcss - - supports-color - - ts-node - - typescript - dev: true - /@leather-wallet/utils@0.4.0: resolution: {integrity: sha512-cgPNyyzVMrqFMaP0LYGv8IJFZBahW745P3vjhfywaTaiRTwfp7Ggr7dLqCWuKc2ltnmNs8ysFUPL6V1CpHyjzA==} dependencies: @@ -11707,7 +11485,6 @@ packages: /bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - dev: false /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -12018,16 +11795,6 @@ packages: run-applescript: 7.0.0 dev: true - /bundle-require@4.1.0(esbuild@0.19.12): - resolution: {integrity: sha512-FeArRFM+ziGkRViKRnSTbHZc35dgmR9yNog05Kn0+ItI59pOAISGvnnIwW1WgFZQW59IxD9QpJnUPkdIPfZuXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.17' - dependencies: - esbuild: 0.19.12 - load-tsconfig: 0.2.5 - dev: true - /bunyan@1.8.15: resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} engines: {'0': node >=0.10.0} @@ -12644,11 +12411,6 @@ packages: graceful-readlink: 1.0.1 dev: true - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: true - /commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -14454,37 +14216,6 @@ packages: - supports-color dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - dev: true - /esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -17239,11 +16970,6 @@ packages: react: 18.2.0 dev: false - /joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - dev: true - /js-cookie@3.0.1: resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} engines: {node: '>=12'} @@ -17787,11 +17513,6 @@ packages: lightningcss-win32-x64-msvc: 1.23.0 dev: true - /lilconfig@3.1.1: - resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} - engines: {node: '>=14'} - dev: true - /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true @@ -17816,11 +17537,6 @@ packages: import-meta-resolve: 4.0.0 dev: true - /load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} @@ -20373,24 +20089,6 @@ packages: postcss: 8.4.38 dev: false - /postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2): - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 3.1.1 - postcss: 8.4.38 - ts-node: 10.9.2(@swc/core@1.4.8)(@types/node@20.12.4)(typescript@5.4.4) - yaml: 2.3.4 - dev: true - /postcss-loader@8.1.1(postcss@8.4.38)(typescript@5.4.4)(webpack@5.91.0): resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==} engines: {node: '>= 18.12.0'} @@ -22640,13 +22338,6 @@ packages: engines: {node: '>= 8'} dev: true - /source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - dependencies: - whatwg-url: 7.1.0 - dev: true - /space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} dev: true @@ -23101,20 +22792,6 @@ packages: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: true - /sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.3.10 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: true - /sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -23449,12 +23126,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - /tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - dependencies: - punycode: 2.3.1 - dev: true - /tr46@4.1.1: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} @@ -23534,10 +23205,6 @@ packages: typescript: 5.4.4 dev: true - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true - /ts-morph@21.0.1: resolution: {integrity: sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==} dependencies: @@ -23639,47 +23306,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - /tsup@8.0.2(@swc/core@1.4.8)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.4.4): - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - dependencies: - '@swc/core': 1.4.8 - bundle-require: 4.1.0(esbuild@0.19.12) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.4(supports-color@5.5.0) - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss: 8.4.38 - postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2) - resolve-from: 5.0.0 - rollup: 4.14.0 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /tsutils@3.21.0(typescript@5.4.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -24647,10 +24273,6 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - /webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: true - /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -24897,14 +24519,6 @@ packages: tr46: 0.0.3 webidl-conversions: 3.0.1 - /whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - dev: true - /when@3.7.7: resolution: {integrity: sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw==} dev: true diff --git a/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts b/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts deleted file mode 100644 index d8812ce8e34..00000000000 --- a/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; - -import { - isFtNameLikeStx, - isTransferableStacksFungibleTokenAsset, -} from './stacks-crypto-asset.utils'; - -describe(isFtNameLikeStx.name, () => { - it('detect impersonating token names', () => { - expect(isFtNameLikeStx('STX')).toBeTruthy(); - expect(isFtNameLikeStx('stx')).toBeTruthy(); - expect(isFtNameLikeStx('stacks')).toBeTruthy(); - expect(isFtNameLikeStx('Stäcks')).toBeTruthy(); - expect(isFtNameLikeStx('Stácks')).toBeTruthy(); - expect(isFtNameLikeStx('Stáçks')).toBeTruthy(); - expect(isFtNameLikeStx('stocks')).toBeFalsy(); - expect(isFtNameLikeStx('miamicoin')).toBeFalsy(); - expect(isFtNameLikeStx('')).toBeFalsy(); - }); -}); - -describe(isTransferableStacksFungibleTokenAsset.name, () => { - test('assets with a name, symbol and decimals are allowed to be transferred', () => { - const asset: StacksFungibleTokenAsset = { - contractId: '', - contractAddress: 'ST6G7N19FKNW24XH5JQ5P5WR1DN10QWMKQSPSTK7', - contractAssetName: 'stella-token', - contractName: 'stella-the-cat', - decimals: 9, - name: 'SteLLa the Cat', - canTransfer: true, - hasMemo: true, - imageCanonicalUri: '', - marketData: null, - symbol: 'CAT', - }; - expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); - }); - - test('a token with no decimals is transferable', () => { - const asset: StacksFungibleTokenAsset = { - contractId: '', - contractAddress: 'ST6G7N19FKNW24XH5JQ5P5WR1DN10QWMKQSPSTK7', - contractAssetName: 'stella-token', - contractName: 'stella-the-cat', - decimals: 0, - name: 'SteLLa the Cat', - canTransfer: true, - hasMemo: true, - imageCanonicalUri: '', - marketData: null, - symbol: 'CAT', - }; - expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); - }); - - test('assets missing either name, symbol or decimals may not be transferred', () => { - const asset = { - name: 'Test token', - symbol: 'TEST', - decimals: undefined, - type: 'fungible-token', - } as unknown as StacksFungibleTokenAsset; - expect(isTransferableStacksFungibleTokenAsset(asset)).toBeFalsy(); - }); - - test('NFTs cannot be sent', () => { - const asset = { type: 'non-fungible-token' } as unknown as StacksFungibleTokenAsset; - - expect(isTransferableStacksFungibleTokenAsset(asset)).toBeFalsy(); - }); -}); diff --git a/src/app/common/crypto-assets/stacks-crypto-asset.utils.ts b/src/app/common/crypto-assets/stacks-crypto-asset.utils.ts deleted file mode 100644 index 88ca3269097..00000000000 --- a/src/app/common/crypto-assets/stacks-crypto-asset.utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; -import { isUndefined } from '@shared/utils'; -import { isValidUrl } from '@shared/utils/validate-url'; - -import { convertUnicodeToAscii } from '../string-utils'; - -export function isFtNameLikeStx(name: string) { - return ['stx', 'stack', 'stacks'].includes(convertUnicodeToAscii(name).toLocaleLowerCase()); -} - -export function getImageCanonicalUri(imageCanonicalUri: string, name: string) { - return imageCanonicalUri && isValidUrl(imageCanonicalUri) && !isFtNameLikeStx(name) - ? imageCanonicalUri - : ''; -} - -export function isTransferableStacksFungibleTokenAsset(asset: StacksFungibleTokenAsset) { - return !isUndefined(asset.decimals) && !isUndefined(asset.name) && !isUndefined(asset.symbol); -} diff --git a/src/app/common/hooks/analytics/use-track-switch-account.ts b/src/app/common/hooks/analytics/use-track-switch-account.ts index cb3f70ad2f4..23614ac1613 100644 --- a/src/app/common/hooks/analytics/use-track-switch-account.ts +++ b/src/app/common/hooks/analytics/use-track-switch-account.ts @@ -1,7 +1,6 @@ import { useCallback } from 'react'; import { queryClient } from '@app/common/persistence'; -import { parseBalanceResponse } from '@app/query/stacks/balance/stx-balance.hooks'; import { useAnalytics } from './use-analytics'; @@ -13,8 +12,9 @@ export function useTrackSwitchAccount() { const accountBalanceCache = queryClient.getQueryData(['get-address-stx-balance', address]); if (!accountBalanceCache) return; try { - const balances = parseBalanceResponse(accountBalanceCache as any); - const hasStxBalance = !!balances?.stx.unlockedStx.amount.isGreaterThan(0); + const hasStxBalance = !!(accountBalanceCache as any).stx.unlockedStx.amount.isGreaterThan( + 0 + ); void analytics.track('switch_account', { index, hasStxBalance }); } finally { } diff --git a/src/app/common/hooks/balance/btc/use-btc-balance.ts b/src/app/common/hooks/balance/btc/use-btc-balance.ts deleted file mode 100644 index aa62c627311..00000000000 --- a/src/app/common/hooks/balance/btc/use-btc-balance.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useMemo } from 'react'; - -import { - createBitcoinCryptoCurrencyAssetTypeWrapper, - useCryptoCurrencyMarketDataMeanAverage, - useNativeSegwitBalance, -} from '@leather-wallet/query'; - -import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; -import { i18nFormatCurrency } from '@app/common/money/format-money'; - -export function useBtcAssetBalance(btcAddress: string) { - const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); - const { - btcBalance: btcAssetBalance, - isLoading, - isFetching, - isInitialLoading, - } = useNativeSegwitBalance(btcAddress); - - return useMemo( - () => ({ - btcAddress, - btcAssetBalance, - btcUsdBalance: i18nFormatCurrency( - baseCurrencyAmountInQuote(btcAssetBalance.balance, btcMarketData) - ), - btcAvailableAssetBalance: createBitcoinCryptoCurrencyAssetTypeWrapper( - btcAssetBalance.balance - ), - btcAvailableUsdBalance: i18nFormatCurrency( - baseCurrencyAmountInQuote(btcAssetBalance.balance, btcMarketData) - ), - isLoading, - isFetching, - isInitialLoading, - }), - [btcAddress, btcAssetBalance, btcMarketData, isLoading, isInitialLoading, isFetching] - ); -} diff --git a/src/app/common/hooks/balance/btc/use-btc-crypto-currency-asset-balance.ts b/src/app/common/hooks/balance/btc/use-btc-crypto-currency-asset-balance.ts deleted file mode 100644 index ec963f48802..00000000000 --- a/src/app/common/hooks/balance/btc/use-btc-crypto-currency-asset-balance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useNativeSegwitBalance } from '@leather-wallet/query'; - -import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; - -export function useBtcCryptoCurrencyAssetBalance() { - const currentBtcSigner = useCurrentAccountNativeSegwitSigner(); - // TODO: it would be better if we could skip providing the empty string to this hook. - const bitcoinBalance = useNativeSegwitBalance(currentBtcSigner?.(0).address ?? ''); - - if (!currentBtcSigner?.(0).address) return undefined; - return bitcoinBalance; -} diff --git a/src/app/common/hooks/balance/stx/use-stx-balance.ts b/src/app/common/hooks/balance/stx/use-stx-balance.ts deleted file mode 100644 index f6e2d718acc..00000000000 --- a/src/app/common/hooks/balance/stx/use-stx-balance.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useMemo } from 'react'; - -import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query'; - -import { createMoney } from '@shared/models/money.model'; -import { isDefined } from '@shared/utils'; - -import { baseCurrencyAmountInQuote, subtractMoney } from '@app/common/money/calculate-money'; -import { i18nFormatCurrency } from '@app/common/money/format-money'; -import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; -import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks'; - -export function useStxBalance() { - const stxBalanceQuery = useCurrentStacksAccountBalances(); - const totalBalance = stxBalanceQuery.data?.stx.balance; - const unlockedStxBalance = stxBalanceQuery.data?.stx.unlockedStx; - - const stxMarketData = useCryptoCurrencyMarketDataMeanAverage('STX'); - const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance(); - - const stxEffectiveBalance = isDefined(totalBalance) - ? subtractMoney(totalBalance, pendingTxsBalance) - : createMoney(0, 'STX'); - - const stxEffectiveUsdBalance = isDefined(totalBalance) - ? i18nFormatCurrency(baseCurrencyAmountInQuote(stxEffectiveBalance, stxMarketData)) - : undefined; - - const stxLockedBalance = stxBalanceQuery.data?.stx.locked; - const stxUsdLockedBalance = isDefined(stxLockedBalance) - ? i18nFormatCurrency(baseCurrencyAmountInQuote(stxLockedBalance, stxMarketData)) - : undefined; - - return useMemo(() => { - return { - stxBalanceQuery, - stxOutboundQuery: pendingTxsBalance, - availableBalance: isDefined(unlockedStxBalance) - ? subtractMoney(unlockedStxBalance, pendingTxsBalance) - : createMoney(0, 'STX'), - stxEffectiveBalance: createStacksCryptoCurrencyAssetTypeWrapper(stxEffectiveBalance.amount), - stxEffectiveUsdBalance, - stxLockedBalance, - stxUsdLockedBalance, - }; - }, [ - stxBalanceQuery, - pendingTxsBalance, - unlockedStxBalance, - stxEffectiveBalance.amount, - stxEffectiveUsdBalance, - stxLockedBalance, - stxUsdLockedBalance, - ]); -} diff --git a/src/app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance.ts b/src/app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance.ts deleted file mode 100644 index 1f0a4387a39..00000000000 --- a/src/app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; -import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; - -export function useStxCryptoCurrencyAssetBalance() { - const { availableBalance: availableStxBalance } = useStxBalance(); - return createStacksCryptoCurrencyAssetTypeWrapper(availableStxBalance.amount); -} diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx index 0d373f33593..11d3a3f5b26 100644 --- a/src/app/common/hooks/balance/use-total-balance.tsx +++ b/src/app/common/hooks/balance/use-total-balance.tsx @@ -6,15 +6,13 @@ import { createMoney } from '@shared/models/money.model'; import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; -import { useStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; - -import { useBtcAssetBalance } from './btc/use-btc-balance'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; +import { useStxCryptoAssetBalance } from '@app/query/stacks/balance/account-balance.hooks'; interface UseTotalBalanceArgs { btcAddress: string; stxAddress: string; } - export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) { // get market data const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); @@ -22,25 +20,28 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) // get stx balance const { - data: balances, + data: balance, isLoading, isInitialLoading, isFetching: isFetchingStacksBalance, - } = useStacksAccountBalances(stxAddress); - const stxBalance = balances ? balances.stx.balance : createMoney(0, 'STX'); + } = useStxCryptoAssetBalance(stxAddress); + const stxBalance = balance ? balance.totalBalance : createMoney(0, 'STX'); // get btc balance const { - btcAvailableAssetBalance, + btcCryptoAssetBalance, isLoading: isLoadingBtcBalance, isFetching: isFetchingBtcBalance, isInitialLoading: isInititalLoadingBtcBalance, - } = useBtcAssetBalance(btcAddress); + } = useBtcCryptoAssetBalanceNativeSegwit(btcAddress); return useMemo(() => { // calculate total balance const stxUsdAmount = baseCurrencyAmountInQuote(stxBalance, stxMarketData); - const btcUsdAmount = baseCurrencyAmountInQuote(btcAvailableAssetBalance.balance, btcMarketData); + const btcUsdAmount = baseCurrencyAmountInQuote( + btcCryptoAssetBalance.availableBalance, + btcMarketData + ); const totalBalance = { ...stxUsdAmount, amount: stxUsdAmount.amount.plus(btcUsdAmount.amount) }; return { @@ -54,14 +55,14 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) isFetching: isFetchingStacksBalance || isFetchingBtcBalance, }; }, [ - btcAvailableAssetBalance.balance, - btcMarketData, - isInitialLoading, - isInititalLoadingBtcBalance, - stxMarketData, stxBalance, + stxMarketData, + btcCryptoAssetBalance.availableBalance, + btcMarketData, isLoading, isLoadingBtcBalance, + isInitialLoading, + isInititalLoadingBtcBalance, isFetchingStacksBalance, isFetchingBtcBalance, ]); diff --git a/src/app/common/hooks/use-transferable-asset-balances.hooks.ts b/src/app/common/hooks/use-transferable-asset-balances.hooks.ts deleted file mode 100644 index 4eddb7d909f..00000000000 --- a/src/app/common/hooks/use-transferable-asset-balances.hooks.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useMemo } from 'react'; - -import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; - -import { useTransferableStacksFungibleTokenAssetBalances } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; -import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -import { useStxBalance } from './balance/stx/use-stx-balance'; - -export function useAllTransferableCryptoAssetBalances(): AllTransferableCryptoAssetBalances[] { - const account = useCurrentStacksAccount(); - - const { availableBalance: availableStxBalance } = useStxBalance(); - const stxCryptoCurrencyAssetBalance = createStacksCryptoCurrencyAssetTypeWrapper( - availableStxBalance.amount - ); - const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances( - account?.address ?? '' - ); - - return useMemo(() => { - return [stxCryptoCurrencyAssetBalance, ...stacksFtAssetBalances]; - }, [stacksFtAssetBalances, stxCryptoCurrencyAssetBalance]); -} diff --git a/src/app/common/stacks-utils.spec.ts b/src/app/common/stacks-utils.spec.ts index e9def73faef..17767b47f13 100644 --- a/src/app/common/stacks-utils.spec.ts +++ b/src/app/common/stacks-utils.spec.ts @@ -1,4 +1,4 @@ -import { stacksValue } from '@app/common/stacks-utils'; +import { isFtNameLikeStx, stacksValue } from '@app/common/stacks-utils'; const uSTX_AMOUNT = 10000480064; // 10,000.480064 @@ -31,3 +31,17 @@ describe('stacksValue tests', () => { expect(value).toEqual('10K STX'); }); }); + +describe(isFtNameLikeStx.name, () => { + it('detect impersonating token names', () => { + expect(isFtNameLikeStx('STX')).toBeTruthy(); + expect(isFtNameLikeStx('stx')).toBeTruthy(); + expect(isFtNameLikeStx('stacks')).toBeTruthy(); + expect(isFtNameLikeStx('Stäcks')).toBeTruthy(); + expect(isFtNameLikeStx('Stácks')).toBeTruthy(); + expect(isFtNameLikeStx('Stáçks')).toBeTruthy(); + expect(isFtNameLikeStx('stocks')).toBeFalsy(); + expect(isFtNameLikeStx('miamicoin')).toBeFalsy(); + expect(isFtNameLikeStx('')).toBeFalsy(); + }); +}); diff --git a/src/app/common/stacks-utils.ts b/src/app/common/stacks-utils.ts index 141d49d35e0..9d4b9ab2004 100644 --- a/src/app/common/stacks-utils.ts +++ b/src/app/common/stacks-utils.ts @@ -3,11 +3,13 @@ import BigNumber from 'bignumber.js'; import { c32addressDecode } from 'c32check'; import { NetworkConfiguration, STX_DECIMALS } from '@shared/constants'; +import { isValidUrl } from '@shared/utils/validate-url'; import { abbreviateNumber } from '@app/common/utils'; import { initBigNumber } from './math/helpers'; import { microStxToStx } from './money/unit-conversion'; +import { convertUnicodeToAscii } from './string-utils'; export const stacksValue = ({ value, @@ -64,3 +66,13 @@ export function validateAddressChain(address: string, currentNetwork: NetworkCon return false; } } + +export function isFtNameLikeStx(name: string) { + return ['stx', 'stack', 'stacks'].includes(convertUnicodeToAscii(name).toLocaleLowerCase()); +} + +export function getSafeImageCanonicalUri(imageCanonicalUri: string, name: string) { + return imageCanonicalUri && isValidUrl(imageCanonicalUri) && !isFtNameLikeStx(name) + ? imageCanonicalUri + : ''; +} diff --git a/src/app/components/account/account-addresses.tsx b/src/app/components/account/account-addresses.tsx index f8feff13b97..28eb7164ab6 100644 --- a/src/app/components/account/account-addresses.tsx +++ b/src/app/components/account/account-addresses.tsx @@ -4,8 +4,8 @@ import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-sepa import { Caption } from '@app/ui/components/typography/caption'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; +import { BitcoinNativeSegwitAccountLoader } from '../loaders/bitcoin-account-loader'; import { StacksAccountLoader } from '../loaders/stacks-account-loader'; -import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; interface AccountAddressesProps { index: number; diff --git a/src/app/components/account/bitcoin-account-loader.tsx b/src/app/components/account/bitcoin-account-loader.tsx deleted file mode 100644 index 18774dc3680..00000000000 --- a/src/app/components/account/bitcoin-account-loader.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { P2Ret } from '@scure/btc-signer'; - -import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; -import { useCurrentAccountIndex } from '@app/store/accounts/account'; -import { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer'; -import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; -import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; - -interface BitcoinAccountLoaderBaseProps { - children(account: Signer): React.ReactNode; -} -interface BtcAccountLoaderCurrentProps extends BitcoinAccountLoaderBaseProps { - current: true; -} -interface BtcAccountLoaderIndexProps extends BitcoinAccountLoaderBaseProps { - index: number; -} - -type BtcAccountLoaderProps = BtcAccountLoaderCurrentProps | BtcAccountLoaderIndexProps; - -export function BitcoinNativeSegwitAccountLoader({ children, ...props }: BtcAccountLoaderProps) { - const isBitcoinEnabled = useConfigBitcoinEnabled(); - - const currentAccountIndex = useCurrentAccountIndex(); - - const properIndex = 'current' in props ? currentAccountIndex : props.index; - - const signer = useNativeSegwitSigner(properIndex); - - if (!signer || !isBitcoinEnabled) return null; - return children(signer(0)); -} - -export function BitcoinTaprootAccountLoader({ children, ...props }: BtcAccountLoaderProps) { - const isBitcoinEnabled = useConfigBitcoinEnabled(); - const network = useCurrentNetwork(); - - const currentAccountIndex = useCurrentAccountIndex(); - - const properIndex = 'current' in props ? currentAccountIndex : props.index; - - const signer = useTaprootSigner(properIndex, network.chain.bitcoin.bitcoinNetwork); - - if (!signer || !isBitcoinEnabled) return null; - return children(signer(0)); -} diff --git a/src/app/components/balance-btc.tsx b/src/app/components/balance-btc.tsx deleted file mode 100644 index d8f0c498faa..00000000000 --- a/src/app/components/balance-btc.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { formatMoney } from '@app/common/money/format-money'; -import { Caption } from '@app/ui/components/typography/caption'; - -import { BitcoinNativeSegwitAccountLoader } from './account/bitcoin-account-loader'; -import { BitcoinBalanceLoader } from './balance/bitcoin-balance-loader'; - -export function BtcBalance() { - return ( - - {signer => ( - - {balance => {formatMoney(balance.balance)}} - - )} - - ); -} diff --git a/src/app/components/balance-stx.tsx b/src/app/components/balance-stx.tsx deleted file mode 100644 index bf72d248603..00000000000 --- a/src/app/components/balance-stx.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useMemo } from 'react'; - -import { stacksValue } from '@app/common/stacks-utils'; -import { useStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; -import { Caption } from '@app/ui/components/typography/caption'; - -interface BalanceProps { - address: string; -} -export function StxBalance(props: BalanceProps) { - const { address } = props; - const { data: balances } = useStacksAccountBalances(address); - - const balance = useMemo( - () => - stacksValue({ - value: balances?.stx?.unlockedStx.amount ?? 0, - withTicker: true, - }), - [balances] - ); - - return {balance}; -} diff --git a/src/app/components/balance/bitcoin-balance-loader.tsx b/src/app/components/balance/bitcoin-balance-loader.tsx deleted file mode 100644 index b3e2f5b4308..00000000000 --- a/src/app/components/balance/bitcoin-balance-loader.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useNativeSegwitBalance } from '@leather-wallet/query'; - -import { BitcoinCryptoCurrencyAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -interface BitcoinBalanceLoaderProps { - address: string; - children(balance: BitcoinCryptoCurrencyAssetBalance, isInitialLoading: boolean): React.ReactNode; -} - -export function BitcoinBalanceLoader({ address, children }: BitcoinBalanceLoaderProps) { - const { btcBalance, isInitialLoading } = useNativeSegwitBalance(address); - return children(btcBalance, isInitialLoading); -} diff --git a/src/app/components/balance/btc-balance.tsx b/src/app/components/balance/btc-balance.tsx new file mode 100644 index 00000000000..a35c5af4ec3 --- /dev/null +++ b/src/app/components/balance/btc-balance.tsx @@ -0,0 +1,17 @@ +import { formatMoney } from '@app/common/money/format-money'; +import { Caption } from '@app/ui/components/typography/caption'; + +import { BitcoinNativeSegwitAccountLoader } from '../loaders/bitcoin-account-loader'; +import { BtcCryptoAssetLoader } from '../loaders/btc-crypto-asset-loader'; + +export function BtcBalance() { + return ( + + {signer => ( + + {asset => {formatMoney(asset.balance.availableBalance)}} + + )} + + ); +} diff --git a/src/app/components/balance/stx-balance.tsx b/src/app/components/balance/stx-balance.tsx new file mode 100644 index 00000000000..9ea9e4e826d --- /dev/null +++ b/src/app/components/balance/stx-balance.tsx @@ -0,0 +1,24 @@ +import { useMemo } from 'react'; + +import { stacksValue } from '@app/common/stacks-utils'; +import { useStxCryptoAssetBalance } from '@app/query/stacks/balance/account-balance.hooks'; +import { Caption } from '@app/ui/components/typography/caption'; + +interface StxBalanceProps { + address: string; +} +export function StxBalance(props: StxBalanceProps) { + const { address } = props; + const { data: balance } = useStxCryptoAssetBalance(address); + + const stxBalance = useMemo( + () => + stacksValue({ + value: balance?.unlockedBalance.amount ?? 0, + withTicker: true, + }), + [balance] + ); + + return {stxBalance}; +} diff --git a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx index ec548c7506f..280e10492aa 100644 --- a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx +++ b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx @@ -12,7 +12,7 @@ import { determineUtxosForSpendAll, } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; export const MAX_FEE_RATE_MULTIPLIER = 50; @@ -23,7 +23,7 @@ interface UseBitcoinCustomFeeArgs { } export function useBitcoinCustomFee({ amount, isSendingMax, recipients }: UseBitcoinCustomFeeArgs) { - const { balance } = useCurrentNativeSegwitAddressBalance(); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); const { data: utxos = [] } = useCurrentNativeSegwitUtxos(); const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); diff --git a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts index 056a12b8ed5..d34a97dc5e6 100644 --- a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts +++ b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts @@ -16,7 +16,7 @@ import { determineUtxosForSpend, determineUtxosForSpendAll, } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection'; -import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { FeesListItem } from './bitcoin-fees-list'; @@ -46,7 +46,7 @@ export function useBitcoinFeesList({ recipient, utxos, }: UseBitcoinFeesListArgs) { - const { balance } = useCurrentNativeSegwitAddressBalance(); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { data: feeRates, isLoading } = useAverageBitcoinFeeRates(); diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx b/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx similarity index 61% rename from src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx rename to src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx index 4362246859c..efe8e0bd245 100644 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx +++ b/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx @@ -2,8 +2,8 @@ import { ReactNode } from 'react'; import { Box, Flex, styled } from 'leather-styles/jsx'; -import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; - +import { spamFilter } from '@app/common/utils/spam-filter'; +import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; import { SkeletonLoader } from '@app/ui/components/skeleton-loader/skeleton-loader'; @@ -11,32 +11,35 @@ import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { Caption } from '@app/ui/components/typography/caption'; import { Pressable } from '@app/ui/pressable/pressable'; -import { parseCryptoCurrencyAssetBalance } from './crypto-currency-asset.utils'; +import { parseCryptoAssetBalance } from './crypto-asset-item.layout.utils'; -interface CryptoCurrencyAssetItemLayoutProps { +interface CryptoAssetItemLayoutProps { additionalBalanceInfo?: ReactNode; - additionalUsdBalanceInfo?: ReactNode; - address?: string; - assetBalance: AllCryptoCurrencyAssetBalances; + additionalBalanceInfoAsFiat?: ReactNode; + asset: AccountCryptoAssetWithDetails; + caption?: string; + fiatBalance?: string; icon: React.ReactNode; isLoading?: boolean; - onClick?(): void; + name: string; + onClick?(asset: AccountCryptoAssetWithDetails): void; rightElement?: React.ReactNode; - usdBalance?: string; } -export function CryptoCurrencyAssetItemLayout({ +export function CryptoAssetItemLayout({ additionalBalanceInfo, - additionalUsdBalanceInfo, - address = '', - assetBalance, + additionalBalanceInfoAsFiat, + asset, + caption, + fiatBalance, icon, + isLoading = false, + name, onClick, rightElement, - usdBalance, - isLoading = false, -}: CryptoCurrencyAssetItemLayoutProps) { - const { balance, dataTestId, formattedBalance, title } = - parseCryptoCurrencyAssetBalance(assetBalance); +}: CryptoAssetItemLayoutProps) { + const { dataTestId, formattedBalance } = parseCryptoAssetBalance(asset.balance); + const { availableBalance } = asset.balance; + const title = spamFilter(name); const titleRight = ( @@ -45,7 +48,7 @@ export function CryptoCurrencyAssetItemLayout({ ) : ( @@ -62,8 +65,8 @@ export function CryptoCurrencyAssetItemLayout({ - {balance.amount.toNumber() > 0 && address ? usdBalance : null} - {additionalUsdBalanceInfo} + {availableBalance.amount.toNumber() > 0 ? fiatBalance : null} + {additionalBalanceInfoAsFiat} @@ -77,7 +80,7 @@ export function CryptoCurrencyAssetItemLayout({ @@ -85,7 +88,7 @@ export function CryptoCurrencyAssetItemLayout({ if (isInteractive) return ( - + onClick(asset)} my="space.02"> {content} ); diff --git a/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts b/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts new file mode 100644 index 00000000000..6cc1c622b67 --- /dev/null +++ b/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts @@ -0,0 +1,21 @@ +import type { CryptoAssetBalance } from '@leather-wallet/models'; +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; + +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals } from '@app/common/stacks-utils'; + +export function parseCryptoAssetBalance(balance: CryptoAssetBalance) { + const { availableBalance } = balance; + + const amount = ftDecimals(availableBalance.amount, availableBalance.decimals); + const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( + '{symbol}', + availableBalance.symbol.toLowerCase() + ); + const formattedBalance = formatBalance(amount); + + return { + dataTestId, + formattedBalance, + }; +} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx deleted file mode 100644 index ff69369ca6c..00000000000 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { type Brc20Token } from '@leather-wallet/query'; -import BigNumber from 'bignumber.js'; -import { styled } from 'leather-styles/jsx'; - -import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; -import { formatBalance } from '@app/common/format-balance'; -import { convertAmountToBaseUnit } from '@app/common/money/calculate-money'; -import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; -import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { Pressable } from '@app/ui/pressable/pressable'; - -interface Brc20TokenAssetItemLayoutProps { - token: Brc20Token; - onClick?(): void; -} -export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetItemLayoutProps) { - const balanceAsString = convertAmountToBaseUnit(token.balance ?? new BigNumber(0)).toString(); - const formattedBalance = formatBalance(balanceAsString); - const balanceAsFiat = convertAssetBalanceToFiat(token); - - return ( - - } - titleLeft={token.tokenData.ticker} - captionLeft="BRC-20" - titleRight={ - - - {formattedBalance.value} - - - } - captionRight={balanceAsFiat} - /> - - ); -} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx deleted file mode 100644 index 377492c0939..00000000000 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { type Brc20Token, useNativeSegwitBalance } from '@leather-wallet/query'; -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Stack } from 'leather-styles/jsx'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; - -import { Brc20TokenAssetItemLayout } from './brc20-token-asset-item.layout'; - -interface Brc20TokenAssetListProps { - tokens: Brc20Token[]; - variant?: string; -} -export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProps) { - const navigate = useNavigate(); - const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); - const { btcBalance: btcCryptoCurrencyAssetBalance } = - useNativeSegwitBalance(currentAccountBtcAddress); - - const hasPositiveBtcBalanceForFees = - variant === 'send' && btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0); - - function navigateToBrc20SendForm(token: Brc20Token) { - const { balance, holderAddress, marketData, tokenData } = token; - navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', tokenData.ticker), { - state: { balance, ticker: tokenData.ticker, holderAddress, marketData }, - }); - } - - return ( - - {tokens.map(token => ( - navigateToBrc20SendForm(token) : undefined} - /> - ))} - - ); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx deleted file mode 100644 index 4e790a4e35f..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; - -import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; -import { CryptoCurrencyAssetIcon } from './crypto-currency-asset-icon'; -import { FungibleTokenAssetItem } from './fungible-token-asset-item'; - -interface CryptoAssetListItemProps { - assetBalance: AllTransferableCryptoAssetBalances; - onClick(): void; -} -export function CryptoAssetListItem(props: CryptoAssetListItemProps) { - const { assetBalance, onClick } = props; - const { blockchain, type } = assetBalance; - - switch (type) { - case 'crypto-currency': - return ( - } - onClick={onClick} - /> - ); - case 'fungible-token': - return ; - default: - return null; - } -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx deleted file mode 100644 index a6bdb402f4e..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Stack } from 'leather-styles/jsx'; - -import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; -import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; - -import { useWalletType } from '@app/common/use-wallet-type'; -import { BitcoinNativeSegwitAccountLoader } from '@app/components/account/bitcoin-account-loader'; -import { BitcoinBalanceLoader } from '@app/components/balance/bitcoin-balance-loader'; -import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; -import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader'; -import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; - -import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; -import { CryptoAssetListItem } from './crypto-asset-list-item'; - -interface CryptoAssetListProps { - cryptoAssetBalances: AllTransferableCryptoAssetBalances[]; - onItemClick(cryptoAssetBalance: AllTransferableCryptoAssetBalances): void; - variant: 'send' | 'fund'; -} -export function CryptoAssetList({ - cryptoAssetBalances, - onItemClick, - variant, -}: CryptoAssetListProps) { - const { whenWallet } = useWalletType(); - - return ( - - - {signer => ( - - {(balance, isLoading) => ( - } - onClick={() => onItemClick(balance)} - isLoading={isLoading} - /> - )} - - )} - - {cryptoAssetBalances.map(cryptoAssetBalance => ( - onItemClick(cryptoAssetBalance)} - assetBalance={cryptoAssetBalance} - key={ - cryptoAssetBalance.asset.name ?? - (cryptoAssetBalance.asset as StacksFungibleTokenAsset).contractAssetName - } - /> - ))} - {variant === 'send' && - whenWallet({ - software: ( - - {() => ( - - {brc20Tokens => } - - )} - - ), - ledger: null, - })} - - ); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-currency-asset-icon.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-currency-asset-icon.tsx deleted file mode 100644 index 340164184aa..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-currency-asset-icon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { Blockchains } from '@shared/models/blockchain.model'; - -import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; -import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; - -export function CryptoCurrencyAssetIcon(props: { blockchain: Blockchains }) { - switch (props.blockchain) { - case 'bitcoin': - return ; - case 'stacks': - return ; - default: - return <>; - } -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx deleted file mode 100644 index 7be8034c33e..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { FlexProps } from 'leather-styles/jsx'; - -import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -import { StacksFungibleTokenAssetItemLayout } from '../stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout'; - -interface FungibleTokenAssetItemProps extends FlexProps { - assetBalance: StacksFungibleTokenAssetBalance; - onClick(): void; -} -export function FungibleTokenAssetItem({ assetBalance, onClick }: FungibleTokenAssetItemProps) { - const { blockchain } = assetBalance; - - switch (blockchain) { - case 'stacks': - return ; - default: - return null; - } -} diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts deleted file mode 100644 index 041ca288ea5..00000000000 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; - -import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; - -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; -import { spamFilter } from '@app/common/utils/spam-filter'; - -export function parseCryptoCurrencyAssetBalance(assetBalance: AllCryptoCurrencyAssetBalances) { - const { asset, balance } = assetBalance; - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals) - : balance.amount.toString(); - const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( - '{symbol}', - balance.symbol.toLowerCase() - ); - const formattedBalance = formatBalance(amount); - const title = spamFilter(asset.name); - - return { - balance, - dataTestId, - formattedBalance, - title, - }; -} diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts deleted file mode 100644 index 78414d38f51..00000000000 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; - -import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; -import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; -import { formatContractId, getTicker } from '@app/common/utils'; -import { spamFilter } from '@app/common/utils/spam-filter'; -import { getAssetName } from '@app/ui/utils/get-asset-name'; - -export function parseStacksFungibleTokenAssetBalance( - assetBalance: StacksFungibleTokenAssetBalance -) { - const { asset, balance } = assetBalance; - const { contractAddress, contractAssetName, contractName, name, symbol } = asset; - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals || 0) - : balance.amount.toString(); - const avatar = `${formatContractId(contractAddress, contractName)}::${contractAssetName}`; - const dataTestId = - symbol && CryptoAssetSelectors.CryptoAssetListItem.replace('{symbol}', symbol.toLowerCase()); - const formattedBalance = formatBalance(amount); - const friendlyName = - name || - (contractAssetName.includes('::') ? getAssetName(contractAssetName) : contractAssetName); - const imageCanonicalUri = getImageCanonicalUri(asset.imageCanonicalUri, asset.name); - const caption = symbol || getTicker(friendlyName); - const title = spamFilter(friendlyName); - const balanceAsFiat = convertAssetBalanceToFiat({ - ...assetBalance.asset, - balance: assetBalance.balance, - }); - - return { - amount, - avatar, - balanceAsFiat, - caption, - dataTestId, - formattedBalance, - imageCanonicalUri, - title, - }; -} diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx deleted file mode 100644 index cf66b4cced8..00000000000 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { styled } from 'leather-styles/jsx'; - -import { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; -import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { Pressable } from '@app/ui/pressable/pressable'; - -import { parseStacksFungibleTokenAssetBalance } from './fungible-token-asset.utils'; - -interface StacksFungibleTokenAssetItemLayoutProps { - assetBalance: StacksFungibleTokenAssetBalance; - onClick?(): void; -} -export function StacksFungibleTokenAssetItemLayout({ - assetBalance, - onClick, -}: StacksFungibleTokenAssetItemLayoutProps) { - const { - amount, - avatar, - balanceAsFiat, - caption, - dataTestId, - formattedBalance, - imageCanonicalUri, - title, - } = parseStacksFungibleTokenAssetBalance(assetBalance); - - return ( - - - {title[0]} - - } - titleLeft={title} - captionLeft={caption} - titleRight={ - - - {formattedBalance.value} - - - } - captionRight={balanceAsFiat} - /> - - ); -} diff --git a/src/app/components/loaders/bitcoin-account-loader.tsx b/src/app/components/loaders/bitcoin-account-loader.tsx index d97252f68a2..42fdf1e9de0 100644 --- a/src/app/components/loaders/bitcoin-account-loader.tsx +++ b/src/app/components/loaders/bitcoin-account-loader.tsx @@ -1,17 +1,52 @@ -import type { P2Ret, P2TROut } from '@scure/btc-signer'; +import { P2Ret } from '@scure/btc-signer'; -import { ZERO_INDEX } from '@shared/constants'; +import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; +import { useCurrentAccountIndex } from '@app/store/accounts/account'; +import { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer'; +import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; +import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer'; -import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; +interface BitcoinAccountLoaderBaseProps { + children(account: Signer): React.ReactNode; + fallback?: React.ReactNode; +} +interface BtcAccountLoaderCurrentProps extends BitcoinAccountLoaderBaseProps { + current: true; +} +interface BtcAccountLoaderIndexProps extends BitcoinAccountLoaderBaseProps { + index: number; +} + +type BtcAccountLoaderProps = BtcAccountLoaderCurrentProps | BtcAccountLoaderIndexProps; + +export function BitcoinNativeSegwitAccountLoader({ + children, + fallback, + ...props +}: BtcAccountLoaderProps) { + const isBitcoinEnabled = useConfigBitcoinEnabled(); + + const currentAccountIndex = useCurrentAccountIndex(); + + const properIndex = 'current' in props ? currentAccountIndex : props.index; -interface CurrentBitcoinSignerLoaderProps { - children(data: { nativeSegwit: Signer; taproot: Signer }): React.ReactNode; + const signer = useNativeSegwitSigner(properIndex); + + if (!signer || !isBitcoinEnabled) return fallback; + return children(signer(0)); } -export function CurrentBitcoinSignerLoader({ children }: CurrentBitcoinSignerLoaderProps) { - const nativeSegwit = useCurrentAccountNativeSegwitSigner()?.(ZERO_INDEX); - const taproot = useCurrentAccountTaprootSigner()?.(ZERO_INDEX); - if (!taproot || !nativeSegwit) return null; - return children({ nativeSegwit, taproot }); + +export function BitcoinTaprootAccountLoader({ children, ...props }: BtcAccountLoaderProps) { + const isBitcoinEnabled = useConfigBitcoinEnabled(); + const network = useCurrentNetwork(); + + const currentAccountIndex = useCurrentAccountIndex(); + + const properIndex = 'current' in props ? currentAccountIndex : props.index; + + const signer = useTaprootSigner(properIndex, network.chain.bitcoin.bitcoinNetwork); + + if (!signer || !isBitcoinEnabled) return null; + return children(signer(0)); } diff --git a/src/app/components/loaders/brc20-tokens-loader.tsx b/src/app/components/loaders/brc20-tokens-loader.tsx index 64fa754a1f8..b7f152d2e61 100644 --- a/src/app/components/loaders/brc20-tokens-loader.tsx +++ b/src/app/components/loaders/brc20-tokens-loader.tsx @@ -1,17 +1,10 @@ -import { type Brc20Token, useBrc20Tokens } from '@leather-wallet/query'; - -import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; +import { useBrc20AccountCryptoAssetsWithDetails } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; +import type { Brc20AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; interface Brc20TokensLoaderProps { - children(tokens: Brc20Token[]): React.ReactNode; + children(tokens: Brc20AccountCryptoAssetWithDetails[]): React.ReactNode; } export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) { - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - const createTaprootSigner = useCurrentAccountTaprootSigner(); - const tokens = useBrc20Tokens({ - createTaprootSigner, - nativeSegwitAddress: nativeSegwitSigner.address, - }); + const tokens = useBrc20AccountCryptoAssetsWithDetails(); return children(tokens); } diff --git a/src/app/components/loaders/btc-crypto-asset-loader.tsx b/src/app/components/loaders/btc-crypto-asset-loader.tsx new file mode 100644 index 00000000000..3ed06ddb8bf --- /dev/null +++ b/src/app/components/loaders/btc-crypto-asset-loader.tsx @@ -0,0 +1,11 @@ +import { useBtcAccountCryptoAssetWithDetails } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks'; +import type { BtcAccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; + +interface BtcCryptoAssetLoaderProps { + address: string; + children(asset: BtcAccountCryptoAssetWithDetails, isInitialLoading: boolean): React.ReactNode; +} +export function BtcCryptoAssetLoader({ address, children }: BtcCryptoAssetLoaderProps) { + const { asset, isInitialLoading } = useBtcAccountCryptoAssetWithDetails(address); + return children(asset, isInitialLoading); +} diff --git a/src/app/components/loaders/current-bitcoin-signer-loader.tsx b/src/app/components/loaders/current-bitcoin-signer-loader.tsx new file mode 100644 index 00000000000..d97252f68a2 --- /dev/null +++ b/src/app/components/loaders/current-bitcoin-signer-loader.tsx @@ -0,0 +1,17 @@ +import type { P2Ret, P2TROut } from '@scure/btc-signer'; + +import { ZERO_INDEX } from '@shared/constants'; + +import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer'; +import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; + +interface CurrentBitcoinSignerLoaderProps { + children(data: { nativeSegwit: Signer; taproot: Signer }): React.ReactNode; +} +export function CurrentBitcoinSignerLoader({ children }: CurrentBitcoinSignerLoaderProps) { + const nativeSegwit = useCurrentAccountNativeSegwitSigner()?.(ZERO_INDEX); + const taproot = useCurrentAccountTaprootSigner()?.(ZERO_INDEX); + if (!taproot || !nativeSegwit) return null; + return children({ nativeSegwit, taproot }); +} diff --git a/src/app/components/loaders/stx-crypto-asset-loader.tsx b/src/app/components/loaders/stx-crypto-asset-loader.tsx new file mode 100644 index 00000000000..72824eb4770 --- /dev/null +++ b/src/app/components/loaders/stx-crypto-asset-loader.tsx @@ -0,0 +1,11 @@ +import type { StxAccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { useStxAccountCryptoAssetWithDetails } from '@app/query/stacks/stx/stx-crypto-asset.hooks'; + +interface StxCryptoAssetLoaderProps { + address: string; + children(asset: StxAccountCryptoAssetWithDetails, isInitialLoading: boolean): React.ReactNode; +} +export function StxCryptoAssetLoader({ address, children }: StxCryptoAssetLoaderProps) { + const { asset, isInitialLoading } = useStxAccountCryptoAssetWithDetails(address); + return children(asset, isInitialLoading); +} diff --git a/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx b/src/app/components/stacks-asset-avatar.tsx similarity index 100% rename from src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx rename to src/app/components/stacks-asset-avatar.tsx diff --git a/src/app/components/transaction/token-transfer-icon.tsx b/src/app/components/transaction/token-transfer-icon.tsx index 3785f97c668..bcb1375623b 100644 --- a/src/app/components/transaction/token-transfer-icon.tsx +++ b/src/app/components/transaction/token-transfer-icon.tsx @@ -1,12 +1,12 @@ import { StacksTx } from '@shared/models/transactions/stacks-transaction.model'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ArrowDownIcon } from '@app/ui/icons/arrow-down-icon'; import { ArrowUpIcon } from '@app/ui/icons/arrow-up-icon'; export function TokenTransferIcon(props: { tx: StacksTx }) { const { tx } = props; - const currentAccountStxAddress = useCurrentAccountStxAddressState(); + const currentAccountStxAddress = useCurrentStacksAccountAddress(); const isSent = tx.sender_address === currentAccountStxAddress; if (isSent) return ; diff --git a/src/app/components/tx-asset-item.tsx b/src/app/components/tx-asset-item.tsx index dfc6602366b..13a06f2f713 100644 --- a/src/app/components/tx-asset-item.tsx +++ b/src/app/components/tx-asset-item.tsx @@ -2,7 +2,7 @@ import { HStack, HstackProps, styled } from 'leather-styles/jsx'; import { isValidUrl } from '@shared/utils/validate-url'; -import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; +import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar'; interface TxAssetItemProps extends HstackProps { iconString: string; diff --git a/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx b/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx index 592017f8c93..e9413e82f4b 100644 --- a/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx +++ b/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx @@ -6,16 +6,16 @@ import { TxTransferDetails, } from '@shared/models/transactions/stacks-transaction.model'; -import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; +import { getSafeImageCanonicalUri } from '@app/common/stacks-utils'; import { calculateTokenTransferAmount, getTxCaption, } from '@app/common/transactions/stacks/transaction.utils'; import { pullContractIdFromIdentity } from '@app/common/utils'; -import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; +import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; -import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; -import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; +import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query'; +import { isFtAsset } from '@app/query/stacks/token-metadata/token-metadata.utils'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ArrowDownIcon } from '@app/ui/icons/arrow-down-icon'; import { ArrowUpIcon } from '@app/ui/icons/arrow-up-icon'; @@ -48,7 +48,7 @@ export function FtTransferItem({ ftTransfer, parentTx }: FtTransferItemProps) { const ftImageCanonicalUri = assetMetadata.image_canonical_uri && assetMetadata.name && - getImageCanonicalUri(assetMetadata.image_canonical_uri, assetMetadata.name); + getSafeImageCanonicalUri(assetMetadata.image_canonical_uri, assetMetadata.name); const icon = isOriginator ? : ; const title = `${assetMetadata.name || 'Token'} Transfer`; const value = `${isOriginator ? '-' : ''}${displayAmount.toFormat()}`; diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index 87a5be7445d..570f48e8161 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -1,90 +1,104 @@ -import { Outlet } from 'react-router-dom'; - -import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { Stack } from 'leather-styles/jsx'; -import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; import { useWalletType } from '@app/common/use-wallet-type'; +import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point'; import { BitcoinNativeSegwitAccountLoader, BitcoinTaprootAccountLoader, -} from '@app/components/account/bitcoin-account-loader'; -import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point'; -import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; -import { RunesAssetList } from '@app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list'; -import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list'; -import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; -import { Stx20TokenAssetList } from '@app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list'; +} from '@app/components/loaders/bitcoin-account-loader'; import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader'; +import { BtcCryptoAssetLoader } from '@app/components/loaders/btc-crypto-asset-loader'; import { RunesLoader } from '@app/components/loaders/runes-loader'; import { Src20TokensLoader } from '@app/components/loaders/src20-tokens-loader'; import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; import { Stx20TokensLoader } from '@app/components/loaders/stx20-tokens-loader'; -import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { StxCryptoAssetLoader } from '@app/components/loaders/stx-crypto-asset-loader'; +import { Brc20TokenAssetList } from '@app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; +import { RunesAssetList } from '@app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list'; +import { Src20TokenAssetList } from '@app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list'; +import { Stx20TokenAssetList } from '@app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list'; +import { StxCryptoAssetItem } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item'; +import { StxCryptoAssetItemFallback } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback'; +import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; - -import { Collectibles } from '../collectibles/collectibles'; -import { PendingBrc20TransferList } from '../pending-brc-20-transfers/pending-brc-20-transfers'; -import { AddStacksLedgerKeysItem } from './components/add-stacks-ledger-keys-item'; -import { ConnectLedgerAssetBtn } from './components/connect-ledger-asset-button'; -import { StacksBalanceListItem } from './components/stacks-balance-list-item'; -import { StacksFungibleTokenAssetList } from './components/stacks-fungible-token-asset-list'; -import { StacksUnsupportedTokenAssetList } from './components/stacks-unsupported-token-asset-list'; -export function AssetsList() { - const hasBitcoinLedgerKeys = useHasBitcoinLedgerKeychain(); - const bitcoinAddressNativeSegwit = useCurrentAccountNativeSegwitAddressIndexZero(); - const network = useCurrentNetwork(); +import { BtcCryptoAssetItem } from './bitcoin/btc-crypto-asset-item/btc-crypto-asset-item'; +import { BtcCryptoAssetItemFallback } from './bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback'; +import { Sip10TokenAssetList } from './stacks/sip10-token-asset-list/sip10-token-asset-list'; +import { Sip10TokenAssetListUnsupported } from './stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported'; - const { btcAvailableAssetBalance, btcAvailableUsdBalance, isInitialLoading } = useBtcAssetBalance( - bitcoinAddressNativeSegwit - ); +export type AssetListVariant = 'interactive' | 'read-only'; +interface AssetListProps { + onClick?(asset: AccountCryptoAssetWithDetails): void; + variant?: AssetListVariant; +} +export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) { + const network = useCurrentNetwork(); const { whenWallet } = useWalletType(); + const isReadOnly = variant === 'read-only'; + return ( - + {whenWallet({ software: ( - } - address={bitcoinAddressNativeSegwit} - isLoading={isInitialLoading} - /> + + {nativeSegwitAccount => ( + + {(asset, isInitialLoading) => ( + + )} + + )} + ), ledger: ( - } - address={bitcoinAddressNativeSegwit} - isLoading={isInitialLoading} - rightElement={ - hasBitcoinLedgerKeys ? undefined : - } - /> + } + > + {nativeSegwitAccount => ( + + {(asset, isInitialLoading) => ( + + )} + + )} + ), })} {/* Temporary duplication during Ledger Bitcoin feature dev */} - {['testnet', 'regtest'].includes(network.chain.bitcoin.bitcoinNetwork) && + {isReadOnly && + ['testnet', 'regtest'].includes(network.chain.bitcoin.bitcoinNetwork) && whenWallet({ software: , ledger: null, })} - }> + }> {account => ( <> - - - - {tokens => } - + + {(asset, isInitialLoading) => ( + + )} + + + {isReadOnly && ( + + {tokens => } + + )} )} @@ -95,28 +109,29 @@ export function AssetsList() { {taprootAccount => ( <> - {tokens => } + {tokens => } - - {tokens => } - - - {runes => } - + {isReadOnly && ( + <> + + {tokens => } + + + {runes => } + + + )} )} )} - - {account => } - - - - - - + {isReadOnly && ( + + {account => } + + )} ); } diff --git a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx new file mode 100644 index 00000000000..875048d35af --- /dev/null +++ b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -0,0 +1,47 @@ +import { useNavigate } from 'react-router-dom'; + +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; +import { Stack } from 'leather-styles/jsx'; + +import { RouteUrls } from '@shared/route-urls'; + +import { capitalize } from '@app/common/utils'; +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import type { AssetListVariant } from '@app/features/asset-list/asset-list'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; +import type { Brc20AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; + +interface Brc20TokenAssetListProps { + assets: Brc20AccountCryptoAssetWithDetails[]; + variant?: AssetListVariant; +} +export function Brc20TokenAssetList({ assets, variant }: Brc20TokenAssetListProps) { + const navigate = useNavigate(); + const { balance, isInitialLoading } = useCurrentBtcAvailableBalanceNativeSegwit(); + + const hasPositiveBtcBalanceForFees = variant === 'interactive' && balance.amount.isGreaterThan(0); + + function navigateToBrc20SendForm(asset: Brc20AccountCryptoAssetWithDetails) { + const { balance, holderAddress, info, marketData } = asset; + navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', info.symbol), { + state: { balance: balance.availableBalance, holderAddress, marketData, ticker: info.symbol }, + }); + } + + return ( + + {assets.map(asset => ( + } + isLoading={isInitialLoading} + key={asset.info.symbol} + name={asset.info.symbol} + onClick={hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(asset) : undefined} + /> + ))} + + ); +} diff --git a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx new file mode 100644 index 00000000000..f7e7c71e797 --- /dev/null +++ b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx @@ -0,0 +1,23 @@ +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import { btcCryptoAssetPlaceholder } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks'; +import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; + +import type { AssetListVariant } from '../../asset-list'; +import { ConnectLedgerButton } from '../../components/connect-ledger-asset-button'; + +interface StxCryptoAssetItemFallbackProps { + variant: AssetListVariant; +} +export function BtcCryptoAssetItemFallback({ variant }: StxCryptoAssetItemFallbackProps) { + const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable(); + if (variant === 'interactive' && !checkBlockchainAvailable('bitcoin')) return null; + return ( + } + name={btcCryptoAssetPlaceholder.info.name} + rightElement={} + /> + ); +} diff --git a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx new file mode 100644 index 00000000000..e84e502d0ef --- /dev/null +++ b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx @@ -0,0 +1,41 @@ +import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query'; + +import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; +import { i18nFormatCurrency } from '@app/common/money/format-money'; +import { capitalize } from '@app/common/utils'; +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import type { + AccountCryptoAssetWithDetails, + BtcAccountCryptoAssetWithDetails, +} from '@app/query/models/crypto-asset.model'; +import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; + +interface BtcCryptoAssetItemProps { + asset: BtcAccountCryptoAssetWithDetails; + isLoading: boolean; + onClick?(asset: AccountCryptoAssetWithDetails): void; + rightElement?: React.ReactNode; +} +export function BtcCryptoAssetItem({ + asset, + isLoading, + onClick, + rightElement, +}: BtcCryptoAssetItemProps) { + const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); + const availableBalanceAsFiat = i18nFormatCurrency( + baseCurrencyAmountInQuote(asset.balance.availableBalance, marketData) + ); + + return ( + } + isLoading={isLoading} + name={capitalize(asset.info.name)} + onClick={onClick} + rightElement={rightElement} + /> + ); +} diff --git a/src/app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-item.layout.tsx b/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-item.layout.tsx similarity index 100% rename from src/app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-item.layout.tsx rename to src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-item.layout.tsx diff --git a/src/app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list.tsx b/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx similarity index 100% rename from src/app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list.tsx rename to src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx diff --git a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx b/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx similarity index 100% rename from src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx rename to src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx diff --git a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx similarity index 100% rename from src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx rename to src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx diff --git a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx b/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx deleted file mode 100644 index 2309e849ab5..00000000000 --- a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import BigNumber from 'bignumber.js'; - -import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; -import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; -import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; - -import { ConnectLedgerAssetBtn } from './connect-ledger-asset-button'; - -export function AddStacksLedgerKeysItem() { - return ( - } - rightElement={} - /> - ); -} diff --git a/src/app/features/asset-list/components/connect-ledger-asset-button.tsx b/src/app/features/asset-list/components/connect-ledger-asset-button.tsx index bc08367fa05..9e596ba057a 100644 --- a/src/app/features/asset-list/components/connect-ledger-asset-button.tsx +++ b/src/app/features/asset-list/components/connect-ledger-asset-button.tsx @@ -1,8 +1,8 @@ import { useNavigate } from 'react-router-dom'; +import type { Blockchains } from '@leather-wallet/models'; import { styled } from 'leather-styles/jsx'; -import { SupportedBlockchains } from '@shared/constants'; import { RouteUrls } from '@shared/route-urls'; import { capitalize } from '@app/common/utils'; @@ -10,10 +10,10 @@ import { immediatelyAttemptLedgerConnection } from '@app/features/ledger/hooks/u import { Button } from '@app/ui/components/button/button'; import { LedgerIcon } from '@app/ui/icons/ledger-icon'; -interface ConnectLedgerAssetBtnProps { - chain: SupportedBlockchains; +interface ConnectLedgerButtonProps { + chain: Blockchains; } -export function ConnectLedgerAssetBtn({ chain }: ConnectLedgerAssetBtnProps) { +export function ConnectLedgerButton({ chain }: ConnectLedgerButtonProps) { const navigate = useNavigate(); const onClick = () => { diff --git a/src/app/features/asset-list/components/stacks-balance-list-item.layout.stories.tsx b/src/app/features/asset-list/components/stacks-balance-list-item.layout.stories.tsx deleted file mode 100644 index 32e8341b0b4..00000000000 --- a/src/app/features/asset-list/components/stacks-balance-list-item.layout.stories.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { TooltipProvider } from '@radix-ui/react-tooltip'; -import { Meta, StoryObj } from '@storybook/react'; -import BigNumber from 'bignumber.js'; - -import { StacksBalanceListItemLayout } from './stacks-balance-list-item.layout'; - -const meta: Meta = { - component: StacksBalanceListItemLayout, - tags: ['autodocs'], - title: 'Feature/StacksBalanceListItem', - argTypes: {}, - parameters: {}, - decorators: [ - Story => ( - - - - ), - ], -}; - -export default meta; - -type Story = StoryObj; - -const symbol = 'STX'; - -export const StacksBalanceItem: Story = { - args: { - address: 'ST1PQHQKV0YX2K1Z0V2VQZGZGZGZGZGZGZGZGZGZG', - stxEffectiveBalance: { - balance: { decimals: 8, amount: new BigNumber(100000000000), symbol }, - blockchain: 'stacks', - type: 'crypto-currency', - asset: { - decimals: 8, - hasMemo: true, - name: 'Stacks', - symbol, - } as const, - }, - stxEffectiveUsdBalance: '$100,000', - }, -}; - -export const StacksBalanceItemWithLockedBalance: Story = { - args: { - address: 'ST1PQHQKV0YX2K1Z0V2VQZGZGZGZGZGZGZGZGZGZG', - stxEffectiveBalance: { - balance: { decimals: 8, amount: new BigNumber(100000000000), symbol }, - blockchain: 'stacks', - type: 'crypto-currency', - asset: { - decimals: 8, - hasMemo: true, - name: 'Stacks', - symbol, - } as const, - }, - stxEffectiveUsdBalance: '$100,000', - stxUsdLockedBalance: '$1,000', - stxLockedBalance: { decimals: 8, amount: new BigNumber(1000000000), symbol }, - }, -}; diff --git a/src/app/features/asset-list/components/stacks-balance-list-item.layout.tsx b/src/app/features/asset-list/components/stacks-balance-list-item.layout.tsx deleted file mode 100644 index 25ab3a29254..00000000000 --- a/src/app/features/asset-list/components/stacks-balance-list-item.layout.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { styled } from 'leather-styles/jsx'; - -import type { StacksCryptoCurrencyAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import type { Money } from '@shared/models/money.model'; - -import { ftDecimals } from '@app/common/stacks-utils'; -import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; -import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; -import { BulletOperator } from '@app/ui/components/bullet-separator/bullet-separator'; -import { Caption } from '@app/ui/components/typography/caption'; - -interface StacksBalanceListItemLayoutProps { - address: string; - stxEffectiveBalance: StacksCryptoCurrencyAssetBalance; - stxEffectiveUsdBalance?: string; - stxLockedBalance?: Money; - stxUsdLockedBalance?: string; - isInitialLoading?: boolean; -} -export function StacksBalanceListItemLayout(props: StacksBalanceListItemLayoutProps) { - const { - address, - stxEffectiveBalance, - stxEffectiveUsdBalance, - stxLockedBalance, - stxUsdLockedBalance, - isInitialLoading, - } = props; - - const stxAdditionalBalanceInfo = stxLockedBalance?.amount.isGreaterThan(0) ? ( - - - {ftDecimals(stxLockedBalance.amount, stxLockedBalance.decimals || 0)} locked - - ) : undefined; - - const stxAdditionalUsdBalanceInfo = stxLockedBalance?.amount.isGreaterThan(0) ? ( - {stxUsdLockedBalance} locked - ) : undefined; - - return ( - } - isLoading={isInitialLoading} - /> - ); -} diff --git a/src/app/features/asset-list/components/stacks-balance-list-item.tsx b/src/app/features/asset-list/components/stacks-balance-list-item.tsx deleted file mode 100644 index bab8510c5b7..00000000000 --- a/src/app/features/asset-list/components/stacks-balance-list-item.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; - -import { StacksBalanceListItemLayout } from './stacks-balance-list-item.layout'; - -interface StacksBalanceListItemProps { - address: string; -} -export function StacksBalanceListItem({ address }: StacksBalanceListItemProps) { - const balanceDetails = useStxBalance(); - return ( - - ); -} diff --git a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.layout.tsx b/src/app/features/asset-list/components/stacks-fungible-token-asset-list.layout.tsx deleted file mode 100644 index a13708304f5..00000000000 --- a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.layout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Stack } from 'leather-styles/jsx'; - -import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -import { StacksFungibleTokenAssetItemLayout } from '@app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout'; - -interface StacksFungibleTokenAssetListLayoutProps { - assetBalances: StacksFungibleTokenAssetBalance[]; -} -export function StacksFungibleTokenAssetListLayout({ - assetBalances, -}: StacksFungibleTokenAssetListLayoutProps) { - if (assetBalances.length === 0) return null; - return ( - - {assetBalances.map(assetBalance => ( - - ))} - - ); -} diff --git a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx b/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx deleted file mode 100644 index 99e82df34a2..00000000000 --- a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useFilteredStacksFungibleTokenList } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; - -import { StacksFungibleTokenAssetListLayout } from './stacks-fungible-token-asset-list.layout'; - -interface StacksFungibleTokenAssetListProps { - address: string; -} -export function StacksFungibleTokenAssetList({ address }: StacksFungibleTokenAssetListProps) { - const stacksFilteredFtAssetBalances = useFilteredStacksFungibleTokenList({ - address, - filter: 'supported', - }); - - return ; -} diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx new file mode 100644 index 00000000000..e7b421e56ac --- /dev/null +++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx @@ -0,0 +1,36 @@ +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar'; +import type { + AccountCryptoAssetWithDetails, + Sip10AccountCryptoAssetWithDetails, +} from '@app/query/models/crypto-asset.model'; + +import { parseSip10TokenCryptoAssetBalance } from './sip10-token-asset-item.utils'; + +interface Sip10TokenAssetItemProps { + asset: Sip10AccountCryptoAssetWithDetails; + onClick?(asset: AccountCryptoAssetWithDetails): void; +} +export function Sip10TokenAssetItem({ asset, onClick }: Sip10TokenAssetItemProps) { + const { avatar, fiatBalance, imageCanonicalUri, title } = + parseSip10TokenCryptoAssetBalance(asset); + + return ( + + {title[0]} + + } + name={asset.info.name} + onClick={onClick} + /> + ); +} diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts new file mode 100644 index 00000000000..e1b09175109 --- /dev/null +++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts @@ -0,0 +1,34 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; + +import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals, getSafeImageCanonicalUri } from '@app/common/stacks-utils'; +import { spamFilter } from '@app/common/utils/spam-filter'; +import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; + +export function parseSip10TokenCryptoAssetBalance(asset: Sip10AccountCryptoAssetWithDetails) { + const { balance, info } = asset; + const { contractId, decimals, imageCanonicalUri, name, symbol } = info; + + const amount = ftDecimals(balance.availableBalance.amount, decimals); + const avatar = contractId; + const dataTestId = + symbol && CryptoAssetSelectors.CryptoAssetListItem.replace('{symbol}', symbol.toLowerCase()); + const formattedBalance = formatBalance(amount); + const safeImageCanonicalUri = getSafeImageCanonicalUri(imageCanonicalUri, name); + const title = spamFilter(name); + const fiatBalance = convertAssetBalanceToFiat({ + ...asset, + balance: asset.balance.availableBalance, + }); + + return { + amount, + avatar, + fiatBalance, + dataTestId, + formattedBalance, + imageCanonicalUri: safeImageCanonicalUri, + title, + }; +} diff --git a/src/app/features/asset-list/components/stacks-unsupported-token-asset-list.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx similarity index 56% rename from src/app/features/asset-list/components/stacks-unsupported-token-asset-list.tsx rename to src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx index ce10286792a..72b52332198 100644 --- a/src/app/features/asset-list/components/stacks-unsupported-token-asset-list.tsx +++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx @@ -1,28 +1,26 @@ import { useState } from 'react'; -import { styled } from 'leather-styles/jsx'; +import { Stack, styled } from 'leather-styles/jsx'; -import { useFilteredStacksFungibleTokenList } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; +import { useFilteredSip10AccountCryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks'; import { Accordion } from '@app/ui/components/accordion/accordion'; -import { StacksFungibleTokenAssetListLayout } from './stacks-fungible-token-asset-list.layout'; +import { Sip10TokenAssetItem } from './sip10-token-asset-item'; const accordionValue = 'accordion-unsupported-token-asset-list'; -export function StacksUnsupportedTokenAssetList({ address }: { address: string }) { - const stacksFilteredFtAssetBalances = useFilteredStacksFungibleTokenList({ +export function Sip10TokenAssetListUnsupported({ address }: { address: string }) { + const [isOpen, setIsOpen] = useState(false); + const assets = useFilteredSip10AccountCryptoAssetsWithDetails({ address, filter: 'unsupported', }); - const [isOpen, setIsOpen] = useState(false); function onValueChange(value: string) { setIsOpen(value === accordionValue); } - if (stacksFilteredFtAssetBalances.length === 0) { - return null; - } + if (!assets.length) return null; return ( @@ -31,7 +29,11 @@ export function StacksUnsupportedTokenAssetList({ address }: { address: string } View {isOpen ? 'fewer' : 'more'} - + + {assets.map(asset => ( + + ))} + diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx new file mode 100644 index 00000000000..28e2b49c71e --- /dev/null +++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx @@ -0,0 +1,29 @@ +import { Stack } from 'leather-styles/jsx'; + +import { isDefined } from '@shared/utils'; + +import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { useFilteredSip10AccountCryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks'; + +import { Sip10TokenAssetItem } from './sip10-token-asset-item'; + +interface Sip10TokenAssetListProps { + address: string; + onClick?(asset: AccountCryptoAssetWithDetails): void; +} +export function Sip10TokenAssetList({ address, onClick }: Sip10TokenAssetListProps) { + const assets = useFilteredSip10AccountCryptoAssetsWithDetails({ + address, + filter: isDefined(onClick) ? 'all' : 'supported', + }); + + if (!assets.length) return null; + + return ( + + {assets.map(asset => ( + + ))} + + ); +} diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx new file mode 100644 index 00000000000..1eb4c256dcd --- /dev/null +++ b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx @@ -0,0 +1,23 @@ +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import { stxCryptoAssetPlaceholder } from '@app/query/stacks/stx/stx-crypto-asset.hooks'; +import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; + +import type { AssetListVariant } from '../../asset-list'; +import { ConnectLedgerButton } from '../../components/connect-ledger-asset-button'; + +interface StxCryptoAssetItemFallbackProps { + variant: AssetListVariant; +} +export function StxCryptoAssetItemFallback({ variant }: StxCryptoAssetItemFallbackProps) { + const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable(); + if (variant === 'interactive' && !checkBlockchainAvailable('stacks')) return null; + return ( + } + name={stxCryptoAssetPlaceholder.info.name} + rightElement={} + /> + ); +} diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx new file mode 100644 index 00000000000..7fe04fcfecb --- /dev/null +++ b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx @@ -0,0 +1,81 @@ +import { TooltipProvider } from '@radix-ui/react-tooltip'; +import { Meta, StoryObj } from '@storybook/react'; +import { QueryClientProvider } from '@tanstack/react-query'; +import BigNumber from 'bignumber.js'; + +import { queryClient } from '@app/common/persistence'; + +import { StxCryptoAssetItem as Component } from './stx-crypto-asset-item'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Feature/StxCryptoAssetItem', + argTypes: {}, + parameters: {}, + decorators: [ + Story => ( + + + + + + ), + ], +}; + +export default meta; + +type Story = StoryObj; + +const symbol = 'STX'; + +const stxCryptoAssetBalance = { + availableBalance: { amount: new BigNumber(100000000000), decimals: 6, symbol }, + availableUnlockedBalance: { amount: new BigNumber(100000000000), decimals: 6, symbol }, + inboundBalance: { amount: new BigNumber(0), decimals: 6, symbol }, + outboundBalance: { amount: new BigNumber(0), decimals: 6, symbol }, + pendingBalance: { amount: new BigNumber(0), decimals: 6, symbol }, + totalBalance: { amount: new BigNumber(0), decimals: 6, symbol }, + unlockedBalance: { amount: new BigNumber(0), decimals: 6, symbol }, +}; + +export const StxCryptoAssetItem: Story = { + args: { + asset: { + info: { + decimals: 6, + hasMemo: true, + name: 'stacks', + symbol: 'STX', + }, + balance: { + ...stxCryptoAssetBalance, + lockedBalance: { amount: new BigNumber(0), decimals: 6, symbol }, + }, + chain: 'stacks', + marketData: null, + type: 'stx', + }, + }, +}; + +export const StxCryptoAssetItemWithLockedBalance: Story = { + args: { + asset: { + info: { + decimals: 6, + hasMemo: true, + name: 'stacks', + symbol: 'STX', + }, + balance: { + ...stxCryptoAssetBalance, + lockedBalance: { amount: new BigNumber(1000000000), decimals: 6, symbol }, + }, + chain: 'stacks', + marketData: null, + type: 'stx', + }, + }, +}; diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx new file mode 100644 index 00000000000..7fd290bac83 --- /dev/null +++ b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx @@ -0,0 +1,54 @@ +import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query'; +import { styled } from 'leather-styles/jsx'; + +import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; +import { i18nFormatCurrency } from '@app/common/money/format-money'; +import { ftDecimals } from '@app/common/stacks-utils'; +import { capitalize } from '@app/common/utils'; +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import type { + AccountCryptoAssetWithDetails, + StxAccountCryptoAssetWithDetails, +} from '@app/query/models/crypto-asset.model'; +import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; +import { BulletOperator } from '@app/ui/components/bullet-separator/bullet-separator'; +import { Caption } from '@app/ui/components/typography/caption'; + +interface StxCryptoAssetItemProps { + asset: StxAccountCryptoAssetWithDetails; + isLoading: boolean; + onClick?(asset: AccountCryptoAssetWithDetails): void; +} +export function StxCryptoAssetItem({ asset, isLoading, onClick }: StxCryptoAssetItemProps) { + const marketData = useCryptoCurrencyMarketDataMeanAverage('STX'); + + const { availableBalance, lockedBalance } = asset.balance; + const showAdditionalInfo = lockedBalance.amount.isGreaterThan(0); + + const lockedBalanceAsFiat = i18nFormatCurrency( + baseCurrencyAmountInQuote(lockedBalance, marketData) + ); + const availableBalanceAsFiat = i18nFormatCurrency( + baseCurrencyAmountInQuote(availableBalance, marketData) + ); + const additionalBalanceInfo = ( + + + {ftDecimals(lockedBalance.amount, lockedBalance.decimals)} locked + + ); + const additionalBalanceInfoAsFiat = {lockedBalanceAsFiat} locked; + + return ( + } + isLoading={isLoading} + name={capitalize(asset.info.name)} + onClick={onClick} + /> + ); +} diff --git a/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx b/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx similarity index 100% rename from src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx rename to src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx diff --git a/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx b/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx similarity index 100% rename from src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx rename to src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx diff --git a/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx b/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx index 566c345e3c1..ca8d932c055 100644 --- a/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx +++ b/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; -import { useNativeSegwitBalance } from '@leather-wallet/query'; import { Box, FlexProps, Stack, styled } from 'leather-styles/jsx'; import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; @@ -11,7 +10,7 @@ import { formatMoney } from '@app/common/money/format-money'; import { BitcoinCustomFee } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee'; import { MAX_FEE_RATE_MULTIPLIER } from '@app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee'; import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; -import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance'; import { BitcoinChooseFeeLayout } from './components/bitcoin-choose-fee.layout'; @@ -48,8 +47,7 @@ export function BitcoinChooseFee({ maxRecommendedFeeRate = 0, ...rest }: BitcoinChooseFeeProps) { - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - const { btcBalance } = useNativeSegwitBalance(nativeSegwitSigner.address); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); const hasAmount = amount.amount.isGreaterThan(0); const [customFeeInitialValue, setCustomFeeInitialValue] = useState(recommendedFeeRate); @@ -88,7 +86,7 @@ export function BitcoinChooseFee({ feesList={feesList} /> - + diff --git a/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts b/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts index 58168c1641b..01a3764ae79 100644 --- a/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts +++ b/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts @@ -3,11 +3,11 @@ import { useState } from 'react'; import { Money, createMoney } from '@shared/models/money.model'; import { subtractMoney, sumMoney } from '@app/common/money/calculate-money'; -import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; export function useValidateBitcoinSpend(amount?: Money, isSendingMax?: boolean) { const [showInsufficientBalanceError, setShowInsufficientBalanceError] = useState(false); - const { balance } = useCurrentNativeSegwitAddressBalance(); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); return { showInsufficientBalanceError, diff --git a/src/app/features/collectibles/collectibles.tsx b/src/app/features/collectibles/collectibles.tsx index 4a134de63c1..e6a34ce1c2f 100644 --- a/src/app/features/collectibles/collectibles.tsx +++ b/src/app/features/collectibles/collectibles.tsx @@ -6,7 +6,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { RouteUrls } from '@shared/route-urls'; import { useWalletType } from '@app/common/use-wallet-type'; -import { CurrentBitcoinSignerLoader } from '@app/components/loaders/bitcoin-account-loader'; +import { CurrentBitcoinSignerLoader } from '@app/components/loaders/current-bitcoin-signer-loader'; import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; import { useConfigNftMetadataEnabled } from '@app/query/common/remote-config/remote-config.query'; diff --git a/src/app/features/collectibles/components/collectible.layout.tsx b/src/app/features/collectibles/components/collectible.layout.tsx index 4f400c1c309..460cd484f6d 100644 --- a/src/app/features/collectibles/components/collectible.layout.tsx +++ b/src/app/features/collectibles/components/collectible.layout.tsx @@ -23,7 +23,7 @@ export function CollectiblesLayout({ }: CollectiblesLayoutProps) { return ( <> - + {title} diff --git a/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx b/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx index ffbb6691fe7..265871ee7d5 100644 --- a/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx +++ b/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { parseIfValidPunycode } from '@app/common/utils'; import { useCurrentAccountNames } from '@app/query/stacks/bns/bns.hooks'; -import { useStacksNonFungibleTokensMetadata } from '@app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.hooks'; +import { useStacksNonFungibleTokensMetadata } from '@app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; import { StacksBnsName } from './stacks-bns-name'; diff --git a/src/app/features/container/total-balance.tsx b/src/app/features/container/total-balance.tsx index 2ca38223807..7070924fd0b 100644 --- a/src/app/features/container/total-balance.tsx +++ b/src/app/features/container/total-balance.tsx @@ -2,8 +2,8 @@ import { Suspense } from 'react'; import { Box, HStack } from 'leather-styles/jsx'; -import { BtcBalance } from '@app/components/balance-btc'; -import { StxBalance } from '@app/components/balance-stx'; +import { BtcBalance } from '@app/components/balance/btc-balance'; +import { StxBalance } from '@app/components/balance/stx-balance'; import { LoadingRectangle } from '@app/components/loading-rectangle'; import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; diff --git a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts index f203f1837da..daf458eb478 100644 --- a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts +++ b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts @@ -12,7 +12,6 @@ import { RouteUrls } from '@shared/route-urls'; import { isError } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; import { btcToSat } from '@app/common/money/unit-conversion'; import { queryClient } from '@app/common/persistence'; import { @@ -24,6 +23,7 @@ import { MAX_FEE_RATE_MULTIPLIER } from '@app/components/bitcoin-custom-fee/hook import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; import { useToast } from '@app/features/toasts/use-toast'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain'; import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; @@ -51,7 +51,7 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) { [btcTx.vin.length, btcTx.vout.length, recipient] ); - const { btcAvailableAssetBalance } = useBtcAssetBalance(currentBitcoinAddress); + const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress); const sendingAmount = getBitcoinTxValue(currentBitcoinAddress, btcTx); const { feesList } = useBitcoinFeesList({ amount: createMoney(btcToSat(sendingAmount), 'BTC'), @@ -163,7 +163,7 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) { } // check if fee is higher than the available balance - return bnValue.isLessThanOrEqualTo(btcAvailableAssetBalance.balance.amount); + return bnValue.isLessThanOrEqualTo(btcCryptoAssetBalance.availableBalance.amount); }, }), }); diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx index 5c5c71c623b..77236b4655a 100644 --- a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx @@ -8,7 +8,6 @@ import { createMoney } from '@shared/models/money.model'; import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; import { RouteUrls } from '@shared/route-urls'; -import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; import { formatMoney } from '@app/common/money/format-money'; import { btcToSat } from '@app/common/money/unit-conversion'; @@ -16,6 +15,7 @@ import { getBitcoinTxValue } from '@app/common/transactions/bitcoin/utils'; import { BitcoinCustomFeeInput } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee-input'; import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; import { LoadingSpinner } from '@app/components/loading-spinner'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { Dialog } from '@app/ui/components/containers/dialog/dialog'; import { Footer } from '@app/ui/components/containers/footers/footer'; @@ -34,11 +34,11 @@ export function IncreaseBtcFeeDialog() { const btcTx = tx; const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); const currentBitcoinAddress = nativeSegwitSigner.address; - const { btcAvailableAssetBalance } = useBtcAssetBalance(currentBitcoinAddress); + const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress); const { isBroadcasting, sizeInfo, onSubmit, validationSchema, recipient } = useBtcIncreaseFee(btcTx); - const balance = formatMoney(btcAvailableAssetBalance.balance); + const balance = formatMoney(btcCryptoAssetBalance.availableBalance); const recipients = [ { @@ -104,7 +104,7 @@ export function IncreaseBtcFeeDialog() { /> - {btcAvailableAssetBalance && Balance: {balance}} + {btcCryptoAssetBalance && Balance: {balance}} diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx index bd4291f0722..3142219c944 100644 --- a/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx @@ -9,7 +9,6 @@ import * as yup from 'yup'; import { RouteUrls } from '@shared/route-urls'; import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh-all-account-data'; -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion'; import { stacksValue } from '@app/common/stacks-utils'; @@ -19,7 +18,7 @@ import { LoadingSpinner } from '@app/components/loading-spinner'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; import { useToast } from '@app/features/toasts/use-toast'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; import { useRawDeserializedTxState, useRawTxIdState } from '@app/store/transactions/raw.hooks'; import { Dialog } from '@app/ui/components/containers/dialog/dialog'; @@ -43,8 +42,7 @@ export function IncreaseStxFeeDialog() { const refreshAccountData = useRefreshAllAccountData(); const tx = useSelectedTx(); const [, setTxId] = useRawTxIdState(); - const { data: balances } = useCurrentStacksAccountBalances(); - const { availableBalance } = useStxBalance(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); const submittedTransactionsActions = useSubmittedTransactionsActions(); const rawTx = useRawDeserializedTxState(); const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); @@ -81,7 +79,7 @@ export function IncreaseStxFeeDialog() { if (!tx || !fee) return ; - const validationSchema = yup.object({ fee: stxFeeValidator(availableBalance) }); + const validationSchema = yup.object({ fee: stxFeeValidator(availableUnlockedBalance) }); const onClose = () => { setRawTxId(null); @@ -132,10 +130,13 @@ export function IncreaseStxFeeDialog() { {tx && } - {balances?.stx.unlockedStx.amount && ( + {availableUnlockedBalance?.amount && ( Balance: - {stacksValue({ value: availableBalance.amount, fixedDecimals: true })} + {stacksValue({ + value: availableUnlockedBalance.amount, + fixedDecimals: true, + })} )} diff --git a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx index f612ffa45b9..61a0b368cd0 100644 --- a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx +++ b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx @@ -2,7 +2,6 @@ import { useNavigate } from 'react-router-dom'; import { fetchInscripionById, - useNativeSegwitBalance, useOrdinalsbotClient, } from '@leather-wallet/query'; import { Box, Flex, HStack, Stack } from 'leather-styles/jsx'; @@ -13,8 +12,9 @@ import { noop } from '@shared/utils'; import { usePressable } from '@app/components/item-hover'; import { StatusPending } from '@app/components/status-pending'; import { StatusReady } from '@app/components/status-ready'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { useCheckOrderStatuses } from '@app/query/bitcoin/ordinals/brc20/use-check-order-status'; -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; + import { OrdinalsbotInscriptionStatus, PendingBrc20Transfer, @@ -92,12 +92,9 @@ function PendingBrcTransfer({ order }: PendingBrcTransferProps) { const [component, bind] = usePressable(order.status === 'ready'); const navigate = useNavigate(); const ordinalsbotClient = useOrdinalsbotClient(); - const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); - const { btcBalance: btcCryptoCurrencyAssetBalance } = - useNativeSegwitBalance(currentAccountBtcAddress); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); - const hasPositiveBtcBalanceForFees = - btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0); + const hasPositiveBtcBalanceForFees = balance.amount.isGreaterThan(0); return ( - + This transaction may take upwards of 30 minutes to confirm. {children} diff --git a/src/app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit.tsx b/src/app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit.tsx index 624b34dd7b5..62f96076b2f 100644 --- a/src/app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit.tsx +++ b/src/app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit.tsx @@ -21,6 +21,7 @@ import { useCurrentAccountNativeSegwitIndexZeroSigner, } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentTaprootAccount } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; + import { Link } from '@app/ui/components/link/link'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; diff --git a/src/app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details.tsx b/src/app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details.tsx index 8530574c866..f795222bddd 100644 --- a/src/app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details.tsx +++ b/src/app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details.tsx @@ -7,8 +7,8 @@ import { AttachmentRow } from '@app/features/stacks-transaction-request/attachme import { ContractPreviewLayout } from '@app/features/stacks-transaction-request/contract-preview'; import { Row } from '@app/features/stacks-transaction-request/row'; import { - useCurrentAccountStxAddressState, useCurrentStacksAccount, + useCurrentStacksAccountAddress, } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; import { CodeBlock } from '@app/ui/components/codeblock'; @@ -18,7 +18,7 @@ function ContractCodeSection() { const transactionRequest = useTransactionRequestState(); const currentAccount = useCurrentStacksAccount(); - const currentAccountStxAddress = useCurrentAccountStxAddressState(); + const currentAccountStxAddress = useCurrentStacksAccountAddress(); if ( !transactionRequest || @@ -63,7 +63,7 @@ function TabButton(props: TabButtonProps) { export function ContractDeployDetails() { const transactionRequest = useTransactionRequestState(); const currentAccount = useCurrentStacksAccount(); - const currentAccountStxAddress = useCurrentAccountStxAddressState(); + const currentAccountStxAddress = useCurrentStacksAccountAddress(); const [tab, setTab] = useState<'details' | 'code'>('details'); if ( diff --git a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts index e79e95cbba4..cc0ce3116bb 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts +++ b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts @@ -11,7 +11,7 @@ import { initialSearchParams } from '@app/common/initial-search-params'; import { stxToMicroStx } from '@app/common/money/unit-conversion'; import { validateStacksAddress } from '@app/common/stacks-utils'; import { TransactionErrorReason } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useContractInterface } from '@app/query/stacks/contract/contract.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -27,12 +27,12 @@ export function useTransactionError() { const { values } = useFormikContext(); const currentAccount = useCurrentStacksAccount(); - const { data: balances } = useCurrentStacksAccountBalances(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); return useMemo(() => { if (!origin) return TransactionErrorReason.ExpiredRequest; - if (!transactionRequest || !balances || !currentAccount) { + if (!transactionRequest || !availableUnlockedBalance || !currentAccount) { return TransactionErrorReason.Generic; } @@ -42,14 +42,14 @@ export function useTransactionError() { if ((contractInterface as any)?.isError) return TransactionErrorReason.NoContract; } - if (balances && !getIsMultisig()) { - const zeroBalance = balances?.stx.unlockedStx.amount.toNumber() === 0; + if (availableUnlockedBalance && !getIsMultisig()) { + const zeroBalance = availableUnlockedBalance.amount.toNumber() === 0; if (transactionRequest.txType === TransactionTypes.STXTransfer) { if (zeroBalance) return TransactionErrorReason.StxTransferInsufficientFunds; const transferAmount = new BigNumber(transactionRequest.amount); - if (transferAmount.gte(balances?.stx.unlockedStx.amount)) + if (transferAmount.gte(availableUnlockedBalance.amount)) return TransactionErrorReason.StxTransferInsufficientFunds; } @@ -57,10 +57,17 @@ export function useTransactionError() { if (zeroBalance) return TransactionErrorReason.FeeInsufficientFunds; const feeValue = stxToMicroStx(values.fee); - if (feeValue.gte(balances?.stx.unlockedStx.amount)) + if (feeValue.gte(availableUnlockedBalance.amount)) return TransactionErrorReason.FeeInsufficientFunds; } } return; - }, [origin, transactionRequest, balances, currentAccount, contractInterface, values.fee]); + }, [ + origin, + transactionRequest, + availableUnlockedBalance, + currentAccount, + contractInterface, + values.fee, + ]); } diff --git a/src/app/features/stacks-transaction-request/post-conditions/fungible-post-condition-item.tsx b/src/app/features/stacks-transaction-request/post-conditions/fungible-post-condition-item.tsx index 3010f23edc2..7ed1c68b3f0 100644 --- a/src/app/features/stacks-transaction-request/post-conditions/fungible-post-condition-item.tsx +++ b/src/app/features/stacks-transaction-request/post-conditions/fungible-post-condition-item.tsx @@ -3,8 +3,7 @@ import { Suspense } from 'react'; import { TransactionTypes } from '@stacks/connect'; import { FungiblePostCondition, addressToString } from '@stacks/transactions'; -import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; -import { ftDecimals } from '@app/common/stacks-utils'; +import { ftDecimals, getSafeImageCanonicalUri } from '@app/common/stacks-utils'; import { getAmountFromPostCondition, getIconStringFromPostCondition, @@ -36,7 +35,7 @@ function FungiblePostConditionItemSuspense( const imageCanonicalUri = asset?.image_canonical_uri && asset.name && - getImageCanonicalUri(asset.image_canonical_uri, asset.name); + getSafeImageCanonicalUri(asset.image_canonical_uri, asset.name); const title = getPostConditionTitle(pc); const iconString = imageCanonicalUri ?? getIconStringFromPostCondition(pc); diff --git a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx index f3383c43070..bb0fad7f8fa 100644 --- a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx +++ b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx @@ -26,7 +26,7 @@ import { PostConditionModeWarning } from '@app/features/stacks-transaction-reque import { PostConditions } from '@app/features/stacks-transaction-request/post-conditions/post-conditions'; import { StxTransferDetails } from '@app/features/stacks-transaction-request/stx-transfer-details/stx-transfer-details'; import { TransactionError } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -55,7 +55,7 @@ export function StacksTransactionSigner({ const transactionRequest = useTransactionRequestState(); const { data: stxFees } = useCalculateStacksTxFees(stacksTransaction); const analytics = useAnalytics(); - const { data: stacksBalances } = useCurrentStacksAccountBalances(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); const navigate = useNavigate(); const { data: nextNonce } = useNextNonce(); const { search } = useLocation(); @@ -78,7 +78,7 @@ export function StacksTransactionSigner({ const validationSchema = !transactionRequest.sponsored && !disableFeeSelection && !isMultisig ? yup.object({ - fee: stxFeeValidator(stacksBalances?.stx.unlockedStx), + fee: stxFeeValidator(availableUnlockedBalance), nonce: nonceValidator, }) : yup.object({ diff --git a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx index f88783c820f..d799820ac05 100644 --- a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx +++ b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx @@ -12,7 +12,7 @@ import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { stacksValue } from '@app/common/stacks-utils'; import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; import { ErrorMessage } from '@app/features/stacks-transaction-request/transaction-error/error-message'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; import { Button } from '@app/ui/components/button/button'; @@ -59,7 +59,7 @@ export const FeeInsufficientFundsErrorMessage = memo(props => { export const StxTransferInsufficientFundsErrorMessage = memo(props => { const pendingTransaction = useTransactionRequestState(); - const { data: balance } = useCurrentStacksAccountBalances(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); return ( { Current balance - {balance + {availableUnlockedBalance ? stacksValue({ - value: balance.stx.unlockedStx.amount, + value: availableUnlockedBalance.amount, withTicker: true, }) : '--'} diff --git a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx index 9e64a0a5a90..40affce4377 100644 --- a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx +++ b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx @@ -1,6 +1,6 @@ import { BitcoinContractRequestSelectors } from '@tests/selectors/bitcoin-contract-request.selectors'; -import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { Button } from '@app/ui/components/button/button'; interface BitcoinContractRequestActionsProps { @@ -17,8 +17,8 @@ export function BitcoinContractRequestActions({ onRejectBitcoinContractOffer, onAcceptBitcoinContractOffer, }: BitcoinContractRequestActionsProps) { - const { btcAvailableAssetBalance } = useBtcAssetBalance(bitcoinAddress); - const canAccept = btcAvailableAssetBalance.balance.amount.isGreaterThan(requiredAmount); + const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(bitcoinAddress); + const canAccept = btcCryptoAssetBalance.availableBalance.amount.isGreaterThan(requiredAmount); return ( <> diff --git a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx index 6010708f8f1..58eb7a9cb2b 100644 --- a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx +++ b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx @@ -1,45 +1,28 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; -import { Box, styled } from 'leather-styles/jsx'; +import { Stack, styled } from 'leather-styles/jsx'; -import { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; import { RouteUrls } from '@shared/route-urls'; -import { isDefined } from '@shared/utils'; -import { useStxCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { CryptoAssetList } from '@app/components/crypto-assets/choose-crypto-asset/crypto-asset-list'; -import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import { capitalize } from '@app/common/utils'; +import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout'; +import { BitcoinNativeSegwitAccountLoader } from '@app/components/loaders/bitcoin-account-loader'; +import { BtcCryptoAssetLoader } from '@app/components/loaders/btc-crypto-asset-loader'; +import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; +import { StxCryptoAssetLoader } from '@app/components/loaders/stx-crypto-asset-loader'; +import { StxCryptoAssetItem } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item'; +import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { Card } from '@app/ui/layout/card/card'; import { Page } from '@app/ui/layout/page/page.layout'; export function ChooseCryptoAssetToFund() { - const stxCryptoCurrencyAssetBalance = useStxCryptoCurrencyAssetBalance(); - - const { whenWallet } = useWalletType(); const navigate = useNavigate(); - const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable(); - - const filteredCryptoAssetBalances = useMemo( - () => - [stxCryptoCurrencyAssetBalance].filter(isDefined).filter(assetBalance => - whenWallet({ - ledger: checkBlockchainAvailable(assetBalance?.blockchain), - software: true, - }) - ), - [stxCryptoCurrencyAssetBalance, checkBlockchainAvailable, whenWallet] - ); - const navigateToFund = useCallback( - (cryptoAssetBalance: AllTransferableCryptoAssetBalances) => { - const { asset } = cryptoAssetBalance; - - const symbol = asset.symbol === '' ? asset.contractAssetName : asset.symbol; - navigate(RouteUrls.Fund.replace(':currency', symbol.toUpperCase())); - }, + (asset: AccountCryptoAssetWithDetails) => + navigate(RouteUrls.Fund.replace(':currency', asset.info.symbol)), [navigate] ); @@ -53,13 +36,37 @@ export function ChooseCryptoAssetToFund() { } > - - - + + + {signer => ( + + {(asset, isLoading) => ( + } + isLoading={isLoading} + name={capitalize(asset.info.name)} + onClick={() => navigateToFund(asset)} + /> + )} + + )} + + + + {account => ( + + {(asset, isInitialLoading) => ( + navigateToFund(asset)} + /> + )} + + )} + + diff --git a/src/app/pages/fund/fund.tsx b/src/app/pages/fund/fund.tsx index 68bb064d802..d2d7f7ef77d 100644 --- a/src/app/pages/fund/fund.tsx +++ b/src/app/pages/fund/fund.tsx @@ -1,15 +1,11 @@ import { Outlet, useParams } from 'react-router-dom'; +import type { BtcCryptoAssetBalance, StxCryptoAssetBalance } from '@leather-wallet/models'; + import type { Blockchains } from '@shared/models/blockchain.model'; -import type { - BitcoinCryptoCurrencyAssetBalance, - StacksCryptoCurrencyAssetBalance, -} from '@shared/models/crypto-asset-balance.model'; import type { CryptoCurrencies } from '@shared/models/currencies.model'; import { RouteUrls } from '@shared/route-urls'; -import { useBtcCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/btc/use-btc-crypto-currency-asset-balance'; -import { useStxCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance'; import { FullPageLoadingSpinner } from '@app/components/loading-spinner'; import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -19,7 +15,7 @@ import { FiatProvidersList } from './fiat-providers-list'; interface FundCryptoCurrencyInfo { address?: string; - balance?: BitcoinCryptoCurrencyAssetBalance | StacksCryptoCurrencyAssetBalance; + balance?: BtcCryptoAssetBalance | StxCryptoAssetBalance; blockchain: Blockchains; route: string; symbol: CryptoCurrencies; @@ -28,31 +24,27 @@ interface FundCryptoCurrencyInfo { export function FundPage() { const currentStxAccount = useCurrentStacksAccount(); const bitcoinSigner = useCurrentAccountNativeSegwitIndexZeroSignerNullable(); - const btcCryptoCurrencyAssetBalance = useBtcCryptoCurrencyAssetBalance(); - const stxCryptoCurrencyAssetBalance = useStxCryptoCurrencyAssetBalance(); const { currency = 'STX' } = useParams(); const fundCryptoCurrencyMap: Record = { BTC: { address: bitcoinSigner?.address, - balance: btcCryptoCurrencyAssetBalance?.btcBalance, - blockchain: 'Bitcoin', + blockchain: 'bitcoin', route: RouteUrls.ReceiveBtc, symbol: currency, }, STX: { address: currentStxAccount?.address, - balance: stxCryptoCurrencyAssetBalance, - blockchain: 'Stacks', + blockchain: 'stacks', route: RouteUrls.ReceiveStx, symbol: currency, }, }; - const { address, balance, blockchain, route, symbol } = + const { address, blockchain, route, symbol } = fundCryptoCurrencyMap[currency as CryptoCurrencies]; - if (!address || !balance) return ; + if (!address) return ; return ( <> diff --git a/src/app/pages/home/components/account-actions.tsx b/src/app/pages/home/components/account-actions.tsx index 9fbc32d8e0c..50d5eebda05 100644 --- a/src/app/pages/home/components/account-actions.tsx +++ b/src/app/pages/home/components/account-actions.tsx @@ -51,6 +51,7 @@ export function AccountActions() { [ChainID.Mainnet]: ( } label="Swap" onClick={() => navigate(RouteUrls.Swap.replace(':base', 'STX').replace(':quote', ''))} diff --git a/src/app/pages/home/components/assets.tsx b/src/app/pages/home/components/assets.tsx new file mode 100644 index 00000000000..2568d5e08f0 --- /dev/null +++ b/src/app/pages/home/components/assets.tsx @@ -0,0 +1,19 @@ +import { Outlet } from 'react-router-dom'; + +import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import { Stack } from 'leather-styles/jsx'; + +import { AssetList } from '@app/features/asset-list/asset-list'; +import { Collectibles } from '@app/features/collectibles/collectibles'; +import { PendingBrc20TransferList } from '@app/features/pending-brc-20-transfers/pending-brc-20-transfers'; + +export function Assets() { + return ( + + + + + + + ); +} diff --git a/src/app/pages/home/components/send-button.tsx b/src/app/pages/home/components/send-button.tsx index 58df5cc0150..33da083cc91 100644 --- a/src/app/pages/home/components/send-button.tsx +++ b/src/app/pages/home/components/send-button.tsx @@ -8,21 +8,25 @@ import { RouteUrls } from '@shared/route-urls'; import { useWalletType } from '@app/common/use-wallet-type'; import { whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { - useStacksCryptoCurrencyAssetBalance, - useTransferableStacksFungibleTokenAssetBalances, -} from '@app/query/stacks/balance/stacks-ft-balances.hooks'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; +import { useStxCryptoAssetBalance } from '@app/query/stacks/balance/account-balance.hooks'; +import { useTransferableSip10CryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks'; +import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { IconButton } from '@app/ui/components/icon-button/icon-button'; import { SendIcon } from '@app/ui/icons'; function SendButtonSuspense() { const navigate = useNavigate(); const { whenWallet } = useWalletType(); - const account = useCurrentStacksAccount(); - const stxAssetBalance = useStacksCryptoCurrencyAssetBalance(account?.address ?? ''); - const ftAssets = useTransferableStacksFungibleTokenAssetBalances(account?.address ?? ''); - const isDisabled = !stxAssetBalance && ftAssets?.length === 0; + const address = useCurrentStacksAccountAddress(); + const btcAddress = useCurrentAccountNativeSegwitIndexZeroSignerNullable()?.address; + const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(btcAddress ?? ''); + const { data: stxCryptoAssetBalance } = useStxCryptoAssetBalance(address); + const stacksFtAssets = useTransferableSip10CryptoAssetsWithDetails(address); + + const isDisabled = + !btcCryptoAssetBalance && !stxCryptoAssetBalance && stacksFtAssets?.length === 0; return ( - } /> + } /> }> {homePageModalRoutes} diff --git a/src/app/pages/receive/receive-dialog.tsx b/src/app/pages/receive/receive-dialog.tsx index c3d189dce5a..390f7463b99 100644 --- a/src/app/pages/receive/receive-dialog.tsx +++ b/src/app/pages/receive/receive-dialog.tsx @@ -11,7 +11,7 @@ import { useLocationState } from '@app/common/hooks/use-location-state'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; import { useZeroIndexTaprootAddress } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { Dialog } from '@app/ui/components/containers/dialog/dialog'; import { Header } from '@app/ui/components/containers/headers/header'; import { Tabs } from '@app/ui/components/tabs/tabs'; @@ -39,7 +39,7 @@ export function ReceiveDialog({ type = 'full' }: ReceiveDialogProps) { const navigate = useNavigate(); const location = useLocation(); const btcAddressNativeSegwit = useCurrentAccountNativeSegwitAddressIndexZero(); - const stxAddress = useCurrentAccountStxAddressState(); + const stxAddress = useCurrentStacksAccountAddress(); const accountIndex = get(location.state, 'accountIndex', undefined); const btcAddressTaproot = useZeroIndexTaprootAddress(accountIndex); diff --git a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx index 1bfa523827f..2cb7f107d19 100644 --- a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx +++ b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx @@ -2,44 +2,38 @@ import { useNavigate } from 'react-router-dom'; import { Box, styled } from 'leather-styles/jsx'; -import { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; import { RouteUrls } from '@shared/route-urls'; -import { useAllTransferableCryptoAssetBalances } from '@app/common/hooks/use-transferable-asset-balances.hooks'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { CryptoAssetList } from '@app/components/crypto-assets/choose-crypto-asset/crypto-asset-list'; +import { AssetList } from '@app/features/asset-list/asset-list'; import { useToast } from '@app/features/toasts/use-toast'; import { useConfigBitcoinSendEnabled } from '@app/query/common/remote-config/remote-config.query'; -import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { getSip10InfoFromAsset } from '@app/query/stacks/sip10/sip10-tokens.hooks'; import { Card } from '@app/ui/layout/card/card'; +import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; export function ChooseCryptoAsset() { - const toast = useToast(); - const allTransferableCryptoAssetBalances = useAllTransferableCryptoAssetBalances(); - - const { whenWallet } = useWalletType(); const navigate = useNavigate(); const isBitcoinSendEnabled = useConfigBitcoinSendEnabled(); + const toast = useToast(); - const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable(); - - function navigateToSendForm(cryptoAssetBalance: AllTransferableCryptoAssetBalances) { - const { asset } = cryptoAssetBalance; - if (asset.symbol === 'BTC' && !isBitcoinSendEnabled) { - return navigate(RouteUrls.SendBtcDisabled); - } - const symbol = asset.symbol === '' ? asset.contractAssetName : asset.symbol.toLowerCase(); - - if (cryptoAssetBalance.type === 'fungible-token') { - const asset = cryptoAssetBalance.asset; - if (!asset.contractId) { - toast.error('Unable to find contract id'); + function navigateToSendForm(asset: AccountCryptoAssetWithDetails) { + switch (asset.type) { + case 'btc': + if (!isBitcoinSendEnabled) return navigate(RouteUrls.SendBtcDisabled); + return navigate(`${RouteUrls.SendCryptoAsset}/${asset.info.symbol.toLowerCase()}`); + case 'sip-10': + const info = getSip10InfoFromAsset(asset); + if (info) { + const { assetName } = getAssetStringParts(info.contractId); + const symbol = !info.symbol ? assetName : info.symbol.toLowerCase(); + return navigate(`${RouteUrls.SendCryptoAsset}/${symbol}/${info.contractId}`); + } + toast.error('No contract id'); return navigate('..'); - } - const contractId = `${asset.contractId.split('::')[0]}`; - return navigate(`${RouteUrls.SendCryptoAsset}/${symbol}/${contractId}`); + default: + return navigate(`${RouteUrls.SendCryptoAsset}/${asset.info.symbol.toLowerCase()}`); } - navigate(`${RouteUrls.SendCryptoAsset}/${symbol}`); } return ( @@ -50,17 +44,8 @@ export function ChooseCryptoAsset() { } > - - navigateToSendForm(cryptoAssetBalance)} - cryptoAssetBalances={allTransferableCryptoAssetBalances.filter(asset => - whenWallet({ - ledger: checkBlockchainAvailable(asset.blockchain), - software: true, - }) - )} - variant="send" - /> + + ); diff --git a/src/app/components/crypto-assets/choose-crypto-asset/send-btc-disabled.tsx b/src/app/pages/send/choose-crypto-asset/send-btc-disabled.tsx similarity index 100% rename from src/app/components/crypto-assets/choose-crypto-asset/send-btc-disabled.tsx rename to src/app/pages/send/choose-crypto-asset/send-btc-disabled.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx rename to src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx rename to src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx rename to src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx rename to src/app/pages/send/send-crypto-asset-form/form/brc20/use-brc20-send-form.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx index 879611088dd..7acabeedc2d 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx @@ -2,7 +2,6 @@ import { Outlet } from 'react-router-dom'; import { useCryptoCurrencyMarketDataMeanAverage, - useNativeSegwitBalance, } from '@leather-wallet/query'; import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; import { Form, Formik } from 'formik'; @@ -13,6 +12,8 @@ import { CryptoCurrencies } from '@shared/models/currencies.model'; import { formatMoney } from '@app/common/money/format-money'; import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog'; +import { useBtcAccountCryptoAssetWithDetails } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks'; + import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { Button } from '@app/ui/components/button/button'; @@ -39,8 +40,9 @@ export function BtcSendForm() { // TODO: unsafe type assumption const btcMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol as 'BTC' | 'STX'); - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - const { btcBalance } = useNativeSegwitBalance(nativeSegwitSigner.address); + const { address } = useCurrentAccountNativeSegwitIndexZeroSigner(); + const { asset } = useBtcAccountCryptoAssetWithDetails(address); + const { balance, info } = asset; const { calcMaxSpend, @@ -83,17 +85,17 @@ export function BtcSendForm() { > Continue - + } > } /> - } - name={btcBalance.asset.name} - symbol={symbol} - /> + } name={info.name} symbol={symbol} /> {currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && ( diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx index ab09405fbcd..9ee3743d782 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx @@ -1,6 +1,5 @@ import { useRef, useState } from 'react'; -import { useNativeSegwitBalance } from '@leather-wallet/query'; import { FormikHelpers, FormikProps } from 'formik'; import * as yup from 'yup'; @@ -25,6 +24,7 @@ import { } from '@app/common/validation/forms/currency-validators'; import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; +import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; @@ -37,9 +37,7 @@ export function useBtcSendForm() { const currentNetwork = useCurrentNetwork(); const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); const { data: utxos = [], refetch } = useCurrentNativeSegwitUtxos(); - const { btcBalance: btcCryptoCurrencyAssetBalance } = useNativeSegwitBalance( - nativeSegwitSigner.address - ); + const { balance } = useCurrentBtcAvailableBalanceNativeSegwit(); const sendFormNavigate = useSendFormNavigate(); const calcMaxSpend = useCalculateMaxBitcoinSpend(); const { onFormStateChange } = useUpdatePersistedSendFormValues(); @@ -61,9 +59,7 @@ export function useBtcSendForm() { amount: yup .number() .concat(btcMinimumSpendValidator()) - .concat( - btcAmountPrecisionValidator(formatPrecisionError(btcCryptoCurrencyAssetBalance.balance)) - ) + .concat(btcAmountPrecisionValidator(formatPrecisionError(balance))) .concat(currencyAmountValidator()) .concat( btcInsufficientBalanceValidator({ diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx similarity index 83% rename from src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx rename to src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx index 643935c0717..fbee8ba9b37 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx @@ -1,4 +1,5 @@ -import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; +import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar'; +import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; import { AmountField } from '../../components/amount-field'; @@ -9,13 +10,9 @@ import { StacksCommonSendForm } from '../stacks/stacks-common-send-form'; import { useSip10SendForm } from './use-sip10-send-form'; interface Sip10TokenSendFormContainerProps { - symbol: string; - contractId: string; + asset: Sip10AccountCryptoAssetWithDetails; } -export function Sip10TokenSendFormContainer({ - symbol, - contractId, -}: Sip10TokenSendFormContainerProps) { +export function Sip10TokenSendFormContainer({ asset }: Sip10TokenSendFormContainerProps) { const { availableTokenBalance, initialValues, @@ -26,7 +23,8 @@ export function Sip10TokenSendFormContainer({ avatar, marketData, decimals, - } = useSip10SendForm({ symbol, contractId }); + symbol, + } = useSip10SendForm({ asset }); const amountField = ( - {({ contractId, symbol }) => ( - - )} - - ); -} - interface Sip10TokenSendFormLoaderProps { - children(data: { symbol: string; contractId: string }): React.JSX.Element; + children(data: { asset: Sip10AccountCryptoAssetWithDetails }): React.ReactNode; } function Sip10TokenSendFormLoader({ children }: Sip10TokenSendFormLoaderProps) { - const { symbol, contractId } = useParams(); + const { contractId } = useParams(); + const asset = useSip10CryptoAssetWithDetails(contractId ?? ''); const toast = useToast(); - if (!symbol || !contractId) { - toast.error('Symbol or contract id not found'); + if (!asset) { + toast.error('Asset not found'); return ; } - return children({ symbol, contractId }); + return children({ asset }); +} + +export function Sip10TokenSendForm() { + return ( + + {({ asset }) => } + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx similarity index 64% rename from src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx rename to src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx index 31d63e80522..41c3b66c600 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx @@ -6,11 +6,10 @@ import * as yup from 'yup'; import { logger } from '@shared/logger'; import { StacksSendFormValues } from '@shared/models/form.model'; -import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; import { convertAmountToBaseUnit } from '@app/common/money/calculate-money'; -import { formatContractId } from '@app/common/utils'; +import { getSafeImageCanonicalUri } from '@app/common/stacks-utils'; import { stacksFungibleTokenAmountValidator } from '@app/common/validation/forms/amount-validators'; -import { useStacksFungibleTokenAssetBalance } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; +import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useFtTokenTransferUnsignedTx, @@ -21,36 +20,31 @@ import { useSendFormNavigate } from '../../hooks/use-send-form-navigate'; import { useStacksCommonSendForm } from '../stacks/use-stacks-common-send-form'; interface UseSip10SendFormArgs { - symbol: string; - contractId: string; + asset: Sip10AccountCryptoAssetWithDetails; } -export function useSip10SendForm({ symbol, contractId }: UseSip10SendFormArgs) { - const assetBalance = useStacksFungibleTokenAssetBalance(contractId); - const generateTx = useGenerateFtTokenTransferUnsignedTx(assetBalance); +export function useSip10SendForm({ asset }: UseSip10SendFormArgs) { + const generateTx = useGenerateFtTokenTransferUnsignedTx(asset.info); const sendFormNavigate = useSendFormNavigate(); - const unsignedTx = useFtTokenTransferUnsignedTx(assetBalance); + const unsignedTx = useFtTokenTransferUnsignedTx(asset.info); const { data: stacksFtFees } = useCalculateStacksTxFees(unsignedTx); - const availableTokenBalance = assetBalance.balance; + const availableTokenBalance = asset.balance.availableBalance; const sendMaxBalance = useMemo( () => convertAmountToBaseUnit(availableTokenBalance), [availableTokenBalance] ); const { initialValues, checkFormValidation, recipient, memo, nonce } = useStacksCommonSendForm({ - symbol, + symbol: asset.info.symbol, availableTokenBalance, }); function createFtAvatar() { - const asset = assetBalance.asset; - - const { contractAddress, contractAssetName, contractName } = asset; return { - avatar: `${formatContractId(contractAddress, contractName)}::${contractAssetName}`, - imageCanonicalUri: getImageCanonicalUri(asset.imageCanonicalUri, asset.name), + avatar: asset.info.contractId, + imageCanonicalUri: getSafeImageCanonicalUri(asset.info.imageCanonicalUri, asset.info.name), }; } @@ -59,9 +53,9 @@ export function useSip10SendForm({ symbol, contractId }: UseSip10SendFormArgs) { initialValues, sendMaxBalance, stacksFtFees, - symbol, - decimals: assetBalance.asset.decimals, - marketData: assetBalance.asset.marketData, + symbol: asset.info.symbol, + decimals: asset.info.decimals, + marketData: asset.marketData, avatar: createFtAvatar(), validationSchema: yup.object({ amount: stacksFungibleTokenAmountValidator(availableTokenBalance), @@ -81,8 +75,8 @@ export function useSip10SendForm({ symbol, contractId }: UseSip10SendFormArgs) { if (!tx) return logger.error('Attempted to generate unsigned tx, but tx is undefined'); sendFormNavigate.toConfirmAndSignStacksSip10Transaction({ - decimals: assetBalance.balance.decimals, - name: assetBalance.asset.name, + decimals: asset.info.decimals, + name: asset.info.name, tx, }); }, diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx index d91454baed6..8e0c9367c0f 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx @@ -57,7 +57,7 @@ export function StacksSendFormConfirmation() { > - + diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/use-stacks-common-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/use-stacks-common-send-form.tsx index de916bb9069..0b8eecbe88b 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/use-stacks-common-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/use-stacks-common-send-form.tsx @@ -12,7 +12,7 @@ import { stxMemoValidator } from '@app/common/validation/forms/memo-validators'; import { stxRecipientValidator } from '@app/common/validation/forms/recipient-validators'; import { nonceValidator } from '@app/common/validation/nonce-validators'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { useSendFormRouteState } from '../../hooks/use-send-form-route-state'; @@ -29,7 +29,7 @@ export function useStacksCommonSendForm({ }: UseStacksCommonSendFormArgs) { const routeState = useSendFormRouteState(); const { data: nextNonce } = useNextNonce(); - const currentAccountStxAddress = useCurrentAccountStxAddressState(); + const currentAccountStxAddress = useCurrentStacksAccountAddress(); const currentNetwork = useCurrentNetworkState(); const initialValues: StacksSendFormValues = createDefaultInitialFormValues({ diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx index 3ed1f488f39..9ab2744c76f 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx @@ -15,7 +15,7 @@ export function StxSendForm() { const stxMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol); const { - availableStxBalance, + availableUnlockedBalance, initialValues, previewTransaction, sendMaxBalance, @@ -26,10 +26,13 @@ export function StxSendForm() { const amountField = ( } bottomInputOverlay={ - + } autoComplete="off" /> @@ -50,7 +53,7 @@ export function StxSendForm() { // FIXME 4370 - need to fix this as fee is actually NumberSchema; in FeeValidatorFactoryArgs // this needs to be the STX fee so it can be validated against HIGH_FEE_AMOUNT_STX fee={fee as unknown as string} - availableTokenBalance={availableStxBalance} + availableTokenBalance={availableUnlockedBalance} /> ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx index 7da098a7a22..d69d206590b 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx @@ -7,7 +7,6 @@ import { STX_DECIMALS } from '@shared/constants'; import { logger } from '@shared/logger'; import { StacksSendFormValues } from '@shared/models/form.model'; -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { convertAmountToBaseUnit } from '@app/common/money/calculate-money'; import { stxAmountValidator, @@ -15,6 +14,7 @@ import { } from '@app/common/validation/forms/amount-validators'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useStacksValidateFeeByNonce } from '@app/query/stacks/mempool/mempool.hooks'; import { @@ -30,31 +30,29 @@ export function useStxSendForm() { const { data: stxFees } = useCalculateStacksTxFees(unsignedTx); const generateTx = useGenerateStxTokenTransferUnsignedTx(); const { onFormStateChange } = useUpdatePersistedSendFormValues(); - const sendFormNavigate = useSendFormNavigate(); const { changeFeeByNonce } = useStacksValidateFeeByNonce(); - - const { availableBalance: availableStxBalance } = useStxBalance(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); const sendMaxBalance = useMemo( () => convertAmountToBaseUnit( - availableStxBalance.amount.minus(stxFees?.estimates[1].fee.amount || 0), + availableUnlockedBalance.amount.minus(stxFees?.estimates[1].fee.amount || 0), STX_DECIMALS ), - [availableStxBalance, stxFees] + [availableUnlockedBalance.amount, stxFees?.estimates] ); const { initialValues, checkFormValidation, recipient, memo, nonce } = useStacksCommonSendForm({ symbol: 'STX', - availableTokenBalance: availableStxBalance, + availableTokenBalance: availableUnlockedBalance, }); // FIXME - I don't this this is the fee, should be value.fee or something from the form - const fee = stxFeeValidator(availableStxBalance); + const fee = stxFeeValidator(availableUnlockedBalance); return { - availableStxBalance, + availableUnlockedBalance, initialValues, onFormStateChange, sendMaxBalance, @@ -62,10 +60,10 @@ export function useStxSendForm() { fee, validationSchema: yup.object({ - amount: stxAmountValidator(availableStxBalance).concat( - stxAvailableBalanceValidator(availableStxBalance) + amount: stxAmountValidator(availableUnlockedBalance).concat( + stxAvailableBalanceValidator(availableUnlockedBalance) ), - fee: stxFeeValidator(availableStxBalance), + fee: stxFeeValidator(availableUnlockedBalance), recipient, memo, nonce, diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 01fec10cac8..4fdc961f637 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -4,11 +4,11 @@ import { Outlet, Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { BroadcastErrorDialog } from '@app/components/broadcast-error-dialog/broadcast-error-dialog'; -import { SendBtcDisabled } from '@app/components/crypto-assets/choose-crypto-asset/send-btc-disabled'; import { FullPageWithHeaderLoadingSpinner } from '@app/components/loading-spinner'; import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container'; +import { SendBtcDisabled } from '@app/pages/send/choose-crypto-asset/send-btc-disabled'; import { AccountGate } from '@app/routes/account-gate'; import { Page } from '@app/ui/layout/page/page.layout'; @@ -19,13 +19,13 @@ import { BtcSentSummary } from '../sent-summary/btc-sent-summary'; import { StxSentSummary } from '../sent-summary/stx-sent-summary'; import { RecipientAccountsDialog } from './components/recipient-accounts-dialog/recipient-accounts-dialog'; import { SendBitcoinAssetContainer } from './family/bitcoin/components/send-bitcoin-asset-container'; -import { Brc20SendForm } from './form/brc-20/brc20-send-form'; -import { Brc20SendFormConfirmation } from './form/brc-20/brc20-send-form-confirmation'; -import { BrcChooseFee } from './form/brc-20/brc-20-choose-fee'; +import { BrcChooseFee } from './form/brc20/brc20-choose-fee'; +import { Brc20SendForm } from './form/brc20/brc20-send-form'; +import { Brc20SendFormConfirmation } from './form/brc20/brc20-send-form-confirmation'; import { BtcChooseFee } from './form/btc/btc-choose-fee'; import { BtcSendForm } from './form/btc/btc-send-form'; import { BtcSendFormConfirmation } from './form/btc/btc-send-form-confirmation'; -import { Sip10TokenSendForm } from './form/stacks-sip10/sip10-token-send-form'; +import { Sip10TokenSendForm } from './form/sip10/sip10-token-send-form'; import { StacksSendFormConfirmation } from './form/stacks/stacks-send-form-confirmation'; import { StxSendForm } from './form/stx/stx-send-form'; diff --git a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx index 5ae67e717e5..83ccef5e333 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx @@ -3,8 +3,8 @@ import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; -import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; -import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; +import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query'; +import { isFtAsset } from '@app/query/stacks/token-metadata/token-metadata.utils'; import { Avatar, defaultFallbackDelay, getAvatarFallback } from '@app/ui/components/avatar/avatar'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; import { Pressable } from '@app/ui/pressable/pressable'; @@ -19,7 +19,7 @@ export function SwapAssetItem({ asset, onClick }: SwapAssetItemProps) { const ftMetadataName = ftMetadata && isFtAsset(ftMetadata) ? ftMetadata.name : asset.name; const displayName = asset.displayName ?? ftMetadataName; const fallback = getAvatarFallback(asset.name); - const balanceAsFiat = convertAssetBalanceToFiat(asset); + const fiatBalance = convertAssetBalanceToFiat(asset); return ( @@ -33,7 +33,7 @@ export function SwapAssetItem({ asset, onClick }: SwapAssetItemProps) { titleLeft={displayName} captionLeft={asset.name} titleRight={formatMoneyWithoutSymbol(asset.balance)} - captionRight={balanceAsFiat} + captionRight={fiatBalance} /> ); diff --git a/src/app/pages/transaction-request/transaction-request.tsx b/src/app/pages/transaction-request/transaction-request.tsx index 049cdd81d6f..356818bec10 100644 --- a/src/app/pages/transaction-request/transaction-request.tsx +++ b/src/app/pages/transaction-request/transaction-request.tsx @@ -29,7 +29,7 @@ import { PostConditions } from '@app/features/stacks-transaction-request/post-co import { StxTransferDetails } from '@app/features/stacks-transaction-request/stx-transfer-details/stx-transfer-details'; import { SubmitAction } from '@app/features/stacks-transaction-request/submit-action'; import { TransactionError } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -47,7 +47,7 @@ function TransactionRequestBase() { const { data: stxFees } = useCalculateStacksTxFees(unsignedTx.transaction); const analytics = useAnalytics(); const generateUnsignedTx = useGenerateUnsignedStacksTransaction(); - const { data: stacksBalances } = useCurrentStacksAccountBalances(); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); const { data: nextNonce } = useNextNonce(); const navigate = useNavigate(); const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); @@ -78,7 +78,7 @@ function TransactionRequestBase() { const validationSchema = !transactionRequest.sponsored ? yup.object({ - fee: stxFeeValidator(stacksBalances?.stx.unlockedStx), + fee: stxFeeValidator(availableUnlockedBalance), nonce: nonceValidator, }) : null; diff --git a/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts new file mode 100644 index 00000000000..cb339fd2460 --- /dev/null +++ b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts @@ -0,0 +1,36 @@ +import { useMemo } from 'react'; + +import type { BtcCryptoAssetBalance, Money } from '@leather-wallet/models'; +import { useGetBitcoinBalanceByAddress } from '@leather-wallet/query'; + +import { createMoney } from '@shared/models/money.model'; + +import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; + +function createBtcCryptoAssetBalance(balance: Money): BtcCryptoAssetBalance { + return { + availableBalance: balance, + // TODO: Asset refactor: can we determine these here or are they nec? + protectedBalance: createMoney(0, 'BTC'), + uneconomicalBalance: createMoney(0, 'BTC'), + }; +} + +// Balance is derived from a single query in address reuse mode +export function useBtcCryptoAssetBalanceNativeSegwit(address: string) { + const { balance, isInitialLoading, isLoading, isFetching } = + useGetBitcoinBalanceByAddress(address); + const btcCryptoAssetBalance = useMemo(() => createBtcCryptoAssetBalance(balance), [balance]); + + return { + btcCryptoAssetBalance, + isInitialLoading, + isLoading, + isFetching, + }; +} + +export function useCurrentBtcAvailableBalanceNativeSegwit() { + const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); + return useGetBitcoinBalanceByAddress(nativeSegwitSigner.address); +} diff --git a/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts b/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts deleted file mode 100644 index 8f4e53f17a8..00000000000 --- a/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useGetBitcoinBalanceByAddress } from '@leather-wallet/query'; - -import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; - -export function useCurrentNativeSegwitAddressBalance() { - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - return useGetBitcoinBalanceByAddress(nativeSegwitSigner.address); -} diff --git a/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts b/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts new file mode 100644 index 00000000000..c91ba70e208 --- /dev/null +++ b/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts @@ -0,0 +1,48 @@ +import type { BtcCryptoAssetInfo } from '@leather-wallet/models'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query'; + +import { BTC_DECIMALS } from '@shared/constants'; +import { createMoney } from '@shared/models/money.model'; + +import { createAccountCryptoAssetWithDetailsFactory } from '@app/query/models/crypto-asset.model'; + +import type { BtcAccountCryptoAssetWithDetails } from '../../models/crypto-asset.model'; +import { useBtcCryptoAssetBalanceNativeSegwit } from '../balance/btc-balance-native-segwit.hooks'; + +const btcCryptoAssetInfo: BtcCryptoAssetInfo = { + decimals: BTC_DECIMALS, + hasMemo: false, + name: 'bitcoin', + symbol: 'BTC', +}; + +const btcCryptoAssetBalancePlaceholder = { + availableBalance: createMoney(0, 'BTC'), + protectedBalance: createMoney(0, 'BTC'), + uneconomicalBalance: createMoney(0, 'BTC'), +}; + +export const btcCryptoAssetPlaceholder = + createAccountCryptoAssetWithDetailsFactory({ + balance: btcCryptoAssetBalancePlaceholder, + chain: 'bitcoin', + info: btcCryptoAssetInfo, + marketData: null, + type: 'btc', + }); + +export function useBtcAccountCryptoAssetWithDetails(address: string) { + const { btcCryptoAssetBalance, isInitialLoading } = useBtcCryptoAssetBalanceNativeSegwit(address); + const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); + + return { + asset: createAccountCryptoAssetWithDetailsFactory({ + balance: btcCryptoAssetBalance, + chain: 'bitcoin', + info: btcCryptoAssetInfo, + marketData, + type: 'btc', + }), + isInitialLoading, + }; +} diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts index 110dac7d1e4..0c8b5d11a30 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts @@ -2,11 +2,24 @@ import { createBrc20TransferInscription, encodeBrc20TransferInscription, useAverageBitcoinFeeRates, + useCalculateBitcoinFiatValue, + useGetBrc20TokensQuery, useOrdinalsbotClient, } from '@leather-wallet/query'; +import BigNumber from 'bignumber.js'; +import { createMarketData, createMarketPair } from '@shared/models/market.model'; +import { createMoney } from '@shared/models/money.model'; + +import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; +import { + type Brc20AccountCryptoAssetWithDetails, + createAccountCryptoAssetWithDetailsFactory, +} from '@app/query/models/crypto-asset.model'; import { useAppDispatch } from '@app/store'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; +import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; import { brc20TransferInitiated } from '@app/store/ordinals/ordinals.slice'; export function useBrc20Transfers(holderAddress: string) { @@ -52,3 +65,52 @@ export function useBrc20Transfers(holderAddress: string) { }, }; } + +export function useBrc20AccountCryptoAssetsWithDetails() { + const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue(); + + const createTaprootSigner = useCurrentAccountTaprootSigner(); + const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); + + const { data: allBrc20TokensResponse } = useGetBrc20TokensQuery({ + createTaprootSigner, + nativeSegwitAddress: nativeSegwitSigner.address, + }); + + const tokens = allBrc20TokensResponse?.pages + .flatMap(page => page.brc20Tokens) + .filter(token => token.length > 0) + .flatMap(token => token); + + return ( + tokens?.map(token => { + const priceAsFiat = calculateBitcoinFiatValue( + createMoney(new BigNumber(token.balance.min_listed_unit_price ?? 0), 'BTC') + ); + return createAccountCryptoAssetWithDetailsFactory({ + balance: { + availableBalance: createMoney( + unitToFractionalUnit(token.info.decimals)(new BigNumber(token.balance.overall_balance)), + token.balance.ticker, + token.info.decimals + ), + }, + chain: 'bitcoin', + holderAddress: token.holderAddress, + info: { + decimals: token.info.decimals, + hasMemo: false, + name: 'brc-20', + symbol: token.info.ticker, + }, + marketData: token.balance.min_listed_unit_price + ? createMarketData( + createMarketPair(token.balance.ticker, 'USD'), + createMoney(priceAsFiat.amount, 'USD') + ) + : null, + type: 'brc-20', + }); + }) ?? [] + ); +} diff --git a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts index e7986efa9fc..6d3b95fcb92 100644 --- a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts +++ b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts @@ -9,11 +9,11 @@ import { type Money, createMoney } from '@shared/models/money.model'; import { isDefined } from '@shared/utils'; import { sortAssetsByName } from '@app/common/asset-utils'; -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; import { pullContractIdFromIdentity } from '@app/common/utils'; -import { useTransferableStacksFungibleTokenAssetBalances } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks'; +import { useTransferableSip10CryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { getAvatarFallback } from '@app/ui/components/avatar/avatar'; import { useAlexSdkLatestPricesQuery } from './alex-sdk-latest-prices.query'; @@ -51,14 +51,12 @@ export function useAlexCurrencyPriceAsMarketData() { ); } -function useMakeSwapAsset() { - const account = useCurrentStacksAccount(); +function useCreateSwapAsset() { + const address = useCurrentStacksAccountAddress(); const { data: prices } = useAlexSdkLatestPricesQuery(); const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); - const { availableBalance: availableStxBalance } = useStxBalance(); - const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances( - account?.address ?? '' - ); + const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance(); + const assets = useTransferableSip10CryptoAssetsWithDetails(address); return useCallback( (tokenInfo?: TokenInfo): SwapAsset | undefined => { @@ -69,43 +67,42 @@ function useMakeSwapAsset() { } const currency = tokenInfo.id as Currency; - const fungibleTokenBalance = stacksFtAssetBalances.find( - balance => tokenInfo.contractAddress === balance.asset.contractId - )?.balance; const principal = pullContractIdFromIdentity(tokenInfo.contractAddress); + const availableBalance = assets.find(a => a.info.contractId === principal)?.balance + .availableBalance; const swapAsset = { currency, fallback: getAvatarFallback(tokenInfo.name), icon: tokenInfo.icon, name: tokenInfo.name, - principal: pullContractIdFromIdentity(tokenInfo.contractAddress), + principal, }; if (currency === Currency.STX) { return { ...swapAsset, - balance: availableStxBalance, + balance: availableUnlockedBalance, displayName: 'Stacks', - marketData: priceAsMarketData(principal, availableStxBalance.symbol), + marketData: priceAsMarketData(principal, availableUnlockedBalance.symbol), }; } return { ...swapAsset, - balance: fungibleTokenBalance ?? createMoney(0, tokenInfo.name, tokenInfo.decimals), - marketData: fungibleTokenBalance - ? priceAsMarketData(principal, fungibleTokenBalance.symbol) + balance: availableBalance ?? createMoney(0, tokenInfo.name, tokenInfo.decimals), + marketData: availableBalance + ? priceAsMarketData(principal, availableBalance.symbol) : priceAsMarketData(principal, tokenInfo.name), }; }, - [availableStxBalance, priceAsMarketData, prices, stacksFtAssetBalances] + [assets, availableUnlockedBalance, priceAsMarketData, prices] ); } export function useAlexSwappableAssets() { - const makeSwapAsset = useMakeSwapAsset(); + const createSwapAsset = useCreateSwapAsset(); return useAlexSdkSwappableCurrencyQuery({ - select: resp => sortAssetsByName(resp.map(makeSwapAsset).filter(isDefined)), + select: resp => sortAssetsByName(resp.map(createSwapAsset).filter(isDefined)), }); } diff --git a/src/app/query/models/crypto-asset.model.ts b/src/app/query/models/crypto-asset.model.ts new file mode 100644 index 00000000000..ac2ecaa1389 --- /dev/null +++ b/src/app/query/models/crypto-asset.model.ts @@ -0,0 +1,59 @@ +import type { + BaseCryptoAssetInfo, + Blockchains, + Brc20CryptoAssetInfo, + BtcCryptoAssetBalance, + BtcCryptoAssetInfo, + CryptoAssetBalance, + CryptoAssetType, + MarketData, + Sip10CryptoAssetInfo, + StxCryptoAssetBalance, + StxCryptoAssetInfo, +} from '@leather-wallet/models'; + +interface AccountCryptoAssetDetails { + balance: CryptoAssetBalance; + chain: Blockchains; + info: BaseCryptoAssetInfo; + marketData: MarketData | null; + type: CryptoAssetType; +} + +export function createAccountCryptoAssetWithDetailsFactory( + args: T +): T { + const { balance, chain, info, marketData, type } = args; + return { + balance, + chain, + info, + marketData, + type, + } as T; +} + +export interface BtcAccountCryptoAssetWithDetails extends AccountCryptoAssetDetails { + balance: BtcCryptoAssetBalance; + info: BtcCryptoAssetInfo; +} + +export interface StxAccountCryptoAssetWithDetails extends AccountCryptoAssetDetails { + balance: StxCryptoAssetBalance; + info: StxCryptoAssetInfo; +} + +export interface Brc20AccountCryptoAssetWithDetails extends AccountCryptoAssetDetails { + holderAddress: string; + info: Brc20CryptoAssetInfo; +} + +export interface Sip10AccountCryptoAssetWithDetails extends AccountCryptoAssetDetails { + info: Sip10CryptoAssetInfo; +} + +export type AccountCryptoAssetWithDetails = + | BtcAccountCryptoAssetWithDetails + | StxAccountCryptoAssetWithDetails + | Brc20AccountCryptoAssetWithDetails + | Sip10AccountCryptoAssetWithDetails; diff --git a/src/app/query/stacks/balance/account-balance.hooks.ts b/src/app/query/stacks/balance/account-balance.hooks.ts new file mode 100644 index 00000000000..0c85fa37011 --- /dev/null +++ b/src/app/query/stacks/balance/account-balance.hooks.ts @@ -0,0 +1,61 @@ +import type { StxCryptoAssetBalance } from '@leather-wallet/models'; +import BigNumber from 'bignumber.js'; + +import { AccountBalanceStxKeys, type AddressBalanceResponse } from '@shared/models/account.model'; +import { Money, createMoney } from '@shared/models/money.model'; + +import { subtractMoney, sumMoney } from '@app/common/money/calculate-money'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { accountBalanceStxKeys } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; + +import { + useMempoolTxsInboundBalance, + useMempoolTxsOutboundBalance, +} from '../mempool/mempool.hooks'; +import { useStacksAccountBalanceQuery } from './account-balance.query'; + +function createStxMoney(resp: AddressBalanceResponse) { + return Object.fromEntries( + accountBalanceStxKeys.map(key => [key, { amount: new BigNumber(resp.stx[key]), symbol: 'STX' }]) + ) as Record; +} + +function createStxCryptoAssetBalance( + stxMoney: Record, + inboundBalance: Money, + outboundBalance: Money +): StxCryptoAssetBalance { + const totalBalance = createMoney(stxMoney.balance.amount, 'STX'); + const unlockedBalance = subtractMoney(stxMoney.balance, stxMoney.locked); + + return { + availableBalance: subtractMoney(totalBalance, outboundBalance), + availableUnlockedBalance: subtractMoney(unlockedBalance, outboundBalance), + inboundBalance, + lockedBalance: createMoney(stxMoney.locked.amount, 'STX'), + outboundBalance, + pendingBalance: subtractMoney(sumMoney([totalBalance, inboundBalance]), outboundBalance), + totalBalance, + unlockedBalance, + }; +} + +export function useStxCryptoAssetBalance(address: string) { + const inboundBalance = useMempoolTxsInboundBalance(address); + const outboundBalance = useMempoolTxsOutboundBalance(address); + return useStacksAccountBalanceQuery(address, { + select: resp => + createStxCryptoAssetBalance(createStxMoney(resp), inboundBalance, outboundBalance), + }); +} + +export function useCurrentStxAvailableUnlockedBalance() { + const address = useCurrentStacksAccountAddress(); + return useStxCryptoAssetBalance(address).data?.unlockedBalance ?? createMoney(0, 'STX'); +} + +export function useStacksAccountBalanceFungibleTokens(address: string) { + return useStacksAccountBalanceQuery(address, { + select: resp => resp.fungible_tokens, + }); +} diff --git a/src/app/query/stacks/balance/stx-balance.query.ts b/src/app/query/stacks/balance/account-balance.query.ts similarity index 100% rename from src/app/query/stacks/balance/stx-balance.query.ts rename to src/app/query/stacks/balance/account-balance.query.ts diff --git a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts deleted file mode 100644 index 2316671b187..00000000000 --- a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import BigNumber from 'bignumber.js'; - -import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; - -import { formatContractId } from '@app/common/utils'; -import { useToast } from '@app/features/toasts/use-toast'; -import { - type SwapAsset, - useAlexCurrencyPriceAsMarketData, - useAlexSwappableAssets, -} from '@app/query/common/alex-sdk/alex-sdk.hooks'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -import { useGetFungibleTokenMetadataListQuery } from '../tokens/fungible-tokens/fungible-token-metadata.query'; -import { isFtAsset } from '../tokens/token-metadata.utils'; -import { - addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance, - convertFtBalancesToStacksFungibleTokenAssetBalanceType, - createStacksCryptoCurrencyAssetTypeWrapper, - createStacksFtCryptoAssetBalanceTypeWrapper, -} from './stacks-ft-balances.utils'; -import { parseBalanceResponse } from './stx-balance.hooks'; -import { useStacksAccountBalanceQuery } from './stx-balance.query'; - -export function useStacksCryptoCurrencyAssetBalance(address: string) { - return useStacksAccountBalanceQuery(address, { - select: resp => - createStacksCryptoCurrencyAssetTypeWrapper(parseBalanceResponse(resp).stx.unlockedStx.amount), - }); -} - -function useStacksFungibleTokenAssetBalances(address: string) { - return useStacksAccountBalanceQuery(address, { - select: resp => convertFtBalancesToStacksFungibleTokenAssetBalanceType(resp.fungible_tokens), - }); -} - -function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) { - const { data: initializedAssetBalances = [] } = useStacksFungibleTokenAssetBalances(address); - const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); - - const ftAssetsMetadata = useGetFungibleTokenMetadataListQuery( - initializedAssetBalances.map(assetBalance => - formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName) - ) - ); - - return useMemo( - () => - initializedAssetBalances.map((assetBalance, i) => { - const metadata = ftAssetsMetadata[i].data; - if (!(metadata && isFtAsset(metadata))) return assetBalance; - return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( - assetBalance, - metadata, - priceAsMarketData( - formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName), - metadata.symbol - ) - ); - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [initializedAssetBalances] - ); -} - -export function useStacksFungibleTokenAssetBalance(contractId: string) { - const toast = useToast(); - const account = useCurrentStacksAccount(); - const navigate = useNavigate(); - const assetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(account?.address ?? ''); - - return useMemo(() => { - const balance = assetBalances.find(assetBalance => - assetBalance.asset.contractId.includes(contractId) - ); - if (!balance) { - toast.error('Unable to find balance by contract id'); - navigate('..'); - } - return balance ?? createStacksFtCryptoAssetBalanceTypeWrapper(new BigNumber(0), contractId); - }, [assetBalances, contractId, navigate, toast]); -} - -export function useTransferableStacksFungibleTokenAssetBalances( - address: string -): StacksFungibleTokenAssetBalance[] { - const assetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(address); - return useMemo( - () => assetBalances.filter(assetBalance => assetBalance.asset.canTransfer), - [assetBalances] - ); -} - -function filterStacksFungibleTokens( - assetBalances: StacksFungibleTokenAssetBalance[], - swapAssets: SwapAsset[], - filter: StacksFtTokensFilter -) { - if (filter === 'supported') { - return assetBalances.filter(assetBalance => - swapAssets.some(swapAsset => swapAsset.principal.includes(assetBalance.asset.contractAddress)) - ); - } - - if (filter === 'unsupported') { - return assetBalances.filter( - assetBalance => - !swapAssets.some(swapAsset => - swapAsset.principal.includes(assetBalance.asset.contractAddress) - ) - ); - } - - return assetBalances; -} - -/** - * @see https://github.com/leather-wallet/issues/issues/16 - */ -type StacksFtTokensFilter = 'all' | 'supported' | 'unsupported'; - -interface useFilteredStacksFungibleTokenListArgs { - address: string; - filter?: StacksFtTokensFilter; -} -export function useFilteredStacksFungibleTokenList({ - address, - filter = 'all', -}: useFilteredStacksFungibleTokenListArgs) { - const stacksFtAssetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(address); - const { data: swapAssets = [] } = useAlexSwappableAssets(); - - return useMemo(() => { - return filterStacksFungibleTokens(stacksFtAssetBalances, swapAssets, filter); - }, [stacksFtAssetBalances, swapAssets, filter]); -} diff --git a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts b/src/app/query/stacks/balance/stacks-ft-balances.utils.ts deleted file mode 100644 index 6d88699c766..00000000000 --- a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { FtMetadataResponse } from '@hirosystems/token-metadata-api-client'; -import BigNumber from 'bignumber.js'; - -import { STX_DECIMALS } from '@shared/constants'; -import type { AccountBalanceResponseBigNumber } from '@shared/models/account.model'; -import type { - StacksCryptoCurrencyAssetBalance, - StacksFungibleTokenAssetBalance, -} from '@shared/models/crypto-asset-balance.model'; -import { type MarketData } from '@shared/models/market.model'; -import { createMoney } from '@shared/models/money.model'; - -import { isTransferableStacksFungibleTokenAsset } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; -import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; - -export function createStacksCryptoCurrencyAssetTypeWrapper( - balance: BigNumber -): StacksCryptoCurrencyAssetBalance { - return { - blockchain: 'stacks', - type: 'crypto-currency', - balance: createMoney(balance, 'STX'), - asset: { - decimals: STX_DECIMALS, - hasMemo: true, - name: 'Stacks', - symbol: 'STX', - }, - }; -} - -export function createStacksFtCryptoAssetBalanceTypeWrapper( - balance: BigNumber, - contractId: string -): StacksFungibleTokenAssetBalance { - const { address, contractName, assetName } = getAssetStringParts(contractId); - return { - blockchain: 'stacks', - type: 'fungible-token', - balance: createMoney(balance, '', 0), - asset: { - contractId, - canTransfer: false, - contractAddress: address, - contractAssetName: assetName, - contractName, - decimals: 0, - hasMemo: false, - imageCanonicalUri: '', - name: '', - marketData: null, - symbol: '', - }, - }; -} - -export function convertFtBalancesToStacksFungibleTokenAssetBalanceType( - ftBalances: AccountBalanceResponseBigNumber['fungible_tokens'] -) { - return ( - Object.entries(ftBalances) - .map(([key, value]) => { - const balance = new BigNumber(value.balance); - return createStacksFtCryptoAssetBalanceTypeWrapper(balance, key); - }) - // Assets users have traded will persist in the api response - .filter(assetBalance => !assetBalance?.balance.amount.isEqualTo(0)) - ); -} - -export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( - assetBalance: StacksFungibleTokenAssetBalance, - metadata: FtMetadataResponse, - marketData: MarketData | null -) { - return { - ...assetBalance, - balance: createMoney( - assetBalance.balance.amount, - metadata.symbol ?? '', - metadata.decimals ?? 0 - ), - asset: { - ...assetBalance.asset, - canTransfer: isTransferableStacksFungibleTokenAsset(assetBalance.asset), - decimals: metadata.decimals ?? 0, - hasMemo: isTransferableStacksFungibleTokenAsset(assetBalance.asset), - imageCanonicalUri: metadata.image_canonical_uri ?? '', - name: metadata.name ?? '', - marketData, - symbol: metadata.symbol ?? '', - }, - }; -} - -export function getStacksFungibleTokenCurrencyAssetBalance( - selectedAssetBalance?: StacksCryptoCurrencyAssetBalance | StacksFungibleTokenAssetBalance -) { - return selectedAssetBalance?.type === 'fungible-token' && selectedAssetBalance.asset.canTransfer - ? selectedAssetBalance - : undefined; -} diff --git a/src/app/query/stacks/balance/stx-balance.hooks.ts b/src/app/query/stacks/balance/stx-balance.hooks.ts deleted file mode 100644 index 82736e0cb5c..00000000000 --- a/src/app/query/stacks/balance/stx-balance.hooks.ts +++ /dev/null @@ -1,42 +0,0 @@ -import BigNumber from 'bignumber.js'; - -import { - AccountBalanceStxKeys, - AccountStxBalanceBigNumber, - AddressBalanceResponse, -} from '@shared/models/account.model'; -import { Money, createMoney } from '@shared/models/money.model'; - -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { accountBalanceStxKeys } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; - -import { useStacksAccountBalanceQuery } from './stx-balance.query'; - -export function parseBalanceResponse(balances: AddressBalanceResponse) { - const stxMoney = Object.fromEntries( - accountBalanceStxKeys.map(key => [ - key, - { amount: new BigNumber(balances.stx[key]), symbol: 'STX' }, - ]) - ) as Record; - - const stx: AccountStxBalanceBigNumber & { unlockedStx: Money } = { - ...balances.stx, - ...stxMoney, - balance: createMoney(stxMoney.balance.amount, 'STX'), - locked: createMoney(stxMoney.locked.amount, 'STX'), - unlockedStx: createMoney(stxMoney.balance.amount.minus(stxMoney.locked.amount), 'STX'), - }; - return { ...balances, stx }; -} - -export function useStacksAccountBalances(address: string) { - return useStacksAccountBalanceQuery(address, { - select: resp => parseBalanceResponse(resp), - }); -} - -export function useCurrentStacksAccountBalances() { - const account = useCurrentStacksAccount(); - return useStacksAccountBalances(account?.address ?? ''); -} diff --git a/src/app/query/stacks/bns/bns.hooks.ts b/src/app/query/stacks/bns/bns.hooks.ts index d0aea0e3fc6..1e428ee51cf 100644 --- a/src/app/query/stacks/bns/bns.hooks.ts +++ b/src/app/query/stacks/bns/bns.hooks.ts @@ -1,11 +1,11 @@ import { logger } from '@shared/logger'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useGetBnsNamesOwnedByAddress } from './bns.query'; export function useCurrentAccountNames() { - const principal = useCurrentAccountStxAddressState(); + const principal = useCurrentStacksAccountAddress(); return useGetBnsNamesOwnedByAddress(principal, { select: resp => { if (principal === '') logger.error('No principal defined'); diff --git a/src/app/query/stacks/mempool/mempool.hooks.ts b/src/app/query/stacks/mempool/mempool.hooks.ts index 1557b6ab0c6..947e9050cbb 100644 --- a/src/app/query/stacks/mempool/mempool.hooks.ts +++ b/src/app/query/stacks/mempool/mempool.hooks.ts @@ -1,21 +1,16 @@ import { useMemo } from 'react'; -import { - MempoolTokenTransferTransaction, - MempoolTransaction, -} from '@stacks/stacks-blockchain-api-types'; -import BigNumber from 'bignumber.js'; - -import { createMoney } from '@shared/models/money.model'; +import { MempoolTransaction } from '@stacks/stacks-blockchain-api-types'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { increaseValueByOneMicroStx } from '@app/common/math/helpers'; import { microStxToStx } from '@app/common/money/unit-conversion'; import { useTransactionsById } from '@app/query/stacks/transactions/transactions-by-id.query'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useStacksConfirmedTransactions } from '../transactions/transactions-with-transfers.hooks'; import { useAccountMempoolQuery } from './mempool.query'; +import { calculatePendingTxsMoneyBalance } from './mempool.utils'; const droppedCache = new Map(); @@ -50,7 +45,7 @@ function useAccountMempoolTransactions(address: string) { } export function useStacksPendingTransactions() { - const address = useCurrentAccountStxAddressState(); + const address = useCurrentStacksAccountAddress(); const { query, transactions } = useAccountMempoolTransactions(address ?? ''); return useMemo(() => { const nonEmptyTransactions = transactions.filter(tx => !!tx) as MempoolTransaction[]; @@ -59,32 +54,32 @@ export function useStacksPendingTransactions() { } export function useCurrentAccountMempool() { - const address = useCurrentAccountStxAddressState(); + const address = useCurrentStacksAccountAddress(); return useAccountMempoolQuery(address ?? ''); } -export function useCurrentAccountMempoolTransactionsBalance() { - const address = useCurrentAccountStxAddressState(); +export function useMempoolTxsInboundBalance(address: string) { const { transactions: pendingTransactions } = useStacksPendingTransactions(); const confirmedTxs = useStacksConfirmedTransactions(); - const pendingOutboundTxs = pendingTransactions.filter(tx => { - if (confirmedTxs.some(confirmedTx => confirmedTx.nonce === tx.nonce)) { - return false; - } - return tx.tx_type === 'token_transfer' && tx.sender_address === address; - }) as unknown as MempoolTokenTransferTransaction[]; + return calculatePendingTxsMoneyBalance({ + address, + confirmedTxs, + pendingTxs: pendingTransactions, + type: 'inbound', + }); +} - const tokenTransferTxsBalance = pendingOutboundTxs.reduce( - (acc, tx) => acc.plus(tx.token_transfer.amount), - new BigNumber(0) - ); - const pendingTxsFeesBalance = pendingOutboundTxs.reduce( - (acc, tx) => acc.plus(tx.fee_rate), - new BigNumber(0) - ); +export function useMempoolTxsOutboundBalance(address: string) { + const { transactions: pendingTransactions } = useStacksPendingTransactions(); + const confirmedTxs = useStacksConfirmedTransactions(); - return createMoney(tokenTransferTxsBalance.plus(pendingTxsFeesBalance), 'STX'); + return calculatePendingTxsMoneyBalance({ + address, + confirmedTxs, + pendingTxs: pendingTransactions, + type: 'outbound', + }); } export function useStacksValidateFeeByNonce() { diff --git a/src/app/query/stacks/mempool/mempool.utils.ts b/src/app/query/stacks/mempool/mempool.utils.ts new file mode 100644 index 00000000000..c2c301a1a95 --- /dev/null +++ b/src/app/query/stacks/mempool/mempool.utils.ts @@ -0,0 +1,62 @@ +import type { + MempoolTokenTransferTransaction, + MempoolTransaction, + Transaction, +} from '@stacks/stacks-blockchain-api-types'; + +import { createMoney } from '@shared/models/money.model'; + +import { sumNumbers } from '@app/common/math/helpers'; + +type PendingTransactionType = 'inbound' | 'outbound'; + +function getInboundPendingTxs( + address: string, + confirmedTxs: Transaction[], + pendingTxs: MempoolTransaction[] +) { + return pendingTxs.filter(tx => { + if (confirmedTxs.some(confirmedTx => confirmedTx.nonce === tx.nonce)) { + return false; + } + return tx.tx_type === 'token_transfer' && tx.token_transfer.recipient_address === address; + }) as unknown as MempoolTokenTransferTransaction[]; +} + +function getOutboundPendingTxs( + address: string, + confirmedTxs: Transaction[], + pendingTxs: MempoolTransaction[] +) { + return pendingTxs.filter(tx => { + if (confirmedTxs.some(confirmedTx => confirmedTx.nonce === tx.nonce)) { + return false; + } + return tx.tx_type === 'token_transfer' && tx.sender_address === address; + }) as unknown as MempoolTokenTransferTransaction[]; +} + +interface CalculatePendingTxsMoneyBalanceArgs { + address: string; + confirmedTxs: Transaction[]; + pendingTxs: MempoolTransaction[]; + type: PendingTransactionType; +} +export function calculatePendingTxsMoneyBalance({ + address, + confirmedTxs, + pendingTxs, + type, +}: CalculatePendingTxsMoneyBalanceArgs) { + const filteredPendingTxs = + type === 'inbound' + ? getInboundPendingTxs(address, confirmedTxs, pendingTxs) + : getOutboundPendingTxs(address, confirmedTxs, pendingTxs); + + const tokenTransferTxsBalance = sumNumbers( + filteredPendingTxs.map(tx => Number(tx.token_transfer.amount)) + ); + const pendingTxsFeesBalance = sumNumbers(filteredPendingTxs.map(tx => Number(tx.fee_rate))); + + return createMoney(tokenTransferTxsBalance.plus(pendingTxsFeesBalance), 'STX'); +} diff --git a/src/app/query/stacks/nonce/account-nonces.query.ts b/src/app/query/stacks/nonce/account-nonces.query.ts index e4ca85d3fd7..a26bb36490b 100644 --- a/src/app/query/stacks/nonce/account-nonces.query.ts +++ b/src/app/query/stacks/nonce/account-nonces.query.ts @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import PQueue from 'p-queue'; import { AppUseQueryConfig } from '@app/query/query-config'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; @@ -36,7 +36,7 @@ type FetchAccountNoncesResp = Awaited( options?: AppUseQueryConfig ) { - const principal = useCurrentAccountStxAddressState(); + const principal = useCurrentStacksAccountAddress(); const network = useCurrentNetworkState(); const client = useStacksClient(); const limiter = useHiroApiRateLimiter(); diff --git a/src/app/query/stacks/sip10/sip10-tokens.hooks.ts b/src/app/query/stacks/sip10/sip10-tokens.hooks.ts new file mode 100644 index 00000000000..d50030f12e1 --- /dev/null +++ b/src/app/query/stacks/sip10/sip10-tokens.hooks.ts @@ -0,0 +1,124 @@ +import { useMemo } from 'react'; + +import type { FtMetadataResponse } from '@hirosystems/token-metadata-api-client'; +import type { Sip10CryptoAssetInfo } from '@leather-wallet/models'; +import BigNumber from 'bignumber.js'; + +import { createMoney } from '@shared/models/money.model'; +import { isDefined, isUndefined } from '@shared/utils'; + +import { getTicker, pullContractIdFromIdentity } from '@app/common/utils'; +import { + useAlexCurrencyPriceAsMarketData, + useAlexSwappableAssets, +} from '@app/query/common/alex-sdk/alex-sdk.hooks'; +import { + type AccountCryptoAssetWithDetails, + type Sip10AccountCryptoAssetWithDetails, + createAccountCryptoAssetWithDetailsFactory, +} from '@app/query/models/crypto-asset.model'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; + +import { useStacksAccountBalanceFungibleTokens } from '../balance/account-balance.hooks'; +import { useStacksAccountFungibleTokenMetadata } from '../token-metadata/fungible-tokens/fungible-token-metadata.hooks'; +import { isFtAsset } from '../token-metadata/token-metadata.utils'; +import { + type Sip10CryptoAssetFilter, + filterSip10AccountCryptoAssetsWithDetails, +} from './sip10-tokens.utils'; + +export function isTransferableSip10Token(asset: Partial) { + return !isUndefined(asset.decimals) && !isUndefined(asset.name) && !isUndefined(asset.symbol); +} + +export function getSip10InfoFromAsset(asset: AccountCryptoAssetWithDetails) { + if ('contractId' in asset.info) return asset.info; + return; +} + +function createSip10CryptoAssetInfo( + contractId: string, + key: string, + token: FtMetadataResponse +): Sip10CryptoAssetInfo { + const { assetName, contractName } = getAssetStringParts(key); + const name = token.name ? token.name : assetName; + + return { + canTransfer: isTransferableSip10Token(token), + contractId, + contractName, + decimals: token.decimals ?? 0, + hasMemo: isTransferableSip10Token(token), + imageCanonicalUri: token.image_canonical_uri ?? '', + name, + symbol: token.symbol ?? getTicker(name), + }; +} + +function useSip10AccountCryptoAssetsWithDetails(address: string) { + const { data: tokens = {} } = useStacksAccountBalanceFungibleTokens(address); + const tokenMetadata = useStacksAccountFungibleTokenMetadata(tokens); + const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); + + return useMemo( + () => + Object.entries(tokens) + .map(([key, value], i) => { + const token = tokenMetadata[i].data; + if (!(token && isFtAsset(token))) return; + const contractId = pullContractIdFromIdentity(key); + + return createAccountCryptoAssetWithDetailsFactory({ + balance: { + availableBalance: createMoney( + new BigNumber(value.balance), + token.symbol ?? getTicker(token.name ?? ''), + token.decimals ?? 0 + ), + }, + chain: 'stacks', + info: createSip10CryptoAssetInfo(contractId, key, token), + marketData: priceAsMarketData(contractId, token.symbol), + type: 'sip-10', + }); + }) + .filter(isDefined) + .filter(asset => asset.balance.availableBalance.amount.isGreaterThan(0)), + [priceAsMarketData, tokenMetadata, tokens] + ); +} + +export function useSip10CryptoAssetWithDetails(contractId: string) { + const address = useCurrentStacksAccountAddress(); + const assets = useSip10AccountCryptoAssetsWithDetails(address); + return useMemo( + () => assets.find(asset => asset.info.contractId === contractId), + [assets, contractId] + ); +} + +interface UseFilteredSip10AccountCryptoAssetsWithDetailsArgs { + address: string; + filter?: Sip10CryptoAssetFilter; +} +export function useFilteredSip10AccountCryptoAssetsWithDetails({ + address, + filter = 'all', +}: UseFilteredSip10AccountCryptoAssetsWithDetailsArgs) { + const assets = useSip10AccountCryptoAssetsWithDetails(address); + const { data: swapAssets = [] } = useAlexSwappableAssets(); + + return useMemo( + () => filterSip10AccountCryptoAssetsWithDetails(assets, swapAssets, filter), + [assets, swapAssets, filter] + ); +} + +export function useTransferableSip10CryptoAssetsWithDetails( + address: string +): Sip10AccountCryptoAssetWithDetails[] { + const assets = useSip10AccountCryptoAssetsWithDetails(address); + return useMemo(() => assets.filter(asset => asset.info.canTransfer), [assets]); +} diff --git a/src/app/query/stacks/sip10/sip10-tokens.spec.ts b/src/app/query/stacks/sip10/sip10-tokens.spec.ts new file mode 100644 index 00000000000..6e92dd062b2 --- /dev/null +++ b/src/app/query/stacks/sip10/sip10-tokens.spec.ts @@ -0,0 +1,32 @@ +import type { FtMetadataResponse } from '@hirosystems/token-metadata-api-client'; + +import { isTransferableSip10Token } from './sip10-tokens.hooks'; + +describe(isTransferableSip10Token.name, () => { + test('assets with a name, symbol and decimals are allowed to be transferred', () => { + const asset: Partial = { + decimals: 9, + name: 'SteLLa the Cat', + symbol: 'CAT', + }; + expect(isTransferableSip10Token(asset)).toBeTruthy(); + }); + + test('a token with no decimals is transferable', () => { + const asset: Partial = { + decimals: 0, + name: 'SteLLa the Cat', + symbol: 'CAT', + }; + expect(isTransferableSip10Token(asset)).toBeTruthy(); + }); + + test('assets missing either name, symbol or decimals may not be transferred', () => { + const asset: Partial = { + name: 'Test token', + symbol: 'TEST', + decimals: undefined, + }; + expect(isTransferableSip10Token(asset)).toBeFalsy(); + }); +}); diff --git a/src/app/query/stacks/sip10/sip10-tokens.utils.ts b/src/app/query/stacks/sip10/sip10-tokens.utils.ts new file mode 100644 index 00000000000..015f5154f60 --- /dev/null +++ b/src/app/query/stacks/sip10/sip10-tokens.utils.ts @@ -0,0 +1,22 @@ +import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; +import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model'; +import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; + +export type Sip10CryptoAssetFilter = 'all' | 'supported' | 'unsupported'; + +export function filterSip10AccountCryptoAssetsWithDetails( + assets: Sip10AccountCryptoAssetWithDetails[], + swapAssets: SwapAsset[], + filter: Sip10CryptoAssetFilter +) { + return assets.filter(asset => { + const { address: contractAddress } = getAssetStringParts(asset.info.contractId); + if (filter === 'supported') { + return swapAssets.some(swapAsset => swapAsset.principal.includes(contractAddress)); + } + if (filter === 'unsupported') { + return !swapAssets.some(swapAsset => swapAsset.principal.includes(contractAddress)); + } + return assets; + }); +} diff --git a/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts b/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts new file mode 100644 index 00000000000..a1552ace86e --- /dev/null +++ b/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts @@ -0,0 +1,58 @@ +import type { StxCryptoAssetInfo } from '@leather-wallet/models'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query'; + +import { STX_DECIMALS } from '@shared/constants'; +import { createMoney } from '@shared/models/money.model'; +import { isUndefined } from '@shared/utils'; + +import { + type StxAccountCryptoAssetWithDetails, + createAccountCryptoAssetWithDetailsFactory, +} from '@app/query/models/crypto-asset.model'; + +import { useStxCryptoAssetBalance } from '../balance/account-balance.hooks'; + +const stxCryptoAssetInfo: StxCryptoAssetInfo = { + decimals: STX_DECIMALS, + hasMemo: true, + name: 'stacks', + symbol: 'STX', +}; + +const stxCryptoAssetBalancePlaceholder = { + availableBalance: createMoney(0, 'STX'), + availableUnlockedBalance: createMoney(0, 'STX'), + inboundBalance: createMoney(0, 'STX'), + lockedBalance: createMoney(0, 'STX'), + outboundBalance: createMoney(0, 'STX'), + pendingBalance: createMoney(0, 'STX'), + totalBalance: createMoney(0, 'STX'), + unlockedBalance: createMoney(0, 'STX'), +}; + +export const stxCryptoAssetPlaceholder = + createAccountCryptoAssetWithDetailsFactory({ + balance: stxCryptoAssetBalancePlaceholder, + chain: 'stacks', + info: stxCryptoAssetInfo, + marketData: null, + type: 'stx', + }); + +export function useStxAccountCryptoAssetWithDetails(address: string) { + const { data: balance, isInitialLoading } = useStxCryptoAssetBalance(address); + const marketData = useCryptoCurrencyMarketDataMeanAverage('STX'); + + if (isUndefined(balance)) return { asset: stxCryptoAssetPlaceholder, isInitialLoading }; + + return { + asset: createAccountCryptoAssetWithDetailsFactory({ + balance, + chain: 'stacks', + info: stxCryptoAssetInfo, + marketData, + type: 'stx', + }), + isInitialLoading, + }; +} diff --git a/src/app/query/stacks/stx20/stx20-tokens.hooks.ts b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts index f3394d4a650..ce3231a5b8b 100644 --- a/src/app/query/stacks/stx20/stx20-tokens.hooks.ts +++ b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts @@ -5,7 +5,7 @@ import { createMoney } from '@shared/models/money.model'; import type { Stx20Balance, Stx20Token } from '../stacks-client'; import { useStx20BalancesQuery } from './stx20-tokens.query'; -function makeStx20Token(token: Stx20Balance): Stx20Token { +function createStx20Token(token: Stx20Balance): Stx20Token { return { balance: createMoney(new BigNumber(token.balance), token.ticker, 0), marketData: null, @@ -15,6 +15,6 @@ function makeStx20Token(token: Stx20Balance): Stx20Token { export function useStx20Tokens(address: string) { return useStx20BalancesQuery(address, { - select: resp => resp.map(balance => makeStx20Token(balance)), + select: resp => resp.map(balance => createStx20Token(balance)), }); } diff --git a/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts new file mode 100644 index 00000000000..ee8966d866c --- /dev/null +++ b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts @@ -0,0 +1,12 @@ +import type { AddressBalanceResponse } from '@shared/models/account.model'; + +import { pullContractIdFromIdentity } from '@app/common/utils'; + +import { useGetFungibleTokenMetadataListQuery } from './fungible-token-metadata.query'; + +export function useStacksAccountFungibleTokenMetadata( + tokens: AddressBalanceResponse['fungible_tokens'] +) { + const tokenContractIds = Object.keys(tokens).map(key => pullContractIdFromIdentity(key)); + return useGetFungibleTokenMetadataListQuery(tokenContractIds); +} diff --git a/src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts similarity index 100% rename from src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts rename to src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts diff --git a/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-holdings.query.ts b/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-holdings.query.ts similarity index 100% rename from src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-holdings.query.ts rename to src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-holdings.query.ts diff --git a/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.hooks.ts b/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.hooks.ts similarity index 100% rename from src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.hooks.ts rename to src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.hooks.ts diff --git a/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts b/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts similarity index 100% rename from src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts rename to src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts diff --git a/src/app/query/stacks/tokens/token-metadata.utils.ts b/src/app/query/stacks/token-metadata/token-metadata.utils.ts similarity index 100% rename from src/app/query/stacks/tokens/token-metadata.utils.ts rename to src/app/query/stacks/token-metadata/token-metadata.utils.ts diff --git a/src/app/query/stacks/transactions/transactions-with-transfers.query.ts b/src/app/query/stacks/transactions/transactions-with-transfers.query.ts index 73659faee96..a999bbdd02b 100644 --- a/src/app/query/stacks/transactions/transactions-with-transfers.query.ts +++ b/src/app/query/stacks/transactions/transactions-with-transfers.query.ts @@ -3,7 +3,7 @@ import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query import { DEFAULT_LIST_LIMIT } from '@shared/constants'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; @@ -18,7 +18,7 @@ const queryOptions: UseQueryOptions = { }; export function useGetAccountTransactionsWithTransfersQuery() { - const principal = useCurrentAccountStxAddressState(); + const principal = useCurrentStacksAccountAddress(); const { chain } = useCurrentNetworkState(); const client = useStacksClient(); const limiter = useHiroApiRateLimiter(); diff --git a/src/app/store/accounts/blockchain/bitcoin/bitcoin.ledger.ts b/src/app/store/accounts/blockchain/bitcoin/bitcoin.ledger.ts index e1647fe0bbb..425caa9fbff 100644 --- a/src/app/store/accounts/blockchain/bitcoin/bitcoin.ledger.ts +++ b/src/app/store/accounts/blockchain/bitcoin/bitcoin.ledger.ts @@ -8,6 +8,8 @@ import { import { selectDefaultWalletBitcoinKeys } from '@app/store/ledger/bitcoin/bitcoin-key.slice'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; +// TODO: Asset refactor: remove if determined unnecessary +// ts-unused-exports:disable-next-line export function useHasBitcoinLedgerKeychain() { const network = useCurrentNetwork(); const accounts = useSelector(selectDefaultWalletBitcoinKeys); diff --git a/src/app/store/accounts/blockchain/stacks/stacks-account.hooks.ts b/src/app/store/accounts/blockchain/stacks/stacks-account.hooks.ts index 70af3b031fe..5e1cafc412f 100644 --- a/src/app/store/accounts/blockchain/stacks/stacks-account.hooks.ts +++ b/src/app/store/accounts/blockchain/stacks/stacks-account.hooks.ts @@ -17,6 +17,9 @@ export function useStacksAccounts() { return useAtomValue(stacksAccountState); } +// TODO: Refactor, we need to use conditional empty strings everywhere +// Can we remove these atoms? + // Comment below from original atom. This pattern encourages view level // implementation details to leak into the state structure. Do not do this. // This contains the state of the current account: @@ -38,7 +41,7 @@ export function useCurrentStacksAccount() { }, [accountIndex, accounts, hasSwitched, signatureIndex, txIndex]); } -export function useCurrentAccountStxAddressState() { +export function useCurrentStacksAccountAddress() { return useCurrentStacksAccount()?.address ?? ''; } diff --git a/src/app/store/accounts/blockchain/utils.ts b/src/app/store/accounts/blockchain/utils.ts index 01b497cbab3..e1f9ca75c53 100644 --- a/src/app/store/accounts/blockchain/utils.ts +++ b/src/app/store/accounts/blockchain/utils.ts @@ -1,18 +1,22 @@ import { useCallback } from 'react'; +import type { Blockchains } from '@leather-wallet/models'; + import { useHasCurrentBitcoinAccount } from './bitcoin/bitcoin.hooks'; import { useHasStacksLedgerKeychain } from './stacks/stacks.hooks'; +// TODO: Asset refactor: remove if determined unnecessary +// ts-unused-exports:disable-next-line export function useCheckLedgerBlockchainAvailable() { const hasBitcoinLedgerKeys = useHasCurrentBitcoinAccount(); const hasStacksLedgerKeys = useHasStacksLedgerKeychain(); return useCallback( - (symbol: string) => { - if (symbol === 'bitcoin') { + (chain: Blockchains) => { + if (chain === 'bitcoin') { return hasBitcoinLedgerKeys; } - if (symbol === 'stacks') { + if (chain === 'stacks') { return hasStacksLedgerKeys; } return false; diff --git a/src/app/store/transactions/post-conditions.hooks.ts b/src/app/store/transactions/post-conditions.hooks.ts index 15133fbd322..9162e91c76b 100644 --- a/src/app/store/transactions/post-conditions.hooks.ts +++ b/src/app/store/transactions/post-conditions.hooks.ts @@ -7,9 +7,9 @@ import { getPostCondition, handlePostConditions, } from '@app/common/transactions/stacks/post-condition.utils'; -import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; -import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query'; +import { isFtAsset } from '@app/query/stacks/token-metadata/token-metadata.utils'; +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useTransactionRequestState } from './requests.hooks'; @@ -34,7 +34,7 @@ export function usePostConditionModeState() { export function usePostConditionState() { const payload = useTransactionRequestState(); - const address = useCurrentAccountStxAddressState(); + const address = useCurrentStacksAccountAddress(); return useMemo(() => formatPostConditionState(payload, address), [address, payload]); } diff --git a/src/app/store/transactions/token-transfer.hooks.ts b/src/app/store/transactions/token-transfer.hooks.ts index 792518f2574..e60c6138d6b 100644 --- a/src/app/store/transactions/token-transfer.hooks.ts +++ b/src/app/store/transactions/token-transfer.hooks.ts @@ -1,6 +1,7 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { useAsync } from 'react-async-hook'; +import type { Sip10CryptoAssetInfo } from '@leather-wallet/models'; import { bytesToHex } from '@stacks/common'; import { TransactionTypes } from '@stacks/connect'; import { @@ -16,7 +17,6 @@ import { uintCV, } from '@stacks/transactions'; -import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; import type { StacksSendFormValues, StacksTransactionFormValues } from '@shared/models/form.model'; import { stxToMicroStx } from '@app/common/money/unit-conversion'; @@ -25,33 +25,13 @@ import { GenerateUnsignedTransactionOptions, generateUnsignedTransaction, } from '@app/common/transactions/stacks/generate-unsigned-txs'; -import { getStacksFungibleTokenCurrencyAssetBalance } from '@app/query/stacks/balance/stacks-ft-balances.utils'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { useCurrentStacksNetworkState } from '@app/store/networks/networks.hooks'; import { makePostCondition } from '@app/store/transactions/transaction.hooks'; +import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; import { useCurrentStacksAccount } from '../accounts/blockchain/stacks/stacks-account.hooks'; -function useMakeFungibleTokenTransfer(assetBalance?: StacksFungibleTokenAssetBalance) { - const currentAccount = useCurrentStacksAccount(); - const network = useCurrentStacksNetworkState(); - - return useMemo(() => { - if (assetBalance && currentAccount && currentAccount.address) { - const { contractAddress, contractAssetName, contractName } = assetBalance.asset; - return { - assetBalance, - contractAssetName, - contractAddress, - contractName, - network, - stxAddress: currentAccount.address, - }; - } - return; - }, [assetBalance, currentAccount, network]); -} - export function useGenerateStxTokenTransferUnsignedTx() { const { data: nextNonce } = useNextNonce(); const network = useCurrentStacksNetworkState(); @@ -99,29 +79,16 @@ export function useStxTokenTransferUnsignedTxState(values?: StacksSendFormValues return tx.result; } -export function useGenerateFtTokenTransferUnsignedTx( - assetBalance: StacksFungibleTokenAssetBalance -) { +export function useGenerateFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAssetInfo) { const { data: nextNonce } = useNextNonce(); const account = useCurrentStacksAccount(); - - const tokenCurrencyAssetBalance = getStacksFungibleTokenCurrencyAssetBalance(assetBalance); - const assetTransferState = useMakeFungibleTokenTransfer(tokenCurrencyAssetBalance); + const network = useCurrentStacksNetworkState(); return useCallback( async (values?: StacksSendFormValues | StacksTransactionFormValues) => { - if (!assetTransferState || !account) return; - const { - assetBalance, - network, - contractAddress, - contractAssetName, - contractName, - stxAddress, - } = assetTransferState; + if (!account) return; const functionName = 'transfer'; - const recipient = values && 'recipient' in values ? createAddress(values.recipient || '') @@ -132,29 +99,31 @@ export function useGenerateFtTokenTransferUnsignedTx( ? someCV(bufferCVFromString(values.memo || '')) : noneCV(); - const realAmount = - assetBalance.type === 'fungible-token' - ? ftUnshiftDecimals(amount, assetBalance.asset.decimals || 0) - : amount; + const amountAsFractionalUnit = ftUnshiftDecimals(amount, assetInfo.decimals || 0); + const { + address: contractAddress, + contractName, + assetName, + } = getAssetStringParts(assetInfo.contractId); const postConditionOptions = { - amount: realAmount, + amount: amountAsFractionalUnit, contractAddress, - contractAssetName, + contractAssetName: assetName, contractName, - stxAddress, + stxAddress: account.address, }; const postConditions = [makePostCondition(postConditionOptions)]; // (transfer (uint principal principal) (response bool uint)) const functionArgs: ClarityValue[] = [ - uintCV(realAmount), - standardPrincipalCVFromAddress(createAddress(stxAddress)), + uintCV(amountAsFractionalUnit), + standardPrincipalCVFromAddress(createAddress(account.address)), standardPrincipalCVFromAddress(recipient), ]; - if (assetBalance.asset.hasMemo) { + if (assetInfo.hasMemo) { functionArgs.push(memo); } @@ -177,13 +146,20 @@ export function useGenerateFtTokenTransferUnsignedTx( return generateUnsignedTransaction(options); }, - [account, assetTransferState, nextNonce] + [ + account, + assetInfo.contractId, + assetInfo.decimals, + assetInfo.hasMemo, + network, + nextNonce?.nonce, + ] ); } -export function useFtTokenTransferUnsignedTx(assetBalance: StacksFungibleTokenAssetBalance) { - const generateTx = useGenerateFtTokenTransferUnsignedTx(assetBalance); +export function useFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAssetInfo) { const account = useCurrentStacksAccount(); + const generateTx = useGenerateFtTokenTransferUnsignedTx(assetInfo); const tx = useAsync(async () => generateTx(), [account]); return tx.result; diff --git a/src/app/store/transactions/transaction.hooks.ts b/src/app/store/transactions/transaction.hooks.ts index 245ef93f4e1..a2eaa416684 100644 --- a/src/app/store/transactions/transaction.hooks.ts +++ b/src/app/store/transactions/transaction.hooks.ts @@ -30,8 +30,8 @@ import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigat import { useToast } from '@app/features/toasts/use-toast'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { - useCurrentAccountStxAddressState, useCurrentStacksAccount, + useCurrentStacksAccountAddress, } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useCurrentStacksNetworkState } from '@app/store/networks/networks.hooks'; @@ -45,7 +45,7 @@ export function useTransactionPostConditions() { export function useUnsignedStacksTransactionBaseState() { const network = useCurrentStacksNetworkState(); const { data: nextNonce } = useNextNonce(); - const stxAddress = useCurrentAccountStxAddressState(); + const stxAddress = useCurrentStacksAccountAddress(); const payload = useTransactionRequestState(); const postConditions = useTransactionPostConditions(); const account = useCurrentStacksAccount(); diff --git a/src/shared/models/account.model.ts b/src/shared/models/account.model.ts index b3b5231ed87..96f12d39e57 100644 --- a/src/shared/models/account.model.ts +++ b/src/shared/models/account.model.ts @@ -1,7 +1,5 @@ import { AddressTokenOfferingLocked } from '@stacks/stacks-blockchain-api-types/generated'; -import type { Money } from './money.model'; - type SelectedKeys = | 'balance' | 'total_sent' @@ -47,17 +45,3 @@ export interface AddressBalanceResponse { >; token_offering_locked?: AddressTokenOfferingLocked; } - -export interface AccountStxBalanceBigNumber - extends Omit { - balance: Money; - total_sent: Money; - total_received: Money; - total_fees_sent: Money; - total_miner_rewards_received: Money; - locked: Money; -} - -export interface AccountBalanceResponseBigNumber extends Omit { - stx: AccountStxBalanceBigNumber; -} diff --git a/src/shared/models/crypto-asset-balance.model.ts b/src/shared/models/crypto-asset-balance.model.ts deleted file mode 100644 index e4274cbeb7c..00000000000 --- a/src/shared/models/crypto-asset-balance.model.ts +++ /dev/null @@ -1,46 +0,0 @@ -import BigNumber from 'bignumber.js'; - -import { - BitcoinCryptoCurrencyAsset, - StacksCryptoCurrencyAsset, - StacksFungibleTokenAsset, - StacksNonFungibleTokenAsset, -} from './crypto-asset.model'; -import { Money } from './money.model'; - -export interface BitcoinCryptoCurrencyAssetBalance { - readonly blockchain: 'bitcoin'; - readonly type: 'crypto-currency'; - readonly asset: BitcoinCryptoCurrencyAsset; - readonly balance: Money; -} - -export interface StacksCryptoCurrencyAssetBalance { - readonly blockchain: 'stacks'; - readonly type: 'crypto-currency'; - readonly asset: StacksCryptoCurrencyAsset; - readonly balance: Money; -} - -export interface StacksFungibleTokenAssetBalance { - readonly blockchain: 'stacks'; - readonly type: 'fungible-token'; - readonly asset: StacksFungibleTokenAsset; - readonly balance: Money; -} - -// ts-unused-exports:disable-next-line -export interface StacksNonFungibleTokenAssetBalance { - readonly blockchain: 'stacks'; - readonly type: 'non-fungible-token'; - readonly asset: StacksNonFungibleTokenAsset; - readonly count: BigNumber; -} - -export type AllCryptoCurrencyAssetBalances = - | BitcoinCryptoCurrencyAssetBalance - | StacksCryptoCurrencyAssetBalance; - -export type AllTransferableCryptoAssetBalances = - | AllCryptoCurrencyAssetBalances - | StacksFungibleTokenAssetBalance; diff --git a/src/shared/models/crypto-asset.model.ts b/src/shared/models/crypto-asset.model.ts deleted file mode 100644 index 78f75dc8adb..00000000000 --- a/src/shared/models/crypto-asset.model.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { MarketData } from './market.model'; - -export interface BitcoinCryptoCurrencyAsset { - decimals: number; - hasMemo: boolean; - name: string; - symbol: 'BTC'; -} - -export interface StacksCryptoCurrencyAsset { - decimals: number; - hasMemo: boolean; - name: string; - symbol: 'STX'; -} - -export interface StacksFungibleTokenAsset { - canTransfer: boolean; - contractId: string; - contractAddress: string; - contractAssetName: string; - contractName: string; - decimals: number; - hasMemo: boolean; - imageCanonicalUri: string; - name: string; - marketData: MarketData | null; - symbol: string; -} - -export interface StacksNonFungibleTokenAsset { - contractAddress: string; - contractAssetName: string; - contractName: string; - imageCanonicalUri: string; - name: string; -} diff --git a/tests/selectors/home.selectors.ts b/tests/selectors/home.selectors.ts index ab42923c2b9..ea28f10278d 100644 --- a/tests/selectors/home.selectors.ts +++ b/tests/selectors/home.selectors.ts @@ -1,4 +1,5 @@ export enum HomePageSelectors { + AssetList = 'asset-list', HomePageContainer = 'home-page-container', ReceiveCryptoAssetBtn = 'receive-crypto-asset-btn', ReceiveBtcNativeSegwitQrCodeBtn = 'receive-native-segwit-qr-code-btn', @@ -11,5 +12,4 @@ export enum HomePageSelectors { BalancesTabBtn = 'tab-balances', SwapBtn = 'swap-btn', FundAccountBtn = 'fund-account-btn', - BalancesList = 'balances-list', } diff --git a/tests/specs/rpc-get-addresses/get-addresses.spec.ts b/tests/specs/rpc-get-addresses/get-addresses.spec.ts index 48b13fc6b15..ac646f86d32 100644 --- a/tests/specs/rpc-get-addresses/get-addresses.spec.ts +++ b/tests/specs/rpc-get-addresses/get-addresses.spec.ts @@ -1,4 +1,3 @@ -import '@leather-wallet/types'; import type { BrowserContext, Page } from '@playwright/test'; import { TEST_PASSWORD } from '@tests/mocks/constants'; import { makeLedgerTestAccountWalletState } from '@tests/page-object-models/onboarding.page';