From 4a8ffe02f32050535d40ef79711c8a1aca68acdb Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 5 Dec 2024 13:50:33 +0200 Subject: [PATCH 01/12] feat: swap widget created --- apps/web-swap-widget/.eslintignore | 0 apps/web-swap-widget/.eslintrc.js | 3 + apps/web-swap-widget/.gitignore | 25 ++ apps/web-swap-widget/README.md | 46 ++++ apps/web-swap-widget/index.html | 20 ++ apps/web-swap-widget/package.json | 65 +++++ apps/web-swap-widget/public/favicon.png | Bin 0 -> 544 bytes .../public/img/pro/dashboard.webp | Bin 0 -> 56280 bytes .../public/img/pro/multisend.webp | Bin 0 -> 48434 bytes .../public/img/swap/dedust.png | Bin 0 -> 3802 bytes .../public/img/swap/stonfi.png | Bin 0 -> 7227 bytes apps/web-swap-widget/public/img/toncoin.svg | 4 + apps/web-swap-widget/public/img/usdt.svg | 4 + apps/web-swap-widget/public/logo-128x128.png | Bin 0 -> 1245 bytes apps/web-swap-widget/public/logo-16x16.png | Bin 0 -> 479 bytes apps/web-swap-widget/public/logo-64x64.png | Bin 0 -> 953 bytes apps/web-swap-widget/public/manifest.json | 15 ++ apps/web-swap-widget/public/robots.txt | 3 + apps/web-swap-widget/src/App.tsx | 234 ++++++++++++++++++ .../src/components/SwapWidgetPage.tsx | 86 +++++++ apps/web-swap-widget/src/i18n.ts | 23 ++ apps/web-swap-widget/src/index.tsx | 7 + apps/web-swap-widget/src/libs/appSdk.ts | 48 ++++ apps/web-swap-widget/src/libs/hooks.ts | 77 ++++++ apps/web-swap-widget/src/libs/router-stub.tsx | 13 + apps/web-swap-widget/src/libs/scroll.ts | 30 +++ apps/web-swap-widget/src/libs/storage.ts | 36 +++ apps/web-swap-widget/src/logo.svg | 1 + apps/web-swap-widget/src/setupTests.ts | 5 + .../src/types/tonkeeper-injection-context.ts | 26 ++ apps/web-swap-widget/src/vite-env.d.ts | 1 + apps/web-swap-widget/task/locales.ts | 23 ++ apps/web-swap-widget/tsconfig.json | 30 +++ apps/web-swap-widget/tsconfig.node.json | 10 + apps/web-swap-widget/vite.config.mts | 31 +++ .../uikit/src/state/swap/useSwapsConfig.ts | 1 - yarn.lock | 40 +++ 37 files changed, 906 insertions(+), 1 deletion(-) create mode 100644 apps/web-swap-widget/.eslintignore create mode 100644 apps/web-swap-widget/.eslintrc.js create mode 100644 apps/web-swap-widget/.gitignore create mode 100644 apps/web-swap-widget/README.md create mode 100644 apps/web-swap-widget/index.html create mode 100644 apps/web-swap-widget/package.json create mode 100644 apps/web-swap-widget/public/favicon.png create mode 100644 apps/web-swap-widget/public/img/pro/dashboard.webp create mode 100644 apps/web-swap-widget/public/img/pro/multisend.webp create mode 100644 apps/web-swap-widget/public/img/swap/dedust.png create mode 100644 apps/web-swap-widget/public/img/swap/stonfi.png create mode 100644 apps/web-swap-widget/public/img/toncoin.svg create mode 100644 apps/web-swap-widget/public/img/usdt.svg create mode 100644 apps/web-swap-widget/public/logo-128x128.png create mode 100644 apps/web-swap-widget/public/logo-16x16.png create mode 100644 apps/web-swap-widget/public/logo-64x64.png create mode 100644 apps/web-swap-widget/public/manifest.json create mode 100644 apps/web-swap-widget/public/robots.txt create mode 100644 apps/web-swap-widget/src/App.tsx create mode 100644 apps/web-swap-widget/src/components/SwapWidgetPage.tsx create mode 100644 apps/web-swap-widget/src/i18n.ts create mode 100644 apps/web-swap-widget/src/index.tsx create mode 100644 apps/web-swap-widget/src/libs/appSdk.ts create mode 100644 apps/web-swap-widget/src/libs/hooks.ts create mode 100644 apps/web-swap-widget/src/libs/router-stub.tsx create mode 100644 apps/web-swap-widget/src/libs/scroll.ts create mode 100644 apps/web-swap-widget/src/libs/storage.ts create mode 100644 apps/web-swap-widget/src/logo.svg create mode 100644 apps/web-swap-widget/src/setupTests.ts create mode 100644 apps/web-swap-widget/src/types/tonkeeper-injection-context.ts create mode 100644 apps/web-swap-widget/src/vite-env.d.ts create mode 100644 apps/web-swap-widget/task/locales.ts create mode 100644 apps/web-swap-widget/tsconfig.json create mode 100644 apps/web-swap-widget/tsconfig.node.json create mode 100644 apps/web-swap-widget/vite.config.mts diff --git a/apps/web-swap-widget/.eslintignore b/apps/web-swap-widget/.eslintignore new file mode 100644 index 000000000..e69de29bb diff --git a/apps/web-swap-widget/.eslintrc.js b/apps/web-swap-widget/.eslintrc.js new file mode 100644 index 000000000..e331ffd24 --- /dev/null +++ b/apps/web-swap-widget/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc.js'] +}; diff --git a/apps/web-swap-widget/.gitignore b/apps/web-swap-widget/.gitignore new file mode 100644 index 000000000..7c6c32d78 --- /dev/null +++ b/apps/web-swap-widget/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +public/locales \ No newline at end of file diff --git a/apps/web-swap-widget/README.md b/apps/web-swap-widget/README.md new file mode 100644 index 000000000..b87cb0044 --- /dev/null +++ b/apps/web-swap-widget/README.md @@ -0,0 +1,46 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/apps/web-swap-widget/index.html b/apps/web-swap-widget/index.html new file mode 100644 index 000000000..36dd28217 --- /dev/null +++ b/apps/web-swap-widget/index.html @@ -0,0 +1,20 @@ + + + + + Tonkeeper Swap + + + + + + + + +
+ + + diff --git a/apps/web-swap-widget/package.json b/apps/web-swap-widget/package.json new file mode 100644 index 000000000..720da9dbe --- /dev/null +++ b/apps/web-swap-widget/package.json @@ -0,0 +1,65 @@ +{ + "name": "@tonkeeper/web-swap-widget", + "version": "0.0.1", + "author": "Ton APPS UK Limited ", + "description": "Web swap widget for Tonkeeper", + "dependencies": { + "@amplitude/analytics-browser": "^2.1.0", + "@aptabase/web": "^0.4.2", + "@tanstack/react-query": "4.3.4", + "@tonkeeper/core": "0.1.0", + "@tonkeeper/locales": "0.1.0", + "@tonkeeper/uikit": "0.1.0", + "buffer": "^6.0.3", + "copy-to-clipboard": "^3.3.3", + "i18next": "^22.1.4", + "i18next-browser-languagedetector": "^7.0.2", + "i18next-http-backend": "^2.0.2", + "process": "^0.11.10", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^12.1.1", + "styled-components": "^6.1.1" + }, + "devDependencies": { + "@testing-library/dom": "^9.3.1", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/fs-extra": "^11.0.4", + "@types/jest": "^27.5.2", + "@types/node": "^20.11.0", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@types/styled-components": "^5.1.26", + "@vitejs/plugin-react": "^4.2.1", + "fs-extra": "^11.2.0", + "react-is": "^18.2.0", + "ts-node": "^10.9.1", + "typescript": "5.2.2", + "vite": "^5.0.11", + "vite-plugin-node-polyfills": "0.17.0" + }, + "scripts": { + "locales": "ts-node ./task/locales", + "start": "yarn locales && vite dev", + "preview": "vite preview", + "build": "tsc && vite build && yarn locales", + "build:web": "yarn build" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "ts-standard": { + "project": "./tsconfig.json" + } +} diff --git a/apps/web-swap-widget/public/favicon.png b/apps/web-swap-widget/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa258c92e5f3ec39b4a72d9c2f446a3437d039e1 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{y5WcW`X4T9Kf0{@%Rk)xvc-@y7n_j!TPT*>%KT|_y!blTMHsx)&flf%6@ZQ_r+x$kOV6D3L$|i zL4s>RTp-HUmC6OW%&;WLFPMQxKtMu5MnT2Ez`-LRBqBaOAv?dIqQ0TIqkqDL`3sh> zU%z?B{{07zpTBpk3v_jqy`&9dXH59}UnWI8xTUh_ z*x_kUkL{egFYaP)zklu99r6LM7HD*x47u~YaR2;(Wgmkkc|Fy>FaqhU8%3uZid4X_iUgK O89ZJ6T-G@yGywpx;`|!` literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/img/pro/dashboard.webp b/apps/web-swap-widget/public/img/pro/dashboard.webp new file mode 100644 index 0000000000000000000000000000000000000000..ae3b91cd117296f932cd4abe3f81e0957a1af8c6 GIT binary patch literal 56280 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�QLyHM8 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 z4f&UlzyMB9Z?32weoaTj+7ti)0<_z}00000000000000000000000000000000000 M00000000000IkfiaR2}S literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/img/pro/multisend.webp b/apps/web-swap-widget/public/img/pro/multisend.webp new file mode 100644 index 0000000000000000000000000000000000000000..96042c2f7cb14fdde378dc5cf93483c2e0bf9f8d GIT binary patch literal 48434 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∋pq*NmZvnbZxm5Z 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 literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/img/swap/dedust.png b/apps/web-swap-widget/public/img/swap/dedust.png new file mode 100644 index 0000000000000000000000000000000000000000..e51d630e9891bb6d3fc40552477369c688072072 GIT binary patch literal 3802 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 literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/img/swap/stonfi.png b/apps/web-swap-widget/public/img/swap/stonfi.png new file mode 100644 index 0000000000000000000000000000000000000000..89519d09e44e9d6907070607d6e2cf359feb60cf GIT binary patch literal 7227 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 JPDHLkV1o7R@5KNB literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/img/toncoin.svg b/apps/web-swap-widget/public/img/toncoin.svg new file mode 100644 index 000000000..34fe73172 --- /dev/null +++ b/apps/web-swap-widget/public/img/toncoin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web-swap-widget/public/img/usdt.svg b/apps/web-swap-widget/public/img/usdt.svg new file mode 100644 index 000000000..3c7aa98e6 --- /dev/null +++ b/apps/web-swap-widget/public/img/usdt.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/web-swap-widget/public/logo-128x128.png b/apps/web-swap-widget/public/logo-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..59d440d36539492812c29e549a44bc1f7ac22160 GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&aTa()7BeuIPXS>@)A&?1prB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E8#0X`wFK*bFI|A(&nZ#)x3 zdT#sgFdxWx?6&BI=aRRc+koi5%c^(ot3Eof`{c0fljo|>?mND^Zv5)7_P^8G_fG47 zc&`88zVV~Wy3fuVK6`HX>AvQ(>-sNl>%Y3J`|h&(v)j5KZfm}}Z2)q=x~}`?vi6JH zx-Ty4Knh&feFc&0p@Kk$8&DP|?y~-?+gh-KwLmpM_Gj0%AdRjdz2AYRxd9CX>II2> z0~!TH5G!0E>Orz#jUW!C8cEKm6cUAwY0PijBOlTeSQ5yqoNbjGBWcEO3Es0>gpRB z8k(A#JNqV1o3&ukl9j7guU)%o)8-vJckkJ^fB*jDC(fO}aPiUO=P%xW`tsxVpT9fi zE@x+8U^?#U;uunK>&+!*C$~faw}&UUXmQMyewW+#;&;*sNgqHK}FSP$q!%Lj79q1b}-*=uKd#TY-(qz;kKu|A(x9Ewa-{C z*3Ziy(Z|O0_T!f|lUUQ{&AcZr%pN?^LE*x5fBlTd-k&Gm{`9h}y}8kQ>z7GO0{1nY zZ&=PB^U`ODc&OgSL+K2cSN}_@ay8ekVbj^WzAvW4w*i}lt+I#CA&CcHdK5NsBsW}UZaSj$ z{IgyClb;U^x~mdKI;Vst0Fn|!0RR91 literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/logo-16x16.png b/apps/web-swap-widget/public/logo-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4e7dd21e73aff51307f806d2fd962385e930d6 GIT binary patch literal 479 zcmV<50U-W~P)CoyZL5q~rkyr+(#E!J+h}4N&u{jgXB+$LE)G6Q zO2|XsL(vVaADT-d6AY|AHMHF8LN(~SKeC-7^HH4u1W92Pj-k5p%&3ZIay0NfHP>24 z4N}Vjpd0zStLQ4fT7_*FdWdgGbgfx*XKR@Sw54E81sN9n8Q&sHgx>ojJ%P@uM2gTE zr9N4G7dRHezBL4%oE}+zH<>aL8Sk&E7;xH(P#tSuAaYfqkqAMfoLA)si2@Rg2}DJm zB$Xl6^|4iciP7~W5o17V3Gz!UyVHu31OoQt@yRv*n}T{pDAVdvekBQN>-LFG;1s zmnw1PZjz`_4Lnb*YG9Fw`YKqX9NaxtLKIcMeUVf9?2qn6K7!=qgI|7Qty);ZDFI0$ Vj5G&nKl%Uw002ovPDHLkV1gb|%i90| literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/logo-64x64.png b/apps/web-swap-widget/public/logo-64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..9048edc7060beaa0796e25a54f31d5f35db3a6e9 GIT binary patch literal 953 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!I14-?iy0WCPk}I_+{y{!Ktah8 z*NBqf{Irtt#G+J&^73-M%)IR4azZu`}%LL>%X|H zgCLNI+d7~Kko*E9UDkaCDP9W{|LV5!yW3hY7b3Xst1C$57uPjF2GHKGu0WL_E#H9R zAd5icSGTo5?pGiKWHQV^plLusSD>XJ!+|P6ih&|-KtYfpASsXnprt?)fZCzzA-Yh= zj7dk@fZ>%~666=mz{teH%F52c!O6|XFDN7?CMTz;q^_ZE#<7921wA znv<7bSXNzATVLPM(AeDC(KBVn%=wF!EL*W^&6>6A*KgRodF!@ayZ7$hf8^Ndb2slk zeE$6P`_JEg{{H*Vn1A;bU^LD5ba4!+xRs>9;l^le?0ReGR%M1IS6TCz4Fy^sY8o3G z7avYdn`JcleSO=c#n*1$+%wmAKXb$^^}nBLm&_`($=o?w{>+ z>ivEL$#reqhv(+87_!;K3M3!6%xcJH7keoA!R4>XCTZ5P{fz$C`vbRSIz{U%9{+Le z=Wfo&Mpxa}T{69AU1R2@&;Id5ieYL-`m@DvtrvgTAl2=nC$B zR;Ubp_le);SZbEtr`%O@TI9Apk$CqqbL|u5gVSUVTi%sYvft{SbJOt2%l*q%pSaEt zoy1f9Yd+tn>Dv!Xvv&Jcay`Q?HL(25G~+hgyQb03k-ZzPd%C=Ex_`o`+I7z{E{+UK z?y7`!^A>zuz+T0sJY(FsyDo&fgzks7~nha;3 zK>ErKAnp`M<5Y5Sd%`@Ir9r1uun7YvwnRMTtH0>VvFQpH9OA;!TXN}tP2bd+i65E+ b85nv?*Kcpz_QM01G8jBv{an^LB{Ts5Kn%?m literal 0 HcmV?d00001 diff --git a/apps/web-swap-widget/public/manifest.json b/apps/web-swap-widget/public/manifest.json new file mode 100644 index 000000000..d2033a3f1 --- /dev/null +++ b/apps/web-swap-widget/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Tonkeeper", + "name": "Tonkeeper Web", + "icons": [ + { + "src": "favicon.png", + "type": "image/png", + "sizes": "32x32" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/apps/web-swap-widget/public/robots.txt b/apps/web-swap-widget/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/apps/web-swap-widget/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx new file mode 100644 index 000000000..b25cf53f6 --- /dev/null +++ b/apps/web-swap-widget/src/App.tsx @@ -0,0 +1,234 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { localizationText } from '@tonkeeper/core/dist/entries/language'; +import { getApiConfig, Network } from '@tonkeeper/core/dist/entries/network'; +import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; +import { FooterGlobalStyle } from '@tonkeeper/uikit/dist/components/Footer'; +import { HeaderGlobalStyle } from '@tonkeeper/uikit/dist/components/Header'; +import { DarkThemeContext } from '@tonkeeper/uikit/dist/components/Icon'; +import { GlobalListStyle } from '@tonkeeper/uikit/dist/components/List'; +import { Loading } from '@tonkeeper/uikit/dist/components/Loading'; +import { SybHeaderGlobalStyle } from '@tonkeeper/uikit/dist/components/SubHeader'; +import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude'; +import { AppContext, IAppContext } from '@tonkeeper/uikit/dist/hooks/appContext'; +import { AppSdkContext } from '@tonkeeper/uikit/dist/hooks/appSdk'; +import { StorageContext } from '@tonkeeper/uikit/dist/hooks/storage'; +import { + I18nContext, + TranslationContext, + useTWithReplaces +} from '@tonkeeper/uikit/dist/hooks/translation'; +import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider'; +import { useDevSettings } from '@tonkeeper/uikit/dist/state/dev'; +import { useUserFiatQuery } from '@tonkeeper/uikit/dist/state/fiat'; +import { useUserLanguage } from '@tonkeeper/uikit/dist/state/language'; +import { useProBackupState } from '@tonkeeper/uikit/dist/state/pro'; +import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; +import { useAccountsStateQuery, useActiveAccountQuery } from '@tonkeeper/uikit/dist/state/wallet'; +import { GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; +import { FC, Suspense, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { BrowserAppSdk } from './libs/appSdk'; +import { useAnalytics, useAppHeight, useAppWidth } from './libs/hooks'; +import { useGlobalPreferencesQuery } from '@tonkeeper/uikit/dist/state/global-preferences'; +import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup'; +import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; +import { useKeyboardHeight } from '@tonkeeper/uikit/dist/pages/import/hooks'; +import { useDebuggingTools } from '@tonkeeper/uikit/dist/hooks/useDebuggingTools'; +import MemoryScroll from '@tonkeeper/uikit/dist/components/MemoryScroll'; +import styled, { css } from 'styled-components'; +import { SwapWidgetPage } from './components/SwapWidgetPage'; +import { Container } from '@tonkeeper/uikit'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30000, + refetchOnWindowFocus: false + } + } +}); + +const sdk = new BrowserAppSdk(); +const TARGET_ENV = 'web'; + +export const App: FC = () => { + const { t: tSimple, i18n } = useTranslation(); + + const t = useTWithReplaces(tSimple); + + const translation = useMemo(() => { + const languages = (import.meta.env.VITE_APP_LOCALES ?? 'en').split(','); + const client: I18nContext = { + t, + i18n: { + enable: true, + reloadResources: i18n.reloadResources, + changeLanguage: i18n.changeLanguage as (lang: string) => Promise, + language: i18n.language, + languages: languages + } + }; + return client; + }, [t, i18n]); + + return ( + + + + + + + + + + + + ); +}; + +const ThemeAndContent = () => { + const { data } = useProBackupState(); + return ( + + + + + + + + + + + ); +}; + +const Loader: FC = () => { + const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery(); + const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery(); + const { data: lang, isLoading: isLangLoading } = useUserLanguage(); + const { data: fiat } = useUserFiatQuery(); + const { data: devSettings } = useDevSettings(); + const { isLoading: globalPreferencesLoading } = useGlobalPreferencesQuery(); + const { isLoading: globalSetupLoading } = useGlobalSetup(); + + const [ios, standalone] = useMemo(() => { + return [sdk.isIOs(), sdk.isStandalone()] as const; + }, []); + + const { i18n } = useTranslation(); + + const tonendpoint = useTonendpoint({ + targetEnv: TARGET_ENV, + build: sdk.version, + lang + }); + const { data: serverConfig } = useTonenpointConfig(tonendpoint); + + useAppHeight(); + + const { data: tracker } = useAnalytics(activeAccount || undefined, accounts, sdk.version); + + useEffect(() => { + if (activeAccount && lang && i18n.language !== localizationText(lang)) { + i18n.reloadResources([localizationText(lang)]).then(() => + i18n.changeLanguage(localizationText(lang)) + ); + } + }, [activeAccount, i18n]); + + if ( + isWalletsLoading || + activeWalletLoading || + isLangLoading || + serverConfig === undefined || + fiat === undefined || + !devSettings || + globalPreferencesLoading || + globalSetupLoading + ) { + return ; + } + + const context: IAppContext = { + mainnetApi: getApiConfig( + serverConfig.mainnetConfig, + Network.MAINNET, + import.meta.env.VITE_APP_TONCONSOLE_HOST + ), + testnetApi: getApiConfig(serverConfig.mainnetConfig, Network.TESTNET), + fiat, + mainnetConfig: serverConfig.mainnetConfig, + testnetConfig: serverConfig.testnetConfig, + tonendpoint, + standalone, + extension: false, + proFeatures: false, + ios, + defaultWalletVersion: WalletVersion.V5R1, + hideMultisig: true, + env: { + tgAuthBotId: import.meta.env.VITE_APP_TG_BOT_ID, + stonfiReferralAddress: import.meta.env.VITE_APP_STONFI_REFERRAL_ADDRESS + } + }; + + return ( + + + + + + + ); +}; + +const FullSizeWrapper = styled(Container)<{ standalone: boolean }>` + ${props => + props.standalone + ? css` + position: fixed; + top: 0; + height: calc(var(--app-height) - 2px); + -webkit-overflow-scrolling: touch; + ` + : css` + @media (min-width: 600px) { + border-left: 1px solid ${props.theme.separatorCommon}; + border-right: 1px solid ${props.theme.separatorCommon}; + } + `}; + + > * { + ${props => + props.standalone && + css` + overflow: auto; + width: var(--app-width); + max-width: 548px; + box-sizing: border-box; + `} + } +`; + +const Wrapper = styled(FullSizeWrapper)<{ standalone: boolean }>` + box-sizing: border-box; + padding-top: 64px; + padding-bottom: ${props => (props.standalone ? '96' : '80')}px; +`; + +const Content: FC<{ + standalone: boolean; +}> = ({ standalone }) => { + useWindowsScroll(); + useAppWidth(standalone); + useKeyboardHeight(); + useTrackLocation(); + useDebuggingTools(); + + return ( + + + + ); +}; diff --git a/apps/web-swap-widget/src/components/SwapWidgetPage.tsx b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx new file mode 100644 index 000000000..126e11ee9 --- /dev/null +++ b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx @@ -0,0 +1,86 @@ +import { styled, useTheme } from 'styled-components'; +import { useEncodeSwapToTonConnectParams } from '@tonkeeper/uikit/dist/state/swap/useEncodeSwap'; +import { useState } from 'react'; +import { + useSelectedSwap, + useSwapFromAmount, + useSwapFromAsset, + useSwapToAsset +} from '@tonkeeper/uikit/dist/state/swap/useSwapForm'; +import { CalculatedSwap } from '@tonkeeper/uikit/dist/state/swap/useCalculatedSwap'; +import { SwapFromField } from '@tonkeeper/uikit/dist/components/swap/SwapFromField'; +import { SwapIcon } from '@tonkeeper/uikit/dist/components/Icon'; +import { SwapToField } from '@tonkeeper/uikit/dist/components/swap/SwapToField'; +import { SwapProviders } from '@tonkeeper/uikit/dist/components/swap/SwapProviders'; +import { SwapButton } from '@tonkeeper/uikit/dist/components/swap/SwapButton'; +import { SwapTokensListNotification } from '@tonkeeper/uikit/dist/components/swap/tokens-list/SwapTokensListNotification'; +import { IconButton } from '@tonkeeper/uikit/dist/components/fields/IconButton'; +import { TonConnectTransactionPayload } from '@tonkeeper/core/dist/entries/tonConnect'; +import { NonNullableFields } from '@tonkeeper/core/dist/utils/types'; + +const MainFormWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + +const ChangeIconStyled = styled(IconButton)` + height: 32px; + width: 32px; + position: absolute; + right: calc(50% - 16px); + bottom: -20px; + border: none; + + background-color: ${props => props.theme.buttonTertiaryBackground}; + + > svg { + transition: color 0.15s ease-in-out; + } + + &:hover { + background-color: ${props => props.theme.buttonTertiaryBackgroundHighlighted}; + > svg { + color: ${props => props.theme.iconPrimary}; + } + } +`; + +export const SwapWidgetPage = () => { + const theme = useTheme(); + const { isLoading, mutateAsync: encode } = useEncodeSwapToTonConnectParams(); + const [modalParams, setModalParams] = useState(null); + const [selectedSwap] = useSelectedSwap(); + const [fromAsset, setFromAsset] = useSwapFromAsset(); + const [toAsset, setToAsset] = useSwapToAsset(); + const [_, setFromAmount] = useSwapFromAmount(); + + const onConfirm = async () => { + const params = await encode(selectedSwap! as NonNullableFields); + // Ton Connect send + + setModalParams(params); + }; + + const onChangeFields = () => { + setFromAsset(toAsset); + setToAsset(fromAsset); + if (selectedSwap?.trade) { + setFromAmount(selectedSwap.trade.to.relativeAmount); + } + }; + + return ( + + + + + + + + {theme.displayType === 'compact' && } + + + + ); +}; diff --git a/apps/web-swap-widget/src/i18n.ts b/apps/web-swap-widget/src/i18n.ts new file mode 100644 index 000000000..58fede7d7 --- /dev/null +++ b/apps/web-swap-widget/src/i18n.ts @@ -0,0 +1,23 @@ +import resources from '@tonkeeper/locales/dist/i18n/default.json'; +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import Backend from 'i18next-http-backend'; +import { initReactI18next } from 'react-i18next'; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + debug: false, + lng: 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources + // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage + // if you're using a language detector, do not define the lng option + fallbackLng: 'en', + interpolation: { + escapeValue: false, // react already safes from xss + }, + }); + +export default i18n; diff --git a/apps/web-swap-widget/src/index.tsx b/apps/web-swap-widget/src/index.tsx new file mode 100644 index 000000000..41b41115a --- /dev/null +++ b/apps/web-swap-widget/src/index.tsx @@ -0,0 +1,7 @@ +import ReactDOM from 'react-dom/client'; +import { App } from './App'; +import './i18n'; + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); + +root.render(); diff --git a/apps/web-swap-widget/src/libs/appSdk.ts b/apps/web-swap-widget/src/libs/appSdk.ts new file mode 100644 index 000000000..5ddd73019 --- /dev/null +++ b/apps/web-swap-widget/src/libs/appSdk.ts @@ -0,0 +1,48 @@ +import { BaseApp } from '@tonkeeper/core/dist/AppSdk'; +import copyToClipboard from 'copy-to-clipboard'; +import packageJson from '../../package.json'; +import { disableScroll, enableScroll, getScrollbarWidth } from './scroll'; +import { BrowserStorage } from './storage'; + +function iOS() { + return ( + ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes( + navigator.platform + ) || + // iPad on iOS 13 detection + (navigator.userAgent.includes('Mac') && 'ontouchend' in document) + ); +} + +export class BrowserAppSdk extends BaseApp { + constructor() { + super(new BrowserStorage()); + } + + copyToClipboard = (value: string, notification?: string) => { + copyToClipboard(value); + + this.topMessage(notification); + }; + + openPage = async (url: string) => { + window.open(url, '_black'); + }; + + disableScroll = disableScroll; + + enableScroll = enableScroll; + + getScrollbarWidth = getScrollbarWidth; + + getKeyboardHeight = () => 0; + + isIOs = iOS; + + isStandalone = () => + iOS() && ((window.navigator as unknown as { standalone: boolean }).standalone as boolean); + + version = packageJson.version ?? 'Unknown'; + + targetEnv = 'web' as const; +} diff --git a/apps/web-swap-widget/src/libs/hooks.ts b/apps/web-swap-widget/src/libs/hooks.ts new file mode 100644 index 000000000..e600e155c --- /dev/null +++ b/apps/web-swap-widget/src/libs/hooks.ts @@ -0,0 +1,77 @@ +import { useQuery } from '@tanstack/react-query'; +import { Account } from '@tonkeeper/core/dist/entries/account'; +import { throttle } from '@tonkeeper/core/dist/utils/common'; +import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics'; +import { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web'; +import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag'; +import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey'; +import { useActiveTonNetwork } from '@tonkeeper/uikit/dist/state/wallet'; +import { useEffect } from 'react'; + +export const useAppHeight = () => { + useEffect(() => { + const appHeight = throttle(() => { + const doc = document.documentElement; + doc.style.setProperty('--app-height', `${window.innerHeight}px`); + }, 50); + window.addEventListener('resize', appHeight); + appHeight(); + + return () => { + window.removeEventListener('resize', appHeight); + }; + }, []); +}; + +export const useAppWidth = (standalone: boolean) => { + useEffect(() => { + const appWidth = throttle(() => { + if (standalone) { + const doc = document.documentElement; + doc.style.setProperty('--app-width', `${window.innerWidth}px`); + } else { + const doc = document.documentElement; + const app = (document.getElementById('root') as HTMLDivElement).childNodes.item( + 0 + ) as HTMLDivElement; + + doc.style.setProperty('--app-width', `${app.clientWidth}px`); + } + }, 50); + window.addEventListener('resize', appWidth); + + appWidth(); + + return () => { + window.removeEventListener('resize', appWidth); + }; + }, [standalone]); +}; + +export const useAnalytics = (activeAccount?: Account, accounts?: Account[], version?: string) => { + const network = useActiveTonNetwork(); + return useQuery( + [QueryKey.analytics, network], + async () => { + const tracker = new AnalyticsGroup( + new AptabaseWeb( + import.meta.env.VITE_APP_APTABASE_HOST, + import.meta.env.VITE_APP_APTABASE, + version + ), + new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID) + ); + + tracker.init({ + application: 'Web', + walletType: toWalletType(activeAccount?.activeTonWallet), + activeAccount: activeAccount!, + accounts: accounts!, + network + }); + + return tracker; + }, + { enabled: accounts != null && activeAccount !== undefined } + ); +}; diff --git a/apps/web-swap-widget/src/libs/router-stub.tsx b/apps/web-swap-widget/src/libs/router-stub.tsx new file mode 100644 index 000000000..4dae5113f --- /dev/null +++ b/apps/web-swap-widget/src/libs/router-stub.tsx @@ -0,0 +1,13 @@ +export const useNavigate = () => { + return () => {}; +}; + +export const useLocation = () => { + return { + pathname: '' + }; +}; + +export const Link = () => { + return <>; +}; diff --git a/apps/web-swap-widget/src/libs/scroll.ts b/apps/web-swap-widget/src/libs/scroll.ts new file mode 100644 index 000000000..d8d76b46d --- /dev/null +++ b/apps/web-swap-widget/src/libs/scroll.ts @@ -0,0 +1,30 @@ +export const disableScroll = () => { + document.documentElement.classList.add('is-locked'); + window.document.body.style.paddingRight = `${getScrollbarWidth()}px`; +}; + +export const enableScroll = () => { + document.documentElement.classList.remove('is-locked'); + window.document.body.style.paddingRight = '0px'; +}; + +export const getScrollbarWidth = () => { + // Creating invisible container + const outer = document.createElement('div'); + outer.style.visibility = 'hidden'; + outer.style.overflow = 'scroll'; // forcing scrollbar to appear + (outer.style as any).msOverflowStyle = 'scrollbar'; // needed for WinJS apps + document.body.appendChild(outer); + + // Creating inner element and placing it in the container + const inner = document.createElement('div'); + outer.appendChild(inner); + + // Calculating difference between container's full width and the child width + const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; + + // Removing temporary elements from the DOM + outer.parentNode!.removeChild(outer); + + return scrollbarWidth; +}; diff --git a/apps/web-swap-widget/src/libs/storage.ts b/apps/web-swap-widget/src/libs/storage.ts new file mode 100644 index 000000000..1ee17aba9 --- /dev/null +++ b/apps/web-swap-widget/src/libs/storage.ts @@ -0,0 +1,36 @@ +import { IStorage } from '@tonkeeper/core/dist/Storage'; + +export class BrowserStorage implements IStorage { + prefix = 'tonkeeper-swap-widget'; + + get = async (key: string) => { + const value = localStorage.getItem(`${this.prefix}_${key}`); + if (!value) return null; + const { payload } = JSON.parse(value) as { payload: R }; + return payload; + }; + + set = async (key: string, payload: R) => { + localStorage.setItem(`${this.prefix}_${key}`, JSON.stringify({ payload })); + return payload; + }; + + setBatch = async >(values: V) => { + Object.entries(values).forEach(([key, payload]) => { + localStorage.setItem(`${this.prefix}_${key}`, JSON.stringify({ payload })); + }); + return values; + }; + + delete = async (key: string) => { + const payload = await this.get(key); + if (payload != null) { + localStorage.removeItem(`${this.prefix}_${key}`); + } + return payload; + }; + + clear = async () => { + localStorage.clear(); + }; +} diff --git a/apps/web-swap-widget/src/logo.svg b/apps/web-swap-widget/src/logo.svg new file mode 100644 index 000000000..9dfc1c058 --- /dev/null +++ b/apps/web-swap-widget/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web-swap-widget/src/setupTests.ts b/apps/web-swap-widget/src/setupTests.ts new file mode 100644 index 000000000..8f2609b7b --- /dev/null +++ b/apps/web-swap-widget/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/apps/web-swap-widget/src/types/tonkeeper-injection-context.ts b/apps/web-swap-widget/src/types/tonkeeper-injection-context.ts new file mode 100644 index 000000000..9a0f33467 --- /dev/null +++ b/apps/web-swap-widget/src/types/tonkeeper-injection-context.ts @@ -0,0 +1,26 @@ +import { getWindow } from '@tonkeeper/core/dist/service/telegramOauth'; + +type UserFriendlyAddress = string; + +export type TonkeeperInjectionContext = { + address: UserFriendlyAddress; + sendTransaction: (params: { + source: UserFriendlyAddress; + valid_until: 1733389156144; + messages: { + address: UserFriendlyAddress; + amount: string; + payload: string; + }[]; + }) => Promise; +}; + +declare global { + interface Window { + tonkeeperStonfi?: TonkeeperInjectionContext; + } +} + +export const getTonkeeperInjectionContext = () => { + return getWindow()?.tonkeeperStonfi; +}; diff --git a/apps/web-swap-widget/src/vite-env.d.ts b/apps/web-swap-widget/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/apps/web-swap-widget/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/web-swap-widget/task/locales.ts b/apps/web-swap-widget/task/locales.ts new file mode 100644 index 000000000..fbfbfa0fd --- /dev/null +++ b/apps/web-swap-widget/task/locales.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; + +console.log('Copy Locales'); +const srcDir = `../../packages/locales/dist/locales`; +const buildDestDir = `dist/locales`; +const devDestDir = `public/locales`; + +console.log(path.resolve(srcDir)); +fs.readdirSync(srcDir).forEach(file => console.log(file)); + +if (!fs.existsSync('build')) { + fs.mkdirSync('build'); +} +if (!fs.existsSync(buildDestDir)) { + fs.mkdirSync(buildDestDir); +} +fs.rmSync(devDestDir, { recursive: true, force: true }); +if (!fs.existsSync(devDestDir)) { + fs.mkdirSync(devDestDir); +} +fs.copySync(srcDir, buildDestDir, { overwrite: true }); +fs.copySync(srcDir, devDestDir, { overwrite: true }); diff --git a/apps/web-swap-widget/tsconfig.json b/apps/web-swap-widget/tsconfig.json new file mode 100644 index 000000000..8f9d0bf15 --- /dev/null +++ b/apps/web-swap-widget/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} diff --git a/apps/web-swap-widget/tsconfig.node.json b/apps/web-swap-widget/tsconfig.node.json new file mode 100644 index 000000000..af7c06e30 --- /dev/null +++ b/apps/web-swap-widget/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web-swap-widget/vite.config.mts b/apps/web-swap-widget/vite.config.mts new file mode 100644 index 000000000..2d2a40619 --- /dev/null +++ b/apps/web-swap-widget/vite.config.mts @@ -0,0 +1,31 @@ +import react from '@vitejs/plugin-react'; +import * as path from 'path'; +import { defineConfig } from 'vite'; +import { nodePolyfills } from 'vite-plugin-node-polyfills'; + +export default defineConfig({ + plugins: [ + nodePolyfills({ + globals: { + Buffer: true, + global: true, + process: true + }, + include: ['stream', 'buffer', 'crypto'] + }), + react() + ], + resolve: { + alias: { + react: path.resolve(__dirname, './node_modules/react'), + 'react-dom': path.resolve(__dirname, './node_modules/react-dom'), + '@ton/core': path.resolve(__dirname, '../../packages/core/node_modules/@ton/core'), + '@ton/crypto': path.resolve(__dirname, '../../packages/core/node_modules/@ton/crypto'), + '@ton/ton': path.resolve(__dirname, '../../packages/core/node_modules/@ton/ton'), + 'react-router-dom': path.resolve(__dirname, './src/libs/router-stub'), + 'styled-components': path.resolve(__dirname, './node_modules/styled-components'), + 'react-i18next': path.resolve(__dirname, './node_modules/react-i18next'), + '@tanstack/react-query': path.resolve(__dirname, './node_modules/@tanstack/react-query') + } + } +}); diff --git a/packages/uikit/src/state/swap/useSwapsConfig.ts b/packages/uikit/src/state/swap/useSwapsConfig.ts index a17f35479..a2f8ea49f 100644 --- a/packages/uikit/src/state/swap/useSwapsConfig.ts +++ b/packages/uikit/src/state/swap/useSwapsConfig.ts @@ -1,4 +1,3 @@ -import { useAppContext } from '../../hooks/appContext'; import { OpenAPI, SwapService } from '@tonkeeper/core/dist/swapsApi'; import { useActiveConfig } from '../wallet'; diff --git a/yarn.lock b/yarn.lock index 2f3a2048e..a4da84c4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7655,6 +7655,46 @@ __metadata: languageName: unknown linkType: soft +"@tonkeeper/web-swap-widget@workspace:apps/web-swap-widget": + version: 0.0.0-use.local + resolution: "@tonkeeper/web-swap-widget@workspace:apps/web-swap-widget" + dependencies: + "@amplitude/analytics-browser": "npm:^2.1.0" + "@aptabase/web": "npm:^0.4.2" + "@tanstack/react-query": "npm:4.3.4" + "@testing-library/dom": "npm:^9.3.1" + "@testing-library/jest-dom": "npm:^5.16.5" + "@testing-library/react": "npm:^13.4.0" + "@testing-library/user-event": "npm:^13.5.0" + "@tonkeeper/core": "npm:0.1.0" + "@tonkeeper/locales": "npm:0.1.0" + "@tonkeeper/uikit": "npm:0.1.0" + "@types/fs-extra": "npm:^11.0.4" + "@types/jest": "npm:^27.5.2" + "@types/node": "npm:^20.11.0" + "@types/react": "npm:^18.0.26" + "@types/react-dom": "npm:^18.0.9" + "@types/styled-components": "npm:^5.1.26" + "@vitejs/plugin-react": "npm:^4.2.1" + buffer: "npm:^6.0.3" + copy-to-clipboard: "npm:^3.3.3" + fs-extra: "npm:^11.2.0" + i18next: "npm:^22.1.4" + i18next-browser-languagedetector: "npm:^7.0.2" + i18next-http-backend: "npm:^2.0.2" + process: "npm:^0.11.10" + react: "npm:^18.2.0" + react-dom: "npm:^18.2.0" + react-i18next: "npm:^12.1.1" + react-is: "npm:^18.2.0" + styled-components: "npm:^6.1.1" + ts-node: "npm:^10.9.1" + typescript: "npm:5.2.2" + vite: "npm:^5.0.11" + vite-plugin-node-polyfills: "npm:0.17.0" + languageName: unknown + linkType: soft + "@tonkeeper/web@workspace:apps/web": version: 0.0.0-use.local resolution: "@tonkeeper/web@workspace:apps/web" From 748dbd3b94acfd79564a174edfc8bea813ca2254 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 5 Dec 2024 17:42:03 +0200 Subject: [PATCH 02/12] feat: standalone swap widget implemented --- apps/web-swap-widget/package.json | 1 + apps/web-swap-widget/src/App.tsx | 119 +++++++++++------- .../src/components/SwapWidgetFooter.tsx | 103 +++++++++++++++ .../src/components/SwapWidgetHeader.tsx | 55 ++++++++ .../src/components/SwapWidgetPage.tsx | 42 +++++-- .../tonkeeper-injection-context.ts | 10 +- packages/locales/src/tonkeeper-web/en.json | 19 +++ packages/locales/src/tonkeeper-web/ru-RU.json | 19 +++ .../uikit/src/components/swap/SwapToField.tsx | 59 +++++---- .../uikit/src/providers/UserThemeProvider.tsx | 20 ++- .../uikit/src/state/swap/useEncodeSwap.ts | 9 +- packages/uikit/src/state/wallet.ts | 1 - yarn.lock | 12 ++ 13 files changed, 374 insertions(+), 95 deletions(-) create mode 100644 apps/web-swap-widget/src/components/SwapWidgetFooter.tsx create mode 100644 apps/web-swap-widget/src/components/SwapWidgetHeader.tsx rename apps/web-swap-widget/src/{types => libs}/tonkeeper-injection-context.ts (71%) diff --git a/apps/web-swap-widget/package.json b/apps/web-swap-widget/package.json index 720da9dbe..3d2004c45 100644 --- a/apps/web-swap-widget/package.json +++ b/apps/web-swap-widget/package.json @@ -7,6 +7,7 @@ "@amplitude/analytics-browser": "^2.1.0", "@aptabase/web": "^0.4.2", "@tanstack/react-query": "4.3.4", + "@ton/core": "^0.56.0", "@tonkeeper/core": "0.1.0", "@tonkeeper/locales": "0.1.0", "@tonkeeper/uikit": "0.1.0", diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index b25cf53f6..0b0726990 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -3,12 +3,8 @@ import { localizationText } from '@tonkeeper/core/dist/entries/language'; import { getApiConfig, Network } from '@tonkeeper/core/dist/entries/network'; import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; -import { FooterGlobalStyle } from '@tonkeeper/uikit/dist/components/Footer'; -import { HeaderGlobalStyle } from '@tonkeeper/uikit/dist/components/Header'; import { DarkThemeContext } from '@tonkeeper/uikit/dist/components/Icon'; -import { GlobalListStyle } from '@tonkeeper/uikit/dist/components/List'; import { Loading } from '@tonkeeper/uikit/dist/components/Loading'; -import { SybHeaderGlobalStyle } from '@tonkeeper/uikit/dist/components/SubHeader'; import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude'; import { AppContext, IAppContext } from '@tonkeeper/uikit/dist/hooks/appContext'; import { AppSdkContext } from '@tonkeeper/uikit/dist/hooks/appSdk'; @@ -26,7 +22,7 @@ import { useProBackupState } from '@tonkeeper/uikit/dist/state/pro'; import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint'; import { useAccountsStateQuery, useActiveAccountQuery } from '@tonkeeper/uikit/dist/state/wallet'; import { GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; -import { FC, Suspense, useEffect, useMemo } from 'react'; +import { FC, PropsWithChildren, Suspense, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { BrowserAppSdk } from './libs/appSdk'; import { useAnalytics, useAppHeight, useAppWidth } from './libs/hooks'; @@ -35,10 +31,12 @@ import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup'; import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; import { useKeyboardHeight } from '@tonkeeper/uikit/dist/pages/import/hooks'; import { useDebuggingTools } from '@tonkeeper/uikit/dist/hooks/useDebuggingTools'; -import MemoryScroll from '@tonkeeper/uikit/dist/components/MemoryScroll'; -import styled, { css } from 'styled-components'; +import styled, { createGlobalStyle } from 'styled-components'; import { SwapWidgetPage } from './components/SwapWidgetPage'; -import { Container } from '@tonkeeper/uikit'; +import { useAccountsStorage } from '@tonkeeper/uikit/dist/hooks/useStorage'; +import { AccountTonWatchOnly } from '@tonkeeper/core/dist/entries/account'; +import { getTonkeeperInjectionContext } from './libs/tonkeeper-injection-context'; +import { Address } from '@ton/core'; const queryClient = new QueryClient({ defaultOptions: { @@ -52,6 +50,18 @@ const queryClient = new QueryClient({ const sdk = new BrowserAppSdk(); const TARGET_ENV = 'web'; +// TODO remove +window.tonkeeperStonfi = { + address: 'UQDHd3ZKCW3h7OH7F9tTQcWuxpR9s71lW7mv7n2RKl8STkZ5', + sendTransaction: async params => { + console.log(params); + return 'boc'; + }, + close: () => { + console.log('close'); + } +}; + export const App: FC = () => { const { t: tSimple, i18n } = useTranslation(); @@ -64,7 +74,7 @@ export const App: FC = () => { i18n: { enable: true, reloadResources: i18n.reloadResources, - changeLanguage: i18n.changeLanguage as (lang: string) => Promise, + changeLanguage: i18n.changeLanguage as unknown as (lang: string) => Promise, language: i18n.language, languages: languages } @@ -87,22 +97,65 @@ export const App: FC = () => { ); }; +const WidgetGlobalStyle = createGlobalStyle` + html, body, #root { + height: 100%; + } + + * { + -webkit-tap-highlight-color: transparent; + } +`; + const ThemeAndContent = () => { const { data } = useProBackupState(); return ( - + - - - - - + + + + ); }; +const ProvideActiveAccount: FC = ({ children }) => { + const storage = useAccountsStorage(); + const [isLoading, setIsLoading] = useState(true); + useEffect(() => { + const addressFriendly = getTonkeeperInjectionContext()?.address; + + if (!addressFriendly) { + return; + } + + const addressRaw = Address.parse(addressFriendly).toRawString(); + + storage + .setAccounts([ + new AccountTonWatchOnly(addressRaw, 'Wallet', '🙂', { + rawAddress: addressRaw, + id: addressRaw + }) + ]) + .then(() => setIsLoading(false)); + }, []); + + if (isLoading) { + return null; + } + + return <>{children}; +}; + const Loader: FC = () => { const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery(); const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery(); @@ -183,38 +236,10 @@ const Loader: FC = () => { ); }; -const FullSizeWrapper = styled(Container)<{ standalone: boolean }>` - ${props => - props.standalone - ? css` - position: fixed; - top: 0; - height: calc(var(--app-height) - 2px); - -webkit-overflow-scrolling: touch; - ` - : css` - @media (min-width: 600px) { - border-left: 1px solid ${props.theme.separatorCommon}; - border-right: 1px solid ${props.theme.separatorCommon}; - } - `}; - - > * { - ${props => - props.standalone && - css` - overflow: auto; - width: var(--app-width); - max-width: 548px; - box-sizing: border-box; - `} - } -`; - -const Wrapper = styled(FullSizeWrapper)<{ standalone: boolean }>` +const Wrapper = styled.div` box-sizing: border-box; - padding-top: 64px; - padding-bottom: ${props => (props.standalone ? '96' : '80')}px; + padding: 0 16px 34px; + height: 100%; `; const Content: FC<{ @@ -227,7 +252,7 @@ const Content: FC<{ useDebuggingTools(); return ( - + ); diff --git a/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx b/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx new file mode 100644 index 000000000..d26517b95 --- /dev/null +++ b/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx @@ -0,0 +1,103 @@ +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { Dot } from '@tonkeeper/uikit/dist/components/Dot'; +import { Body3Class } from '@tonkeeper/uikit'; + +const Wrapper = styled.div` + text-align: center; +`; + +const Row = styled.div` + display: inline-flex; + align-items: center; + color: ${p => p.theme.textTertiary}; + ${Body3Class} +`; + +const Link = styled.a` + display: inline-flex; + align-items: center; + text-decoration: unset; + color: ${p => p.theme.textSecondary}; + + &:active { + color: ${p => p.theme.textPrimary}; + } +`; + +export const SwapWidgetFooter = () => { + const { t } = useTranslation(); + return ( + + + {t('legal_powered_by')} + + + Dedust + + , + + + STON.fi + +  {t('and')} TON + + + + {t('legal_privacy')} + + + + {t('legal_terms')} + + + + ); +}; + +const DedustIcon = () => { + return ( + + + + + + ); +}; + +const StonfiIcon = () => { + return ( + + + + + + + ); +}; diff --git a/apps/web-swap-widget/src/components/SwapWidgetHeader.tsx b/apps/web-swap-widget/src/components/SwapWidgetHeader.tsx new file mode 100644 index 000000000..61d14c219 --- /dev/null +++ b/apps/web-swap-widget/src/components/SwapWidgetHeader.tsx @@ -0,0 +1,55 @@ +import { FC } from 'react'; +import styled from 'styled-components'; +import { SwapRefreshButton } from '@tonkeeper/uikit/dist/components/swap/icon-buttons/SwapRefreshButton'; +import { SwapSettingsButton } from '@tonkeeper/uikit/dist/components/swap/icon-buttons/SwapSettingsButton'; +import { Label2 } from '@tonkeeper/uikit'; +import { IconButtonTransparentBackground } from '@tonkeeper/uikit/dist/components/fields/IconButton'; +import { CloseIcon } from '@tonkeeper/uikit/dist/components/Icon'; +import { useTranslation } from 'react-i18next'; +import { getTonkeeperInjectionContext } from '../libs/tonkeeper-injection-context'; + +const Wrapper = styled.div` + margin: 8px -10px 0; + position: relative; + display: flex; + align-items: center; + height: 36px; + box-sizing: border-box; +`; + +const ButtonsWrapper = styled.div` + position: absolute; + inset: 0; + gap: 12px; + display: flex; + align-items: center; +`; + +const CloseButton = styled(IconButtonTransparentBackground)` + margin-left: auto; +`; + +const Heading = styled(Label2)` + margin: 0 auto; +`; + +export const SwapWidgetHeader: FC = () => { + const { t } = useTranslation(); + + const onClose = () => { + getTonkeeperInjectionContext()?.close(); + }; + + return ( + + + + + + + + + {t('swap_title')} + + ); +}; diff --git a/apps/web-swap-widget/src/components/SwapWidgetPage.tsx b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx index 126e11ee9..4db864c3a 100644 --- a/apps/web-swap-widget/src/components/SwapWidgetPage.tsx +++ b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx @@ -1,4 +1,4 @@ -import { styled, useTheme } from 'styled-components'; +import { styled } from 'styled-components'; import { useEncodeSwapToTonConnectParams } from '@tonkeeper/uikit/dist/state/swap/useEncodeSwap'; import { useState } from 'react'; import { @@ -11,19 +11,25 @@ import { CalculatedSwap } from '@tonkeeper/uikit/dist/state/swap/useCalculatedSw import { SwapFromField } from '@tonkeeper/uikit/dist/components/swap/SwapFromField'; import { SwapIcon } from '@tonkeeper/uikit/dist/components/Icon'; import { SwapToField } from '@tonkeeper/uikit/dist/components/swap/SwapToField'; -import { SwapProviders } from '@tonkeeper/uikit/dist/components/swap/SwapProviders'; import { SwapButton } from '@tonkeeper/uikit/dist/components/swap/SwapButton'; import { SwapTokensListNotification } from '@tonkeeper/uikit/dist/components/swap/tokens-list/SwapTokensListNotification'; import { IconButton } from '@tonkeeper/uikit/dist/components/fields/IconButton'; -import { TonConnectTransactionPayload } from '@tonkeeper/core/dist/entries/tonConnect'; import { NonNullableFields } from '@tonkeeper/core/dist/utils/types'; +import { SwapWidgetHeader } from './SwapWidgetHeader'; +import { getTonkeeperInjectionContext } from '../libs/tonkeeper-injection-context'; +import { SwapWidgetFooter } from './SwapWidgetFooter'; const MainFormWrapper = styled.div` + height: 100%; display: flex; flex-direction: column; gap: 0.5rem; `; +const Spacer = styled.div` + flex: 1; +`; + const ChangeIconStyled = styled(IconButton)` height: 32px; width: 32px; @@ -47,9 +53,10 @@ const ChangeIconStyled = styled(IconButton)` `; export const SwapWidgetPage = () => { - const theme = useTheme(); - const { isLoading, mutateAsync: encode } = useEncodeSwapToTonConnectParams(); - const [modalParams, setModalParams] = useState(null); + const { isLoading, mutateAsync: encode } = useEncodeSwapToTonConnectParams({ + ignoreBattery: true + }); + const [hasBeenSent, setHasBeenSent] = useState(false); const [selectedSwap] = useSelectedSwap(); const [fromAsset, setFromAsset] = useSwapFromAsset(); const [toAsset, setToAsset] = useSwapToAsset(); @@ -57,9 +64,20 @@ export const SwapWidgetPage = () => { const onConfirm = async () => { const params = await encode(selectedSwap! as NonNullableFields); - // Ton Connect send - setModalParams(params); + const ctx = getTonkeeperInjectionContext()!; + + ctx.sendTransaction({ + source: ctx.address, + // legacy tonkeeper api, timestamp in ms + valid_until: params.valid_until * 1000, + messages: params.messages.map(m => ({ + address: m.address, + amount: m.amount.toString(), + payload: m.payload + })) + }); + setHasBeenSent(true); }; const onChangeFields = () => { @@ -72,14 +90,16 @@ export const SwapWidgetPage = () => { return ( + - - {theme.displayType === 'compact' && } - + + + + ); diff --git a/apps/web-swap-widget/src/types/tonkeeper-injection-context.ts b/apps/web-swap-widget/src/libs/tonkeeper-injection-context.ts similarity index 71% rename from apps/web-swap-widget/src/types/tonkeeper-injection-context.ts rename to apps/web-swap-widget/src/libs/tonkeeper-injection-context.ts index 9a0f33467..ac3ed10a6 100644 --- a/apps/web-swap-widget/src/types/tonkeeper-injection-context.ts +++ b/apps/web-swap-widget/src/libs/tonkeeper-injection-context.ts @@ -1,23 +1,25 @@ import { getWindow } from '@tonkeeper/core/dist/service/telegramOauth'; type UserFriendlyAddress = string; +type TimestampMS = number; -export type TonkeeperInjectionContext = { +export type TonkeeperInjection = { address: UserFriendlyAddress; + close: () => void; sendTransaction: (params: { source: UserFriendlyAddress; - valid_until: 1733389156144; + valid_until: TimestampMS; messages: { address: UserFriendlyAddress; amount: string; - payload: string; + payload?: string; }[]; }) => Promise; }; declare global { interface Window { - tonkeeperStonfi?: TonkeeperInjectionContext; + tonkeeperStonfi?: TonkeeperInjection; } } diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 7b196ba8c..8b12a35ae 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -19,6 +19,7 @@ "add_wallet_new_multisig_description": "For joint management and protection of cryptocurrency funds", "add_wallet_new_multisig_title": "New Multisig Wallet", "all_assets_jettons": "All Assets", + "and": "and", "appExtensionDescription": "Your extension wallet on The Open Network", "appName": "Tonkeeper", "appTitle": "Tonkeeper — wallet for TON", @@ -107,6 +108,7 @@ "enter_password": "Enter password", "export_dot_csv": "Export .CSV", "force_reload": "Force Reload", + "help": "Help", "hide": "Hide", "hide_others": "Hide Others", "hide_tonkeeper_pro": "Hide Tonkeeper Pro", @@ -167,6 +169,7 @@ "ledger_steps_connect": "Connect Ledger to your device", "ledger_steps_install_ton": "Install TON App ", "ledger_steps_open_ton": "Unlock it and open TON App", + "legal_powered_by": "Powered by", "Localization": "Language", "Lock_screen": "Lock screen", "logout_on_unlock_many": "This will erase keys to all your wallets. Make sure you have backed up your secret recovery phrases.", @@ -367,6 +370,22 @@ "transaction_type_mint": "Mint", "transaction_type_purchase": "Purchase", "try_again": "Try Again", + "two_fa_confirm_tg_cannot_access_tg": "Can’t access your Telegram account?", + "two_fa_confirm_tg_description": "Go to the @tonkeeper_2fa_bot and tap «Confirm» to complete the transaction.", + "two_fa_confirm_tg_title": "Confirm Transaction in Telegram", + "two_fa_send_continue_with_tg": "Continue with Telegram", + "two_fa_settings_heading_description": "This experimental feature allows you to enable two-factor authentication (2FA) for added wallet security. It will prompt you for confirmation in Telegram during large transactions and help restore your wallet if you lose your mnemonic. Installation steps:", + "two_fa_settings_heading_title": "Two-Factor Authentication", + "two_fa_settings_set_up_deploy_step_button": "Activate 2FA", + "two_fa_settings_set_up_deploy_step_description": "Confirm transaction to install 2FA extension", + "two_fa_settings_set_up_tg_connection_modal_copy_button": "Copy Link", + "two_fa_settings_set_up_tg_connection_modal_heading": "Scan QR code below with your mobile phone or open Telegram on this device to connect.", + "two_fa_settings_set_up_tg_connection_modal_open_button": "Open Telegram", + "two_fa_settings_set_up_tg_step_description": "Confirm your connection in your Telegram ", + "two_fa_settings_warning_balance_required": "A TON balance is required to install or uninstall the extension.", + "two_fa_settings_warning_battery_gasless": "Battery mode and gasless transactions are not compatible with 2FA.", + "two_fa_settings_warning_wallet_will_stop": "The same wallet will stop working on your other devices.", + "two_fa_short": "2FA", "txActions_USDT_transfer": "USDT Transfer", "Undo": "Undo", "Unexpected_QR_Code": "Unexpected QR Code", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 8a9d6c459..f4f21613a 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -18,6 +18,7 @@ "add_wallet_modal_mam_title": "Новый аккаунт мульти-кошелек", "add_wallet_new_multisig_description": "Для совместного управления и защиты криптовалюты", "add_wallet_new_multisig_title": "Новый Multisig кошелек ", + "and": "и", "appExtensionDescription": "Your extension wallet on The Open Network", "appName": "Tonkeeper", "appTitle": "Tonkeeper — wallet for TON", @@ -105,6 +106,7 @@ "enter_password": "Введите пароль", "export_dot_csv": "Экспорт в .CSV", "force_reload": "Принудительная перезагрузка", + "help": "Помощь", "hide": "Скрыть", "hide_others": "Скрыть остальных", "hide_tonkeeper_pro": "Скрыть Tonkeeper Pro", @@ -160,6 +162,7 @@ "ledger_steps_connect": "Подключите Ledger к своему устройству", "ledger_steps_install_ton": "Установить приложение ", "ledger_steps_open_ton": "Разблокируйте его и откройте приложение TON ", + "legal_powered_by": "Совместно с", "Localization": "Язык", "Lock_screen": "Экран блокировки", "logout_on_unlock_many": "Доступ к кошелькам будет отключен. Убедитесь, что вы сохранили секретные ключи.", @@ -354,6 +357,22 @@ "transaction_type_mint": "Создание", "transaction_type_purchase": "Покупка", "try_again": "Повторить", + "two_fa_confirm_tg_cannot_access_tg": "Не можете получить доступ к своему Telegram аккаунту?", + "two_fa_confirm_tg_description": "Перейдите в @tonkeeper_2fa_bot и нажмите «Подтвердить», чтобы завершить транзакцию.", + "two_fa_confirm_tg_title": "Подтвердите транзакцию в Telegram", + "two_fa_send_continue_with_tg": "Продолжить с Telegram", + "two_fa_settings_heading_description": "Эта экспериментальная функция позволяет включить двухфакторную аутентификацию (2FA) для дополнительной безопасности кошелька. Она будет запрашивать подтверждение в Telegram при крупных транзакциях и поможет восстановить ваш кошелек, если вы потеряете мнемоническую фразу. Шаги установки:", + "two_fa_settings_heading_title": "Двухфакторная аутентификация", + "two_fa_settings_set_up_deploy_step_button": "Активировать 2FA", + "two_fa_settings_set_up_deploy_step_description": "Подтвердите транзакцию для установки расширения 2FA", + "two_fa_settings_set_up_tg_connection_modal_copy_button": "Копировать ссылку", + "two_fa_settings_set_up_tg_connection_modal_heading": "Отсканируйте QR-код ниже с помощью вашего мобильного телефона или откройте Telegram на этом устройстве для подключения.", + "two_fa_settings_set_up_tg_connection_modal_open_button": "Открыть Телеграм", + "two_fa_settings_set_up_tg_step_description": "Подтвердите соединение в Telegram ", + "two_fa_settings_warning_balance_required": "Для установки или удаления расширения требуется баланс TON.", + "two_fa_settings_warning_battery_gasless": "Батарейка Tonkeeper и безгазовые транзакции не работают с двухфакторной аутентификацией.", + "two_fa_settings_warning_wallet_will_stop": "Этот же кошелек перестанет работать на других ваших устройствах.", + "two_fa_short": "2ФА", "txActions_USDT_transfer": "Перевод USDT", "Undo": "Отменить", "Unexpected_QR_Code": "Неожиданный QR-код", diff --git a/packages/uikit/src/components/swap/SwapToField.tsx b/packages/uikit/src/components/swap/SwapToField.tsx index 0e8e86b78..45be007c0 100644 --- a/packages/uikit/src/components/swap/SwapToField.tsx +++ b/packages/uikit/src/components/swap/SwapToField.tsx @@ -9,6 +9,7 @@ import { Skeleton } from '../shared/Skeleton'; import { SwapTransactionInfo } from './SwapTransactionInfo'; import { SwapRate } from './SwapRate'; import { useTranslation } from '../../hooks/translation'; +import { FC } from 'react'; const FiledContainerStyled = styled.div` background: ${p => p.theme.backgroundContent}; @@ -73,7 +74,7 @@ const Num2Tertiary = styled(Num2)` color: ${p => p.theme.textTertiary}; `; -export const SwapToField = () => { +export const SwapToField: FC<{ separateInfo?: boolean }> = ({ separateInfo }) => { const { t } = useTranslation(); const [toAsset, setToAsset] = useSwapToAsset(); const { isFetching } = useCalculatedSwap(); @@ -81,28 +82,38 @@ export const SwapToField = () => { const [selectedSwap] = useSelectedSwap(); return ( - - - {t('swap_receive')} - - - - - - {!selectedSwap?.trade && isFetching ? ( - - ) : selectedSwap?.trade ? ( - {selectedSwap.trade.to.stringRelativeAmount} - ) : ( - 0 - )} - - - - - - - - + <> + + + {t('swap_receive')} + + + + + + {!selectedSwap?.trade && isFetching ? ( + + ) : selectedSwap?.trade ? ( + {selectedSwap.trade.to.stringRelativeAmount} + ) : ( + 0 + )} + + + + + + + {!separateInfo && } + + {separateInfo && ( + + + + )} + ); }; diff --git a/packages/uikit/src/providers/UserThemeProvider.tsx b/packages/uikit/src/providers/UserThemeProvider.tsx index e5be6c5f6..0970f99a5 100644 --- a/packages/uikit/src/providers/UserThemeProvider.tsx +++ b/packages/uikit/src/providers/UserThemeProvider.tsx @@ -8,8 +8,9 @@ export const UserThemeProvider: FC< displayType?: 'compact' | 'full-width'; isPro?: boolean; isProSupported?: boolean; + isInsideTonkeeper?: boolean; }> -> = ({ children, displayType, isPro, isProSupported }) => { +> = ({ children, displayType, isPro, isProSupported, isInsideTonkeeper }) => { const { data: uiPreferences, isFetched: isUIPreferencesLoaded } = useUserUIPreferences(); const { mutateAsync } = useMutateUserUIPreferences(); const isProPrev = usePrevious(isPro); @@ -31,7 +32,7 @@ export const UserThemeProvider: FC< themeName = themeName || 'dark'; - const theme = availableThemes[themeName]; + let theme = availableThemes[themeName]; if (displayType) { theme.displayType = displayType; @@ -41,8 +42,21 @@ export const UserThemeProvider: FC< window.document.body.style.background = theme.backgroundPage; + if (isInsideTonkeeper) { + theme = { + ...theme, + corner3xSmall: '2px', + corner2xSmall: '4px', + cornerExtraSmall: '6px', + cornerSmall: '8px', + cornerMedium: '12px', + cornerLarge: '16px', + cornerFull: '100%' + }; + } + return [theme, themeName]; - }, [uiPreferences?.theme, displayType, isPro, isProPrev]); + }, [uiPreferences?.theme, displayType, isPro, isProPrev, isInsideTonkeeper]); useEffect(() => { if (currentTheme && uiPreferences && currentThemeName !== uiPreferences.theme) { diff --git a/packages/uikit/src/state/swap/useEncodeSwap.ts b/packages/uikit/src/state/swap/useEncodeSwap.ts index c16e162e2..1c3bf69b3 100644 --- a/packages/uikit/src/state/swap/useEncodeSwap.ts +++ b/packages/uikit/src/state/swap/useEncodeSwap.ts @@ -3,7 +3,6 @@ import { CalculatedSwap } from './useCalculatedSwap'; import type { SwapService } from '@tonkeeper/core/dist/swapsApi'; import { assertUnreachable, NonNullableFields } from '@tonkeeper/core/dist/utils/types'; import { Address } from '@ton/core'; -import { useAppContext } from '../../hooks/appContext'; import { useSwapsConfig } from './useSwapsConfig'; import BigNumber from 'bignumber.js'; import { useSwapOptions } from './useSwapOptions'; @@ -44,7 +43,7 @@ export function useEncodeSwap() { }); } -export function useEncodeSwapToTonConnectParams() { +export function useEncodeSwapToTonConnectParams(options: { ignoreBattery?: boolean } = {}) { const { mutateAsync: encode } = useEncodeSwap(); const { data: batteryBalance } = useBatteryBalance(); const { excessAccount: batteryExcess } = useBatteryServiceConfig(); @@ -54,9 +53,9 @@ export function useEncodeSwapToTonConnectParams() { async swap => { const resultsPromises = [encode(swap)]; - const batterySwapsEnabled = activeWalletConfig - ? activeWalletConfig.batterySettings.enabledForSwaps - : true; + const batterySwapsEnabled = + (activeWalletConfig ? activeWalletConfig.batterySettings.enabledForSwaps : true) && + !options.ignoreBattery; if (batteryBalance?.batteryUnitsBalance.gt(0) && batterySwapsEnabled) { resultsPromises.push( encode({ ...swap, excessAddress: Address.parse(batteryExcess).toRawString() }) diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index ae9d239e7..8703dd4ad 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -49,7 +49,6 @@ import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { useAccountsStorage } from '../hooks/useStorage'; import { anyOfKeysParts, QueryKey } from '../libs/queryKey'; -import { useDevSettings } from './dev'; import { getAccountMnemonic, getPasswordByNotification } from './mnemonic'; import { useCheckTouchId } from './password'; import { seeIfMnemonicValid } from '@tonkeeper/core/dist/service/mnemonicService'; diff --git a/yarn.lock b/yarn.lock index a4da84c4b..218796261 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7306,6 +7306,17 @@ __metadata: languageName: node linkType: hard +"@ton/core@npm:^0.56.0": + version: 0.56.3 + resolution: "@ton/core@npm:0.56.3" + dependencies: + symbol.inspect: "npm:1.0.1" + peerDependencies: + "@ton/crypto": ">=3.2.0" + checksum: 10/5fe0284bc66789e4b408cce7d459d30d0f8477ce981337ef7bfe7beeee93eda575e8178338fb3d73d47cd8f78065ed84988b82db839b463a39e576be39831243 + languageName: node + linkType: hard + "@ton/crypto-primitives@npm:2.0.0": version: 2.0.0 resolution: "@ton/crypto-primitives@npm:2.0.0" @@ -7666,6 +7677,7 @@ __metadata: "@testing-library/jest-dom": "npm:^5.16.5" "@testing-library/react": "npm:^13.4.0" "@testing-library/user-event": "npm:^13.5.0" + "@ton/core": "npm:^0.56.0" "@tonkeeper/core": "npm:0.1.0" "@tonkeeper/locales": "npm:0.1.0" "@tonkeeper/uikit": "npm:0.1.0" From e48fc820ce9153efd16e0b86493cc63d44524278 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 5 Dec 2024 17:42:42 +0200 Subject: [PATCH 03/12] chore: remove connection stub --- apps/web-swap-widget/src/App.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index 0b0726990..cc662d0a2 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -50,18 +50,6 @@ const queryClient = new QueryClient({ const sdk = new BrowserAppSdk(); const TARGET_ENV = 'web'; -// TODO remove -window.tonkeeperStonfi = { - address: 'UQDHd3ZKCW3h7OH7F9tTQcWuxpR9s71lW7mv7n2RKl8STkZ5', - sendTransaction: async params => { - console.log(params); - return 'boc'; - }, - close: () => { - console.log('close'); - } -}; - export const App: FC = () => { const { t: tSimple, i18n } = useTranslation(); From 13cf4aa7b5d1361f0707c0e6ef3f2dcfdda25f74 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 6 Dec 2024 13:09:03 +0200 Subject: [PATCH 04/12] fix: update web swap widget analytics --- apps/web-swap-widget/src/App.tsx | 2 +- apps/web-swap-widget/src/libs/hooks.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index cc662d0a2..f1a9fc0a7 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -48,7 +48,7 @@ const queryClient = new QueryClient({ }); const sdk = new BrowserAppSdk(); -const TARGET_ENV = 'web'; +const TARGET_ENV = 'web'; // 'web-swap-widget' TODO export const App: FC = () => { const { t: tSimple, i18n } = useTranslation(); diff --git a/apps/web-swap-widget/src/libs/hooks.ts b/apps/web-swap-widget/src/libs/hooks.ts index e600e155c..b59bc8000 100644 --- a/apps/web-swap-widget/src/libs/hooks.ts +++ b/apps/web-swap-widget/src/libs/hooks.ts @@ -63,7 +63,7 @@ export const useAnalytics = (activeAccount?: Account, accounts?: Account[], vers ); tracker.init({ - application: 'Web', + application: 'Web-swap-widget', walletType: toWalletType(activeAccount?.activeTonWallet), activeAccount: activeAccount!, accounts: accounts!, From d0f629756eb3c4b5cd5cc09dad63c7570fc86f23 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 9 Dec 2024 12:36:31 +0100 Subject: [PATCH 05/12] Swap Widget CD --- .github/workflows/cd.yaml | 6 ++ .github/workflows/pull-request.yaml | 6 ++ .github/workflows/swap-widget-build.yaml | 103 +++++++++++++++++++++++ apps/web-swap-widget/package.json | 4 +- package.json | 1 + turbo.json | 4 + 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/swap-widget-build.yaml diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index e14679728..958fae35b 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -95,6 +95,12 @@ jobs: environment: 'main' secrets: inherit + swap-widget-build: + uses: ./.github/workflows/swap-widget-build.yaml + with: + environment: 'main' + secrets: inherit + web-tests: needs: web-build uses: ./.github/workflows/web-tests.yaml diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 965bbb454..bea5c4af7 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -263,6 +263,12 @@ jobs: uses: ./.github/workflows/ipad-build.yaml secrets: inherit + swap-widget-build: + uses: ./.github/workflows/swap-widget-build.yaml + with: + environment: ${{ github.head_ref }} + secrets: inherit + web-tests: needs: web-build uses: ./.github/workflows/web-tests.yaml diff --git a/.github/workflows/swap-widget-build.yaml b/.github/workflows/swap-widget-build.yaml new file mode 100644 index 000000000..54e438ad6 --- /dev/null +++ b/.github/workflows/swap-widget-build.yaml @@ -0,0 +1,103 @@ +name: Tonkeeper Swap Widget Build +on: + workflow_call: + inputs: + environment: + required: true + type: string + secrets: + REACT_APP_AMPLITUDE_EXTENSION: + required: true + REACT_APP_MEASUREMENT_ID: + required: true + VITE_APP_APTABASE: + required: true + REACT_APP_TG_BOT_ID: + required: true + REACT_APP_STONFI_REFERRAL_ADDRESS: + required: true + CLOUDFLARE_API_TOKEN: + required: true + CLOUDFLARE_ACCOUNT_ID: + required: true + outputs: + deployment-url: + description: 'The app deployment url' + value: ${{ jobs.web-build.outputs.deployment-url }} +env: + node-version: 20.11.1 + +jobs: + web-build: + name: web-build + runs-on: ubuntu-latest + timeout-minutes: 10 + + outputs: + deployment-url: ${{ steps.deploy.outputs.deployment-url }} + steps: + - name: Checkout to git repository + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.node-version }} + + - name: Enable Corepack + run: | + corepack enable + + - name: Yarn cache + uses: actions/cache@v4 + with: + path: ./.yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Run install + uses: borales/actions-yarn@v5 + with: + cmd: install + + - name: Run build + uses: borales/actions-yarn@v5 + env: + VITE_APP_MEASUREMENT_ID: ${{ secrets.REACT_APP_MEASUREMENT_ID }} + VITE_APP_APTABASE: ${{ secrets.VITE_APP_APTABASE }} + VITE_APP_APTABASE_HOST: https://anonymous-analytics.tonkeeper.com + VITE_APP_LOCALES: en,zh_TW,zh_CN,id,ru,it,es,uk,tr,bg,uz,bn + VITE_APP_TONCONSOLE_HOST: https://pro.tonconsole.com + VITE_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} + VITE_APP_STONFI_REFERRAL_ADDRESS: ${{ secrets.REACT_APP_STONFI_REFERRAL_ADDRESS }} + with: + cmd: build:swap-widget + + - name: Publish to Cloudflare Pages + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: + pages deploy apps/web-swap-widget/dist --project-name=tonkeeper-swap-widget + --branch=${{ inputs.environment }} + + - name: Summary + run: | + echo '### Successful swap widget deployment 🚀🚀🚀' >> $GITHUB_STEP_SUMMARY + echo 'Well done!' >> $GITHUB_STEP_SUMMARY + echo 'Link to test environment:' >> $GITHUB_STEP_SUMMARY + echo '${{ steps.deploy.outputs.deployment-url }}' >> $GITHUB_STEP_SUMMARY + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v3 + if: github.event_name == 'pull_request' + with: + message: | + ### Successful swap widget deployment 🚀🚀🚀 + Well done! + Link to test environment: + ${{ steps.deploy.outputs.deployment-url }} + comment-tag: swap_widget_deploy diff --git a/apps/web-swap-widget/package.json b/apps/web-swap-widget/package.json index 3d2004c45..838189601 100644 --- a/apps/web-swap-widget/package.json +++ b/apps/web-swap-widget/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/web-swap-widget", - "version": "0.0.1", + "version": "3.0.0", "author": "Ton APPS UK Limited ", "description": "Web swap widget for Tonkeeper", "dependencies": { @@ -46,7 +46,7 @@ "start": "yarn locales && vite dev", "preview": "vite preview", "build": "tsc && vite build && yarn locales", - "build:web": "yarn build" + "build:swap-widget": "yarn build" }, "browserslist": { "production": [ diff --git a/package.json b/package.json index 42c3afbd1..0b48158ad 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:extension": "npx turbo build:extension", "build:desktop": "npx turbo build:desktop", "build:ipad": "npx turbo build:ipad", + "build:swap-widget": "npx turbo build:swap-widget", "build:pkg": "npx turbo build:pkg" }, "private": true, diff --git a/turbo.json b/turbo.json index 1cc934d5a..2b14104b1 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,10 @@ "dependsOn": ["^build:pkg"], "outputs": [".next/**", "dist/**"] }, + "build:swap-widget": { + "dependsOn": ["^build:pkg"], + "outputs": [".next/**", "dist/**"] + }, "build:ipad": { "dependsOn": ["^build:pkg"], "outputs": [".next/**", "dist/**"] From bbee05e2442657177572f6214a50f5287f6288e2 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 9 Dec 2024 12:41:25 +0100 Subject: [PATCH 06/12] Update name --- .github/workflows/swap-widget-build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swap-widget-build.yaml b/.github/workflows/swap-widget-build.yaml index 54e438ad6..17beade23 100644 --- a/.github/workflows/swap-widget-build.yaml +++ b/.github/workflows/swap-widget-build.yaml @@ -28,8 +28,8 @@ env: node-version: 20.11.1 jobs: - web-build: - name: web-build + swap-widget: + name: Swap Widget runs-on: ubuntu-latest timeout-minutes: 10 From 6e790b339a151427495209d23dbf9412b8aa3351 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 9 Dec 2024 12:58:29 +0100 Subject: [PATCH 07/12] Rename --- .github/workflows/pull-request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index bea5c4af7..c9eee8e68 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -1,4 +1,4 @@ -name: Tonkeeper Pull-Request +name: PR on: workflow_dispatch: pull_request: From 51848c562509411ff3bde80a403464091f521927 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 9 Dec 2024 17:38:29 +0100 Subject: [PATCH 08/12] fix: handle swap cancel; support `ft` & `tt` query params --- apps/web-swap-widget/src/App.tsx | 10 ++-- .../src/components/SwapWidgetFooter.tsx | 18 +++---- .../src/components/SwapWidgetPage.tsx | 2 +- apps/web-swap-widget/src/libs/hooks.ts | 51 ++++++++++++++++++- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index f1a9fc0a7..ed7aeb104 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -4,7 +4,6 @@ import { getApiConfig, Network } from '@tonkeeper/core/dist/entries/network'; import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification'; import { DarkThemeContext } from '@tonkeeper/uikit/dist/components/Icon'; -import { Loading } from '@tonkeeper/uikit/dist/components/Loading'; import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude'; import { AppContext, IAppContext } from '@tonkeeper/uikit/dist/hooks/appContext'; import { AppSdkContext } from '@tonkeeper/uikit/dist/hooks/appSdk'; @@ -25,7 +24,7 @@ import { GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle'; import { FC, PropsWithChildren, Suspense, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { BrowserAppSdk } from './libs/appSdk'; -import { useAnalytics, useAppHeight, useAppWidth } from './libs/hooks'; +import { useAnalytics, useAppHeight, useApplyQueryParams, useAppWidth } from './libs/hooks'; import { useGlobalPreferencesQuery } from '@tonkeeper/uikit/dist/state/global-preferences'; import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup'; import { useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; @@ -188,7 +187,7 @@ const Loader: FC = () => { globalPreferencesLoading || globalSetupLoading ) { - return ; + return null; } const context: IAppContext = { @@ -238,6 +237,11 @@ const Content: FC<{ useKeyboardHeight(); useTrackLocation(); useDebuggingTools(); + const isApplied = useApplyQueryParams(); + + if (!isApplied) { + return null; + } return ( diff --git a/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx b/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx index d26517b95..0c91e5429 100644 --- a/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx +++ b/apps/web-swap-widget/src/components/SwapWidgetFooter.tsx @@ -3,20 +3,20 @@ import { useTranslation } from 'react-i18next'; import { Dot } from '@tonkeeper/uikit/dist/components/Dot'; import { Body3Class } from '@tonkeeper/uikit'; -const Wrapper = styled.div` - text-align: center; -`; +const Wrapper = styled.div``; const Row = styled.div` - display: inline-flex; + display: flex; align-items: center; + justify-content: center; color: ${p => p.theme.textTertiary}; ${Body3Class} `; const Link = styled.a` - display: inline-flex; + display: flex; align-items: center; + justify-content: center; text-decoration: unset; color: ${p => p.theme.textSecondary}; @@ -31,11 +31,11 @@ export const SwapWidgetFooter = () => { {t('legal_powered_by')} - + {/* Dedust - , + ,*/} STON.fi @@ -55,7 +55,7 @@ export const SwapWidgetFooter = () => { ); }; -const DedustIcon = () => { +/*const DedustIcon = () => { return ( { ); -}; +};*/ const StonfiIcon = () => { return ( diff --git a/apps/web-swap-widget/src/components/SwapWidgetPage.tsx b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx index 4db864c3a..417366fc5 100644 --- a/apps/web-swap-widget/src/components/SwapWidgetPage.tsx +++ b/apps/web-swap-widget/src/components/SwapWidgetPage.tsx @@ -76,7 +76,7 @@ export const SwapWidgetPage = () => { amount: m.amount.toString(), payload: m.payload })) - }); + }).finally(() => setHasBeenSent(false)); setHasBeenSent(true); }; diff --git a/apps/web-swap-widget/src/libs/hooks.ts b/apps/web-swap-widget/src/libs/hooks.ts index b59bc8000..197030c7e 100644 --- a/apps/web-swap-widget/src/libs/hooks.ts +++ b/apps/web-swap-widget/src/libs/hooks.ts @@ -1,12 +1,16 @@ import { useQuery } from '@tanstack/react-query'; import { Account } from '@tonkeeper/core/dist/entries/account'; -import { throttle } from '@tonkeeper/core/dist/utils/common'; +import { seeIfValidTonAddress, throttle } from '@tonkeeper/core/dist/utils/common'; import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics'; import { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web'; import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag'; import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey'; import { useActiveTonNetwork } from '@tonkeeper/uikit/dist/state/wallet'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; +import { useSwapFromAsset, useSwapToAsset } from '@tonkeeper/uikit/dist/state/swap/useSwapForm'; +import { useAllSwapAssets } from '@tonkeeper/uikit/dist/state/swap/useSwapAssets'; +import { tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; +import { Address } from '@ton/core'; export const useAppHeight = () => { useEffect(() => { @@ -75,3 +79,46 @@ export const useAnalytics = (activeAccount?: Account, accounts?: Account[], vers { enabled: accounts != null && activeAccount !== undefined } ); }; + +export const useApplyQueryParams = () => { + const [_, setFromAsset] = useSwapFromAsset(); + const [__, setToAsset] = useSwapToAsset(); + const { data: allAssets } = useAllSwapAssets(); + const [isApplied, setIsApplied] = useState(false); + + useEffect(() => { + if (!allAssets || isApplied) { + return; + } + setIsApplied(true); + const searchParams = new URLSearchParams(window.location.search); + const fromAssetParam = searchParams.get('ft'); + const toAssetParam = searchParams.get('tt'); + + const fromAssetToSet = + fromAssetParam && seeIfValidTonAddress(fromAssetParam) + ? allAssets.find( + asset => + tonAssetAddressToString(asset.address).toLowerCase() === + Address.parse(fromAssetParam).toRawString() + ) + : undefined; + if (fromAssetToSet) { + setFromAsset(fromAssetToSet); + } + + const toAssetToSet = + toAssetParam && seeIfValidTonAddress(toAssetParam) + ? allAssets.find( + asset => + tonAssetAddressToString(asset.address).toLowerCase() === + Address.parse(toAssetParam).toRawString() + ) + : undefined; + if (toAssetToSet) { + setToAsset(toAssetToSet); + } + }, [allAssets, isApplied]); + + return isApplied; +}; From f4fb83f4a609ebc6e953982f833ddec541748116 Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 10 Dec 2024 09:47:14 +0100 Subject: [PATCH 09/12] fix: test env added --- apps/web-swap-widget/src/App.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index ed7aeb104..fca6058cd 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -46,6 +46,17 @@ const queryClient = new QueryClient({ } }); +window.tonkeeperStonfi = { + address: 'UQDHd3ZKCW3h7OH7F9tTQcWuxpR9s71lW7mv7n2RKl8STkZ5', + sendTransaction: async params => { + console.log(params); + return 'boc'; + }, + close: () => { + console.log('close'); + } +}; + const sdk = new BrowserAppSdk(); const TARGET_ENV = 'web'; // 'web-swap-widget' TODO From 2d10b93fd3ddc9e2e6446d7248aa345f5ecaa329 Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 10 Dec 2024 11:26:38 +0100 Subject: [PATCH 10/12] fix: update env name --- apps/web-swap-widget/src/App.tsx | 13 +------------ apps/web-swap-widget/src/libs/hooks.ts | 2 +- packages/core/src/AppSdk.ts | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/web-swap-widget/src/App.tsx b/apps/web-swap-widget/src/App.tsx index fca6058cd..1a32849cc 100644 --- a/apps/web-swap-widget/src/App.tsx +++ b/apps/web-swap-widget/src/App.tsx @@ -46,19 +46,8 @@ const queryClient = new QueryClient({ } }); -window.tonkeeperStonfi = { - address: 'UQDHd3ZKCW3h7OH7F9tTQcWuxpR9s71lW7mv7n2RKl8STkZ5', - sendTransaction: async params => { - console.log(params); - return 'boc'; - }, - close: () => { - console.log('close'); - } -}; - const sdk = new BrowserAppSdk(); -const TARGET_ENV = 'web'; // 'web-swap-widget' TODO +const TARGET_ENV = 'swap-widget-web'; export const App: FC = () => { const { t: tSimple, i18n } = useTranslation(); diff --git a/apps/web-swap-widget/src/libs/hooks.ts b/apps/web-swap-widget/src/libs/hooks.ts index 197030c7e..5fe41d80a 100644 --- a/apps/web-swap-widget/src/libs/hooks.ts +++ b/apps/web-swap-widget/src/libs/hooks.ts @@ -67,7 +67,7 @@ export const useAnalytics = (activeAccount?: Account, accounts?: Account[], vers ); tracker.init({ - application: 'Web-swap-widget', + application: 'Swap-widget-web', walletType: toWalletType(activeAccount?.activeTonWallet), activeAccount: activeAccount!, accounts: accounts!, diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 48f6712a1..0c29413ac 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -182,4 +182,4 @@ export class MockAppSdk extends BaseApp { } } -export type TargetEnv = 'web' | 'extension' | 'desktop' | 'twa' | 'tablet'; +export type TargetEnv = 'web' | 'extension' | 'desktop' | 'twa' | 'tablet' | 'swap-widget-web'; From fbb0978f373d78b430f82a12f70901d5daa1080a Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Wed, 11 Dec 2024 10:43:21 +0100 Subject: [PATCH 11/12] Deploy workflow --- .github/workflows/deploy-swap-widget.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/deploy-swap-widget.yaml diff --git a/.github/workflows/deploy-swap-widget.yaml b/.github/workflows/deploy-swap-widget.yaml new file mode 100644 index 000000000..e7b7aca13 --- /dev/null +++ b/.github/workflows/deploy-swap-widget.yaml @@ -0,0 +1,18 @@ +name: Tonkeeper Swap Widget Deploy +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Deploy to env + required: true + options: + - dev + - main + +jobs: + swap-widget: + uses: ./.github/workflows/swap-widget-build.yaml + with: + environment: ${{ inputs.environment }} + secrets: inherit From 6c0ae72302da818b6f5bd6eed1dd5f9526943541 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Wed, 11 Dec 2024 10:48:16 +0100 Subject: [PATCH 12/12] Update ref address --- .github/workflows/swap-widget-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swap-widget-build.yaml b/.github/workflows/swap-widget-build.yaml index 17beade23..8153164a3 100644 --- a/.github/workflows/swap-widget-build.yaml +++ b/.github/workflows/swap-widget-build.yaml @@ -70,7 +70,7 @@ jobs: VITE_APP_LOCALES: en,zh_TW,zh_CN,id,ru,it,es,uk,tr,bg,uz,bn VITE_APP_TONCONSOLE_HOST: https://pro.tonconsole.com VITE_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} - VITE_APP_STONFI_REFERRAL_ADDRESS: ${{ secrets.REACT_APP_STONFI_REFERRAL_ADDRESS }} + VITE_APP_STONFI_REFERRAL_ADDRESS: UQCV6ZyNxqQ4Um30lhk2_1EgnzB6KMN8bHgxDOFAq3irZfgx with: cmd: build:swap-widget