From f070d545dfce607189d27a4a8fea1001003d1ef2 Mon Sep 17 00:00:00 2001 From: Alex Perry Date: Wed, 18 Sep 2024 11:03:55 +0200 Subject: [PATCH] feat: bitflow implemenation, closes leather-io/issues#99 --- .github/workflows/build-extension.yml | 4 + .github/workflows/development-extension.yml | 4 + .github/workflows/playwright.yml | 4 + .github/workflows/publish-extensions.yml | 4 + package.json | 3 + pnpm-lock.yaml | 331 +++++++++++++++++- ...ntainer.tsx => bitflow-swap-container.tsx} | 153 ++++---- src/app/pages/swap/bitflow-swap.utils.ts | 12 + .../components/swap-asset-list.tsx | 6 +- .../components/swap-details/swap-details.tsx | 15 +- src/app/pages/swap/components/swap-review.tsx | 1 + ...use-alex-swap.tsx => use-bitflow-swap.tsx} | 49 ++- .../hooks/use-bitflow-swappable-assets.tsx | 86 +++++ src/app/pages/swap/swap.context.ts | 1 + .../bitflow-available-tokens.query.ts | 19 + .../bitflow-possible-swaps.query.ts | 20 ++ src/app/routes/app-routes.tsx | 4 +- src/shared/environment.ts | 4 + src/shared/utils/alex-sdk.ts | 3 - src/shared/utils/bitflow-sdk.ts | 15 + webpack/webpack.config.base.js | 7 +- 21 files changed, 641 insertions(+), 104 deletions(-) rename src/app/pages/swap/{alex-swap-container.tsx => bitflow-swap-container.tsx} (55%) create mode 100644 src/app/pages/swap/bitflow-swap.utils.ts rename src/app/pages/swap/hooks/{use-alex-swap.tsx => use-bitflow-swap.tsx} (51%) create mode 100644 src/app/pages/swap/hooks/use-bitflow-swappable-assets.tsx create mode 100644 src/app/query/bitflow-sdk/bitflow-available-tokens.query.ts create mode 100644 src/app/query/bitflow-sdk/bitflow-possible-swaps.query.ts delete mode 100644 src/shared/utils/alex-sdk.ts create mode 100644 src/shared/utils/bitflow-sdk.ts 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 c62127e6c40..5da46ec1d48 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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b17c6ec698..20bb930e01a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,9 +205,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 +277,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 @@ -4701,6 +4710,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==} @@ -4719,6 +4731,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==} @@ -6634,6 +6649,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'} @@ -7155,6 +7173,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==} @@ -7233,6 +7258,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==} @@ -7597,6 +7625,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'} @@ -8319,6 +8350,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'} @@ -8643,6 +8677,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==} @@ -9207,6 +9245,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'} @@ -9351,6 +9392,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 @@ -9850,6 +9894,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==} @@ -9915,6 +9962,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==} @@ -10187,6 +10237,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'} @@ -10330,12 +10383,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==} @@ -10695,6 +10757,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'} @@ -11058,6 +11147,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 @@ -11925,6 +12017,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'} @@ -11966,6 +12065,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==} @@ -12036,6 +12138,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'} @@ -12781,6 +12886,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==} @@ -13189,6 +13300,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==} @@ -13584,6 +13701,10 @@ packages: resolution: {integrity: sha512-LI7GzDuAZmNKOY0/LY4nB3ifh6kYMvBimFTHVpA6wNEl3gw59QrLbTAnJb7vQzPd1qXPz+BtKJZaYORXWMerrA==} engines: {node: ^18.17||>=20} + semver@2.3.2: + resolution: {integrity: sha512-abLdIKCosKfpnmhS52NCTjO4RiLspDfsn37prjzGrp9im5DPJOgh82Os92vtwGh6XdQryKI/7SREZnV+aqiXrA==} + hasBin: true + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -13931,6 +14052,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'} @@ -13958,6 +14082,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==} @@ -14521,6 +14648,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==} @@ -15232,6 +15362,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'} @@ -20144,7 +20290,7 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 - '@redux-devtools/app-core@1.0.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@reduxjs/toolkit@2.2.3(react-redux@9.1.0(@types/react@18.3.3)(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@5.0.1))(react@18.3.1))(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@4.2.1))(react@18.3.1)(redux-persist@6.0.0(react@18.3.1)(redux@4.2.1))(redux@4.2.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))': + '@redux-devtools/app-core@1.0.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@reduxjs/toolkit@2.2.3(react-redux@9.1.0(@types/react@18.3.3)(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@5.0.1))(react@18.3.1))(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@4.2.1))(react@18.3.1)(redux-persist@6.0.0(react@18.3.1)(redux@5.0.1))(redux@4.2.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))': dependencies: '@babel/runtime': 7.25.4 '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1) @@ -20169,7 +20315,7 @@ snapshots: react-is: 18.3.1 react-redux: 8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@4.2.1) redux: 4.2.1 - redux-persist: 6.0.0(react@18.3.1)(redux@4.2.1) + redux-persist: 6.0.0(react@18.3.1)(redux@5.0.1) styled-components: 5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) transitivePeerDependencies: - '@reduxjs/toolkit' @@ -20178,7 +20324,7 @@ snapshots: '@redux-devtools/app@6.1.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@reduxjs/toolkit@2.2.3(react-redux@9.1.0(@types/react@18.3.3)(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@5.0.1))(react@18.3.1))(@types/react-dom@18.3.0)(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))': dependencies: '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1) - '@redux-devtools/app-core': 1.0.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@reduxjs/toolkit@2.2.3(react-redux@9.1.0(@types/react@18.3.3)(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@5.0.1))(react@18.3.1))(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@4.2.1))(react@18.3.1)(redux-persist@6.0.0(react@18.3.1)(redux@4.2.1))(redux@4.2.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)) + '@redux-devtools/app-core': 1.0.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@reduxjs/toolkit@2.2.3(react-redux@9.1.0(@types/react@18.3.3)(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@5.0.1))(react@18.3.1))(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.3.3)(encoding@0.1.13)(react@18.2.0))(react@18.3.1)(redux@4.2.1))(react@18.3.1)(redux-persist@6.0.0(react@18.3.1)(redux@5.0.1))(redux@4.2.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)) '@redux-devtools/ui': 1.3.2(@types/react@18.3.3)(@types/styled-components@5.1.34)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)) '@types/react': 18.3.3 '@types/styled-components': 5.1.34 @@ -20933,6 +21079,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 @@ -20991,6 +21144,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 @@ -23555,6 +23719,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 @@ -24161,6 +24329,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 @@ -24279,6 +24459,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 @@ -24723,6 +24909,8 @@ snapshots: dependencies: mimic-response: 1.0.1 + clone@0.1.19: {} + clone@1.0.4: {} clone@2.1.2: {} @@ -25482,6 +25670,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 @@ -25834,6 +26026,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 @@ -26745,6 +26941,8 @@ snapshots: dependencies: is-callable: 1.2.7 + foreach@2.0.6: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -26923,6 +27121,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 @@ -27543,6 +27745,8 @@ snapshots: dependencies: postcss: 8.4.38 + idb-wrapper@1.7.2: {} + ieee754@1.2.1: {} ignore@5.3.1: {} @@ -27590,6 +27794,8 @@ snapshots: indent-string@5.0.0: {} + indexof@0.0.1: {} + infer-owner@1.0.4: optional: true @@ -27808,6 +28014,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: @@ -27912,10 +28120,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: {} @@ -28379,6 +28593,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: 2.3.2 + xtend: 3.0.0 + leven@3.1.0: {} levn@0.4.1: @@ -28676,6 +28948,8 @@ snapshots: lru-cache@7.18.3: {} + ltgt@2.2.1: {} + lz-string@1.5.0: {} magic-string@0.30.10: @@ -30059,6 +30333,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: {} @@ -30105,6 +30387,8 @@ snapshots: obuf@1.1.2: {} + octal@1.0.0: {} + ohash@1.1.3: {} on-exit-leak-free@2.1.2: {} @@ -30201,6 +30485,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: @@ -31006,6 +31292,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: @@ -31538,6 +31828,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 @@ -32027,6 +32331,8 @@ snapshots: dependencies: semver: 7.6.3 + semver@2.3.2: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -32523,6 +32829,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 @@ -32575,6 +32883,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 @@ -33118,6 +33428,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 @@ -34008,6 +34320,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/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/bitflow-swap-container.tsx similarity index 55% rename from src/app/pages/swap/alex-swap-container.tsx rename to src/app/pages/swap/bitflow-swap-container.tsx index 638df0b5695..80c2fea57d8 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/bitflow-swap-container.tsx @@ -1,22 +1,21 @@ import { useState } from 'react'; -import { Outlet } from 'react-router-dom'; +import { Outlet, useNavigate } from 'react-router-dom'; import { bytesToHex } from '@stacks/common'; -import { ContractCallPayload, TransactionTypes } from '@stacks/connect'; +import { type 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 { isDefined, isError, isUndefined } from '@leather.io/utils'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; -import { alex } from '@shared/utils/alex-sdk'; +import { bitflow } from '@shared/utils/bitflow-sdk'; import { migratePositiveAssetBalancesToTop } from '@app/common/asset-utils'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; @@ -26,25 +25,28 @@ import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/s 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 { oneHundredMillion, useAlexSwap } from './hooks/use-alex-swap'; +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 alexSwapRoutes = generateSwapRoutes(); +export const bitflowSwapRoutes = generateSwapRoutes(); -function AlexSwapContainer() { +function BitflowSwapContainer() { const [isSendingMax, setIsSendingMax] = useState(false); - const navigate = useSwapNavigate(); - const { setIsLoading } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); + 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 { + fetchRouteQuote, fetchQuoteAmount, isFetchingExchangeRate, onSetIsFetchingExchangeRate, @@ -52,8 +54,7 @@ function AlexSwapContainer() { slippage, swapAssets, swapSubmissionData, - } = useAlexSwap(); - const broadcastStacksSwap = useStacksBroadcastSwap(); + } = useBitflowSwap(); async function onSubmitSwapForReview(values: SwapFormValues) { if (isUndefined(values.swapAssetBase) || isUndefined(values.swapAssetQuote)) { @@ -61,19 +62,24 @@ function AlexSwapContainer() { return; } - const [router, lpFee] = await Promise.all([ - alex.getRouter(values.swapAssetBase.currency, values.swapAssetQuote.currency), - alex.getFeeRate(values.swapAssetBase.currency, values.swapAssetQuote.currency), - ]); + 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: new BigNumber(Number(lpFee)).dividedBy(oneHundredMillion).toNumber(), + liquidityFee: estimateLiquidityFee(routeQuote.route.dex_path), nonce: values.nonce, - protocol: 'ALEX', - router: router.map(x => swapAssets.find(asset => asset.currency === x)).filter(isDefined), + 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, @@ -83,10 +89,12 @@ function AlexSwapContainer() { timestamp: new Date().toISOString(), }); - navigate(RouteUrls.SwapReview); + swapNavigate(RouteUrls.SwapReview); } async function onSubmitSwap() { + if (isLoading) return; + if (isUndefined(currentAccount) || isUndefined(swapSubmissionData)) { logger.error('Error submitting swap data to sign'); return; @@ -102,60 +110,65 @@ function AlexSwapContainer() { 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 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 (error) {} + } catch (e) { + navigate(RouteUrls.SwapError, { + state: { + message: isError(e) ? e.message : '', + title: 'Swap Error', + }, + }); + } finally { + setIsIdle(); + } } const swapContextValue: SwapContext = { 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-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-details/swap-details.tsx b/src/app/pages/swap/components/swap-details/swap-details.tsx index a10573a5560..c5952314b6d 100644 --- a/src/app/pages/swap/components/swap-details/swap-details.tsx +++ b/src/app/pages/swap/components/swap-details/swap-details.tsx @@ -54,12 +54,21 @@ export function SwapDetails() { ) ); + const getFormattedPoweredBy = () => { + const uniqueDexPath = Array.from(new Set(swapSubmissionData.dexPath)); + const isOnlySwapProtocol = + uniqueDexPath.length === 1 && uniqueDexPath[0] === swapSubmissionData.protocol; + return isOnlySwapProtocol || !uniqueDexPath.length + ? swapSubmissionData.protocol + : `${uniqueDexPath.join(', ')} via ${swapSubmissionData.protocol}`; + }; + return ( (); const [slippage, _setSlippage] = useState(0.04); const [isFetchingExchangeRate, setIsFetchingExchangeRate] = useState(false); const address = useCurrentStacksAccountAddress(); - const { data: swapAssets = [] } = useAlexSwappableAssets(address); + const { data: swapAssets = [] } = useBitflowSwappableAssets(address); - async function fetchQuoteAmount( + async function fetchRouteQuote( 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); + ): Promise { + if (!baseAmount || !base || !quote) return; try { setIsFetchingExchangeRate(true); - const result = await alex.getAmountTo(base.currency, amountAsBigInt, quote.currency); - setIsFetchingExchangeRate(false); - return new BigNumber(Number(result)).dividedBy(oneHundredMillion).toString(); + 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 ALEX', e); - setIsFetchingExchangeRate(false); + logger.error('Error fetching exchange rate from Bitflow', e); return; + } finally { + setIsFetchingExchangeRate(false); } } + async function fetchQuoteAmount( + base: SwapAsset, + quote: SwapAsset, + baseAmount: string + ): Promise { + const routeQuote = await fetchRouteQuote(base, quote, baseAmount); + if (!routeQuote) return; + return String(routeQuote.quote); + } + return { + fetchRouteQuote, fetchQuoteAmount, isFetchingExchangeRate, onSetIsFetchingExchangeRate: (value: boolean) => setIsFetchingExchangeRate(value), 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..6a614d4ab63 --- /dev/null +++ b/src/app/pages/swap/hooks/use-bitflow-swappable-assets.tsx @@ -0,0 +1,86 @@ +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, + sortAssetsByName, +} from '@leather.io/utils'; + +import { createGetBitflowAvailableTokensQueryOptions } from '@app/query/bitflow-sdk/bitflow-available-tokens.query'; + +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 => sortAssetsByName(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..231722cd462 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; diff --git a/src/app/query/bitflow-sdk/bitflow-available-tokens.query.ts b/src/app/query/bitflow-sdk/bitflow-available-tokens.query.ts new file mode 100644 index 00000000000..ee6336c87b2 --- /dev/null +++ b/src/app/query/bitflow-sdk/bitflow-available-tokens.query.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import { bitflow } from '@shared/utils/bitflow-sdk'; + +export function createGetBitflowAvailableTokensQueryOptions() { + return { + queryKey: ['get-bitflow-available-tokens'], + queryFn: () => bitflow.getAvailableTokens(), + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryDelay: 1000 * 60, + staleTime: 1000 * 60 * 10, + }; +} + +export function useGetBitflowAvailableTokensQuery() { + return useQuery(createGetBitflowAvailableTokensQueryOptions()); +} diff --git a/src/app/query/bitflow-sdk/bitflow-possible-swaps.query.ts b/src/app/query/bitflow-sdk/bitflow-possible-swaps.query.ts new file mode 100644 index 00000000000..a7414726e6b --- /dev/null +++ b/src/app/query/bitflow-sdk/bitflow-possible-swaps.query.ts @@ -0,0 +1,20 @@ +import { useQuery } from '@tanstack/react-query'; + +import { bitflow } from '@shared/utils/bitflow-sdk'; + +export function createGetBitflowPossibleSwapsQueryOptions(token: string) { + return { + enabled: !!token, + queryKey: ['get-bitflow-possible-swaps', token], + queryFn: () => bitflow.getPossibleSwaps(token), + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryDelay: 1000 * 60, + staleTime: 1000 * 60 * 10, + }; +} + +export function useGetBitflowPossibleSwapsQuery(token: string) { + return useQuery(createGetBitflowPossibleSwapsQueryOptions(token)); +} diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 1cf14be6412..ee5c2afc6aa 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -42,7 +42,7 @@ import { RequestError } from '@app/pages/request-error/request-error'; import { BroadcastError } from '@app/pages/send/broadcast-error/broadcast-error'; import { sendOrdinalRoutes } from '@app/pages/send/ordinal-inscription/ordinal-routes'; import { sendCryptoAssetFormRoutes } from '@app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes'; -import { alexSwapRoutes } from '@app/pages/swap/alex-swap-container'; +import { bitflowSwapRoutes } from '@app/pages/swap/bitflow-swap-container'; import { UnauthorizedRequest } from '@app/pages/unauthorized-request/unauthorized-request'; import { Unlock } from '@app/pages/unlock'; import { ViewSecretKey } from '@app/pages/view-secret-key/view-secret-key'; @@ -192,7 +192,7 @@ function useAppRoutes() { } /> - {alexSwapRoutes} + {bitflowSwapRoutes} {/* OnBoarding Routes */}