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/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/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-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);
+ 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..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..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/pages/swap/swap.utils.ts b/src/app/pages/swap/swap.utils.ts
index 12582d1fec6..7cff499e1aa 100644
--- a/src/app/pages/swap/swap.utils.ts
+++ b/src/app/pages/swap/swap.utils.ts
@@ -1,11 +1,25 @@
import type { MarketData, Money } from '@leather.io/models';
+import type { SwapAsset } from '@leather.io/query';
import {
baseCurrencyAmountInQuote,
createMoney,
+ i18nFormatCurrency,
isMoneyGreaterThanZero,
unitToFractionalUnit,
} from '@leather.io/utils';
+export function convertSwapAssetBalanceToFiat(asset: SwapAsset) {
+ if (
+ !asset.marketData ||
+ (!!asset.balance &&
+ isMoneyGreaterThanZero(asset.balance) &&
+ !isMoneyGreaterThanZero(asset.marketData.price))
+ )
+ // adjustment for assets with balance but no price data
+ return '-';
+ return i18nFormatCurrency(baseCurrencyAmountInQuote(asset.balance, asset.marketData));
+}
+
export function convertInputAmountValueToFiat(balance: Money, price: MarketData, value: string) {
const valueAsMoney = createMoney(
unitToFractionalUnit(balance.decimals)(value),
@@ -16,3 +30,32 @@ export function convertInputAmountValueToFiat(balance: Money, price: MarketData,
if (!isMoneyGreaterThanZero(valueAsMoney)) return;
return baseCurrencyAmountInQuote(valueAsMoney, price);
}
+
+export function toCommaSeparatedWithAnd(list: string[]) {
+ let result = '';
+ if (list.length) result = list[list.length - 1];
+ if (list.length > 1) result = `${list[list.length - 2]} and ${result}`;
+ if (list.length > 2) result = `${list.slice(0, -2).join(', ')}, ${result}`;
+ return result;
+}
+
+export function sortSwapAssets(assets: SwapAsset[]) {
+ return assets
+ .sort((a, b) => {
+ const nameA = (a.displayName ?? a.name).toLowerCase();
+ const nameB = (b.displayName ?? b.name).toLowerCase();
+ if (nameA < nameB) return -1;
+ if (nameA > nameB) return 1;
+ return 0;
+ })
+ .sort((a, b) => {
+ if (a.name === 'STX') return -1;
+ if (b.name !== 'STX') return 1;
+ return 0;
+ })
+ .sort((a, b) => {
+ if (a.name === 'BTC') return -1;
+ if (b.name !== 'BTC') return 1;
+ return 0;
+ });
+}
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 */}
{
+ try {
+ return new BitflowSDK({
+ API_HOST: BITFLOW_API_HOST,
+ API_KEY: BITFLOW_API_KEY,
+ STACKS_API_HOST: BITFLOW_STACKS_API_HOST,
+ READONLY_CALL_API_HOST: BITFLOW_READONLY_CALL_API_HOST,
+ });
+ } catch (e) {
+ logger.error('Bitflow SDK initialization failed');
+ // return fallback dummy object
+ return {} as BitflowSDK;
+ }
+})();
diff --git a/webpack/webpack.config.base.js b/webpack/webpack.config.base.js
index 988fce885d7..761b8261deb 100755
--- a/webpack/webpack.config.base.js
+++ b/webpack/webpack.config.base.js
@@ -117,8 +117,10 @@ export const config = {
vm: require.resolve('vm-browserify'),
assert: require.resolve('assert'),
'lottie-web': path.resolve('node_modules/lottie-web/build/player/lottie_light.js'),
- fs: false,
- path: false,
+ fs: require.resolve('browserify-fs'),
+ path: require.resolve('path-browserify'),
+ os: require.resolve('os-browserify/browser'),
+ process: require.resolve('process/browser'),
},
},
externals: {
@@ -291,6 +293,7 @@ export const config = {
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
+ process: 'process/browser',
}),
new ProgressBarPlugin(),