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