diff --git a/.env.example b/.env.example index da97eefb6f3..69061d2eab0 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,7 @@ SENTRY_DSN=sentrydsn TRANSAK_API_KEY=transakapikey WALLET_ENVIRONMENT=development BESTINSLOT_API_KEY=bestinslotapikey +BITFLOW_API_HOST=bitflowapihost +BITFLOW_API_KEY=bitflowapikey +BITFLOW_STACKS_API_HOST=bitflowstacksapihost +BITFLOW_READONLY_CALL_API_HOST=bitflowreadonlycallapihost \ No newline at end of file diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 3b7eb8705b4..a0c9787face 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -55,6 +55,10 @@ jobs: SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY_STAGING }} TRANSAK_API_KEY: ${{ secrets.TRANSAK_API_KEY }} BESTINSLOT_API_KEY: ${{ secrets.BESTINSLOT_API_KEY }} + BITFLOW_API_HOST: ${{ secrets.BITFLOW_API_HOST }} + BITFLOW_API_KEY: ${{ secrets.BITFLOW_API_KEY }} + BITFLOW_STACKS_API_HOST: ${{ secrets.BITFLOW_STACKS_API_HOST }} + BITFLOW_READONLY_CALL_API_HOST: ${{ secrets.BITFLOW_READONLY_CALL_API_HOST }} PR_NUMBER: ${{ github.event.number }} COMMIT_SHA: ${{ needs.sha-hash.outputs.SHORT_SHA }} diff --git a/.github/workflows/development-extension.yml b/.github/workflows/development-extension.yml index 9ebbac976df..c0e91cb75ea 100644 --- a/.github/workflows/development-extension.yml +++ b/.github/workflows/development-extension.yml @@ -13,6 +13,10 @@ env: SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY_STAGING }} TRANSAK_API_KEY: ${{ secrets.TRANSAK_API_KEY }} BESTINSLOT_API_KEY: ${{ secrets.BESTINSLOT_API_KEY }} + BITFLOW_API_HOST: ${{ secrets.BITFLOW_API_HOST }} + BITFLOW_API_KEY: ${{ secrets.BITFLOW_API_KEY }} + BITFLOW_STACKS_API_HOST: ${{ secrets.BITFLOW_STACKS_API_HOST }} + BITFLOW_READONLY_CALL_API_HOST: ${{ secrets.BITFLOW_READONLY_CALL_API_HOST }} PREVIEW_RELEASE: true WALLET_ENVIRONMENT: preview diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 571dadd554e..f8a36c9da17 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -3,6 +3,10 @@ name: Integration tests env: CI: true WALLET_ENVIRONMENT: testing + BITFLOW_API_HOST: ${{ secrets.BITFLOW_API_HOST }} + BITFLOW_API_KEY: ${{ secrets.BITFLOW_API_KEY }} + BITFLOW_STACKS_API_HOST: ${{ secrets.BITFLOW_STACKS_API_HOST }} + BITFLOW_READONLY_CALL_API_HOST: ${{ secrets.BITFLOW_READONLY_CALL_API_HOST }} on: push: diff --git a/.github/workflows/publish-extensions.yml b/.github/workflows/publish-extensions.yml index 3f5dbce891d..a165f95b704 100644 --- a/.github/workflows/publish-extensions.yml +++ b/.github/workflows/publish-extensions.yml @@ -13,6 +13,10 @@ env: SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }} TRANSAK_API_KEY: ${{ secrets.TRANSAK_API_KEY }} BESTINSLOT_API_KEY: ${{ secrets.BESTINSLOT_API_KEY }} + BITFLOW_API_HOST: ${{ secrets.BITFLOW_API_HOST }} + BITFLOW_API_KEY: ${{ secrets.BITFLOW_API_KEY }} + BITFLOW_STACKS_API_HOST: ${{ secrets.BITFLOW_STACKS_API_HOST }} + BITFLOW_READONLY_CALL_API_HOST: ${{ secrets.BITFLOW_READONLY_CALL_API_HOST }} WALLET_ENVIRONMENT: production IS_PUBLISHING: true diff --git a/package.json b/package.json index 0af026ea85c..1fcbbbe0864 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,9 @@ "bignumber.js": "9.1.2", "bitcoin-address-validation": "2.2.1", "bitcoinjs-lib": "6.1.5", + "bitflow-sdk": "1.6.1", "bn.js": "5.2.1", + "browserify-fs": "1.0.0", "c32check": "2.0.0", "chroma-js": "2.4.2", "coinselect": "3.1.13", @@ -219,6 +221,7 @@ "micro-packed": "0.3.2", "object-hash": "3.0.0", "observable-hooks": "4.2.3", + "os-browserify": "0.3.0", "p-queue": "8.0.1", "pino": "8.19.0", "postcss-preset-env": "9.5.4", @@ -379,7 +382,8 @@ }, "pnpm": { "overrides": { - "eslint": "8.56.0" + "eslint": "8.56.0", + "levelup>semver": "5.7.2" } }, "keywords": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3727d99411c..58c075fcdb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,7 @@ overrides: eslint: 8.56.0 path-to-regexp: 0.1.10 ws: 8.17.1 + levelup>semver: 5.7.2 importers: @@ -30,7 +31,7 @@ importers: version: 0.7.0(encoding@0.1.13) '@coinbase/cbpay-js': specifier: 2.1.0 - version: 2.1.0(regenerator-runtime@0.14.1) + version: 2.1.0(regenerator-runtime@0.13.11) '@fungible-systems/zone-file': specifier: 2.0.0 version: 2.0.0 @@ -205,9 +206,15 @@ importers: bitcoinjs-lib: specifier: 6.1.5 version: 6.1.5 + bitflow-sdk: + specifier: 1.6.1 + version: 1.6.1(encoding@0.1.13) bn.js: specifier: 5.2.1 version: 5.2.1 + browserify-fs: + specifier: 1.0.0 + version: 1.0.0 c32check: specifier: 2.0.0 version: 2.0.0 @@ -271,6 +278,9 @@ importers: observable-hooks: specifier: 4.2.3 version: 4.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.1) + os-browserify: + specifier: 0.3.0 + version: 0.3.0 p-queue: specifier: 8.0.1 version: 8.0.1 @@ -2454,7 +2464,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.18.28': resolution: {integrity: sha512-fvbVPId6s6etindzP6Nzos/CS1NurMVy4JKozjebArHr63tBid5i/UY5Pp+4wTCAM20gB2SjRdwcwoL6HFC4Iw==} @@ -4633,6 +4643,9 @@ packages: '@stacks/network@6.13.0': resolution: {integrity: sha512-Ss/Da4BNyPBBj1OieM981fJ7SkevKqLPkzoI1+Yo7cYR2df+0FipIN++Z4RfpJpc8ne60vgcx7nJZXQsiGhKBQ==} + '@stacks/network@6.16.0': + resolution: {integrity: sha512-uqz9Nb6uf+SeyCKENJN+idt51HAfEeggQKrOMfGjpAeFgZV2CR66soB/ci9+OVQR/SURvasncAz2ScI1blfS8A==} + '@stacks/profile@6.15.0': resolution: {integrity: sha512-+m11HYHU45+f98FySsVmofeLFWxnhnwZ5jbREoD2f53fmBulsSbJpDUVg3w4aPdj6hg4+o7rkg09gbirIXNWBw==} @@ -4651,6 +4664,9 @@ packages: '@stacks/transactions@6.15.0': resolution: {integrity: sha512-P6XKDcqqycPy+KBJBw8+5N+u57D8moJN7msYdde1gYXERmvOo9ht/MNREWWQ7SAM7Nlhau5mpezCdYCzXOCilQ==} + '@stacks/transactions@6.16.1': + resolution: {integrity: sha512-yCtUM+8IN0QJbnnlFhY1wBW7Q30Cxje3Zmy8DgqdBoM/EPPWadez/8wNWFANVAMyUZeQ9V/FY+8MAw4E+pCReA==} + '@stacks/wallet-sdk@6.15.0': resolution: {integrity: sha512-VBMiWe5UAyDnvc2w8/XN7QuSkbXTnAJ5rvtzedb7yXKgIBMSjE+gQnUm0XasbNDRHc58Ag76IAMAIKh4ZAMC4w==} @@ -6513,6 +6529,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + abstract-leveldown@0.12.4: + resolution: {integrity: sha512-TOod9d5RDExo6STLMGa+04HGkl+TlMfbDnTyN93/ETJ9DpQ0DaYLqcMZlbXvdc4W3vVo1Qrl+WhSp8zvDsJ+jA==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -7031,6 +7050,13 @@ packages: resolution: {integrity: sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==} engines: {node: '>=8.0.0'} + bitflow-sdk@1.6.1: + resolution: {integrity: sha512-V6TUstTBNorR+WadWSaiKEjNXy25unIAICLtrUXTZMrJtOfUIfUZx7W7hWmoUjBE9k8z/dOfjH1HUbF0wRxWZQ==} + engines: {node: '>=14.0.0'} + + bl@0.8.2: + resolution: {integrity: sha512-pfqikmByp+lifZCS0p6j6KreV6kNU6Apzpm2nKOk+94cZb/jvle55+JxWiByUQ0Wo/+XnDXEy5MxxKMb6r0VIw==} + bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} @@ -7109,6 +7135,9 @@ packages: browserify-des@1.0.2: resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} + browserify-fs@1.0.0: + resolution: {integrity: sha512-8LqHRPuAEKvyTX34R6tsw4bO2ro6j9DmlYBhiYWHRM26Zv2cBw1fJOU0NeUQ0RkXkPn/PFBjhA0dm4AgaBurTg==} + browserify-rsa@4.1.0: resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==} @@ -7476,6 +7505,9 @@ packages: clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + clone@0.1.19: + resolution: {integrity: sha512-IO78I0y6JcSpEPHzK4obKdsL7E7oLdRVDVOLwr2Hkbjsb+Eoz0dxW6tef0WizoKu0gLC4oZSZuEF4U2K6w1WQw==} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -8198,6 +8230,9 @@ packages: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} + deferred-leveldown@0.2.0: + resolution: {integrity: sha512-+WCbb4+ez/SZ77Sdy1iadagFiVzMB89IKOBhglgnUkVxOxRWmmFsz8UDSNWh4Rhq+3wr/vMFlYj+rdEwWUDdng==} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -8521,6 +8556,10 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -9085,6 +9124,9 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreach@2.0.6: + resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -9239,6 +9281,9 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + fwd-stream@1.0.4: + resolution: {integrity: sha512-q2qaK2B38W07wfPSQDKMiKOD5Nzv2XyuvQlrmh1q0pxyHNanKHq8lwQ6n9zHucAwA5EbzRJKEgds2orn88rYTg==} + fx-runner@1.3.0: resolution: {integrity: sha512-5b37H4GCyhF+Nf8xk9mylXoDq4wb7pbGAXxlCXp/631UTeeZomWSYcEGXumY4wk8g2QAqjPMGdWW+RbNt8PUcA==} hasBin: true @@ -9733,6 +9778,9 @@ packages: peerDependencies: postcss: ^8.1.0 + idb-wrapper@1.7.2: + resolution: {integrity: sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -9798,6 +9846,9 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + indexof@0.0.1: + resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} + infer-owner@1.0.4: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} @@ -10070,6 +10121,9 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-object@0.1.2: + resolution: {integrity: sha512-GkfZZlIZtpkFrqyAXPQSRBMsaHAw+CgoKe2HXAkjd/sfoI9+hS8PT4wg2rJxdQyUKr7N2vHJbg7/jQtE5l5vBQ==} + is-path-cwd@2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} @@ -10213,12 +10267,21 @@ packages: resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} engines: {node: '>=12'} + is@0.2.7: + resolution: {integrity: sha512-ajQCouIvkcSnl2iRdK70Jug9mohIHVX9uKpoWnl115ov0R5mzBvRrXxrnHbsA+8AdwCwc/sfw7HXmd4I5EJBdQ==} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbuffer@0.0.0: + resolution: {integrity: sha512-xU+NoHp+YtKQkaM2HsQchYn0sltxMxew0HavMfHbjnucBoTSGbw745tL+Z7QBANleWM1eEQMenEpi174mIeS4g==} + isexe@1.1.2: resolution: {integrity: sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw==} @@ -10574,6 +10637,33 @@ packages: resolution: {integrity: sha512-sWdvMTR5CkebNlM0Mam9ROdpsD7Y4087kj4cbIaCCq8IXShCQ44vE3j0wTmt+sHp13eETgY63OWN1rkuIfMfuQ==} engines: {node: '>=14'} + level-blobs@0.1.7: + resolution: {integrity: sha512-n0iYYCGozLd36m/Pzm206+brIgXP8mxPZazZ6ZvgKr+8YwOZ8/PPpYC5zMUu2qFygRN8RO6WC/HH3XWMW7RMVg==} + + level-filesystem@1.2.0: + resolution: {integrity: sha512-PhXDuCNYpngpxp3jwMT9AYBMgOvB6zxj3DeuIywNKmZqFj2djj9XfT2XDVslfqmo0Ip79cAd3SBy3FsfOZPJ1g==} + + level-fix-range@1.0.2: + resolution: {integrity: sha512-9llaVn6uqBiSlBP+wKiIEoBa01FwEISFgHSZiyec2S0KpyLUkGR4afW/FCZ/X8y+QJvzS0u4PGOlZDdh1/1avQ==} + + level-fix-range@2.0.0: + resolution: {integrity: sha512-WrLfGWgwWbYPrHsYzJau+5+te89dUbENBg3/lsxOs4p2tYOhCHjbgXxBAj4DFqp3k/XBwitcRXoCh8RoCogASA==} + + level-hooks@4.5.0: + resolution: {integrity: sha512-fxLNny/vL/G4PnkLhWsbHnEaRi+A/k8r5EH/M77npZwYL62RHi2fV0S824z3QdpAk6VTgisJwIRywzBHLK4ZVA==} + + level-js@2.2.4: + resolution: {integrity: sha512-lZtjt4ZwHE00UMC1vAb271p9qzg8vKlnDeXfIesH3zL0KxhHRDjClQLGLWhyR0nK4XARnd4wc/9eD1ffd4PshQ==} + + level-peek@1.0.6: + resolution: {integrity: sha512-TKEzH5TxROTjQxWMczt9sizVgnmJ4F3hotBI48xCTYvOKd/4gA/uY0XjKkhJFo6BMic8Tqjf6jFMLWeg3MAbqQ==} + + level-sublevel@5.2.3: + resolution: {integrity: sha512-tO8jrFp+QZYrxx/Gnmjawuh1UBiifpvKNAcm4KCogesWr1Nm2+ckARitf+Oo7xg4OHqMW76eAqQ204BoIlscjA==} + + levelup@0.18.6: + resolution: {integrity: sha512-uB0auyRqIVXx+hrpIUtol4VAPhLRcnxcOsd2i2m6rbFIDarO5dnrupLOStYYpEcu8ZT087Z9HEuYw1wjr6RL6Q==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -10995,6 +11085,9 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + ltgt@2.2.1: + resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -11862,6 +11955,13 @@ packages: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} + object-keys@0.2.0: + resolution: {integrity: sha512-XODjdR2pBh/1qrjPcbSeSgEtKbYo7LqYNq64/TPuCf7j9SfDD3i21yatKoIy39yIWNvVM59iutfQQpCv1RfFzA==} + deprecated: Please update to the latest object-keys + + object-keys@0.4.0: + resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -11903,6 +12003,9 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + octal@1.0.0: + resolution: {integrity: sha512-nnda7W8d+A3vEIY+UrDQzzboPf1vhs4JYVhff5CDkq9QNoZY7Xrxeo/htox37j9dZf7yNHevZzqtejWgy1vCqQ==} + ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} @@ -11973,6 +12076,9 @@ packages: resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + os-homedir@1.0.2: resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} engines: {node: '>=0.10.0'} @@ -12735,6 +12841,12 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + prr@0.0.0: + resolution: {integrity: sha512-LmUECmrW7RVj6mDWKjTXfKug7TFGdiz9P18HMcO4RHL+RW7MCOGNvpj5j47Rnp6ne6r4fZ2VzyUWEpKbg+tsjQ==} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -13146,6 +13258,12 @@ packages: resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} engines: {node: '>=10.13'} + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -13888,6 +14006,9 @@ packages: resolution: {integrity: sha512-h+7wLeFiYegOdgTfTxjRsrT7/Op7grnKEIHWgaO1RTHwcwk7xRreMr3S8XpDfDMesSxzgM2V4CxNCFAGo6ssnA==} engines: {node: '>= 10'} + string-range@1.2.2: + resolution: {integrity: sha512-tYft6IFi8SjplJpxCUxyqisD3b+R2CSkomrtJYCkvuf1KuCAWgz7YXt4O0jip7efpfCemwHEzTEAO8EuOYgh3w==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -13915,6 +14036,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -14475,6 +14599,9 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} + typedarray-to-buffer@1.0.4: + resolution: {integrity: sha512-vjMKrfSoUDN8/Vnqitw2FmstOfuJ73G6CrSEKnf11A6RmasVxHqfeBcnTb6RsL4pTMuV5Zsv9IiHRphMZyckUw==} + typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} @@ -15184,6 +15311,22 @@ packages: resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} engines: {node: '>=0.4.0'} + xtend@2.0.6: + resolution: {integrity: sha512-fOZg4ECOlrMl+A6Msr7EIFcON1L26mb4NY5rurSkOex/TWhazOrg6eXD/B0XkuiYcYhQDWLXzQxLMVJ7LXwokg==} + engines: {node: '>=0.4'} + + xtend@2.1.2: + resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} + engines: {node: '>=0.4'} + + xtend@2.2.0: + resolution: {integrity: sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==} + engines: {node: '>=0.4'} + + xtend@3.0.0: + resolution: {integrity: sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==} + engines: {node: '>=0.4'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -16567,9 +16710,9 @@ snapshots: picocolors: 1.1.0 sisteransi: 1.0.5 - '@coinbase/cbpay-js@2.1.0(regenerator-runtime@0.14.1)': + '@coinbase/cbpay-js@2.1.0(regenerator-runtime@0.13.11)': optionalDependencies: - regenerator-runtime: 0.14.1 + regenerator-runtime: 0.13.11 '@cspotcode/source-map-support@0.8.1': dependencies: @@ -21035,6 +21178,13 @@ snapshots: transitivePeerDependencies: - encoding + '@stacks/network@6.16.0(encoding@0.1.13)': + dependencies: + '@stacks/common': 6.16.0 + cross-fetch: 3.1.8(encoding@0.1.13) + transitivePeerDependencies: + - encoding + '@stacks/profile@6.15.0(encoding@0.1.13)': dependencies: '@stacks/common': 6.13.0 @@ -21093,6 +21243,17 @@ snapshots: transitivePeerDependencies: - encoding + '@stacks/transactions@6.16.1(encoding@0.1.13)': + dependencies: + '@noble/hashes': 1.1.5 + '@noble/secp256k1': 1.7.1 + '@stacks/common': 6.16.0 + '@stacks/network': 6.16.0(encoding@0.1.13) + c32check: 2.0.0 + lodash.clonedeep: 4.5.0 + transitivePeerDependencies: + - encoding + '@stacks/wallet-sdk@6.15.0(encoding@0.1.13)': dependencies: '@scure/bip32': 1.1.3 @@ -23527,6 +23688,10 @@ snapshots: dependencies: event-target-shim: 5.0.1 + abstract-leveldown@0.12.4: + dependencies: + xtend: 3.0.0 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -24131,6 +24296,18 @@ snapshots: typeforce: 1.18.0 varuint-bitcoin: 1.1.2 + bitflow-sdk@1.6.1(encoding@0.1.13): + dependencies: + '@stacks/network': 6.13.0(encoding@0.1.13) + '@stacks/transactions': 6.16.1(encoding@0.1.13) + dotenv: 16.4.5 + transitivePeerDependencies: + - encoding + + bl@0.8.2: + dependencies: + readable-stream: 1.0.34 + bl@1.2.3: dependencies: readable-stream: 2.3.8 @@ -24249,6 +24426,12 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + browserify-fs@1.0.0: + dependencies: + level-filesystem: 1.2.0 + level-js: 2.2.4 + levelup: 0.18.6 + browserify-rsa@4.1.0: dependencies: bn.js: 5.2.1 @@ -24695,6 +24878,8 @@ snapshots: dependencies: mimic-response: 1.0.1 + clone@0.1.19: {} + clone@1.0.4: {} clone@2.1.2: {} @@ -25458,6 +25643,10 @@ snapshots: defer-to-connect@2.0.1: {} + deferred-leveldown@0.2.0: + dependencies: + abstract-leveldown: 0.12.4 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -25810,6 +25999,10 @@ snapshots: err-code@2.0.3: {} + errno@0.1.8: + dependencies: + prr: 1.0.1 + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -26688,6 +26881,8 @@ snapshots: dependencies: is-callable: 1.2.7 + foreach@2.0.6: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -26868,6 +27063,10 @@ snapshots: functions-have-names@1.2.3: {} + fwd-stream@1.0.4: + dependencies: + readable-stream: 1.0.34 + fx-runner@1.3.0: dependencies: commander: 2.9.0 @@ -27479,6 +27678,8 @@ snapshots: dependencies: postcss: 8.4.38 + idb-wrapper@1.7.2: {} + ieee754@1.2.1: {} ignore@5.3.1: {} @@ -27526,6 +27727,8 @@ snapshots: indent-string@5.0.0: {} + indexof@0.0.1: {} + infer-owner@1.0.4: optional: true @@ -27744,6 +27947,8 @@ snapshots: is-obj@2.0.0: {} + is-object@0.1.2: {} + is-path-cwd@2.2.0: {} is-path-in-cwd@2.1.0: @@ -27848,10 +28053,16 @@ snapshots: is-yarn-global@0.4.1: {} + is@0.2.7: {} + + isarray@0.0.1: {} + isarray@1.0.0: {} isarray@2.0.5: {} + isbuffer@0.0.0: {} + isexe@1.1.2: {} isexe@2.0.0: {} @@ -28309,6 +28520,64 @@ snapshots: bip32-path: 0.4.2 bitcoinjs-lib: 6.1.5 + level-blobs@0.1.7: + dependencies: + level-peek: 1.0.6 + once: 1.4.0 + readable-stream: 1.1.14 + + level-filesystem@1.2.0: + dependencies: + concat-stream: 1.6.2 + errno: 0.1.8 + fwd-stream: 1.0.4 + level-blobs: 0.1.7 + level-peek: 1.0.6 + level-sublevel: 5.2.3 + octal: 1.0.0 + once: 1.4.0 + xtend: 2.2.0 + + level-fix-range@1.0.2: {} + + level-fix-range@2.0.0: + dependencies: + clone: 0.1.19 + + level-hooks@4.5.0: + dependencies: + string-range: 1.2.2 + + level-js@2.2.4: + dependencies: + abstract-leveldown: 0.12.4 + idb-wrapper: 1.7.2 + isbuffer: 0.0.0 + ltgt: 2.2.1 + typedarray-to-buffer: 1.0.4 + xtend: 2.1.2 + + level-peek@1.0.6: + dependencies: + level-fix-range: 1.0.2 + + level-sublevel@5.2.3: + dependencies: + level-fix-range: 2.0.0 + level-hooks: 4.5.0 + string-range: 1.2.2 + xtend: 2.0.6 + + levelup@0.18.6: + dependencies: + bl: 0.8.2 + deferred-leveldown: 0.2.0 + errno: 0.1.8 + prr: 0.0.0 + readable-stream: 1.0.34 + semver: 5.7.2 + xtend: 3.0.0 + leven@3.1.0: {} levn@0.4.1: @@ -28647,6 +28916,8 @@ snapshots: lru-cache@7.18.3: {} + ltgt@2.2.1: {} + lz-string@1.5.0: {} magic-string@0.30.10: @@ -30030,6 +30301,14 @@ snapshots: call-bind: 1.0.7 define-properties: 1.2.1 + object-keys@0.2.0: + dependencies: + foreach: 2.0.6 + indexof: 0.0.1 + is: 0.2.7 + + object-keys@0.4.0: {} + object-keys@1.1.1: {} object-path@0.11.8: {} @@ -30076,6 +30355,8 @@ snapshots: obuf@1.1.2: {} + octal@1.0.0: {} + ohash@1.1.3: {} on-exit-leak-free@2.1.2: {} @@ -30172,6 +30453,8 @@ snapshots: strip-ansi: 7.1.0 wcwidth: 1.0.1 + os-browserify@0.3.0: {} + os-homedir@1.0.2: {} os-locale@5.0.0: @@ -31008,6 +31291,10 @@ snapshots: proxy-from-env@1.1.0: {} + prr@0.0.0: {} + + prr@1.0.1: {} + psl@1.9.0: {} public-encrypt@4.0.3: @@ -31537,6 +31824,20 @@ snapshots: js-yaml: 4.1.0 strip-bom: 4.0.0 + readable-stream@1.0.34: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + + readable-stream@1.1.14: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -32521,6 +32822,8 @@ snapshots: end-of-stream: 1.4.4 stream-to-array: 2.3.0 + string-range@1.2.2: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -32573,6 +32876,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@0.10.31: {} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -33114,6 +33419,8 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + typedarray-to-buffer@1.0.4: {} + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 @@ -34004,6 +34311,19 @@ snapshots: xmlhttprequest-ssl@2.0.0: {} + xtend@2.0.6: + dependencies: + is-object: 0.1.2 + object-keys: 0.2.0 + + xtend@2.1.2: + dependencies: + object-keys: 0.4.0 + + xtend@2.2.0: {} + + xtend@3.0.0: {} + xtend@4.0.2: {} y18n@4.0.3: {} diff --git a/src/app/common/hooks/use-filtered-sip10-tokens.ts b/src/app/common/hooks/use-filtered-sip10-tokens.ts new file mode 100644 index 00000000000..6e35ce7975d --- /dev/null +++ b/src/app/common/hooks/use-filtered-sip10-tokens.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; + +import { + type Sip10CryptoAssetFilter, + filterSip10Tokens, + useAlexSwappableAssets, + useFilteredSip10Tokens, +} from '@leather.io/query'; + +import { useBitflowSwappableAssets } from '@app/pages/swap/hooks/use-bitflow-swappable-assets'; + +interface UseSip10TokensArgs { + address: string; + filter?: Sip10CryptoAssetFilter; +} +// TODO: Migrate to mono +export function useCombinedFilteredSip10Tokens({ address, filter = 'all' }: UseSip10TokensArgs) { + const { isLoading, tokens = [] } = useFilteredSip10Tokens({ address }); + const { data: alexSwapAssets = [] } = useAlexSwappableAssets(address); + const { data: bitflowSwapAssets = [] } = useBitflowSwappableAssets(address); + const filteredTokens = useMemo( + () => filterSip10Tokens([...alexSwapAssets, ...bitflowSwapAssets], tokens, filter), + [alexSwapAssets, bitflowSwapAssets, tokens, filter] + ); + return { isLoading, tokens: filteredTokens }; +} diff --git a/src/app/components/loaders/sip10-tokens-loader.tsx b/src/app/components/loaders/sip10-tokens-loader.tsx index 0456dbf16ff..2d4b9cce699 100644 --- a/src/app/components/loaders/sip10-tokens-loader.tsx +++ b/src/app/components/loaders/sip10-tokens-loader.tsx @@ -1,8 +1,6 @@ -import { - type Sip10CryptoAssetFilter, - type Sip10TokenAssetDetails, - useFilteredSip10Tokens, -} from '@leather.io/query'; +import { type Sip10CryptoAssetFilter, type Sip10TokenAssetDetails } from '@leather.io/query'; + +import { useCombinedFilteredSip10Tokens } from '@app/common/hooks/use-filtered-sip10-tokens'; interface Sip10TokensLoaderProps { address: string; @@ -10,6 +8,6 @@ interface Sip10TokensLoaderProps { children(isLoading: boolean, tokens: Sip10TokenAssetDetails[]): React.ReactNode; } export function Sip10TokensLoader({ address, filter, children }: Sip10TokensLoaderProps) { - const { isLoading, tokens = [] } = useFilteredSip10Tokens({ address, filter }); + const { isLoading, tokens = [] } = useCombinedFilteredSip10Tokens({ address, filter }); return children(isLoading, tokens); } diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx deleted file mode 100644 index 638df0b5695..00000000000 --- a/src/app/pages/swap/alex-swap-container.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { useState } from 'react'; -import { Outlet } from 'react-router-dom'; - -import { bytesToHex } from '@stacks/common'; -import { ContractCallPayload, TransactionTypes } from '@stacks/connect'; -import { - AnchorMode, - PostConditionMode, - serializeCV, - serializePostCondition, -} from '@stacks/transactions'; -import BigNumber from 'bignumber.js'; - -import { defaultSwapFee } from '@leather.io/query'; -import { isDefined, isUndefined } from '@leather.io/utils'; - -import { logger } from '@shared/logger'; -import { RouteUrls } from '@shared/route-urls'; -import { alex } from '@shared/utils/alex-sdk'; - -import { migratePositiveAssetBalancesToTop } from '@app/common/asset-utils'; -import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; -import { Content, Page } from '@app/components/layout'; -import { PageHeader } from '@app/features/container/headers/page.header'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks'; -import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; - -import { SwapForm } from './components/swap-form'; -import { generateSwapRoutes } from './generate-swap-routes'; -import { oneHundredMillion, useAlexSwap } from './hooks/use-alex-swap'; -import { useStacksBroadcastSwap } from './hooks/use-stacks-broadcast-swap'; -import { SwapFormValues } from './hooks/use-swap-form'; -import { useSwapNavigate } from './hooks/use-swap-navigate'; -import { SwapContext, SwapProvider } from './swap.context'; - -export const alexSwapRoutes = generateSwapRoutes(); - -function AlexSwapContainer() { - const [isSendingMax, setIsSendingMax] = useState(false); - const navigate = useSwapNavigate(); - const { setIsLoading } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); - const currentAccount = useCurrentStacksAccount(); - const generateUnsignedTx = useGenerateStacksContractCallUnsignedTx(); - const signTx = useSignStacksTransaction(); - - const { - fetchQuoteAmount, - isFetchingExchangeRate, - onSetIsFetchingExchangeRate, - onSetSwapSubmissionData, - slippage, - swapAssets, - swapSubmissionData, - } = useAlexSwap(); - const broadcastStacksSwap = useStacksBroadcastSwap(); - - async function onSubmitSwapForReview(values: SwapFormValues) { - if (isUndefined(values.swapAssetBase) || isUndefined(values.swapAssetQuote)) { - logger.error('Error submitting swap for review'); - return; - } - - const [router, lpFee] = await Promise.all([ - alex.getRouter(values.swapAssetBase.currency, values.swapAssetQuote.currency), - alex.getFeeRate(values.swapAssetBase.currency, values.swapAssetQuote.currency), - ]); - - onSetSwapSubmissionData({ - fee: defaultSwapFee.amount.toString(), - feeCurrency: values.feeCurrency, - feeType: values.feeType, - liquidityFee: new BigNumber(Number(lpFee)).dividedBy(oneHundredMillion).toNumber(), - nonce: values.nonce, - protocol: 'ALEX', - router: router.map(x => swapAssets.find(asset => asset.currency === x)).filter(isDefined), - slippage, - sponsored: false, - swapAmountBase: values.swapAmountBase, - swapAmountQuote: values.swapAmountQuote, - swapAssetBase: values.swapAssetBase, - swapAssetQuote: values.swapAssetQuote, - timestamp: new Date().toISOString(), - }); - - navigate(RouteUrls.SwapReview); - } - - async function onSubmitSwap() { - if (isUndefined(currentAccount) || isUndefined(swapSubmissionData)) { - logger.error('Error submitting swap data to sign'); - return; - } - - if ( - isUndefined(swapSubmissionData.swapAssetBase) || - isUndefined(swapSubmissionData.swapAssetQuote) - ) { - logger.error('No assets selected to perform swap'); - return; - } - - setIsLoading(); - - const fromAmount = BigInt( - new BigNumber(swapSubmissionData.swapAmountBase) - .multipliedBy(oneHundredMillion) - .dp(0) - .toString() - ); - - const minToAmount = BigInt( - new BigNumber(swapSubmissionData.swapAmountQuote) - .multipliedBy(oneHundredMillion) - .multipliedBy(new BigNumber(1).minus(slippage)) - .dp(0) - .toString() - ); - - const tx = await alex.runSwap( - currentAccount?.address, - swapSubmissionData.swapAssetBase.currency, - swapSubmissionData.swapAssetQuote.currency, - fromAmount, - minToAmount - ); - - // TODO: Add choose fee step - const tempFormValues = { - fee: swapSubmissionData.fee, - feeCurrency: swapSubmissionData.feeCurrency, - feeType: swapSubmissionData.feeType, - nonce: swapSubmissionData.nonce, - }; - - const payload: ContractCallPayload = { - anchorMode: AnchorMode.Any, - contractAddress: tx.contractAddress, - contractName: tx.contractName, - functionName: tx.functionName, - functionArgs: tx.functionArgs.map(x => bytesToHex(serializeCV(x))), - postConditionMode: PostConditionMode.Deny, - postConditions: tx.postConditions.map(pc => bytesToHex(serializePostCondition(pc))), - publicKey: currentAccount?.stxPublicKey, - sponsored: swapSubmissionData.sponsored, - txType: TransactionTypes.ContractCall, - }; - - const unsignedTx = await generateUnsignedTx(payload, tempFormValues); - if (!unsignedTx) return logger.error('Attempted to generate unsigned tx, but tx is undefined'); - - try { - const signedTx = await signTx(unsignedTx); - if (!signedTx) - return logger.error('Attempted to generate raw tx, but signed tx is undefined'); - - return await broadcastStacksSwap(signedTx); - } catch (error) {} - } - - const swapContextValue: SwapContext = { - fetchQuoteAmount, - isFetchingExchangeRate, - isSendingMax, - onSetIsFetchingExchangeRate, - onSetIsSendingMax: value => setIsSendingMax(value), - onSubmitSwapForReview, - onSubmitSwap, - swappableAssetsBase: migratePositiveAssetBalancesToTop(swapAssets), - swappableAssetsQuote: swapAssets, - swapSubmissionData, - }; - - return ( - - {/* Swap uses routed dialogs to choose assets so needs onBackLocation to go Home */} - - - - - - - - - - ); -} diff --git a/src/app/pages/swap/bitflow-swap-container.tsx b/src/app/pages/swap/bitflow-swap-container.tsx new file mode 100644 index 00000000000..7ac3e2523ee --- /dev/null +++ b/src/app/pages/swap/bitflow-swap-container.tsx @@ -0,0 +1,206 @@ +import { useState } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; + +import { bytesToHex } from '@stacks/common'; +import { type ContractCallPayload, TransactionTypes } from '@stacks/connect'; +import { + AnchorMode, + PostConditionMode, + serializeCV, + serializePostCondition, +} from '@stacks/transactions'; + +import { defaultSwapFee } from '@leather.io/query'; +import { isDefined, isError, isUndefined } from '@leather.io/utils'; + +import { logger } from '@shared/logger'; +import { RouteUrls } from '@shared/route-urls'; +import { bitflow } from '@shared/utils/bitflow-sdk'; + +import { migratePositiveAssetBalancesToTop } from '@app/common/asset-utils'; +import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; +import { Content, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks'; +import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; + +import { estimateLiquidityFee, formatDexPathItem } from './bitflow-swap.utils'; +import { SwapForm } from './components/swap-form'; +import { generateSwapRoutes } from './generate-swap-routes'; +import { useBitflowSwap } from './hooks/use-bitflow-swap'; +import { useStacksBroadcastSwap } from './hooks/use-stacks-broadcast-swap'; +import { SwapFormValues } from './hooks/use-swap-form'; +import { useSwapNavigate } from './hooks/use-swap-navigate'; +import { SwapContext, SwapProvider } from './swap.context'; + +export const bitflowSwapRoutes = generateSwapRoutes(); + +function BitflowSwapContainer() { + const [isSendingMax, setIsSendingMax] = useState(false); + const navigate = useNavigate(); + const swapNavigate = useSwapNavigate(); + const { setIsLoading, setIsIdle, isLoading } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); + const currentAccount = useCurrentStacksAccount(); + const generateUnsignedTx = useGenerateStacksContractCallUnsignedTx(); + const signTx = useSignStacksTransaction(); + const broadcastStacksSwap = useStacksBroadcastSwap(); + const [isPreparingSwapReview, setIsPreparingSwapReview] = useState(false); + const { + fetchRouteQuote, + fetchQuoteAmount, + isFetchingExchangeRate, + onSetIsFetchingExchangeRate, + onSetSwapSubmissionData, + slippage, + swapAssets, + swapSubmissionData, + } = useBitflowSwap(); + + async function onSubmitSwapForReview(values: SwapFormValues) { + try { + setIsPreparingSwapReview(true); + if (isUndefined(values.swapAssetBase) || isUndefined(values.swapAssetQuote)) { + logger.error('Error submitting swap for review'); + return; + } + + const routeQuote = await fetchRouteQuote( + values.swapAssetBase, + values.swapAssetQuote, + values.swapAmountBase + ); + if (!routeQuote) return; + + onSetSwapSubmissionData({ + fee: defaultSwapFee.amount.toString(), + feeCurrency: values.feeCurrency, + feeType: values.feeType, + liquidityFee: estimateLiquidityFee(routeQuote.route.dex_path), + nonce: values.nonce, + protocol: 'Bitflow', + dexPath: routeQuote.route.dex_path.map(formatDexPathItem), + router: routeQuote.route.token_path + .map(x => swapAssets.find(asset => asset.currency === x)) + .filter(isDefined), + slippage, + sponsored: false, + swapAmountBase: values.swapAmountBase, + swapAmountQuote: values.swapAmountQuote, + swapAssetBase: values.swapAssetBase, + swapAssetQuote: values.swapAssetQuote, + timestamp: new Date().toISOString(), + }); + swapNavigate(RouteUrls.SwapReview); + } finally { + setIsPreparingSwapReview(false); + } + } + + async function onSubmitSwap() { + if (isLoading) return; + + if (isUndefined(currentAccount) || isUndefined(swapSubmissionData)) { + logger.error('Error submitting swap data to sign'); + return; + } + + if ( + isUndefined(swapSubmissionData.swapAssetBase) || + isUndefined(swapSubmissionData.swapAssetQuote) + ) { + logger.error('No assets selected to perform swap'); + return; + } + + setIsLoading(); + + try { + const routeQuote = await fetchRouteQuote( + swapSubmissionData.swapAssetBase, + swapSubmissionData.swapAssetQuote, + swapSubmissionData.swapAmountBase + ); + if (!routeQuote) return; + + const swapExecutionData = { + route: routeQuote.route, + amount: Number(swapSubmissionData.swapAmountBase), + tokenXDecimals: routeQuote.tokenXDecimals, + tokenYDecimals: routeQuote.tokenYDecimals, + }; + + const swapParams = await bitflow.getSwapParams( + swapExecutionData, + currentAccount.address, + swapSubmissionData.slippage + ); + + const tempFormValues = { + fee: swapSubmissionData.fee, + feeCurrency: swapSubmissionData.feeCurrency, + feeType: swapSubmissionData.feeType, + nonce: swapSubmissionData.nonce, + }; + + const payload: ContractCallPayload = { + anchorMode: AnchorMode.Any, + contractAddress: swapParams.contractAddress, + contractName: swapParams.contractName, + functionName: swapParams.functionName, + functionArgs: swapParams.functionArgs.map(x => bytesToHex(serializeCV(x))), + postConditionMode: PostConditionMode.Deny, + postConditions: swapParams.postConditions.map(pc => bytesToHex(serializePostCondition(pc))), + publicKey: currentAccount?.stxPublicKey, + sponsored: swapSubmissionData.sponsored, + txType: TransactionTypes.ContractCall, + }; + + const unsignedTx = await generateUnsignedTx(payload, tempFormValues); + if (!unsignedTx) + return logger.error('Attempted to generate unsigned tx, but tx is undefined'); + + const signedTx = await signTx(unsignedTx); + if (!signedTx) + return logger.error('Attempted to generate raw tx, but signed tx is undefined'); + return await broadcastStacksSwap(signedTx); + } catch (e) { + navigate(RouteUrls.SwapError, { + state: { + message: isError(e) ? e.message : '', + title: 'Swap Error', + }, + }); + } finally { + setIsIdle(); + } + } + + const swapContextValue: SwapContext = { + fetchQuoteAmount, + isFetchingExchangeRate, + isSendingMax, + isPreparingSwapReview, + onSetIsFetchingExchangeRate, + onSetIsSendingMax: value => setIsSendingMax(value), + onSubmitSwapForReview, + onSubmitSwap, + swappableAssetsBase: migratePositiveAssetBalancesToTop(swapAssets), + swappableAssetsQuote: swapAssets, + swapSubmissionData, + }; + + return ( + + {/* Swap uses routed dialogs to choose assets so needs onBackLocation to go Home */} + + + + + + + + + + ); +} diff --git a/src/app/pages/swap/bitflow-swap.utils.ts b/src/app/pages/swap/bitflow-swap.utils.ts new file mode 100644 index 00000000000..a8643b224f9 --- /dev/null +++ b/src/app/pages/swap/bitflow-swap.utils.ts @@ -0,0 +1,12 @@ +import BigNumber from 'bignumber.js'; + +import { capitalize } from '@leather.io/utils'; + +export function estimateLiquidityFee(dexPath: string[]) { + return new BigNumber(dexPath.length).times(0.3).toNumber(); +} + +export function formatDexPathItem(dex: string) { + const name = dex.split('_')[0]; + return name === 'ALEX' ? name : capitalize(name.toLowerCase()); +} 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 c0ca426ad3e..4f6a577d584 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 @@ -10,7 +10,7 @@ import { } from '@leather.io/ui'; import { formatMoneyWithoutSymbol } from '@leather.io/utils'; -import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; +import { convertSwapAssetBalanceToFiat } from '@app/pages/swap/swap.utils'; interface SwapAssetItemProps { asset: SwapAsset; @@ -22,7 +22,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 fiatBalance = convertAssetBalanceToFiat(asset); + const fiatBalance = convertSwapAssetBalanceToFiat(asset); return ( diff --git a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx index 0384ee6bc89..6e613c7d929 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx @@ -73,11 +73,7 @@ export function SwapAssetList({ assets, type }: SwapAssetList) { return ( {selectableAssets.map(asset => ( - onSelectAsset(asset)} - /> + onSelectAsset(asset)} /> ))} ); diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx index bf93398fe80..a8b899623d3 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx @@ -7,6 +7,7 @@ import { formatMoneyWithoutSymbol, i18nFormatCurrency, isDefined, + isMoneyGreaterThanZero, isUndefined, } from '@leather.io/utils'; @@ -81,7 +82,11 @@ export function SwapAssetSelectBase() { showError={!!(showError && values.swapAssetQuote)} swapAmountInput={ diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx index ff5d99b6c4c..0793cae9287 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx @@ -1,6 +1,11 @@ import { useField } from 'formik'; -import { formatMoneyWithoutSymbol, i18nFormatCurrency, isDefined } from '@leather.io/utils'; +import { + formatMoneyWithoutSymbol, + i18nFormatCurrency, + isDefined, + isMoneyGreaterThanZero, +} from '@leather.io/utils'; import { RouteUrls } from '@shared/route-urls'; @@ -38,7 +43,11 @@ export function SwapAssetSelectQuote() { ) : ( diff --git a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx index 4f34764fa28..e8ab720c26f 100644 --- a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx +++ b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx @@ -12,7 +12,7 @@ interface SwapAssetItemLayoutProps { export function SwapAssetItemLayout({ caption, icon, symbol, value }: SwapAssetItemLayoutProps) { return ( } + img={} spacing="space.03" width="100%" > diff --git a/src/app/pages/swap/components/swap-details/swap-details.tsx b/src/app/pages/swap/components/swap-details/swap-details.tsx index a10573a5560..9f433638174 100644 --- a/src/app/pages/swap/components/swap-details/swap-details.tsx +++ b/src/app/pages/swap/components/swap-details/swap-details.tsx @@ -16,6 +16,7 @@ import { getEstimatedConfirmationTime } from '@app/common/transactions/stacks/tr import { SwapSubmissionData, useSwapContext } from '@app/pages/swap/swap.context'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; +import { toCommaSeparatedWithAnd } from '../../swap.utils'; import { SwapDetailLayout } from './swap-detail.layout'; import { SwapDetailsLayout } from './swap-details.layout'; @@ -54,12 +55,21 @@ export function SwapDetails() { ) ); + const getFormattedPoweredBy = () => { + const uniqueDexList = Array.from(new Set(swapSubmissionData.dexPath)); + const isOnlySwapProtocol = + uniqueDexList.length === 1 && uniqueDexList[0] === swapSubmissionData.protocol; + return isOnlySwapProtocol || !uniqueDexList.length + ? swapSubmissionData.protocol + : `${toCommaSeparatedWithAnd(uniqueDexList)} via ${swapSubmissionData.protocol}`; + }; + return ( (); - const [slippage, _setSlippage] = useState(0.04); - const [isFetchingExchangeRate, setIsFetchingExchangeRate] = useState(false); - const address = useCurrentStacksAccountAddress(); - const { data: swapAssets = [] } = useAlexSwappableAssets(address); - - async function fetchQuoteAmount( - base: SwapAsset, - quote: SwapAsset, - baseAmount: string - ): Promise { - const amount = new BigNumber(baseAmount).multipliedBy(oneHundredMillion).dp(0).toString(); - const amountAsBigInt = isNaN(Number(amount)) ? BigInt(0) : BigInt(amount); - try { - setIsFetchingExchangeRate(true); - const result = await alex.getAmountTo(base.currency, amountAsBigInt, quote.currency); - setIsFetchingExchangeRate(false); - return new BigNumber(Number(result)).dividedBy(oneHundredMillion).toString(); - } catch (e) { - logger.error('Error fetching exchange rate from ALEX', e); - setIsFetchingExchangeRate(false); - return; - } - } - - return { - fetchQuoteAmount, - isFetchingExchangeRate, - onSetIsFetchingExchangeRate: (value: boolean) => setIsFetchingExchangeRate(value), - onSetSwapSubmissionData: (value: SwapSubmissionData) => setSwapSubmissionData(value), - slippage, - swapAssets, - swapSubmissionData, - }; -} diff --git a/src/app/pages/swap/hooks/use-bitflow-swap.tsx b/src/app/pages/swap/hooks/use-bitflow-swap.tsx new file mode 100644 index 00000000000..7a0a7bfeb04 --- /dev/null +++ b/src/app/pages/swap/hooks/use-bitflow-swap.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react'; + +import type { RouteQuote } from 'bitflow-sdk'; + +import { type SwapAsset } from '@leather.io/query'; + +import { logger } from '@shared/logger'; +import { bitflow } from '@shared/utils/bitflow-sdk'; + +import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; + +import { SwapSubmissionData } from '../swap.context'; +import { useBitflowSwappableAssets } from './use-bitflow-swappable-assets'; + +export function useBitflowSwap() { + const [swapSubmissionData, setSwapSubmissionData] = useState(); + const [slippage, _setSlippage] = useState(0.04); + const [isFetchingExchangeRate, setIsFetchingExchangeRate] = useState(false); + const address = useCurrentStacksAccountAddress(); + const { data: swapAssets = [] } = useBitflowSwappableAssets(address); + + async function fetchRouteQuote( + base: SwapAsset, + quote: SwapAsset, + baseAmount: string + ): Promise { + if (!baseAmount || !base || !quote) return; + try { + const result = await bitflow.getQuoteForRoute( + base.currency, + quote.currency, + Number(baseAmount) + ); + if (!result.bestRoute) { + logger.error('No swap route found'); + return; + } + return result.bestRoute; + } catch (e) { + logger.error('Error fetching exchange rate from Bitflow', e); + return; + } + } + + async function fetchQuoteAmount( + base: SwapAsset, + quote: SwapAsset, + baseAmount: string + ): Promise { + setIsFetchingExchangeRate(true); + const routeQuote = await fetchRouteQuote(base, quote, baseAmount); + setIsFetchingExchangeRate(false); + if (!routeQuote) return; + return String(routeQuote.quote); + } + + return { + fetchRouteQuote, + fetchQuoteAmount, + isFetchingExchangeRate, + onSetIsFetchingExchangeRate: (value: boolean) => setIsFetchingExchangeRate(value), + onSetSwapSubmissionData: (value: SwapSubmissionData) => setSwapSubmissionData(value), + slippage, + swapAssets, + swapSubmissionData, + }; +} diff --git a/src/app/pages/swap/hooks/use-bitflow-swappable-assets.tsx b/src/app/pages/swap/hooks/use-bitflow-swappable-assets.tsx new file mode 100644 index 00000000000..bbff32bded6 --- /dev/null +++ b/src/app/pages/swap/hooks/use-bitflow-swappable-assets.tsx @@ -0,0 +1,87 @@ +import { useCallback } from 'react'; + +import { useQuery } from '@tanstack/react-query'; +import { Currency } from 'alex-sdk'; +import BigNumber from 'bignumber.js'; +import type { Token } from 'bitflow-sdk'; + +import { createMarketData, createMarketPair } from '@leather.io/models'; +import { + type SwapAsset, + useAlexCurrencyPriceAsMarketData, + useAlexSdkLatestPricesQuery, + useStxAvailableUnlockedBalance, + useTransferableSip10Tokens, +} from '@leather.io/query'; +import { + convertAmountToFractionalUnit, + createMoney, + getPrincipalFromContractId, + isDefined, +} from '@leather.io/utils'; + +import { createGetBitflowAvailableTokensQueryOptions } from '@app/query/bitflow-sdk/bitflow-available-tokens.query'; + +import { sortSwapAssets } from '../swap.utils'; + +const BITFLOW_STX_CURRENCY: Currency = 'token-stx' as Currency; +const USD_DECIMAL_PRECISION = 2; + +function useCreateSwapAsset(address: string) { + const { data: prices } = useAlexSdkLatestPricesQuery(); + const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); + const availableUnlockedBalance = useStxAvailableUnlockedBalance(address); + const sip10Tokens = useTransferableSip10Tokens(address); + + return useCallback( + (token?: Token): SwapAsset | undefined => { + if (!prices || !token || !token.tokenContract) return; + + const swapAsset = { + currency: token.tokenId as Currency, + fallback: token.symbol.slice(0, 2), + icon: token.icon, + name: token.symbol, + displayName: token.name, + principal: token.tokenContract, + }; + + if (token.tokenId === BITFLOW_STX_CURRENCY) { + const price = convertAmountToFractionalUnit( + new BigNumber(prices[Currency.STX] ?? 0), + USD_DECIMAL_PRECISION + ); + return { + ...swapAsset, + balance: availableUnlockedBalance, + displayName: 'Stacks', + marketData: createMarketData( + createMarketPair(availableUnlockedBalance.symbol, 'USD'), + createMoney(price, 'USD') + ), + }; + } + + const availableBalance = sip10Tokens.find( + sip10Token => getPrincipalFromContractId(sip10Token.info.contractId) === token.tokenContract + )?.balance.availableBalance; + + return { + ...swapAsset, + balance: availableBalance ?? createMoney(0, token.symbol, token.tokenDecimals), + marketData: availableBalance + ? priceAsMarketData(swapAsset.principal, availableBalance.symbol) + : priceAsMarketData(swapAsset.principal, token.symbol), + }; + }, + [availableUnlockedBalance, priceAsMarketData, prices, sip10Tokens] + ); +} + +export function useBitflowSwappableAssets(address: string) { + const createSwapAsset = useCreateSwapAsset(address); + return useQuery({ + ...createGetBitflowAvailableTokensQueryOptions(), + select: resp => sortSwapAssets(resp.map(createSwapAsset).filter(isDefined)), + }); +} diff --git a/src/app/pages/swap/swap.context.ts b/src/app/pages/swap/swap.context.ts index fee322cdef9..bdabc0d7493 100644 --- a/src/app/pages/swap/swap.context.ts +++ b/src/app/pages/swap/swap.context.ts @@ -8,6 +8,7 @@ export interface SwapSubmissionData extends SwapFormValues { liquidityFee: number; protocol: string; router: SwapAsset[]; + dexPath: string[]; slippage: number; sponsored: boolean; timestamp: string; @@ -17,6 +18,7 @@ export interface SwapContext { fetchQuoteAmount(from: SwapAsset, to: SwapAsset, fromAmount: string): Promise; isFetchingExchangeRate: boolean; isSendingMax: boolean; + isPreparingSwapReview: boolean; onSetIsFetchingExchangeRate(value: boolean): void; onSetIsSendingMax(value: boolean): void; onSubmitSwapForReview(values: SwapFormValues): Promise | void; diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx index e9973007e5e..7320294e540 100644 --- a/src/app/pages/swap/swap.tsx +++ b/src/app/pages/swap/swap.tsx @@ -16,7 +16,12 @@ import { SwapFormValues } from './hooks/use-swap-form'; import { useSwapContext } from './swap.context'; export function Swap() { - const { isFetchingExchangeRate, swappableAssetsBase, swappableAssetsQuote } = useSwapContext(); + const { + isFetchingExchangeRate, + isPreparingSwapReview, + swappableAssetsBase, + swappableAssetsQuote, + } = useSwapContext(); const { dirty, isValid, setFieldValue, values, validateForm } = useFormikContext(); const { base, quote } = useParams(); @@ -51,7 +56,8 @@ export function Swap() { footer={