├── public ├── _redirects ├── robots.txt ├── icons │ ├── icon.png │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── icon-120x96.png │ ├── icon-128x128.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ ├── icon-192x192.png │ ├── icon-200x200.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ ├── unknown_asset.png │ └── small-icon-140x140.png ├── tc-verify.json ├── animations │ ├── diamond.lottie │ ├── duck-clap.lottie │ ├── duck-airdrop.lottie │ ├── duck-alert.lottie │ ├── duck-chain.lottie │ ├── duck-chart.lottie │ ├── duck-money.lottie │ └── duck-not-found.lottie ├── external-assets │ ├── roll.jpg │ ├── ston.png │ ├── ton.png │ ├── tonco.jpg │ ├── torch.png │ ├── catton.jpeg │ ├── dedust.png │ ├── ston-v2.png │ ├── bot-greeting.png │ ├── tol-season6-airdrop.png │ ├── tol-season7-airdrop.png │ ├── tol-season6-last-call.png │ ├── tol-season7-last-call.png │ ├── tol-season7-10-days-left.jpeg │ ├── tol-season7-15-days-left.png │ ├── tol-season7-2-days-left.png │ ├── tol-season7-4-days-left.png │ └── trading-competitions │ │ ├── kuku.png │ │ ├── bolgur.png │ │ └── empty.png ├── _headers ├── tonconnect-manifest.json └── manifest.json ├── src ├── polyfill.ts ├── types │ ├── balances-record.type.ts │ ├── get-swap-history-data.type.ts │ ├── get-user-auth.type.ts │ ├── get-claim-rewards.type.ts │ ├── get-task-check.type.ts │ ├── array-element.type.ts │ ├── message.type.ts │ └── get-wallet-data.type.ts ├── enums │ ├── theme.enum.ts │ ├── explorer.enum.ts │ ├── risk-tolerance.enum.ts │ ├── swap-status.enum.ts │ └── task-type.enum.ts ├── utils │ ├── style.utils.ts │ ├── promise.utils.ts │ ├── number.utils.ts │ ├── boc.utils.ts │ ├── swap-assets.utils.ts │ ├── sentry.utils.ts │ ├── format-number.utils.ts │ ├── toast.utils.ts │ ├── percent.utils.ts │ ├── date.utils.ts │ ├── get-max-sent-amount.utils.ts │ ├── clipboard.utils.ts │ ├── big-int.utils.ts │ ├── balances-record.utils.ts │ └── asset.utils.ts ├── vite-env.d.ts ├── store │ ├── interfaces │ │ ├── payload-with-request.interface.ts │ │ └── swap-history-data.interface.ts │ ├── actions.ts │ ├── types.ts │ ├── dev │ │ ├── dev-selectors.ts │ │ ├── dev-state.ts │ │ ├── dev-actions.ts │ │ └── dev-reducers.ts │ ├── security │ │ ├── security-selectors.ts │ │ ├── security-actions.ts │ │ ├── security-state.ts │ │ ├── security-epics.ts │ │ └── security-reducers.ts │ ├── trading-competition │ │ ├── trading-competition-selectors.ts │ │ ├── trading-competition-actions.ts │ │ ├── trading-competition-state.ts │ │ ├── trading-competition-epics.ts │ │ └── trading-competition-reducers.ts │ ├── utils │ │ ├── create-entity.ts │ │ └── create-actions.ts │ ├── initialized │ │ ├── runtime-state.ts │ │ ├── runtime-actions.ts │ │ ├── runtime-selectors.ts │ │ └── runtime-reducers.ts │ ├── assets │ │ ├── assets-actions.ts │ │ ├── assets-state.ts │ │ ├── assets-selectors.ts │ │ └── assets-epics.ts │ ├── index.ts │ ├── settings │ │ ├── settings-selectors.ts │ │ ├── settings-state.ts │ │ ├── settings-actions.ts │ │ └── settings-reducers.ts │ ├── swap-routes │ │ ├── swap-routes-selectors.ts │ │ ├── swap-routes-actions.ts │ │ └── swap-routes-state.ts │ ├── root-state │ │ └── root-state-epics.ts │ └── create-store.ts ├── components │ ├── settings-modal │ │ ├── risk-tolerance │ │ │ ├── risk-tolerance.module.css │ │ │ ├── risk-tolerance-button.tsx │ │ │ ├── risk-tolerance.tsx │ │ │ └── risk-tolerance-info.tsx │ │ ├── max-slippage │ │ │ └── max-slippage.module.css │ │ ├── theme │ │ │ ├── theme.tsx │ │ │ └── theme-button.tsx │ │ ├── max-splits │ │ │ ├── max-splits-button.tsx │ │ │ └── max-splits.tsx │ │ ├── explorer │ │ │ ├── explorer.tsx │ │ │ └── explorer-button.tsx │ │ └── settings-modal.module.css │ ├── lottie │ │ ├── lottie.props.ts │ │ ├── lottie-with-suspense.tsx │ │ └── lottie.tsx │ ├── points-modal │ │ ├── social-tasks │ │ │ ├── assets │ │ │ │ ├── snapx.jpg │ │ │ │ ├── intract.png │ │ │ │ ├── invite.png │ │ │ │ ├── jvault.jpeg │ │ │ │ ├── staking.png │ │ │ │ ├── ton-app.png │ │ │ │ ├── twitter.png │ │ │ │ ├── blockLabs.jpeg │ │ │ │ ├── clayton.jpeg │ │ │ │ ├── dao-lama.png │ │ │ │ ├── kuku-coin.jpeg │ │ │ │ ├── notPixel.jpeg │ │ │ │ ├── parraton.jpeg │ │ │ │ ├── referral.png │ │ │ │ ├── telegram.png │ │ │ │ ├── terminal.jpeg │ │ │ │ ├── ton-hedge.jpeg │ │ │ │ ├── apps-center.jpg │ │ │ │ ├── tonStation.jpeg │ │ │ │ └── torch-finance.jpeg │ │ │ ├── social-tasks.module.css │ │ │ ├── tasks-end │ │ │ │ ├── tasks-end.module.css │ │ │ │ └── tasks-end.tsx │ │ │ ├── task-header │ │ │ │ ├── task-header.module.css │ │ │ │ └── task-header.tsx │ │ │ ├── task-status │ │ │ │ ├── task-status.module.css │ │ │ │ └── task-status.tsx │ │ │ ├── divider │ │ │ │ ├── divider.module.css │ │ │ │ └── divider.tsx │ │ │ └── task-item │ │ │ │ ├── task-item.module.css │ │ │ │ └── task-item.tsx │ │ ├── points-modal.module.css │ │ ├── points-modal.tsx │ │ └── invite-friends │ │ │ └── invite-friends.module.css │ ├── swap-form │ │ ├── ads-swiper │ │ │ ├── trading-competition │ │ │ │ ├── kuku.png │ │ │ │ └── trading-competition.module.css │ │ │ ├── custom-swiper.css │ │ │ ├── ads-swiper-with-suspense.tsx │ │ │ ├── ads-swiper.module.css │ │ │ ├── earn-fees │ │ │ │ └── earn-fees.module.css │ │ │ ├── farm-volume │ │ │ │ └── farm-volume.module.css │ │ │ └── ads-swiper.tsx │ │ ├── swap-details │ │ │ ├── swap-details-header │ │ │ │ ├── swap-details-header.props.ts │ │ │ │ ├── swap-details-header.module.css │ │ │ │ └── swap-details-header.tsx │ │ │ └── route-info │ │ │ │ ├── swap-route-step │ │ │ │ ├── swap-route-step.module.css │ │ │ │ └── swap-route-step.tsx │ │ │ │ ├── route-info.tsx │ │ │ │ └── route-info.module.css │ │ ├── custom-input │ │ │ └── asset-selector │ │ │ │ ├── asset-list │ │ │ │ └── asset-list-item │ │ │ │ │ ├── asset-list-item.props.ts │ │ │ │ │ └── asset-list-item.module.css │ │ │ │ ├── asset-no-result │ │ │ │ ├── asset-no-result.module.css │ │ │ │ └── asset-no-result.tsx │ │ │ │ └── utils │ │ │ │ └── sort-assets.utils.ts │ │ ├── pending-swap │ │ │ ├── pending-swap.module.css │ │ │ └── pending-swap.tsx │ │ ├── toggle-assets-button │ │ │ ├── toggle-assets-button.tsx │ │ │ └── toggle-assets-button.module.css │ │ ├── connect-wallet-button │ │ │ └── connect-wallet-button.tsx │ │ ├── swap-form.module.css │ │ ├── swap-disabled │ │ │ ├── swap-disabled.tsx │ │ │ └── swap-disabled.module.css │ │ └── hooks │ │ │ └── use-exchange-rate.hook.ts │ ├── history-modal │ │ ├── swaps-history │ │ │ ├── swaps-history.module.css │ │ │ ├── history-data │ │ │ │ ├── info-row │ │ │ │ │ └── info-row.module.css │ │ │ │ └── history-data.module.css │ │ │ └── swaps-history.tsx │ │ ├── history-modal.module.css │ │ ├── pending-swap │ │ │ └── pending-swap.module.css │ │ └── history-modal.tsx │ ├── progress-bar │ │ ├── progress-bar.module.css │ │ └── progress-bar.tsx │ ├── rewards-modal │ │ ├── rewards-modal.module.css │ │ ├── rewards-modal.tsx │ │ └── referrer-stats │ │ │ └── referrer-stats.module.css │ ├── trading-competition-modal │ │ ├── trading-competition-modal.module.css │ │ └── trading-competition-modal.tsx │ ├── skeleton │ │ ├── skeleton.module.css │ │ └── skeleton.tsx │ ├── header │ │ ├── header.module.css │ │ ├── header-container │ │ │ ├── header-container.tsx │ │ │ └── header-container.module.css │ │ └── points-score │ │ │ ├── points-score.tsx │ │ │ └── points-score.module.css │ ├── error-element │ │ └── error-element.tsx │ └── footer │ │ └── footer.module.css ├── interfaces │ ├── transaction-info.interface.ts │ ├── icon-props.interface.ts │ ├── modal-props.intefrace.ts │ ├── ton-balance-response.interface.ts │ ├── balance-object.interface.ts │ └── wallet-points-swate.interface.ts ├── shared │ ├── form-button │ │ ├── main-button │ │ │ └── main-button.props.ts │ │ └── form-button.tsx │ ├── content-container │ │ ├── content-container.module.css │ │ └── content-container.tsx │ └── tooltip │ │ ├── tooltip-context.ts │ │ ├── tooltip-options.interface.ts │ │ ├── use-tooltip-context.hook.ts │ │ ├── tooltip.tsx │ │ ├── tooltip-icon.module.css │ │ ├── tooltip-content.tsx │ │ ├── tooltip-icon.tsx │ │ └── tooltip-triget.tsx ├── assets │ ├── fonts │ │ ├── sf-pro-text-regular-webfont.woff │ │ └── sf-pro-text-regular-webfont.woff2 │ └── icons │ │ ├── LoadingIcon │ │ ├── LoadingIcon.tsx │ │ └── LoadingIcon.module.css │ │ ├── ChevronRightIcon │ │ └── ChevronRightIcon.tsx │ │ ├── ChevronDoubleDownIcon │ │ └── ChevronDoubleDownIcon.tsx │ │ ├── ChevronDownIcon │ │ └── ChevronDownIcon.tsx │ │ ├── SearchIcon │ │ └── SearchIcon.tsx │ │ ├── ArrowUpDownIcon │ │ └── ArrowUpDownIcon.tsx │ │ ├── XCircledIcon │ │ └── XCircledIcon.tsx │ │ ├── CheckmarkIcon │ │ └── CheckmarkIcon.tsx │ │ ├── LogoutIcon │ │ └── LogoutIcon.tsx │ │ ├── InfoIcon │ │ └── InfoIcon.tsx │ │ ├── DollarIcon │ │ └── DollarIcon.tsx │ │ ├── CopyIcon │ │ └── CopyIcon.tsx │ │ ├── HistoryIcon │ │ └── HistoryIcon.tsx │ │ ├── ExternalLinkIcon │ │ └── ExternalLinkIcon.tsx │ │ ├── XIcon │ │ └── XIcon.tsx │ │ ├── TwitterIcon │ │ └── TwitterIcon.tsx │ │ ├── toast-success-icon │ │ └── toast-success-icon.tsx │ │ ├── toast-error-icon │ │ └── toast-error-icon.tsx │ │ ├── ChatIcon │ │ └── ChatIcon.tsx │ │ ├── TelegramIcon │ │ └── TelegramIcon.tsx │ │ └── DiamondIcon │ │ └── DiamondIcon.tsx ├── contexts │ ├── modals │ │ ├── modals.hook.ts │ │ └── modals.context.ts │ └── swap-form │ │ ├── swap-form.hook.ts │ │ └── swap-form.context.ts ├── tonconnect │ ├── TonConnectUIContext.ts │ ├── utils │ │ ├── web.ts │ │ └── errors.ts │ ├── errors │ │ ├── ton-connect-ui-react.error.ts │ │ └── ton-connect-provider-not-set.error.ts │ ├── useTonAddress.ts │ ├── useTonConnectUI.ts │ └── useTonWallet.ts ├── app │ └── app.module.css ├── secrets.ts ├── hooks │ ├── use-open-ton-connect-modal.hook.ts │ ├── use-modal-state.hook.ts │ ├── use-prevent-scrolling.hook.ts │ ├── use-ton-connect-modal-status.hook.ts │ ├── use-referral-link.hook.ts │ ├── use-enable-back-button.hook.ts │ ├── use-state-version-check.hook.ts │ ├── use-wallet-address.hook.ts │ ├── use-is-main-button-available.hook.ts │ ├── use-div-height.hook.ts │ ├── use-disable-main-button.hook.ts │ ├── use-explorer-links.hook.ts │ ├── use-theme-styles.hook.ts │ ├── use-update-assets-list.hook.ts │ └── use-analytics.hook.ts ├── ReactToastify.css └── main.tsx ├── docs ├── assets │ ├── main banner.png │ ├── not example.png │ └── usdt example.png ├── smart-contract-events-diagram.md └── TMA-development.md ├── .prettierrc.json ├── contracts ├── rainbow-wallet │ ├── utils │ │ ├── dict.utils.fc │ │ ├── messaging.utils.fc │ │ └── jetton_receive_effect.utils.fc │ ├── constants.fc │ ├── handlers │ │ ├── owner_withdraw_ton_op.fc │ │ ├── owner_withdraw_jetton_op.fc │ │ ├── owner_make_a_swap_op.fc │ │ └── owner_jetton_transfer_notification.fc │ └── storage.fc └── build.ts ├── tsconfig.node.json ├── .github └── workflows │ ├── code-quality-check │ └── action.yml │ ├── code-quality-check.yml │ └── deploy.yml ├── .gitignore ├── vite.config.ts ├── tsconfig.json ├── index.html └── .eslintrc.json /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /src/polyfill.ts: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'buffer'; 2 | 3 | window.Buffer = Buffer; 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Sitemap: https://rainbow.ag/sitemap.xml 4 | -------------------------------------------------------------------------------- /src/types/balances-record.type.ts: -------------------------------------------------------------------------------- 1 | export type BalancesRecord = Record; 2 | -------------------------------------------------------------------------------- /public/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon.png -------------------------------------------------------------------------------- /src/enums/theme.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Theme { 2 | Dark = 'dark', 3 | Light = 'light' 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/style.utils.ts: -------------------------------------------------------------------------------- 1 | export const getClassName = (...classNames: string[]) => classNames.join(' '); 2 | -------------------------------------------------------------------------------- /docs/assets/main banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/docs/assets/main banner.png -------------------------------------------------------------------------------- /docs/assets/not example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/docs/assets/not example.png -------------------------------------------------------------------------------- /public/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-72x72.png -------------------------------------------------------------------------------- /public/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-96x96.png -------------------------------------------------------------------------------- /public/tc-verify.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": "IiwXjSsHcEYAAAAAZvFEMLtGtywhHZSacO_b-GuDGUR_D5Rwoq0XTelOlENH9L_k" 3 | } -------------------------------------------------------------------------------- /docs/assets/usdt example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/docs/assets/usdt example.png -------------------------------------------------------------------------------- /public/icons/icon-120x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-120x96.png -------------------------------------------------------------------------------- /public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-144x144.png -------------------------------------------------------------------------------- /public/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-152x152.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-200x200.png -------------------------------------------------------------------------------- /public/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/types/get-swap-history-data.type.ts: -------------------------------------------------------------------------------- 1 | export type GetSwapHistoryDataParams = { 2 | bocHash: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/animations/diamond.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/diamond.lottie -------------------------------------------------------------------------------- /public/external-assets/roll.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/roll.jpg -------------------------------------------------------------------------------- /public/external-assets/ston.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/ston.png -------------------------------------------------------------------------------- /public/external-assets/ton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/ton.png -------------------------------------------------------------------------------- /public/external-assets/tonco.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tonco.jpg -------------------------------------------------------------------------------- /public/external-assets/torch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/torch.png -------------------------------------------------------------------------------- /public/icons/unknown_asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/unknown_asset.png -------------------------------------------------------------------------------- /src/enums/explorer.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Explorer { 2 | TONScan = 'TONScan', 3 | Tonviewer = 'Tonviewer' 4 | } 5 | -------------------------------------------------------------------------------- /public/animations/duck-clap.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-clap.lottie -------------------------------------------------------------------------------- /public/external-assets/catton.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/catton.jpeg -------------------------------------------------------------------------------- /public/external-assets/dedust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/dedust.png -------------------------------------------------------------------------------- /public/external-assets/ston-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/ston-v2.png -------------------------------------------------------------------------------- /src/enums/risk-tolerance.enum.ts: -------------------------------------------------------------------------------- 1 | export enum RiskTolerance { 2 | Safe = 1, 3 | Normal = 2, 4 | Risky = 3 5 | } 6 | -------------------------------------------------------------------------------- /public/animations/duck-airdrop.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-airdrop.lottie -------------------------------------------------------------------------------- /public/animations/duck-alert.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-alert.lottie -------------------------------------------------------------------------------- /public/animations/duck-chain.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-chain.lottie -------------------------------------------------------------------------------- /public/animations/duck-chart.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-chart.lottie -------------------------------------------------------------------------------- /public/animations/duck-money.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-money.lottie -------------------------------------------------------------------------------- /public/icons/small-icon-140x140.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/icons/small-icon-140x140.png -------------------------------------------------------------------------------- /src/store/interfaces/payload-with-request.interface.ts: -------------------------------------------------------------------------------- 1 | export interface PayloadWithRequest { 2 | requestId: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/get-user-auth.type.ts: -------------------------------------------------------------------------------- 1 | export type GetUserAuthParams = { 2 | initData: string; 3 | refParent?: string; 4 | }; 5 | -------------------------------------------------------------------------------- /public/animations/duck-not-found.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/animations/duck-not-found.lottie -------------------------------------------------------------------------------- /public/external-assets/bot-greeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/bot-greeting.png -------------------------------------------------------------------------------- /src/store/actions.ts: -------------------------------------------------------------------------------- 1 | import {createAction} from '@reduxjs/toolkit'; 2 | 3 | export const resetState = createAction('RESET_STATE'); 4 | -------------------------------------------------------------------------------- /src/types/get-claim-rewards.type.ts: -------------------------------------------------------------------------------- 1 | export type GetClaimRewardsParams = { 2 | address: string; 3 | initData: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/promise.utils.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number) => 2 | new Promise(resolve => setTimeout(() => resolve('wake'), ms)); 3 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | export interface LoadableEntityState { 2 | data: T; 3 | error?: string; 4 | isLoading: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/number.utils.ts: -------------------------------------------------------------------------------- 1 | export const clamp = (value: number, min: number, max: number) => 2 | Math.min(Math.max(value, min), max); 3 | -------------------------------------------------------------------------------- /src/components/settings-modal/risk-tolerance/risk-tolerance.module.css: -------------------------------------------------------------------------------- 1 | .info_text { 2 | font-size: 14px; 3 | margin-top: var(--step); 4 | } -------------------------------------------------------------------------------- /src/interfaces/transaction-info.interface.ts: -------------------------------------------------------------------------------- 1 | export interface TransactionInfo { 2 | senderRawAddress: string; 3 | bocHash: string; 4 | } 5 | -------------------------------------------------------------------------------- /public/external-assets/tol-season6-airdrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season6-airdrop.png -------------------------------------------------------------------------------- /public/external-assets/tol-season7-airdrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-airdrop.png -------------------------------------------------------------------------------- /src/components/lottie/lottie.props.ts: -------------------------------------------------------------------------------- 1 | export interface LottieProps { 2 | src: string; 3 | speed?: number; 4 | className?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/shared/form-button/main-button/main-button.props.ts: -------------------------------------------------------------------------------- 1 | export interface MainButtonProps { 2 | text: string; 3 | onClick: () => void; 4 | } 5 | -------------------------------------------------------------------------------- /public/external-assets/tol-season6-last-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season6-last-call.png -------------------------------------------------------------------------------- /public/external-assets/tol-season7-last-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-last-call.png -------------------------------------------------------------------------------- /src/assets/fonts/sf-pro-text-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/assets/fonts/sf-pro-text-regular-webfont.woff -------------------------------------------------------------------------------- /public/external-assets/tol-season7-10-days-left.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-10-days-left.jpeg -------------------------------------------------------------------------------- /public/external-assets/tol-season7-15-days-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-15-days-left.png -------------------------------------------------------------------------------- /public/external-assets/tol-season7-2-days-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-2-days-left.png -------------------------------------------------------------------------------- /public/external-assets/tol-season7-4-days-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/tol-season7-4-days-left.png -------------------------------------------------------------------------------- /public/external-assets/trading-competitions/kuku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/trading-competitions/kuku.png -------------------------------------------------------------------------------- /src/assets/fonts/sf-pro-text-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/assets/fonts/sf-pro-text-regular-webfont.woff2 -------------------------------------------------------------------------------- /src/types/get-task-check.type.ts: -------------------------------------------------------------------------------- 1 | export type GetTaskCheckParams = { 2 | initData: string; 3 | taskType: string; 4 | walletAddress?: string; 5 | }; 6 | -------------------------------------------------------------------------------- /docs/smart-contract-events-diagram.md: -------------------------------------------------------------------------------- 1 | # rainbow_wallet smart contract events diagram 2 | ![smart-contract-events-diagram.svg](assets%2Fsmart-contract-events-diagram.svg) -------------------------------------------------------------------------------- /public/external-assets/trading-competitions/bolgur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/trading-competitions/bolgur.png -------------------------------------------------------------------------------- /public/external-assets/trading-competitions/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/public/external-assets/trading-competitions/empty.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/snapx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/snapx.jpg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/intract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/intract.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/invite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/invite.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/jvault.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/jvault.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/staking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/staking.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/ton-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/ton-app.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/twitter.png -------------------------------------------------------------------------------- /src/store/dev/dev-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useDevVersionSelector = () => 4 | useSelector(({dev}) => dev.stateVersion); 5 | -------------------------------------------------------------------------------- /src/store/dev/dev-state.ts: -------------------------------------------------------------------------------- 1 | export interface DevState { 2 | stateVersion: number; 3 | } 4 | 5 | export const devInitialState: DevState = { 6 | stateVersion: 0 7 | }; 8 | -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | /tonconnect-manifest.json 2 | Access-Control-Allow-Origin: * 3 | Access-Control-Allow-Methods: GET, OPTIONS 4 | Access-Control-Allow-Headers: Content-Type 5 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/blockLabs.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/blockLabs.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/clayton.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/clayton.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/dao-lama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/dao-lama.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/kuku-coin.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/kuku-coin.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/notPixel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/notPixel.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/parraton.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/parraton.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/referral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/referral.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/telegram.png -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/terminal.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/terminal.jpeg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/ton-hedge.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/ton-hedge.jpeg -------------------------------------------------------------------------------- /src/types/array-element.type.ts: -------------------------------------------------------------------------------- 1 | export type ArrayElement = 2 | ArrayType extends readonly (infer ElementType)[] ? ElementType : never; 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "bracketSpacing": false, 4 | "arrowParens": "avoid", 5 | "trailingComma": "none", 6 | "printWidth": 80, 7 | "singleQuote": true 8 | } -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/apps-center.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/apps-center.jpg -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/tonStation.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/tonStation.jpeg -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/trading-competition/kuku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/swap-form/ads-swiper/trading-competition/kuku.png -------------------------------------------------------------------------------- /src/store/dev/dev-actions.ts: -------------------------------------------------------------------------------- 1 | import {createAction} from '@reduxjs/toolkit'; 2 | 3 | export const setDevVersionAction = createAction( 4 | 'dev/SET_STATE_VERSION' 5 | ); 6 | -------------------------------------------------------------------------------- /public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://rainbow.ag", 3 | "name": "Rainbow Swap \uD83C\uDF08", 4 | "iconUrl": "https://rainbow.ag/icons/small-icon-140x140.png" 5 | } -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/assets/torch-finance.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xblackbot/rainbow-swap/HEAD/src/components/points-modal/social-tasks/assets/torch-finance.jpeg -------------------------------------------------------------------------------- /src/contexts/modals/modals.hook.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | 3 | import {ModalsContext} from './modals.context'; 4 | 5 | export const useModals = () => useContext(ModalsContext); 6 | -------------------------------------------------------------------------------- /src/enums/swap-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SwapStatusEnum { 2 | Pending = 'Pending', 3 | Success = 'Success', 4 | Failed = 'Failed', 5 | PartiallyFilled = 'Partially Filled' 6 | } 7 | -------------------------------------------------------------------------------- /src/store/security/security-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useAppStatusSelector = () => 4 | useSelector(({security}) => security.appStatus.data); 5 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/custom-swiper.css: -------------------------------------------------------------------------------- 1 | .swiper-pagination { 2 | display: none; 3 | } 4 | 5 | .swiper-pagination-bullet { 6 | background-color: var(--hint-color) !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/icon-props.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IconProps { 2 | width?: number; 3 | height?: number; 4 | fill?: string; 5 | className?: string; 6 | onClick?: () => void; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/history-modal/swaps-history/swaps-history.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: var(--2-step); 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/contexts/swap-form/swap-form.hook.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | 3 | import {SwapFormContext} from './swap-form.context'; 4 | 5 | export const useSwapForm = () => useContext(SwapFormContext); 6 | -------------------------------------------------------------------------------- /src/interfaces/modal-props.intefrace.ts: -------------------------------------------------------------------------------- 1 | import {EmptyFn} from '@rnw-community/shared'; 2 | 3 | export interface ModalProps { 4 | isOpen: boolean; 5 | onOpen?: EmptyFn; 6 | onClose: EmptyFn; 7 | } 8 | -------------------------------------------------------------------------------- /src/tonconnect/TonConnectUIContext.ts: -------------------------------------------------------------------------------- 1 | import {TonConnectUI} from '@tonconnect/ui'; 2 | import {createContext} from 'react'; 3 | 4 | export const TonConnectUIContext = createContext(null); 5 | -------------------------------------------------------------------------------- /src/tonconnect/utils/web.ts: -------------------------------------------------------------------------------- 1 | export function isClientSide(): boolean { 2 | return typeof window !== 'undefined'; 3 | } 4 | 5 | export function isServerSide(): boolean { 6 | return !isClientSide(); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/app.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | min-width: var(--app-min-width); 7 | min-height: 100vh; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/message.type.ts: -------------------------------------------------------------------------------- 1 | import {SendTransactionRequest} from '@tonconnect/ui'; 2 | 3 | import {ArrayElement} from './array-element.type'; 4 | 5 | export type Message = ArrayElement; 6 | -------------------------------------------------------------------------------- /src/shared/content-container/content-container.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | min-width: var(--app-min-width); 6 | max-width: var(--app-max-width); 7 | } 8 | -------------------------------------------------------------------------------- /src/store/trading-competition/trading-competition-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useTradingCompetitionDateSelector = () => 4 | useSelector(({tradingCompetition}) => tradingCompetition.data); 5 | -------------------------------------------------------------------------------- /src/utils/boc.utils.ts: -------------------------------------------------------------------------------- 1 | export const bocToHash = async (boc: string) => { 2 | const {Cell} = await import('@ton/core'); 3 | 4 | const cell = Cell.fromBoc(Buffer.from(boc, 'base64'))[0]; 5 | 6 | return cell.hash().toString('hex'); 7 | }; 8 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/utils/dict.utils.fc: -------------------------------------------------------------------------------- 1 | (slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; 2 | (cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; 3 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/swap-details-header/swap-details-header.props.ts: -------------------------------------------------------------------------------- 1 | import {Asset} from 'rainbow-swap-sdk'; 2 | 3 | export interface SwapDetailsHeaderProps { 4 | inputAsset: Asset; 5 | outputAsset: Asset; 6 | routesLength: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-context.ts: -------------------------------------------------------------------------------- 1 | import {createContext} from 'react'; 2 | 3 | import {useTooltip} from './use-tooltip.hook'; 4 | 5 | type ContextType = ReturnType | null; 6 | 7 | export const TooltipContext = createContext(null); 8 | -------------------------------------------------------------------------------- /src/store/security/security-actions.ts: -------------------------------------------------------------------------------- 1 | import {AppStatus} from 'rainbow-swap-sdk'; 2 | 3 | import {createActions} from '../utils/create-actions'; 4 | 5 | export const loadAppStatusActions = createActions( 6 | 'security/LOAD_APP_STATUS' 7 | ); 8 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-options.interface.ts: -------------------------------------------------------------------------------- 1 | import type {Placement} from '@floating-ui/react'; 2 | 3 | export interface TooltipOptions { 4 | initialOpen?: boolean; 5 | placement?: Placement; 6 | open?: boolean; 7 | onOpenChange?: (open: boolean) => void; 8 | } 9 | -------------------------------------------------------------------------------- /src/store/utils/create-entity.ts: -------------------------------------------------------------------------------- 1 | import {LoadableEntityState} from '../types'; 2 | 3 | export const createEntity = ( 4 | data: T, 5 | isLoading = false, 6 | error?: string 7 | ): LoadableEntityState => ({ 8 | data, 9 | isLoading, 10 | error 11 | }); 12 | -------------------------------------------------------------------------------- /src/store/initialized/runtime-state.ts: -------------------------------------------------------------------------------- 1 | export interface RuntimeState { 2 | isAssetsInitialized: boolean; 3 | assetsSearchValue: string; 4 | } 5 | 6 | export const initializedInitialState: RuntimeState = { 7 | isAssetsInitialized: false, 8 | assetsSearchValue: '' 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /src/secrets.ts: -------------------------------------------------------------------------------- 1 | export const TELEGRAM_ANALYTICS_TOKEN = 2 | 'eyJhcHBfbmFtZSI6IlJhaW5ib3dfU3dhcCIsImFwcF91cmwiOiJodHRwcy8vdC5tZS9yYWluYm93X3N3YXBfYm90IiwiYXBwX2RvbWFpbiI6Imh0dHBzOi8vcmFpbmJvdy5hZy8ifQ==!n76sN/YfeGQPebwg+kwu37g/hfEKzDhd4XrZFMPoiQE='; 3 | export const TELEGRAM_ANALYTICS_APP_NAME = 'Rainbow_Swap'; 4 | -------------------------------------------------------------------------------- /src/store/initialized/runtime-actions.ts: -------------------------------------------------------------------------------- 1 | import {createAction} from '@reduxjs/toolkit'; 2 | 3 | export const setAssetsSearchValue = createAction( 4 | 'runtime/SET_ASSETS_SEARCH_VALUE' 5 | ); 6 | 7 | export const assetsInitializedAction = createAction( 8 | 'runtime/ASSETS_INITIALIZED' 9 | ); 10 | -------------------------------------------------------------------------------- /src/store/initialized/runtime-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useIsAssetsInitializedSelector = () => 4 | useSelector(({runtime}) => runtime.isAssetsInitialized); 5 | 6 | export const useAssetsSearchValueSelector = () => 7 | useSelector(({runtime}) => runtime.assetsSearchValue); 8 | -------------------------------------------------------------------------------- /src/components/swap-form/custom-input/asset-selector/asset-list/asset-list-item/asset-list-item.props.ts: -------------------------------------------------------------------------------- 1 | import {Asset} from 'rainbow-swap-sdk'; 2 | 3 | export interface AssetListItemProps { 4 | isLoading: boolean; 5 | dataArray: { 6 | asset: Asset; 7 | balance: string; 8 | onClick: () => void; 9 | }[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/icons/LoadingIcon/LoadingIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './LoadingIcon.module.css'; 4 | import {IconProps} from '../../../interfaces/icon-props.interface'; 5 | 6 | export const LoadingIcon: FC = ({width = 20, height = 20}) => ( 7 |
8 | ); 9 | -------------------------------------------------------------------------------- /src/assets/icons/LoadingIcon/LoadingIcon.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | border: 2px solid var(--text-color); 3 | border-top: 2px solid var(--hint-color); 4 | border-radius: 50%; 5 | animation: spin 1s linear infinite; 6 | } 7 | 8 | @keyframes spin { 9 | 0% { transform: rotate(0deg); } 10 | 100% { transform: rotate(360deg); } 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/ton-balance-response.interface.ts: -------------------------------------------------------------------------------- 1 | export interface TonBalanceArray { 2 | address: string; 3 | balance: string; 4 | last_activity: string; 5 | status: string; 6 | interfaces: string[]; 7 | name: string; 8 | is_scam: boolean; 9 | memo_required: boolean; 10 | get_methods: string[]; 11 | is_wallet: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/social-tasks.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | color: var(--text-color); 3 | font-size: 16px; 4 | margin: 0 var(--2-step) var(--2-step); 5 | } 6 | 7 | .divider { 8 | width: unset; 9 | margin-left: var(--2-step); 10 | margin-right: var(--2-step); 11 | } 12 | 13 | .footer { 14 | margin-bottom: var(--4-step); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/points-modal/points-modal.module.css: -------------------------------------------------------------------------------- 1 | .content_container{ 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | overflow-y: scroll; 6 | } 7 | 8 | .footer_container { 9 | margin-top: auto; 10 | padding: var(--2-step); 11 | background-color: var(--header-bg-color); 12 | border-top: 1px solid var(--border-color); 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/use-open-ton-connect-modal.hook.ts: -------------------------------------------------------------------------------- 1 | import {useCallback, useContext} from 'react'; 2 | 3 | import {TonConnectUIContext} from '../tonconnect/TonConnectUIContext'; 4 | 5 | export const useOpenTonConnectModal = () => { 6 | const tonConnectUI = useContext(TonConnectUIContext); 7 | 8 | return useCallback(() => tonConnectUI?.modal.open(), [tonConnectUI?.modal]); 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/tasks-end/tasks-end.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | margin: 24px 0 48px; 6 | font-size: 15px; 7 | color: var(--hint-color); 8 | } 9 | 10 | .duck_alert_fallback { 11 | width: 30%; 12 | aspect-ratio: 1 / 1; 13 | margin-bottom: var(--2-step); 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/code-quality-check/action.yml: -------------------------------------------------------------------------------- 1 | name: Code quality check 2 | description: Installs dependencies and checks TS & linting 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - run: yarn install 7 | shell: bash 8 | 9 | - name: Check TypeScript 10 | run: yarn ts 11 | shell: bash 12 | 13 | - name: Check ESLint 14 | run: yarn lint 15 | shell: bash 16 | -------------------------------------------------------------------------------- /src/hooks/use-modal-state.hook.ts: -------------------------------------------------------------------------------- 1 | import {useCallback, useMemo, useState} from 'react'; 2 | 3 | export const useModalState = () => { 4 | const [isOpen, setIsOpen] = useState(false); 5 | 6 | const open = useCallback(() => setIsOpen(true), []); 7 | const close = useCallback(() => setIsOpen(false), []); 8 | 9 | return useMemo(() => ({isOpen, open, close}), [close, isOpen, open]); 10 | }; 11 | -------------------------------------------------------------------------------- /src/shared/tooltip/use-tooltip-context.hook.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | 3 | import {TooltipContext} from './tooltip-context'; 4 | 5 | export const useTooltipContext = () => { 6 | const context = useContext(TooltipContext); 7 | 8 | if (context == null) { 9 | throw new Error('Tooltip components must be wrapped in '); 10 | } 11 | 12 | return context; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-header/task-header.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | margin: var(--2-step); 5 | } 6 | 7 | .image { 8 | width: var(--5-step); 9 | height: var(--5-step); 10 | border-radius: 999999px; 11 | margin-right: var(--step); 12 | } 13 | 14 | .name { 15 | font-size: 16px; 16 | color: var(--text-color); 17 | } -------------------------------------------------------------------------------- /src/components/history-modal/history-modal.module.css: -------------------------------------------------------------------------------- 1 | .content_container{ 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | padding: var(--2-step); 6 | overflow-y: scroll; 7 | } 8 | 9 | .footer_container { 10 | margin-top: auto; 11 | padding: var(--2-step); 12 | background-color: var(--header-bg-color); 13 | border-top: 1px solid var(--border-color); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/progress-bar/progress-bar.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: var(--step); 4 | background-color: var(--attention-bg); 5 | border-radius: var(--step); 6 | overflow: hidden; 7 | } 8 | 9 | .fill { 10 | width: 0; 11 | height: 100%; 12 | background-color: var(--button-color); 13 | border-radius: var(--step); 14 | transition: width 0.5s ease; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/rewards-modal/rewards-modal.module.css: -------------------------------------------------------------------------------- 1 | .content_container{ 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | padding: var(--2-step); 6 | overflow-y: scroll; 7 | } 8 | 9 | .footer_container { 10 | margin-top: auto; 11 | padding: var(--2-step); 12 | background-color: var(--header-bg-color); 13 | border-top: 1px solid var(--border-color); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/swap-assets.utils.ts: -------------------------------------------------------------------------------- 1 | export const swapAssets = ( 2 | newAssetAddress: string, 3 | otherAssetAddress: string, 4 | setCurrentAssetAddress: (address: string) => void, 5 | handleToggleAssetsClick: () => void 6 | ) => { 7 | if (newAssetAddress === otherAssetAddress) { 8 | handleToggleAssetsClick(); 9 | } else { 10 | setCurrentAssetAddress(newAssetAddress); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/swap-form/pending-swap/pending-swap.module.css: -------------------------------------------------------------------------------- 1 | .loader_spinner { 2 | border: 2px solid rgba(var(--attention-color), 0.3); 3 | border-top: 2px solid var(--button-color); 4 | border-radius: 50%; 5 | width: 12px; 6 | height: 12px; 7 | animation: spin 1s linear infinite; 8 | } 9 | 10 | @keyframes spin { 11 | 0% { transform: rotate(0deg); } 12 | 100% { transform: rotate(360deg); } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Sentry Config File 27 | .env.sentry-build-plugin 28 | -------------------------------------------------------------------------------- /src/components/trading-competition-modal/trading-competition-modal.module.css: -------------------------------------------------------------------------------- 1 | .content_container{ 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | padding: var(--2-step); 6 | overflow-y: scroll; 7 | } 8 | 9 | .footer_container { 10 | margin-top: auto; 11 | padding: var(--2-step); 12 | background-color: var(--header-bg-color); 13 | border-top: 1px solid var(--border-color); 14 | } 15 | -------------------------------------------------------------------------------- /src/store/dev/dev-reducers.ts: -------------------------------------------------------------------------------- 1 | import {createReducer} from '@reduxjs/toolkit'; 2 | 3 | import {setDevVersionAction} from './dev-actions'; 4 | import {devInitialState, DevState} from './dev-state'; 5 | 6 | export const devReducers = createReducer(devInitialState, builder => { 7 | builder.addCase(setDevVersionAction, (state, {payload}) => ({ 8 | ...state, 9 | stateVersion: payload 10 | })); 11 | }); 12 | -------------------------------------------------------------------------------- /src/store/utils/create-actions.ts: -------------------------------------------------------------------------------- 1 | import {createAction} from '@reduxjs/toolkit'; 2 | 3 | export const createActions = < 4 | CreatePayload = void, 5 | SuccessPayload = void, 6 | FailPayload = string 7 | >( 8 | type: string 9 | ) => ({ 10 | submit: createAction(type), 11 | success: createAction(`${type}-SUCCESS`), 12 | fail: createAction(`${type}-FAIL`) 13 | }); 14 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import {defineConfig} from 'vite'; 3 | import mkcert from 'vite-plugin-mkcert'; 4 | import Terminal from 'vite-plugin-terminal'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: process.env.VITE_BASE_URL ?? '/', 9 | plugins: [react(), mkcert({force: true}), Terminal()], 10 | build: { 11 | minify: 'terser' 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/swap-form/custom-input/asset-selector/asset-no-result/asset-no-result.module.css: -------------------------------------------------------------------------------- 1 | .noResultDiv { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | font-size: 15px; 7 | margin-top: 48px; 8 | color: var(--hint-color); 9 | } 10 | 11 | .duck_not_found_fallback { 12 | width: 30%; 13 | aspect-ratio: 1 / 1; 14 | 15 | margin-bottom: var(--2-step); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-status/task-status.module.css: -------------------------------------------------------------------------------- 1 | .in_review { 2 | display: inline-flex; 3 | box-sizing: border-box; 4 | align-items: center; 5 | height: 28px; 6 | padding: 2px 10px; 7 | border-radius: 20px; 8 | font-size: 14px; 9 | line-height: 20px; 10 | background: #FBE5B9; 11 | color: #9D502A; 12 | cursor: pointer; 13 | } 14 | 15 | .chevron_right { 16 | color: var(--text-color); 17 | } -------------------------------------------------------------------------------- /src/store/security/security-state.ts: -------------------------------------------------------------------------------- 1 | import {AppStatus} from 'rainbow-swap-sdk'; 2 | 3 | import {LoadableEntityState} from '../types'; 4 | import {createEntity} from '../utils/create-entity'; 5 | 6 | export interface SecurityState { 7 | appStatus: LoadableEntityState; 8 | } 9 | 10 | export const securityInitialState: SecurityState = { 11 | appStatus: createEntity({ 12 | isSwapsEnabled: true, 13 | message: '' 14 | }) 15 | }; 16 | -------------------------------------------------------------------------------- /src/store/assets/assets-actions.ts: -------------------------------------------------------------------------------- 1 | import {Asset, AssetsListParams} from 'rainbow-swap-sdk'; 2 | 3 | import {PayloadWithRequest} from '../interfaces/payload-with-request.interface'; 4 | import {createActions} from '../utils/create-actions'; 5 | 6 | export const loadAssetsListActions = createActions< 7 | AssetsListParams & PayloadWithRequest, 8 | {list: Asset[]} & PayloadWithRequest, 9 | {error: string} & PayloadWithRequest 10 | >('assets/LOAD_ASSETS_LIST'); 11 | -------------------------------------------------------------------------------- /src/store/trading-competition/trading-competition-actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetTradingCompetitionDataParams, 3 | TradingCompetitionDataResponse 4 | } from '../../types/get-trading-competition-data.type'; 5 | import {createActions} from '../utils/create-actions'; 6 | 7 | export const loadTradingCompetitionDataActions = createActions< 8 | GetTradingCompetitionDataParams, 9 | TradingCompetitionDataResponse 10 | >('tradingCompetition/LOAD_TRADING_COMPETITION_DATA'); 11 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-header/task-header.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './task-header.module.css'; 4 | 5 | interface Props { 6 | name: string; 7 | imageSrc: string; 8 | } 9 | 10 | export const TaskHeader: FC = ({name, imageSrc}) => ( 11 |
12 | {name} 13 |

{name}

14 |
15 | ); 16 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/ads-swiper-with-suspense.tsx: -------------------------------------------------------------------------------- 1 | import {lazy, Suspense} from 'react'; 2 | 3 | import styles from './ads-swiper.module.css'; 4 | import {Skeleton} from '../../skeleton/skeleton'; 5 | 6 | const AdsSwiper = lazy(() => import('./ads-swiper')); 7 | 8 | export const AdsSwiperWithSuspense = () => ( 9 | } 11 | > 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/components/history-modal/swaps-history/history-data/info-row/info-row.module.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | gap: var(--half-step); 6 | } 7 | 8 | .description { 9 | font-size: 14px; 10 | color: var(--hint-color); 11 | } 12 | 13 | .asset_value { 14 | font-size: 14px; 15 | color: var(--text-color); 16 | } 17 | 18 | .usd_value { 19 | font-size: 12px; 20 | color: var(--hint-color); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/tasks-end/tasks-end.tsx: -------------------------------------------------------------------------------- 1 | import styles from './tasks-end.module.css'; 2 | import {LottieWithSuspense} from '../../../lottie/lottie-with-suspense'; 3 | 4 | export const TasksEnd = () => ( 5 |
6 | 10 |

That's it, more coming soon!

11 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/utils/sentry.utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | catchError, 3 | Observable, 4 | ObservableInput, 5 | ObservedValueOf, 6 | OperatorFunction 7 | } from 'rxjs'; 8 | 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | export const sentryCatchError = >( 11 | selector: (err: any, caught: Observable) => O 12 | ): OperatorFunction> => 13 | catchError((error, caught) => { 14 | return selector(error, caught); 15 | }); 16 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/constants.fc: -------------------------------------------------------------------------------- 1 | const int op::bounce = 0xffffffff; 2 | const int op::excesses = 0xd53276db; 3 | const int op::jetton_transfer = 0xf8a7ea5; 4 | const int op::jetton_transfer_notification = 0x7362d09c; 5 | 6 | const int op::make_a_swap = 430800; 7 | const int op::withdraw_ton = 430801; 8 | const int op::withdraw_jetton = 430802; 9 | 10 | const int op::dedust_payout = 0x474f86cf; 11 | 12 | const int error::unknown_op = 8800; 13 | const int error::unknown_jetton_receive_effect_body_type = 8801; 14 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/divider/divider.module.css: -------------------------------------------------------------------------------- 1 | .divider { 2 | width: 100%; 3 | margin: var(--2-step) 0; 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-between; 7 | } 8 | 9 | .line { 10 | width: 100%; 11 | height: 1px; 12 | background-color: var(--border-color); 13 | } 14 | 15 | .chunk { 16 | width: 45%; 17 | height: 1px; 18 | background-color: var(--border-color); 19 | } 20 | 21 | .chevron { 22 | stroke: var(--hint-color); 23 | } -------------------------------------------------------------------------------- /src/interfaces/balance-object.interface.ts: -------------------------------------------------------------------------------- 1 | interface BalanceObject { 2 | balance: bigint; 3 | wallet_address: { 4 | address: string; 5 | is_scam: boolean; 6 | is_wallet: boolean; 7 | }; 8 | jetton: { 9 | address: string; 10 | name: string; 11 | symbol: string; 12 | decimals: number; 13 | image: string; 14 | verification: string; 15 | }; 16 | } 17 | 18 | export interface BalancesArray { 19 | balances: BalanceObject[]; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/progress-bar/progress-bar.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './progress-bar.module.css'; 4 | import {getClassName} from '../../utils/style.utils'; 5 | 6 | interface Props { 7 | progress: number; 8 | className?: string; 9 | } 10 | 11 | export const ProgressBar: FC = ({progress, className = ''}) => ( 12 |
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /src/tonconnect/utils/errors.ts: -------------------------------------------------------------------------------- 1 | import {TonConnectUI} from '@tonconnect/ui'; 2 | 3 | import {TonConnectProviderNotSetError} from '../errors/ton-connect-provider-not-set.error'; 4 | 5 | export function checkProvider( 6 | provider: TonConnectUI | null 7 | ): provider is TonConnectUI { 8 | if (!provider) { 9 | throw new TonConnectProviderNotSetError( 10 | 'You should add on the top of the app to use TonConnect' 11 | ); 12 | } 13 | 14 | return true; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/swap-form/custom-input/asset-selector/asset-no-result/asset-no-result.tsx: -------------------------------------------------------------------------------- 1 | import styles from './asset-no-result.module.css'; 2 | import {LottieWithSuspense} from '../../../../lottie/lottie-with-suspense'; 3 | 4 | export const AssetNoResult = () => ( 5 |
6 | 10 |

No assets found

11 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/hooks/use-prevent-scrolling.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | export const usePreventScroll = (isOpen: boolean) => { 4 | useEffect(() => { 5 | const htmlElement = document.documentElement; 6 | const originalOverflowY = htmlElement.style.overflowY; 7 | 8 | if (isOpen) { 9 | htmlElement.style.overflow = 'hidden'; 10 | } 11 | 12 | return () => { 13 | htmlElement.style.overflow = originalOverflowY; 14 | }; 15 | }, [isOpen]); 16 | }; 17 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import {TooltipContext} from './tooltip-context'; 2 | import {TooltipOptions} from './tooltip-options.interface'; 3 | import {useTooltip} from './use-tooltip.hook'; 4 | 5 | export const Tooltip = ({ 6 | children, 7 | ...options 8 | }: {children: React.ReactNode} & TooltipOptions) => { 9 | const tooltip = useTooltip(options); 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/shared/content-container/content-container.tsx: -------------------------------------------------------------------------------- 1 | import {FC, PropsWithChildren} from 'react'; 2 | 3 | import styles from './content-container.module.css'; 4 | import {getClassName} from '../../utils/style.utils'; 5 | 6 | interface Props extends PropsWithChildren { 7 | className?: string; 8 | } 9 | 10 | export const ContentContainer: FC = ({className = '', children}) => { 11 | return ( 12 |
13 | {children} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/tonconnect/errors/ton-connect-ui-react.error.ts: -------------------------------------------------------------------------------- 1 | import {TonConnectUIError} from '@tonconnect/ui'; 2 | 3 | /** 4 | * Base class for TonConnectUIReact errors. You can check if the error was triggered by the @tonconnect/ui-react using `err instanceof TonConnectUIReactError`. 5 | */ 6 | export class TonConnectUIReactError extends TonConnectUIError { 7 | constructor(...args: ConstructorParameters) { 8 | super(...args); 9 | 10 | Object.setPrototypeOf(this, TonConnectUIReactError.prototype); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/lottie/lottie-with-suspense.tsx: -------------------------------------------------------------------------------- 1 | import {FC, Suspense, lazy} from 'react'; 2 | 3 | import {LottieProps} from './lottie.props'; 4 | import {Skeleton} from '../skeleton/skeleton'; 5 | 6 | const Lottie = lazy(() => import('./lottie')); 7 | 8 | export const LottieWithSuspense: FC = ({ 9 | src, 10 | speed, 11 | className 12 | }) => ( 13 | }> 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/skeleton/skeleton.module.css: -------------------------------------------------------------------------------- 1 | .skeleton_loading { 2 | background: linear-gradient(90deg, var(--border-color) 25%, var(--spinner-color-empty-area) 50%, var(--border-color) 75%); 3 | border-radius: 999999px; 4 | background-size: 200% 100%; 5 | animation: skeleton-loading 3s infinite; 6 | } 7 | 8 | .skeleton_loading * { 9 | opacity: 0; 10 | } 11 | 12 | @keyframes skeleton-loading { 13 | 0% { 14 | background-position: 200% 0; 15 | } 16 | 100% { 17 | background-position: -200% 0; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useDispatch as useRawDispatch, 3 | useSelector as useRawSelector 4 | } from 'react-redux'; 5 | 6 | import {createStore} from './create-store'; 7 | import {rootReducer} from './root-state/root-state.reducers'; 8 | 9 | export const {store, persistor} = createStore(); 10 | 11 | export type RootState = ReturnType; 12 | type AppDispatch = typeof store.dispatch; 13 | 14 | export const useDispatch = useRawDispatch.withTypes(); 15 | export const useSelector = useRawSelector.withTypes(); 16 | -------------------------------------------------------------------------------- /contracts/build.ts: -------------------------------------------------------------------------------- 1 | import {compileFunc} from '@ton-community/func-js'; 2 | import fs from 'fs'; 3 | 4 | const buildContract = async (contractLocation: string) => { 5 | const result = await compileFunc({ 6 | targets: [contractLocation], 7 | sources: path => fs.readFileSync(path).toString() 8 | }); 9 | 10 | if (result.status === 'error') { 11 | throw result.message; 12 | } 13 | 14 | console.log('Contract code BOC:\n'); 15 | console.log(result.codeBoc); 16 | }; 17 | 18 | buildContract('contracts/rainbow-wallet/contract.fc'); 19 | -------------------------------------------------------------------------------- /src/components/header/header.module.css: -------------------------------------------------------------------------------- 1 | .left_div { 2 | flex-grow: 0; 3 | flex-shrink: 0; 4 | flex-basis: auto; 5 | display: flex; 6 | align-items: center; 7 | margin-right: var(--step); 8 | } 9 | 10 | .right_div { 11 | flex-grow: 1; 12 | flex-shrink: 1; 13 | flex-basis: 0; 14 | min-width: 0; 15 | display: flex; 16 | align-items: center; 17 | justify-content: flex-end; 18 | } 19 | 20 | .logo_text path { 21 | fill: var(--text-color); 22 | } 23 | 24 | .header_triangle_logo { 25 | width: calc(4 * var(--step)); 26 | } -------------------------------------------------------------------------------- /src/components/swap-form/toggle-assets-button/toggle-assets-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './toggle-assets-button.module.css'; 4 | import {ArrowUpDownIcon} from '../../../assets/icons/ArrowUpDownIcon/ArrowUpDownIcon'; 5 | 6 | interface Props { 7 | onClick: () => void; 8 | } 9 | 10 | export const ToggleAssetsButton: FC = ({onClick}) => ( 11 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/hooks/use-ton-connect-modal-status.hook.ts: -------------------------------------------------------------------------------- 1 | import {useContext, useEffect, useState} from 'react'; 2 | 3 | import {TonConnectUIContext} from '../tonconnect/TonConnectUIContext'; 4 | 5 | export const useTonConnectModalStatus = () => { 6 | const tonConnectUI = useContext(TonConnectUIContext); 7 | const [status, setStatus] = useState(tonConnectUI?.modal.state.status); 8 | 9 | useEffect(() => { 10 | tonConnectUI?.onModalStateChange(value => { 11 | setStatus(value.status); 12 | }); 13 | }, [tonConnectUI]); 14 | 15 | return status; 16 | }; 17 | -------------------------------------------------------------------------------- /src/store/assets/assets-state.ts: -------------------------------------------------------------------------------- 1 | import {Asset, AssetsRecord} from 'rainbow-swap-sdk'; 2 | 3 | import {DEFAULT_ASSETS_RECORD} from '../../data/assets-record'; 4 | import {LoadableEntityState} from '../types'; 5 | import {createEntity} from '../utils/create-entity'; 6 | 7 | export interface AssetsState { 8 | list: LoadableEntityState; 9 | record: AssetsRecord; 10 | lastRequestId?: string; 11 | } 12 | 13 | export const assetsInitialState: AssetsState = { 14 | list: createEntity(Object.values(DEFAULT_ASSETS_RECORD)), 15 | record: DEFAULT_ASSETS_RECORD 16 | }; 17 | -------------------------------------------------------------------------------- /src/store/trading-competition/trading-competition-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EMPTY_TRADING_COMPETITION_DATA, 3 | TradingCompetitionDataResponse 4 | } from '../../types/get-trading-competition-data.type'; 5 | import {LoadableEntityState} from '../types'; 6 | import {createEntity} from '../utils/create-entity'; 7 | 8 | export interface TradingCompetitionState { 9 | data: LoadableEntityState; 10 | } 11 | 12 | export const tradingCompetitionInitialState: TradingCompetitionState = { 13 | data: createEntity(EMPTY_TRADING_COMPETITION_DATA) 14 | }; 15 | -------------------------------------------------------------------------------- /.github/workflows/code-quality-check.yml: -------------------------------------------------------------------------------- 1 | name: Code quality check 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main", "development" ] 6 | 7 | jobs: 8 | code-quality-check: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Clone repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Use Node 20 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | cache: 'yarn' 20 | 21 | - name: Install dependencies and code quality check 22 | uses: ./.github/workflows/code-quality-check 23 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/swap-details-header/swap-details-header.module.css: -------------------------------------------------------------------------------- 1 | .error_text { 2 | font-size: 14px; 3 | color: var(--error-color); 4 | } 5 | 6 | .attention_container { 7 | display: flex; 8 | align-items: center; 9 | gap: var(--half-step) 10 | } 11 | 12 | .attention_text { 13 | font-size: 14px; 14 | color: rgba(var(--danger-color)); 15 | } 16 | 17 | .rate_text { 18 | font-size: 14px; 19 | cursor: pointer; 20 | color: var(--text-color); 21 | } 22 | 23 | .usd_text { 24 | font-size: 14px; 25 | color: var(--hint-color); 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/format-number.utils.ts: -------------------------------------------------------------------------------- 1 | export function formatNumber(number: number, fixedValue: number): string { 2 | if (isNaN(number)) { 3 | return '0'; 4 | } 5 | 6 | const decimalPart = number.toString().split('.')[1]; 7 | 8 | if (decimalPart === undefined || decimalPart.length <= fixedValue) { 9 | return number.toString(); 10 | } 11 | 12 | const decimalsMultiplier = Math.pow(10, fixedValue); 13 | const roundedNumber = 14 | Math.floor(number * decimalsMultiplier) / decimalsMultiplier; 15 | 16 | return roundedNumber.toFixed(fixedValue); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/utils/messaging.utils.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../storage.fc"; 3 | 4 | () forward_message_op(int op, int query_id) impure inline { 5 | cell message_body = begin_cell() 6 | .store_uint(op, 32) 7 | .store_uint(query_id, 64) 8 | .end_cell(); 9 | 10 | cell message = begin_cell() 11 | .store_uint(0x18, 6) 12 | .store_slice(storage::owner_address) 13 | .store_coins(0) 14 | .store_uint(1, 107) 15 | .store_ref(message_body) 16 | .end_cell(); 17 | 18 | send_raw_message(message, 64); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/skeleton/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import {FC, PropsWithChildren} from 'react'; 2 | 3 | import styles from './skeleton.module.css'; 4 | import {getClassName} from '../../utils/style.utils'; 5 | 6 | interface Props extends PropsWithChildren { 7 | isLoading: boolean; 8 | className?: string; 9 | } 10 | 11 | export const Skeleton: FC = ({isLoading, className, children}) => ( 12 |
18 | {children} 19 |
20 | ); 21 | -------------------------------------------------------------------------------- /src/components/swap-form/toggle-assets-button/toggle-assets-button.module.css: -------------------------------------------------------------------------------- 1 | .currency_selector_button { 2 | display: flex; 3 | height: 48px; 4 | width: 48px; 5 | background-color: var(--secondary-bg-color); 6 | position: absolute; 7 | cursor: pointer; 8 | left: 50%; 9 | bottom: calc(-1 * var(--half-step)); 10 | transform: translate(-50%, 50%); 11 | justify-content: center; 12 | align-items: center; 13 | border-radius: var(--2-step); 14 | border: 2px solid var(--bg-color); 15 | z-index: 1; 16 | } 17 | 18 | .icon{ 19 | color: var(--button-color); 20 | } -------------------------------------------------------------------------------- /src/hooks/use-referral-link.hook.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../store'; 2 | import {useWalletAddress} from './use-wallet-address.hook'; 3 | import {IS_TMA, TELEGRAM_APP_LINK, WEB_LINK} from '../globals'; 4 | 5 | export const useReferralLink = () => { 6 | const userRefHash = useSelector( 7 | ({wallet}) => wallet.pointsState.walletPoints.data.refHash 8 | ); 9 | const walletAddress = useWalletAddress(); 10 | 11 | if (IS_TMA) { 12 | return `${TELEGRAM_APP_LINK}?startapp=${userRefHash}`; 13 | } else { 14 | return `${WEB_LINK}?r=${walletAddress}`; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/store/settings/settings-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useMaxSlippageSelector = () => 4 | useSelector(({settings}) => settings.maxSlippage); 5 | 6 | export const useRiskToleranceSelector = () => 7 | useSelector(({settings}) => settings.riskTolerance); 8 | 9 | export const useMaxSplitsSelector = () => 10 | useSelector(({settings}) => settings.maxSplits); 11 | 12 | export const useThemeSelector = () => 13 | useSelector(({settings}) => settings.theme); 14 | 15 | export const useExplorerSelector = () => 16 | useSelector(({settings}) => settings.explorer); 17 | -------------------------------------------------------------------------------- /src/tonconnect/errors/ton-connect-provider-not-set.error.ts: -------------------------------------------------------------------------------- 1 | import {TonConnectUIReactError} from './ton-connect-ui-react.error'; 2 | 3 | /** 4 | * Thrown when either not added to the top of the tags tree, 5 | * either there is an attempt using TonConnect UI hook or inside 6 | */ 7 | export class TonConnectProviderNotSetError extends TonConnectUIReactError { 8 | constructor(...args: ConstructorParameters) { 9 | super(...args); 10 | 11 | Object.setPrototypeOf(this, TonConnectProviderNotSetError.prototype); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/toast.utils.ts: -------------------------------------------------------------------------------- 1 | import {toast} from 'react-toastify'; 2 | 3 | import {ToastErrorIcon} from '../assets/icons/toast-error-icon/toast-error-icon'; 4 | import {ToastSuccessIcon} from '../assets/icons/toast-success-icon/toast-success-icon'; 5 | 6 | export const showSuccessToast = (text: string) => 7 | toast.success(text, {icon: ToastSuccessIcon}); 8 | 9 | export const showErrorToast = (text: string) => 10 | toast.error(text, {icon: ToastErrorIcon}); 11 | 12 | export const showLoadingToast = (text: string) => toast.loading(text); 13 | 14 | export const showInfoToast = (text: string) => toast.info(text, {}); 15 | -------------------------------------------------------------------------------- /src/components/header/header-container/header-container.tsx: -------------------------------------------------------------------------------- 1 | import {FC, PropsWithChildren} from 'react'; 2 | 3 | import styles from './header-container.module.css'; 4 | import {ContentContainer} from '../../../shared/content-container/content-container'; 5 | 6 | export const HeaderContainer: FC = ({children}) => ( 7 | <> 8 |
9 |
10 | 11 |
{children}
12 |
13 |
14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/store/settings/settings-state.ts: -------------------------------------------------------------------------------- 1 | import {Explorer} from '../../enums/explorer.enum'; 2 | import {RiskTolerance} from '../../enums/risk-tolerance.enum'; 3 | import {Theme} from '../../enums/theme.enum'; 4 | 5 | export interface SettingsState { 6 | maxSlippage: string; 7 | riskTolerance: RiskTolerance; 8 | maxSplits: number; 9 | theme: Theme; 10 | explorer: Explorer; 11 | } 12 | 13 | export const settingsInitialState: SettingsState = { 14 | maxSlippage: '3.00', 15 | riskTolerance: RiskTolerance.Normal, 16 | maxSplits: 4, 17 | theme: Theme.Dark, 18 | explorer: Explorer.Tonviewer 19 | }; 20 | -------------------------------------------------------------------------------- /src/contexts/modals/modals.context.ts: -------------------------------------------------------------------------------- 1 | import {EmptyFn, emptyFn} from '@rnw-community/shared'; 2 | import {createContext} from 'react'; 3 | 4 | interface ModalsContextValues { 5 | openPointsModal: EmptyFn; 6 | openRewardsModal: EmptyFn; 7 | openHistoryModal: EmptyFn; 8 | openSettingsModal: EmptyFn; 9 | openTradingCompetitionModal: EmptyFn; 10 | } 11 | 12 | export const ModalsContext = createContext({ 13 | openPointsModal: emptyFn, 14 | openRewardsModal: emptyFn, 15 | openHistoryModal: emptyFn, 16 | openSettingsModal: emptyFn, 17 | openTradingCompetitionModal: emptyFn 18 | }); 19 | -------------------------------------------------------------------------------- /src/types/get-wallet-data.type.ts: -------------------------------------------------------------------------------- 1 | import { 2 | emptyWalletPoints, 3 | WalletPointsState 4 | } from '../interfaces/wallet-points-swate.interface'; 5 | import {SwapHistoryData} from '../store/interfaces/swap-history-data.interface'; 6 | 7 | export type GetWalletDataParams = { 8 | address: string; 9 | initData: string; 10 | refParent?: string; 11 | }; 12 | 13 | export type WalletDataResponse = { 14 | pointsState: WalletPointsState; 15 | swapHistory: SwapHistoryData[]; 16 | }; 17 | 18 | export const EMPTY_WALLET_DATA: WalletDataResponse = { 19 | pointsState: emptyWalletPoints, 20 | swapHistory: [] 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/settings-modal/max-slippage/max-slippage.module.css: -------------------------------------------------------------------------------- 1 | .input_container { 2 | display: flex; 3 | align-items: center; 4 | padding: var(--half-step); 5 | border: 1px solid var(--border-color); 6 | border-radius: var(--2-step); 7 | } 8 | 9 | .input_container:focus-within { 10 | border-color: var(--attention-bg); 11 | } 12 | 13 | .input { 14 | width: 44px; 15 | padding: 0 var(--half-step); 16 | text-align: right; 17 | font-size: 16px; 18 | color: var(--text-color); 19 | background: transparent; 20 | } 21 | 22 | .percent_symbol { 23 | font-size: 16px; 24 | color: var(--hint-color); 25 | } -------------------------------------------------------------------------------- /src/components/settings-modal/theme/theme.tsx: -------------------------------------------------------------------------------- 1 | import sharedStyles from '../settings-modal.module.css'; 2 | import {ThemeButton} from './theme-button'; 3 | import {Theme} from '../../../enums/theme.enum'; 4 | 5 | export const ThemeSetting = () => ( 6 |
7 |

Theme

8 |
9 | 10 |
11 | 12 |
13 |
14 | ); 15 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-icon.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | line-height: 0; 3 | background: transparent; 4 | cursor: pointer; 5 | } 6 | 7 | .icon { 8 | height: var(--2-step); 9 | width: var(--2-step); 10 | fill: var(--hint-color); 11 | } 12 | 13 | .tooltip { 14 | width: max-content; 15 | max-width: min(340px, calc(100vw - var(--2-step))); 16 | box-sizing: border-box; 17 | padding: var(--half-step) var(--step); 18 | font-size: 14px; 19 | color: var(--text-color); 20 | background-color: var(--secondary-bg-color); 21 | border-radius: var(--step); 22 | border: 1px solid var(--border-color); 23 | z-index: 100; 24 | } -------------------------------------------------------------------------------- /src/utils/percent.utils.ts: -------------------------------------------------------------------------------- 1 | import {formatNumber} from './format-number.utils'; 2 | 3 | export const calcPercentDiff = ( 4 | inputAssetUsdAmount: number, 5 | outputAssetUsdAmount: number 6 | ): number | undefined => { 7 | if (!inputAssetUsdAmount || !outputAssetUsdAmount) return undefined; 8 | 9 | return ( 10 | ((outputAssetUsdAmount - inputAssetUsdAmount) / inputAssetUsdAmount) * 11 | 100 12 | ); 13 | }; 14 | 15 | export const formatPercentDiff = (percent?: number): string | undefined => { 16 | if (percent === undefined) return undefined; 17 | 18 | return `(${percent >= 0 ? '+' : ''}${formatNumber(percent, 2)}%)`; 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/ads-swiper.module.css: -------------------------------------------------------------------------------- 1 | .swiper { 2 | width: 100%; 3 | height: calc(20 * var(--step)); 4 | margin-bottom: var(--2-step); 5 | border-radius: var(--border-radius) !important; 6 | } 7 | 8 | .swiperSlide { 9 | position: relative; 10 | overflow: hidden; 11 | box-sizing: border-box; 12 | padding: var(--2-step); 13 | border-radius: var(--border-radius); 14 | } 15 | 16 | .swiperSlideBlue { 17 | background: var(--attention-bg); 18 | } 19 | 20 | .swiperSlideGreen { 21 | background: rgba(var(--green-color), 0.25); 22 | } 23 | 24 | .swiperSlidePink { 25 | background: rgba(var(--purple-color), 0.25); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/utils/jetton_receive_effect.utils.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../constants.fc"; 3 | 4 | (cell) get_jetton_receive_effect_body(slice jetton_receive_effect, int query_id, int amount) impure inline { 5 | int effect_op = jetton_receive_effect~load_uint(32); 6 | slice effect_body_slice = jetton_receive_effect~load_ref().begin_parse(); 7 | 8 | return begin_cell() 9 | .store_uint(effect_op, 32) 10 | .store_uint(query_id, 64) 11 | .store_coins(amount) 12 | .store_slice(effect_body_slice) 13 | ;; All other data is stored inside jetton_receive_effect 14 | ;; ... 15 | .end_cell(); 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/use-enable-back-button.hook.ts: -------------------------------------------------------------------------------- 1 | import {EmptyFn} from '@rnw-community/shared'; 2 | import {useEffect} from 'react'; 3 | 4 | import {IS_TMA} from '../globals'; 5 | 6 | export const useEnableBackButton = (isVisible: boolean, onClick: EmptyFn) => { 7 | useEffect(() => { 8 | if (IS_TMA && isVisible) { 9 | window.Telegram.WebApp.BackButton.show(); 10 | window.Telegram.WebApp.BackButton.onClick(onClick); 11 | 12 | return () => { 13 | window.Telegram.WebApp.BackButton.hide(); 14 | window.Telegram.WebApp.BackButton.offClick(onClick); 15 | }; 16 | } 17 | }, [isVisible, onClick]); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/error-element/error-element.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | import {isProd} from '../../globals'; 4 | import {useDispatch} from '../../store'; 5 | import {resetState} from '../../store/actions'; 6 | 7 | // eslint-disable-next-line react-refresh/only-export-components 8 | const ProductionErrorElement = () => { 9 | const dispatch = useDispatch(); 10 | 11 | useEffect(() => { 12 | dispatch(resetState()); 13 | 14 | location.reload(); 15 | }, [dispatch]); 16 | 17 | return null; 18 | }; 19 | 20 | export const getErrorElement = () => { 21 | if (isProd) { 22 | return ; 23 | } 24 | 25 | return undefined; 26 | }; 27 | -------------------------------------------------------------------------------- /src/store/initialized/runtime-reducers.ts: -------------------------------------------------------------------------------- 1 | import {createReducer} from '@reduxjs/toolkit'; 2 | 3 | import {assetsInitializedAction, setAssetsSearchValue} from './runtime-actions'; 4 | import {initializedInitialState, RuntimeState} from './runtime-state'; 5 | 6 | export const runtimeReducers = createReducer( 7 | initializedInitialState, 8 | builder => { 9 | builder.addCase(assetsInitializedAction, state => ({ 10 | ...state, 11 | isAssetsInitialized: true 12 | })); 13 | 14 | builder.addCase(setAssetsSearchValue, (state, {payload}) => ({ 15 | ...state, 16 | assetsSearchValue: payload 17 | })); 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /src/store/interfaces/swap-history-data.interface.ts: -------------------------------------------------------------------------------- 1 | import {SwapStatusEnum} from '../../enums/swap-status.enum'; 2 | 3 | export interface SwapHistoryData { 4 | timestamp: number; 5 | bocHash: string; 6 | status: SwapStatusEnum; 7 | completedMessageCount: number; 8 | sentInfo?: Info; 9 | receivedInfo?: Info; 10 | returnedInfo?: Info; 11 | intermediateTokensInfo?: Info[]; 12 | } 13 | 14 | interface Info { 15 | amount: number; 16 | usdAmount: number; 17 | symbol: string; 18 | } 19 | 20 | export const EMPTY_SWAP_HISTORY_DATA: SwapHistoryData = { 21 | timestamp: 0, 22 | bocHash: '', 23 | status: SwapStatusEnum.Pending, 24 | completedMessageCount: 0 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/history-modal/pending-swap/pending-swap.module.css: -------------------------------------------------------------------------------- 1 | .progress_container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | 7 | .animation_container { 8 | height: 140px; 9 | aspect-ratio: 2 / 1; 10 | } 11 | 12 | .progress_bar { 13 | height: var(--step); 14 | max-width: 380px; 15 | } 16 | 17 | .progress_description { 18 | justify-content: center; 19 | font-size: 14px; 20 | color: var(--hint-color); 21 | text-align: center; 22 | margin-top: var(--step); 23 | margin-bottom: var(--step); 24 | } 25 | 26 | .link_icon { 27 | width: var(--2-step); 28 | height: var(--2-step); 29 | fill: var(--button-color); 30 | } 31 | -------------------------------------------------------------------------------- /src/store/swap-routes/swap-routes-selectors.ts: -------------------------------------------------------------------------------- 1 | import {useSelector} from '../index'; 2 | 3 | export const useIsRoutesLoadingSelector = () => 4 | useSelector(({swapRoutes}) => swapRoutes.lastRequestId !== undefined); 5 | 6 | export const useSwapMessagesSelector = () => 7 | useSelector( 8 | ({swapRoutes}) => swapRoutes.bestRouteResponse.data.swapMessages 9 | ); 10 | 11 | export const useSwapDisplayDataSelector = () => 12 | useSelector( 13 | ({swapRoutes}) => swapRoutes.bestRouteResponse.data.displayData 14 | ); 15 | 16 | export const useSwapMessageCountSelector = () => 17 | useSelector( 18 | ({swapRoutes}) => swapRoutes.bestRouteResponse.data.messageCount 19 | ); 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/handlers/owner_withdraw_ton_op.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../constants.fc"; 3 | #include "../storage.fc"; 4 | 5 | () handle_owner_withdraw_ton_op(int query_id, slice in_msg_body) impure inline { 6 | int amount = in_msg_body~load_coins(); 7 | 8 | cell message_body = begin_cell() 9 | .store_uint(op::excesses, 32) 10 | .store_uint(query_id, 64) 11 | .end_cell(); 12 | 13 | cell message = begin_cell() 14 | .store_uint(0x18, 6) 15 | .store_slice(storage::owner_address) 16 | .store_coins(amount) 17 | .store_uint(1, 107) 18 | .store_ref(message_body) 19 | .end_cell(); 20 | 21 | send_raw_message(message, 64); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/header/header-container/header-container.module.css: -------------------------------------------------------------------------------- 1 | .replacement { 2 | height: 56px; 3 | padding-top: var(--app-safe-area-inset-top); 4 | } 5 | 6 | .container { 7 | height: 56px; 8 | padding-top: var(--app-safe-area-inset-top); 9 | background-color: var(--bg-color); 10 | position: fixed; 11 | width: 100%; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | top: 0; 16 | z-index: 10; 17 | border-bottom: 1px solid var(--border-color); 18 | } 19 | 20 | .inner_container { 21 | display: flex; 22 | width: 100%; 23 | box-sizing: border-box; 24 | justify-content: space-between; 25 | align-items: center; 26 | padding: var(--step); 27 | } -------------------------------------------------------------------------------- /src/store/assets/assets-selectors.ts: -------------------------------------------------------------------------------- 1 | import {getAsset} from '../../utils/asset.utils'; 2 | import {useSelector} from '../index'; 3 | 4 | export const useAssetsListSelector = () => 5 | useSelector(({assets}) => assets.list.data); 6 | 7 | export const useAssetsRecordSelector = () => 8 | useSelector(({assets}) => assets.record); 9 | 10 | export const useIsAssetsLoadingSelector = () => 11 | useSelector(({assets}) => assets.lastRequestId !== undefined); 12 | 13 | export const useAssetSelector = (address: string) => 14 | useSelector( 15 | ({assets}) => getAsset(address, assets.record), 16 | (a, b) => 17 | a.address + '_' + a.usdExchangeRate === 18 | b.address + '_' + b.usdExchangeRate 19 | ); 20 | -------------------------------------------------------------------------------- /src/assets/icons/ChevronRightIcon/ChevronRightIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const ChevronRightIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }) => ( 11 | 21 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/route-info/swap-route-step/swap-route-step.module.css: -------------------------------------------------------------------------------- 1 | .route_step_div { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | background: var(--secondary-bg-color); 6 | border-radius: var(--step); 7 | padding: 8px 4px; 8 | } 9 | 10 | .route_step_direction_div { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | position: relative; 15 | 16 | } 17 | 18 | .route_step_direction_div img { 19 | width: 24px; 20 | align-self: center; 21 | border-radius: 50%; 22 | object-fit: cover; 23 | } 24 | 25 | .route_step_direction_div :first-child { 26 | margin-right: 4px; 27 | } 28 | 29 | .route_step_direction_div :last-child { 30 | margin-left: -7%; 31 | } -------------------------------------------------------------------------------- /src/hooks/use-state-version-check.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | import {PROD_STATE_VERSION} from '../globals'; 4 | import {useDispatch} from '../store'; 5 | import {resetState} from '../store/actions'; 6 | import {setDevVersionAction} from '../store/dev/dev-actions'; 7 | import {useDevVersionSelector} from '../store/dev/dev-selectors'; 8 | 9 | export const useStateVersionCheck = () => { 10 | const dispatch = useDispatch(); 11 | const stateVersion = useDevVersionSelector(); 12 | 13 | useEffect(() => { 14 | if (stateVersion !== PROD_STATE_VERSION) { 15 | dispatch(resetState()); 16 | dispatch(setDevVersionAction(PROD_STATE_VERSION)); 17 | } 18 | }, [dispatch, stateVersion]); 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/date.utils.ts: -------------------------------------------------------------------------------- 1 | export const formatTimestamp = (timestamp: number) => { 2 | const date = new Date(timestamp); 3 | const now = new Date(); 4 | const isCurrentYear = date.getFullYear() === now.getFullYear(); 5 | 6 | const time = new Intl.DateTimeFormat('en-US', { 7 | hour: '2-digit', 8 | minute: '2-digit', 9 | hour12: false 10 | }).format(date); 11 | 12 | const dateOptions: Intl.DateTimeFormatOptions = { 13 | month: 'short', 14 | day: 'numeric' 15 | }; 16 | 17 | if (!isCurrentYear) { 18 | dateOptions.year = 'numeric'; 19 | } 20 | 21 | const datePart = new Intl.DateTimeFormat('en-US', dateOptions).format(date); 22 | 23 | return `${time}, ${datePart}`; 24 | }; 25 | -------------------------------------------------------------------------------- /src/assets/icons/ChevronDoubleDownIcon/ChevronDoubleDownIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const ChevronDoubleDownIcon: FC = ({ 6 | width = 24, 7 | height = 24, 8 | className 9 | }): JSX.Element => ( 10 | 18 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/hooks/use-wallet-address.hook.ts: -------------------------------------------------------------------------------- 1 | import {isNotEmptyString} from '@rnw-community/shared'; 2 | import {useEffect, useMemo, useRef} from 'react'; 3 | 4 | import {useTonAddress} from '../tonconnect/useTonAddress'; 5 | 6 | export const useWalletAddress = () => { 7 | const walletAddress = useTonAddress(); 8 | 9 | return useMemo( 10 | () => (isNotEmptyString(walletAddress) ? walletAddress : undefined), 11 | [walletAddress] 12 | ); 13 | }; 14 | 15 | export const useWalletAddressRef = () => { 16 | const ref = useRef(undefined); 17 | const walletAddress = useWalletAddress(); 18 | 19 | useEffect(() => { 20 | ref.current = walletAddress; 21 | }, [walletAddress]); 22 | 23 | return ref; 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/use-is-main-button-available.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | 3 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 4 | const getIsAvailable = () => { 5 | if (['ios', 'macos'].includes(window.Telegram.WebApp.platform)) { 6 | return true; 7 | } 8 | 9 | // On Android devices MainButton text updates only after the touch 10 | return false; 11 | }; 12 | 13 | export const useIsMainButtonAvailable = () => { 14 | const [isAvailable, setIsAvailable] = useState(getIsAvailable()); 15 | 16 | useEffect(() => { 17 | // @ts-ignore 18 | window.Telegram.WebApp.onEvent('fullscreenChanged', () => { 19 | setIsAvailable(getIsAvailable()); 20 | }); 21 | }, []); 22 | 23 | return isAvailable; 24 | }; 25 | -------------------------------------------------------------------------------- /src/store/settings/settings-actions.ts: -------------------------------------------------------------------------------- 1 | import {createAction} from '@reduxjs/toolkit'; 2 | 3 | import {Explorer} from '../../enums/explorer.enum'; 4 | import {RiskTolerance} from '../../enums/risk-tolerance.enum'; 5 | import {Theme} from '../../enums/theme.enum'; 6 | 7 | export const setMaxSlippageAction = createAction( 8 | 'settings/SET_MAX_SLIPPAGE' 9 | ); 10 | 11 | export const setRiskToleranceAction = createAction( 12 | 'settings/SET_RISK_TOLERANCE' 13 | ); 14 | 15 | export const setMaxSplitsAction = createAction( 16 | 'settings/SET_MAX_SPLITS' 17 | ); 18 | 19 | export const setThemeAction = createAction('settings/SET_THEME'); 20 | 21 | export const setExplorerAction = createAction( 22 | 'settings/SET_EXPLORER' 23 | ); 24 | -------------------------------------------------------------------------------- /src/assets/icons/ChevronDownIcon/ChevronDownIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | interface Props { 4 | width?: string; 5 | height?: string; 6 | className?: string; 7 | } 8 | 9 | export const ChevronDownIcon: FC = ({ 10 | width = '16px', 11 | height = '16px', 12 | className = '' 13 | }): JSX.Element => ( 14 | 23 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/swap-form/pending-swap/pending-swap.tsx: -------------------------------------------------------------------------------- 1 | import styles from './pending-swap.module.css'; 2 | import {useModals} from '../../../contexts/modals/modals.hook'; 3 | import {usePendingSwapSelector} from '../../../store/wallet/wallet-selectors'; 4 | import {Button} from '../../button/button'; 5 | 6 | export const PendingSwap = () => { 7 | const modals = useModals(); 8 | 9 | const pendingSwap = usePendingSwapSelector(); 10 | 11 | const handleClick = () => modals.openHistoryModal(); 12 | 13 | return ( 14 | pendingSwap.bocHash && ( 15 | 19 | ) 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/store/swap-routes/swap-routes-actions.ts: -------------------------------------------------------------------------------- 1 | import {BestRouteResponse} from 'rainbow-swap-sdk'; 2 | 3 | import {RiskTolerance} from '../../enums/risk-tolerance.enum'; 4 | import {PayloadWithRequest} from '../interfaces/payload-with-request.interface'; 5 | import {createActions} from '../utils/create-actions'; 6 | 7 | export const loadSwapRoutesActions = createActions< 8 | { 9 | inputAssetAmount: string; 10 | inputAssetAddress: string; 11 | outputAssetAddress: string; 12 | senderAddress: string | undefined; 13 | riskTolerance: RiskTolerance; 14 | maxSplits: number; 15 | maxSlippage: number; 16 | } & PayloadWithRequest, 17 | BestRouteResponse & PayloadWithRequest, 18 | {error: string} & PayloadWithRequest 19 | >('swap-route/LOAD_SWAP_ROUTES'); 20 | -------------------------------------------------------------------------------- /src/assets/icons/SearchIcon/SearchIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | interface Props { 4 | width?: string; 5 | height?: string; 6 | className?: string; 7 | } 8 | 9 | export const SearchIcon: FC = ({ 10 | width = '16px', 11 | height = '16px', 12 | className = '' 13 | }): JSX.Element => ( 14 | 23 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/swap-form/connect-wallet-button/connect-wallet-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC, useCallback} from 'react'; 2 | 3 | import {trackButtonClick} from '../../../hooks/use-analytics.hook'; 4 | import {useOpenTonConnectModal} from '../../../hooks/use-open-ton-connect-modal.hook'; 5 | import {FormButton} from '../../../shared/form-button/form-button'; 6 | 7 | interface Props { 8 | onClick: () => void; 9 | } 10 | 11 | export const ConnectWalletButton: FC = ({onClick}) => { 12 | const openTonConnectModal = useOpenTonConnectModal(); 13 | 14 | const handleClick = useCallback(() => { 15 | trackButtonClick('Connect'); 16 | openTonConnectModal(); 17 | 18 | onClick(); 19 | }, [onClick, openTonConnectModal]); 20 | 21 | return ; 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/icons/ArrowUpDownIcon/ArrowUpDownIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | interface Props { 4 | width?: string; 5 | height?: string; 6 | className?: string; 7 | } 8 | 9 | export const ArrowUpDownIcon: FC = ({ 10 | width = '16px', 11 | height = '16px', 12 | className = '' 13 | }): JSX.Element => ( 14 | 23 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-item/task-item.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | padding: var(--step) var(--2-step); 5 | cursor: pointer; 6 | } 7 | 8 | .container:hover { 9 | background-color: var(--secondary-bg-color); 10 | } 11 | 12 | .image { 13 | width: var(--5-step); 14 | height: var(--5-step); 15 | border-radius: var(--step); 16 | margin-right: var(--step); 17 | } 18 | 19 | .text_container { 20 | min-width: 0; 21 | } 22 | 23 | .title { 24 | font-size: 14px; 25 | color: var(--text-color); 26 | } 27 | 28 | .description { 29 | font-size: 14px; 30 | color: var(--hint-color); 31 | white-space: nowrap; 32 | text-overflow: ellipsis; 33 | overflow: hidden; 34 | } 35 | 36 | .childrenContainer { 37 | display: flex; 38 | margin-left: auto; 39 | } -------------------------------------------------------------------------------- /src/components/rewards-modal/rewards-modal.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {ReferrerStats} from './referrer-stats/referrer-stats'; 4 | import styles from './rewards-modal.module.css'; 5 | import {ModalProps} from '../../interfaces/modal-props.intefrace'; 6 | import {BottomSheet} from '../../shared/bottom-sheet/bottom-sheet'; 7 | import {FormButton} from '../../shared/form-button/form-button'; 8 | 9 | export const RewardsModal: FC = ({isOpen, onClose}) => ( 10 | 11 |
12 | 13 |
14 | 19 |
20 | ); 21 | -------------------------------------------------------------------------------- /src/hooks/use-div-height.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useMemo, useRef, useState} from 'react'; 2 | 3 | export const useDivHeight = () => { 4 | const ref = useRef(null); 5 | const [height, setHeight] = useState(0); 6 | 7 | useEffect(() => { 8 | const resizeObserver = new ResizeObserver(entries => { 9 | if (entries[0]) { 10 | setHeight(entries[0].contentRect.height); 11 | } 12 | }); 13 | 14 | if (ref.current) { 15 | const target = ref.current; 16 | resizeObserver.observe(target); 17 | 18 | return () => { 19 | resizeObserver.unobserve(target); 20 | }; 21 | } 22 | }, []); 23 | 24 | return useMemo( 25 | () => ({ 26 | height, 27 | ref 28 | }), 29 | [height] 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/route-info/swap-route-step/swap-route-step.tsx: -------------------------------------------------------------------------------- 1 | import {RouteStepDisplayData} from 'rainbow-swap-sdk'; 2 | import {FC} from 'react'; 3 | 4 | import styles from './swap-route-step.module.css'; 5 | 6 | interface Props { 7 | routeStep: RouteStepDisplayData; 8 | } 9 | 10 | export const SwapRouteStep: FC = ({routeStep}) => ( 11 |
12 |
13 | {routeStep.dex.name} 14 | {routeStep.inputAsset.symbol} 18 | {routeStep.outputAsset.symbol} 22 |
23 |
24 | ); 25 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-content.tsx: -------------------------------------------------------------------------------- 1 | import {FloatingPortal, useMergeRefs} from '@floating-ui/react'; 2 | import {forwardRef} from 'react'; 3 | 4 | import {useTooltipContext} from './use-tooltip-context.hook'; 5 | 6 | export const TooltipContent = forwardRef< 7 | HTMLDivElement, 8 | React.HTMLProps 9 | >(({style, ...props}, propRef) => { 10 | const context = useTooltipContext(); 11 | const ref = useMergeRefs([context.refs.setFloating, propRef]); 12 | 13 | if (!context.open) return null; 14 | 15 | return ( 16 | 17 |
25 | 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/assets/icons/XCircledIcon/XCircledIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | interface Props { 3 | width?: string; 4 | height?: string; 5 | className?: string; 6 | onClick?: () => void; 7 | } 8 | 9 | export const XCircledIcon: FC = ({ 10 | width = '16px', 11 | height = '16px', 12 | className = '', 13 | onClick 14 | }): JSX.Element => ( 15 | 25 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/divider/divider.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './divider.module.css'; 4 | import {ChevronDoubleDownIcon} from '../../../../assets/icons/ChevronDoubleDownIcon/ChevronDoubleDownIcon'; 5 | import {getClassName} from '../../../../utils/style.utils'; 6 | 7 | interface Props { 8 | withArrow?: boolean; 9 | className?: string; 10 | } 11 | 12 | export const Divider: FC = ({withArrow = false, className = ''}) => ( 13 |
14 | {withArrow ? ( 15 | <> 16 |
17 | 18 |
19 | 20 | ) : ( 21 |
22 | )} 23 |
24 | ); 25 | -------------------------------------------------------------------------------- /src/components/settings-modal/max-splits/max-splits-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {useDispatch} from '../../../store'; 4 | import {setMaxSplitsAction} from '../../../store/settings/settings-actions'; 5 | import {useMaxSplitsSelector} from '../../../store/settings/settings-selectors'; 6 | import {Button} from '../../button/button'; 7 | 8 | interface Props { 9 | value: number; 10 | } 11 | 12 | export const MaxSplitsButton: FC = ({value}) => { 13 | const dispatch = useDispatch(); 14 | const maxSplits = useMaxSplitsSelector(); 15 | 16 | const handleClick = () => dispatch(setMaxSplitsAction(value)); 17 | 18 | return ( 19 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/header/points-score/points-score.tsx: -------------------------------------------------------------------------------- 1 | import styles from './points-score.module.css'; 2 | import {useModals} from '../../../contexts/modals/modals.hook'; 3 | import {useWalletAddress} from '../../../hooks/use-wallet-address.hook'; 4 | import {usePointsSelector} from '../../../store/wallet/wallet-selectors'; 5 | 6 | export const PointsScore = () => { 7 | const walletAddress = useWalletAddress(); 8 | const modals = useModals(); 9 | const points = usePointsSelector(); 10 | 11 | const handleClick = () => modals.openPointsModal(); 12 | 13 | return ( 14 |
15 |
16 |

17 | {walletAddress && points > 0 ? `${points} XP` : 'Farm XP'} 18 |

19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-status/task-status.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './task-status.module.css'; 4 | import {CheckmarkIcon} from '../../../../assets/icons/CheckmarkIcon/CheckmarkIcon'; 5 | import {ChevronRightIcon} from '../../../../assets/icons/ChevronRightIcon/ChevronRightIcon'; 6 | import {LoadingIcon} from '../../../../assets/icons/LoadingIcon/LoadingIcon'; 7 | 8 | interface Props { 9 | points: number; 10 | isLoading: boolean; 11 | } 12 | 13 | export const TaskStatus: FC = ({points, isLoading}) => { 14 | if (isLoading) { 15 | return ; 16 | } 17 | 18 | if (points === -1) { 19 | return

In review

; 20 | } 21 | 22 | if (points > 0) { 23 | return ; 24 | } 25 | 26 | return ; 27 | }; 28 | -------------------------------------------------------------------------------- /src/tonconnect/useTonAddress.ts: -------------------------------------------------------------------------------- 1 | import {CHAIN, toUserFriendlyAddress} from '@tonconnect/ui'; 2 | import {useMemo} from 'react'; 3 | 4 | import {useTonWallet} from './useTonWallet'; 5 | 6 | /** 7 | * Use it to get user's current ton wallet address. If wallet is not connected hook will return empty string. 8 | * @param [userFriendly=true] allows to choose format of the address. 9 | */ 10 | export function useTonAddress(userFriendly = true): string { 11 | const wallet = useTonWallet(); 12 | return useMemo(() => { 13 | if (wallet) { 14 | return userFriendly 15 | ? toUserFriendlyAddress( 16 | wallet.account.address, 17 | wallet.account.chain === CHAIN.TESTNET 18 | ) 19 | : wallet.account.address; 20 | } else { 21 | return ''; 22 | } 23 | }, [wallet, userFriendly]); 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/icons/CheckmarkIcon/CheckmarkIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const CheckmarkIcon: FC = ({ 6 | width = 24, 7 | height = 24, 8 | fill = '#34CC4E' 9 | }) => ( 10 | 18 | checkmark 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/utils/get-max-sent-amount.utils.ts: -------------------------------------------------------------------------------- 1 | import {fromNano} from './big-int.utils'; 2 | import {formatNumber} from './format-number.utils'; 3 | import { 4 | GAS_AMOUNT, 5 | JETTON_TRANSFER_GAS_AMOUNT, 6 | TON, 7 | TON_DECIMALS 8 | } from '../globals'; 9 | 10 | const MAX_BATCH_SIZE = 4; 11 | 12 | export const getMaxSentAmount = (balance: string, address: string) => { 13 | if (address === TON) { 14 | const gasAmount = GAS_AMOUNT; 15 | const jettonGasAmount = 3n * JETTON_TRANSFER_GAS_AMOUNT; 16 | const totalGasAmount = gasAmount + jettonGasAmount; 17 | 18 | const tonBalanceWithFee = 19 | parseFloat(balance) - 20 | parseFloat(fromNano(totalGasAmount, TON_DECIMALS)) * MAX_BATCH_SIZE; 21 | 22 | const tonBalance = Math.max(tonBalanceWithFee, 0); 23 | return formatNumber(tonBalance, TON_DECIMALS); 24 | } 25 | 26 | return balance; 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/settings-modal/explorer/explorer.tsx: -------------------------------------------------------------------------------- 1 | import sharedStyles from '../settings-modal.module.css'; 2 | import {ExplorerButton} from './explorer-button'; 3 | import {Explorer} from '../../../enums/explorer.enum'; 4 | 5 | export const ExplorerSetting = () => ( 6 | <> 7 |
8 |

Explorer

9 |
10 | 11 |
12 | 13 |
14 |
15 |

16 | Choose your preferred blockchain explorer to view transaction 17 | details. 18 |

19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-form.module.css: -------------------------------------------------------------------------------- 1 | .body_div { 2 | display: flex; 3 | flex-direction: column; 4 | margin: var(--2-step) var(--step); 5 | } 6 | 7 | .input_asset_container { 8 | position: relative; 9 | margin-bottom: var(--half-step); 10 | } 11 | 12 | .output_asset_container { 13 | position: relative; 14 | margin-bottom: var(--2-step); 15 | } 16 | 17 | .swapform_header{ 18 | display: flex; 19 | height: 29px; 20 | justify-content: space-between; 21 | align-items: center; 22 | margin-bottom: var(--2-step); 23 | font-size: 22px; 24 | padding: 0 var(--2-step); 25 | } 26 | 27 | .error_text { 28 | font-size: 14px; 29 | color: var(--error-color); 30 | } 31 | 32 | .icons_div{ 33 | display: flex; 34 | justify-content: space-between; 35 | align-items: center; 36 | gap: var(--step); 37 | } 38 | 39 | .ident_container { 40 | height: var(--2-step); 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/icons/LogoutIcon/LogoutIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const LogoutIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/trading-competition-modal/trading-competition-modal.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {CompetitionInfo} from './competition-info/competition-info'; 4 | import styles from './trading-competition-modal.module.css'; 5 | import {ModalProps} from '../../interfaces/modal-props.intefrace'; 6 | import {BottomSheet} from '../../shared/bottom-sheet/bottom-sheet'; 7 | import {FormButton} from '../../shared/form-button/form-button'; 8 | 9 | export const TradingCompetitionModal: FC = ({isOpen, onClose}) => ( 10 | 11 |
12 | 13 |
14 | 19 |
20 | ); 21 | -------------------------------------------------------------------------------- /src/utils/clipboard.utils.ts: -------------------------------------------------------------------------------- 1 | export const copyToClipboard = async (text: string) => { 2 | try { 3 | await navigator.clipboard.writeText(text); 4 | } catch { 5 | copyToClipboardOld(text); 6 | } 7 | }; 8 | 9 | // A hack for old android to get clipboard copy feature ready 10 | const copyToClipboardOld = (textToCopy: string) => { 11 | const textarea = document.createElement('textarea'); 12 | textarea.value = textToCopy; 13 | 14 | // Move the textarea outside the viewport to make it invisible 15 | textarea.style.position = 'absolute'; 16 | textarea.style.left = '-99999999px'; 17 | 18 | document.body.prepend(textarea); 19 | 20 | // highlight the content of the textarea element 21 | textarea.select(); 22 | 23 | try { 24 | document.execCommand('copy'); 25 | } catch (err) { 26 | console.log(err); 27 | } finally { 28 | textarea.remove(); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/settings-modal/theme/theme-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {Theme} from '../../../enums/theme.enum'; 4 | import {useDispatch} from '../../../store'; 5 | import {setThemeAction} from '../../../store/settings/settings-actions'; 6 | import {useThemeSelector} from '../../../store/settings/settings-selectors'; 7 | import {Button} from '../../button/button'; 8 | 9 | interface Props { 10 | value: Theme; 11 | title: string; 12 | } 13 | 14 | export const ThemeButton: FC = ({value, title}) => { 15 | const dispatch = useDispatch(); 16 | const theme = useThemeSelector(); 17 | 18 | const handleClick = () => dispatch(setThemeAction(value)); 19 | 20 | return ( 21 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/points-modal/social-tasks/task-item/task-item.tsx: -------------------------------------------------------------------------------- 1 | import {FC, PropsWithChildren} from 'react'; 2 | 3 | import styles from './task-item.module.css'; 4 | 5 | interface Props extends PropsWithChildren { 6 | imageSrc: string; 7 | title: string; 8 | description: string; 9 | onClick: () => void; 10 | } 11 | 12 | export const TaskItem: FC = ({ 13 | imageSrc, 14 | title, 15 | description, 16 | children, 17 | onClick 18 | }) => { 19 | return ( 20 |
21 | {title} 22 |
23 |

{title}

24 |

{description}

25 |
26 |
{children}
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/swap-form/custom-input/asset-selector/utils/sort-assets.utils.ts: -------------------------------------------------------------------------------- 1 | import {Asset} from 'rainbow-swap-sdk'; 2 | 3 | import {TON} from '../../../../../globals'; 4 | import {BalancesRecord} from '../../../../../types/balances-record.type'; 5 | 6 | export const sortAssets = ( 7 | assets: Asset[], 8 | balances: BalancesRecord 9 | ): Asset[] => { 10 | const ton = assets.find(asset => asset.address === TON); 11 | const otherAssets = assets.filter(asset => asset.address !== TON); 12 | 13 | const sortedAssets = otherAssets.sort((a, b) => { 14 | const aBalance = balances[a.address] || '0'; 15 | const bBalance = balances[b.address] || '0'; 16 | 17 | const aUsdValue = parseFloat(aBalance) * a.usdExchangeRate; 18 | 19 | const bUsdValue = parseFloat(bBalance) * b.usdExchangeRate; 20 | 21 | return bUsdValue - aUsdValue; 22 | }); 23 | 24 | return ton ? [ton, ...sortedAssets] : sortedAssets; 25 | }; 26 | -------------------------------------------------------------------------------- /src/store/security/security-epics.ts: -------------------------------------------------------------------------------- 1 | import {getAppStatus} from 'rainbow-swap-sdk'; 2 | import {combineEpics, Epic} from 'redux-observable'; 3 | import {from, of} from 'rxjs'; 4 | import {map, switchMap} from 'rxjs/operators'; 5 | import {Action} from 'ts-action'; 6 | import {ofType} from 'ts-action-operators'; 7 | 8 | import {loadAppStatusActions} from './security-actions'; 9 | import {sentryCatchError} from '../../utils/sentry.utils'; 10 | 11 | const loadAppStatusEpic: Epic = action$ => 12 | action$.pipe( 13 | ofType(loadAppStatusActions.submit), 14 | switchMap(() => 15 | from(getAppStatus()).pipe( 16 | map(response => loadAppStatusActions.success(response)), 17 | sentryCatchError(err => 18 | of(loadAppStatusActions.fail(err.message)) 19 | ) 20 | ) 21 | ) 22 | ); 23 | 24 | export const securityEpics = combineEpics(loadAppStatusEpic); 25 | -------------------------------------------------------------------------------- /src/components/history-modal/history-modal.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './history-modal.module.css'; 4 | import {PendingSwap} from './pending-swap/pending-swap'; 5 | import {SwapsHistory} from './swaps-history/swaps-history'; 6 | import {ModalProps} from '../../interfaces/modal-props.intefrace'; 7 | import {BottomSheet} from '../../shared/bottom-sheet/bottom-sheet'; 8 | import {FormButton} from '../../shared/form-button/form-button'; 9 | 10 | export const HistoryModal: FC = ({isOpen, onClose}) => ( 11 | 12 |
13 | 14 | 15 |
16 | 21 |
22 | ); 23 | -------------------------------------------------------------------------------- /src/components/points-modal/points-modal.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {InviteFriends} from './invite-friends/invite-friends'; 4 | import styles from './points-modal.module.css'; 5 | import {SocialTasks} from './social-tasks/social-tasks'; 6 | import {ModalProps} from '../../interfaces/modal-props.intefrace'; 7 | import {BottomSheet} from '../../shared/bottom-sheet/bottom-sheet'; 8 | import {FormButton} from '../../shared/form-button/form-button'; 9 | 10 | export const PointsModal: FC = ({isOpen, onClose}) => ( 11 | 12 |
13 | 14 | 15 |
16 | 21 |
22 | ); 23 | -------------------------------------------------------------------------------- /src/components/settings-modal/explorer/explorer-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {Explorer} from '../../../enums/explorer.enum'; 4 | import {useDispatch} from '../../../store'; 5 | import {setExplorerAction} from '../../../store/settings/settings-actions'; 6 | import {useExplorerSelector} from '../../../store/settings/settings-selectors'; 7 | import {Button} from '../../button/button'; 8 | 9 | interface Props { 10 | value: Explorer; 11 | title: string; 12 | } 13 | 14 | export const ExplorerButton: FC = ({value, title}) => { 15 | const dispatch = useDispatch(); 16 | const explorer = useExplorerSelector(); 17 | 18 | const handleClick = () => dispatch(setExplorerAction(value)); 19 | 20 | return ( 21 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Rainbow Swap - Aggregator on TON 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/interfaces/wallet-points-swate.interface.ts: -------------------------------------------------------------------------------- 1 | export interface WalletPointsState { 2 | refHash: string; 3 | refParent: string | undefined; 4 | bonusPoints: number; 5 | tapTapPoints: number; 6 | referralPoints: number; 7 | swapVolumePoints: number; 8 | rewardsState: { 9 | usersReferred: number; 10 | walletsReferred: number; 11 | refereesVolume: number; 12 | totalRewardsEarned: string; 13 | unclaimedRewards: string; 14 | }; 15 | tasksState: Record; 16 | } 17 | 18 | export const emptyWalletPoints: WalletPointsState = { 19 | refHash: '', 20 | refParent: undefined, 21 | bonusPoints: 0, 22 | tapTapPoints: 0, 23 | referralPoints: 0, 24 | swapVolumePoints: 0, 25 | rewardsState: { 26 | usersReferred: 0, 27 | walletsReferred: 0, 28 | refereesVolume: 0, 29 | totalRewardsEarned: '0', 30 | unclaimedRewards: '0' 31 | }, 32 | tasksState: {} 33 | }; 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: UI deployment 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | 13 | steps: 14 | - name: Clone repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Use Node 20 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | cache: 'yarn' 22 | 23 | - name: Install dependencies and code quality check 24 | uses: ./.github/workflows/code-quality-check 25 | 26 | - name: Create build 27 | run: yarn build 28 | env: 29 | VITE_BASE_URL: /rainbow-swap/ 30 | 31 | - name: Copy index.html to 404.html 32 | run: cp dist/index.html dist/404.html 33 | 34 | - name: Deploy to Github Pages 35 | uses: peaceiris/actions-gh-pages@v4 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: ./dist 39 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/handlers/owner_withdraw_jetton_op.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../constants.fc"; 3 | #include "../storage.fc"; 4 | 5 | () handle_owner_withdraw_jetton_op(int query_id, slice in_msg_body) impure inline { 6 | slice destination = in_msg_body~load_msg_addr(); 7 | int amount = in_msg_body~load_coins(); 8 | 9 | cell message_body = begin_cell() 10 | .store_uint(op::jetton_transfer, 32) 11 | .store_uint(query_id, 64) 12 | .store_coins(amount) 13 | .store_slice(storage::owner_address) 14 | .store_slice(storage::owner_address) 15 | .store_maybe_ref(null()) 16 | .store_coins(0) 17 | .store_maybe_ref(null()) 18 | .end_cell(); 19 | 20 | cell message = begin_cell() 21 | .store_uint(0x18, 6) 22 | .store_slice(destination) 23 | .store_coins(0) 24 | .store_uint(1, 107) 25 | .store_ref(message_body) 26 | .end_cell(); 27 | 28 | send_raw_message(message, 64); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/rewards-modal/referrer-stats/referrer-stats.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .title_container { 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | margin-bottom: var(--step); 11 | } 12 | 13 | .title { 14 | color: var(--text-color); 15 | font-size: 16px; 16 | } 17 | 18 | .description { 19 | color: var(--hint-color); 20 | font-size: 14px; 21 | } 22 | 23 | .skeleton { 24 | text-align: end; 25 | min-width: var(--5-step); 26 | } 27 | 28 | .stats_container { 29 | display: flex; 30 | flex-direction: column; 31 | gap: var(--step); 32 | margin-bottom: var(--2-step); 33 | } 34 | 35 | .row { 36 | display: flex; 37 | align-items: center; 38 | justify-content: space-between; 39 | font-size: 14px; 40 | color: var(--hint-color); 41 | } 42 | 43 | .copy_button { 44 | margin-bottom: var(--step); 45 | } 46 | 47 | .copy_icon { 48 | stroke: var(--text-color); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/points-modal/invite-friends/invite-friends.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--step); 5 | text-align: center; 6 | border-radius: var(--border-radius); 7 | margin: 0 var(--2-step) var(--step); 8 | } 9 | 10 | .animation_container { 11 | width: 20%; 12 | aspect-ratio: 1 / 1; 13 | margin: var(--2-step); 14 | align-self: center; 15 | overflow: hidden; 16 | } 17 | 18 | .animation_container canvas { 19 | transform: scale(2) !important; 20 | } 21 | 22 | .title { 23 | color: var(--text-color); 24 | font-size: 22px; 25 | } 26 | 27 | .description { 28 | font-size: 14px; 29 | color: var(--hint-color); 30 | } 31 | 32 | .counter_text { 33 | text-align: center; 34 | font-size: 14px; 35 | color: var(--hint-color); 36 | } 37 | 38 | .counter_value { 39 | color: var(--text-color); 40 | } 41 | 42 | .copy_button { 43 | margin-top: var(--2-step); 44 | } 45 | 46 | .copy_icon { 47 | stroke: var(--text-color); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/settings-modal/risk-tolerance/risk-tolerance-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import {RiskTolerance} from '../../../enums/risk-tolerance.enum'; 4 | import {useDispatch} from '../../../store'; 5 | import {setRiskToleranceAction} from '../../../store/settings/settings-actions'; 6 | import {useRiskToleranceSelector} from '../../../store/settings/settings-selectors'; 7 | import {Button} from '../../button/button'; 8 | 9 | interface Props { 10 | value: RiskTolerance; 11 | title: string; 12 | } 13 | 14 | export const RiskToleranceButton: FC = ({value, title}) => { 15 | const dispatch = useDispatch(); 16 | const riskTolerance = useRiskToleranceSelector(); 17 | 18 | const handleClick = () => dispatch(setRiskToleranceAction(value)); 19 | 20 | return ( 21 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/store/security/security-reducers.ts: -------------------------------------------------------------------------------- 1 | import {createReducer} from '@reduxjs/toolkit'; 2 | 3 | import {loadAppStatusActions} from './security-actions'; 4 | import {securityInitialState, SecurityState} from './security-state'; 5 | import {createEntity} from '../utils/create-entity'; 6 | 7 | export const securityReducers = createReducer( 8 | securityInitialState, 9 | builder => { 10 | builder.addCase(loadAppStatusActions.submit, state => ({ 11 | ...state, 12 | appStatus: createEntity(state.appStatus.data, true) 13 | })); 14 | builder.addCase(loadAppStatusActions.success, (state, {payload}) => ({ 15 | ...state, 16 | appStatus: createEntity(payload, false) 17 | })); 18 | builder.addCase( 19 | loadAppStatusActions.fail, 20 | (state, {payload: error}) => ({ 21 | ...state, 22 | appStatus: createEntity(state.appStatus.data, false, error) 23 | }) 24 | ); 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/handlers/owner_make_a_swap_op.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../storage.fc"; 3 | 4 | () handle_owner_make_a_swap_op(int query_id, slice in_msg_body) impure inline { 5 | slice destination = in_msg_body~load_msg_addr(); 6 | cell body = in_msg_body~load_ref(); 7 | slice jetton_sender_address_to_listen = in_msg_body~load_msg_addr(); 8 | cell jetton_receive_effect = in_msg_body~load_ref(); 9 | 10 | int key_len = 267 + 64; 11 | slice index = begin_cell() 12 | .store_slice(jetton_sender_address_to_listen) 13 | .store_uint(query_id, 64) 14 | .end_cell() 15 | .begin_parse(); 16 | 17 | storage::effects_dict~dict_set(key_len, index, jetton_receive_effect.begin_parse()); 18 | save_storage(); 19 | 20 | cell message = begin_cell() 21 | .store_uint(0x18, 6) 22 | .store_slice(destination) 23 | .store_coins(0) 24 | .store_uint(1, 107) 25 | .store_ref(body) 26 | .end_cell(); 27 | 28 | send_raw_message(message, 64); 29 | } 30 | -------------------------------------------------------------------------------- /src/tonconnect/useTonConnectUI.ts: -------------------------------------------------------------------------------- 1 | import {TonConnectUI, TonConnectUiOptions} from '@tonconnect/ui'; 2 | import {useCallback, useContext} from 'react'; 3 | 4 | import {TonConnectUIContext} from './TonConnectUIContext'; 5 | import {checkProvider} from './utils/errors'; 6 | import {isServerSide} from './utils/web'; 7 | 8 | /** 9 | * Use it to get access to the `TonConnectUI` instance and UI options updating function. 10 | */ 11 | export function useTonConnectUI(): [ 12 | TonConnectUI, 13 | (options: TonConnectUiOptions) => void 14 | ] { 15 | const tonConnectUI = useContext(TonConnectUIContext); 16 | const setOptions = useCallback( 17 | (options: TonConnectUiOptions) => { 18 | if (tonConnectUI) { 19 | tonConnectUI!.uiOptions = options; 20 | } 21 | }, 22 | [tonConnectUI] 23 | ); 24 | 25 | if (isServerSide()) { 26 | return [null as unknown as TonConnectUI, () => {}]; 27 | } 28 | 29 | checkProvider(tonConnectUI); 30 | return [tonConnectUI!, setOptions]; 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/use-disable-main-button.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | const DEFAULT_TEXT_COLOR = '#FFFFFF'; 4 | const DEFAULT_HINT_COLOR = '#96969d'; 5 | 6 | export const useDisableMainButton = (condition: boolean) => { 7 | useEffect(() => { 8 | if (condition) { 9 | const prevMainButtonColor = window.Telegram.WebApp.MainButton.color; 10 | const prevMainButtonTextColor = 11 | window.Telegram.WebApp.MainButton.textColor; 12 | window.Telegram.WebApp.MainButton.disable(); 13 | window.Telegram.WebApp.MainButton.color = DEFAULT_HINT_COLOR; 14 | window.Telegram.WebApp.MainButton.textColor = DEFAULT_TEXT_COLOR; 15 | 16 | return () => { 17 | window.Telegram.WebApp.MainButton.enable(); 18 | window.Telegram.WebApp.MainButton.color = prevMainButtonColor; 19 | window.Telegram.WebApp.MainButton.textColor = 20 | prevMainButtonTextColor; 21 | }; 22 | } 23 | }, [condition]); 24 | }; 25 | -------------------------------------------------------------------------------- /src/tonconnect/useTonWallet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConnectedWallet, 3 | Wallet, 4 | WalletInfoWithOpenMethod 5 | } from '@tonconnect/ui'; 6 | import {useEffect, useState} from 'react'; 7 | 8 | import {useTonConnectUI} from './useTonConnectUI'; 9 | 10 | /** 11 | * Use it to get user's current ton wallet. If wallet is not connected hook will return null. 12 | */ 13 | export function useTonWallet(): 14 | | Wallet 15 | | (Wallet & WalletInfoWithOpenMethod) 16 | | null { 17 | const [tonConnectUI] = useTonConnectUI(); 18 | const [wallet, setWallet] = useState< 19 | Wallet | (Wallet & WalletInfoWithOpenMethod) | null 20 | >(tonConnectUI?.wallet || null); 21 | 22 | useEffect(() => { 23 | if (tonConnectUI) { 24 | setWallet(tonConnectUI.wallet); 25 | return tonConnectUI.onStatusChange( 26 | (value: ConnectedWallet | null) => { 27 | setWallet(value); 28 | } 29 | ); 30 | } 31 | }, [tonConnectUI]); 32 | 33 | return wallet; 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/icons/InfoIcon/InfoIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const InfoIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /src/components/settings-modal/max-splits/max-splits.tsx: -------------------------------------------------------------------------------- 1 | import {MaxSplitsButton} from './max-splits-button'; 2 | import sharedStyles from '../settings-modal.module.css'; 3 | 4 | export const MaxSplitsSetting = () => ( 5 | <> 6 |
7 |

Parallel Transactions

8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 |

19 | Configure the maximum number of parallel transactions to create more 20 | profitable routing strategies. 21 |

22 | 23 | ); 24 | -------------------------------------------------------------------------------- /src/components/footer/footer.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: auto; 3 | } 4 | 5 | .inner_container { 6 | display: flex; 7 | flex-direction: column; 8 | gap: var(--half-step); 9 | padding: var(--step); 10 | color: var(--hint-color); 11 | } 12 | 13 | .container_row { 14 | display: flex; 15 | } 16 | 17 | .container_column { 18 | display: flex; 19 | flex-direction: column; 20 | gap: var(--half-step); 21 | } 22 | 23 | .container_icon { 24 | display: flex; 25 | align-items: center; 26 | gap: var(--half-step); 27 | padding: var(--step); 28 | font-size: 14px; 29 | } 30 | 31 | .container_a { 32 | cursor: pointer; 33 | transition: color 0.3s ease-in-out; 34 | } 35 | 36 | .container_a:-webkit-any-link { 37 | color: var(--hint-color); 38 | } 39 | 40 | .container_a:visited { 41 | color: var(--hint-color); 42 | } 43 | 44 | .container_a:hover { 45 | color: var(--text-color); 46 | } 47 | 48 | .support_button { 49 | margin-left: auto; 50 | } 51 | 52 | .copyright_text { 53 | font-size: 10px; 54 | margin: 0 auto; 55 | } 56 | -------------------------------------------------------------------------------- /src/contexts/swap-form/swap-form.context.ts: -------------------------------------------------------------------------------- 1 | import {emptyFn} from '@rnw-community/shared'; 2 | import {Asset} from 'rainbow-swap-sdk'; 3 | import {Dispatch, SetStateAction, createContext} from 'react'; 4 | 5 | import {DEFAULT_ASSETS_RECORD} from '../../data/assets-record'; 6 | import {TON, USDT} from '../../globals'; 7 | 8 | interface SwapFormContextValues { 9 | outputAssetAddress: string; 10 | setOutputAssetAddress: Dispatch>; 11 | inputAssetAddress: string; 12 | setInputAssetAddress: Dispatch>; 13 | inputAssetAmount: string; 14 | setInputAssetAmount: Dispatch>; 15 | inputAsset: Asset; 16 | outputAsset: Asset; 17 | } 18 | 19 | export const SwapFormContext = createContext({ 20 | outputAssetAddress: USDT, 21 | setOutputAssetAddress: emptyFn, 22 | inputAssetAddress: TON, 23 | setInputAssetAddress: emptyFn, 24 | inputAssetAmount: '', 25 | setInputAssetAmount: emptyFn, 26 | inputAsset: DEFAULT_ASSETS_RECORD[TON], 27 | outputAsset: DEFAULT_ASSETS_RECORD[USDT] 28 | }); 29 | -------------------------------------------------------------------------------- /src/assets/icons/DollarIcon/DollarIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const DollarIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 27 | 34 | 35 | ); 36 | -------------------------------------------------------------------------------- /src/hooks/use-explorer-links.hook.ts: -------------------------------------------------------------------------------- 1 | import {Explorer} from '../enums/explorer.enum'; 2 | import {useExplorerSelector} from '../store/settings/settings-selectors'; 3 | 4 | const WalletLinksRecord: Record = { 5 | [Explorer.TONScan]: 'https://tonscan.org/address', 6 | [Explorer.Tonviewer]: 'https://tonviewer.com' 7 | }; 8 | 9 | const TransactionLinksRecord: Record = { 10 | [Explorer.TONScan]: 'https://tonscan.org/tx', 11 | [Explorer.Tonviewer]: 'https://tonviewer.com/transaction' 12 | }; 13 | 14 | export const useExplorerLinks = () => { 15 | const explorer = useExplorerSelector(); 16 | 17 | const getWalletLink = (address: string) => { 18 | const explorerLink = WalletLinksRecord[explorer]; 19 | 20 | return explorerLink + '/' + address; 21 | }; 22 | 23 | const getTransactionLink = (bocHash?: string) => { 24 | const transactionLink = TransactionLinksRecord[explorer]; 25 | 26 | return transactionLink + '/' + bocHash; 27 | }; 28 | 29 | return { 30 | getWalletLink, 31 | getTransactionLink 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/assets/icons/CopyIcon/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const CopyIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 24 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/header/points-score/points-score.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 2px; 3 | margin-right: var(--step); 4 | border-radius: 20px; 5 | background: linear-gradient( 6 | 90deg, 7 | #ff0000 0%, 8 | #ff9a00 10%, 9 | #d0de21 20%, 10 | #4fdc4a 30%, 11 | #3fdad8 40%, 12 | #2fc9e2 50%, 13 | #1c7fee 60%, 14 | #5f15f2 70%, 15 | #ba0cf8 80%, 16 | #fb07d9 90%, 17 | #ff0000 100% 18 | ); 19 | background-size: 200% 200%; 20 | animation: anim 6s linear infinite; 21 | cursor: pointer; 22 | overflow: hidden; 23 | white-space: nowrap; 24 | text-overflow: ellipsis; 25 | } 26 | 27 | .container_body { 28 | padding: 5px 10px; 29 | border-radius: 20px; 30 | background-color: var(--bg-color); 31 | } 32 | 33 | .text { 34 | font-size: 15px; 35 | line-height: 22px; 36 | color: var(--text-color); 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | } 40 | 41 | @keyframes anim { 42 | 100% { 43 | background-position-x: -200%; 44 | } 45 | } -------------------------------------------------------------------------------- /src/store/root-state/root-state-epics.ts: -------------------------------------------------------------------------------- 1 | import {Epic, StateObservable, combineEpics} from 'redux-observable'; 2 | import {Observable} from 'rxjs'; 3 | 4 | import {sentryCatchError} from '../../utils/sentry.utils'; 5 | import {assetsEpics} from '../assets/assets-epics'; 6 | import {securityEpics} from '../security/security-epics'; 7 | import {swapRoutesEpics} from '../swap-routes/swap-routes-epics'; 8 | import {tradingCompetitionEpics} from '../trading-competition/trading-competition-epics'; 9 | import {walletEpics} from '../wallet/wallet-epics'; 10 | 11 | /* eslint-disable @typescript-eslint/no-explicit-any */ 12 | const rootStateEpics: Epic[] = [ 13 | assetsEpics, 14 | swapRoutesEpics, 15 | walletEpics, 16 | securityEpics, 17 | tradingCompetitionEpics 18 | ]; 19 | 20 | export const rootEpic = ( 21 | action$: Observable, 22 | store$: StateObservable, 23 | dependencies: any 24 | ) => 25 | combineEpics(...rootStateEpics)(action$, store$, dependencies).pipe( 26 | sentryCatchError((error, source) => { 27 | console.log(error); 28 | return source; 29 | }) 30 | ); 31 | -------------------------------------------------------------------------------- /src/utils/big-int.utils.ts: -------------------------------------------------------------------------------- 1 | export const toNano = (src: string, decimals: number) => { 2 | const precision = 10n ** BigInt(decimals); 3 | 4 | // Check sign 5 | let neg = false; 6 | while (src.startsWith('-')) { 7 | neg = !neg; 8 | src = src.slice(1); 9 | } 10 | // Split string 11 | if (src === '.') { 12 | throw Error('Invalid number'); 13 | } 14 | const parts = src.split('.'); 15 | if (parts.length > 2) { 16 | throw Error('Invalid number'); 17 | } 18 | // Prepare parts 19 | let whole = parts[0]; 20 | let frac = parts[1]; 21 | if (!whole) { 22 | whole = '0'; 23 | } 24 | if (!frac) { 25 | frac = ''; 26 | } 27 | if (frac.length > decimals) { 28 | throw Error('Invalid number'); 29 | } 30 | while (frac.length < decimals) { 31 | frac += '0'; 32 | } 33 | // Convert 34 | let r = BigInt(whole) * precision + BigInt(frac); 35 | if (neg) { 36 | r = -r; 37 | } 38 | return r; 39 | }; 40 | 41 | export const fromNano = (value: bigint, decimals: number) => 42 | String(Number(value) / 10 ** decimals); 43 | -------------------------------------------------------------------------------- /docs/TMA-development.md: -------------------------------------------------------------------------------- 1 | # How to run development application as TMA (inside Telegram Bot) 2 | 3 | ### 0. Enable https 4 | Install [vite-plugin-mkcert](https://www.npmjs.com/package/vite-plugin-mkcert). 5 | 6 | ### 1. Start the application 7 | `yarn start` 8 | 9 | ### 2. Install the root CA 10 | You will see `The list of generated files` in the Terminal. 11 | Go to the `.vite-plugin-mkcert` folder and install `rootCA.pem` to your mobile device. 12 | 13 | On iOS, you can either use AirDrop, email the CA to yourself, or serve it from an HTTP server. After opening it, you need to [install the profile in Settings > Profile Downloaded](https://github.com/FiloSottile/mkcert/issues/233#issuecomment-690110809) and then [enable full trust in it](https://support.apple.com/en-nz/HT204477). 14 | 15 | For Android, you will have to install the CA and then enable user roots in the development build of your app. See [this StackOverflow answer](https://stackoverflow.com/a/22040887/749014). 16 | 17 | ### 3. Open the TMA 18 | Send message to the [@rainbow_swap_bot](https://t.me/rainbow_swap_bot) 19 | `/dev your_app_network_url` 20 | 21 | `your_app_network_url` - could be found in Terminal 22 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/handlers/owner_jetton_transfer_notification.fc: -------------------------------------------------------------------------------- 1 | #include "../stdlib.fc"; 2 | #include "../storage.fc"; 3 | 4 | () handle_owner_jetton_transfer_notification(int query_id, slice in_msg_body) impure inline { 5 | slice forward_payload = in_msg_body~load_maybe_ref().begin_parse(); 6 | 7 | slice destination = forward_payload~load_msg_addr(); 8 | cell body = forward_payload~load_ref(); 9 | slice jetton_sender_address_to_listen = forward_payload~load_msg_addr(); 10 | cell jetton_receive_effect = forward_payload~load_ref(); 11 | 12 | int key_len = 267 + 64; 13 | slice index = begin_cell() 14 | .store_slice(jetton_sender_address_to_listen) 15 | .store_uint(query_id, 64) 16 | .end_cell() 17 | .begin_parse(); 18 | 19 | storage::effects_dict~dict_set(key_len, index, jetton_receive_effect.begin_parse()); 20 | save_storage(); 21 | 22 | cell message = begin_cell() 23 | .store_uint(0x18, 6) 24 | .store_slice(destination) 25 | .store_coins(0) 26 | .store_uint(1, 107) 27 | .store_ref(body) 28 | .end_cell(); 29 | 30 | send_raw_message(message, 64); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/route-info/route-info.tsx: -------------------------------------------------------------------------------- 1 | import {RouteDisplayData} from 'rainbow-swap-sdk'; 2 | import {FC, Fragment} from 'react'; 3 | 4 | import styles from './route-info.module.css'; 5 | import {SwapRouteStep} from './swap-route-step/swap-route-step'; 6 | 7 | interface Props { 8 | route: RouteDisplayData; 9 | } 10 | 11 | export const RouteInfo: FC = ({route}) => { 12 | const inputPercent = route.inputPercent.toFixed(2); 13 | 14 | return ( 15 |
16 |
17 |

{inputPercent + '%'}

18 |
19 | {route.routeSteps.map((routeStep, index) => ( 20 | 21 | {index === 0 ?
: null} 22 | 23 |
24 |
25 | ))} 26 |
27 |

{inputPercent + '%'}

28 |
29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/store/trading-competition/trading-competition-epics.ts: -------------------------------------------------------------------------------- 1 | import {combineEpics, Epic} from 'redux-observable'; 2 | import {from, map, of, switchMap} from 'rxjs'; 3 | import {Action} from 'ts-action'; 4 | import {ofType, toPayload} from 'ts-action-operators'; 5 | 6 | import {loadTradingCompetitionDataActions} from './trading-competition-actions'; 7 | import {getTradingCompetitionData} from '../../utils/api.utils'; 8 | import {sentryCatchError} from '../../utils/sentry.utils'; 9 | 10 | const loadTradingCompetitionDataEpic: Epic = action$ => 11 | action$.pipe( 12 | ofType(loadTradingCompetitionDataActions.submit), 13 | toPayload(), 14 | switchMap(payload => 15 | from(getTradingCompetitionData(payload)).pipe( 16 | map(response => 17 | loadTradingCompetitionDataActions.success(response) 18 | ), 19 | sentryCatchError(err => 20 | of(loadTradingCompetitionDataActions.fail(err.message)) 21 | ) 22 | ) 23 | ) 24 | ); 25 | 26 | export const tradingCompetitionEpics = combineEpics( 27 | loadTradingCompetitionDataEpic 28 | ); 29 | -------------------------------------------------------------------------------- /src/utils/balances-record.utils.ts: -------------------------------------------------------------------------------- 1 | import {AxiosResponse} from 'axios'; 2 | 3 | import {fromNano} from './big-int.utils'; 4 | import {TON, TON_DECIMALS} from '../globals'; 5 | import {BalancesArray} from '../interfaces/balance-object.interface'; 6 | import {TonBalanceArray} from '../interfaces/ton-balance-response.interface'; 7 | import {BalancesRecord} from '../types/balances-record.type'; 8 | 9 | export const getBalancesRecord = async ( 10 | jettonsResponse: AxiosResponse, 11 | accountResponse: AxiosResponse 12 | ) => { 13 | const {Address} = await import('@ton/core'); 14 | 15 | const balancesRecord: BalancesRecord = {}; 16 | 17 | jettonsResponse.data.balances.forEach(balanceObject => { 18 | const parsedAddress = Address.parse( 19 | balanceObject.jetton.address 20 | ).toString(); 21 | balancesRecord[parsedAddress] = fromNano( 22 | balanceObject.balance, 23 | balanceObject.jetton.decimals 24 | ); 25 | }); 26 | 27 | balancesRecord[TON] = fromNano( 28 | BigInt(accountResponse.data.balance), 29 | TON_DECIMALS 30 | ); 31 | 32 | return balancesRecord; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/asset.utils.ts: -------------------------------------------------------------------------------- 1 | import {isDefined} from '@rnw-community/shared'; 2 | import {Asset, AssetsRecord} from 'rainbow-swap-sdk'; 3 | 4 | export const EMPTY_ASSET: Asset = { 5 | address: 'unknown_token', 6 | slug: 'unknown_token', 7 | symbol: '???', 8 | name: 'Unknown token', 9 | image: '/icons/unknown_asset.png', 10 | decimals: 0, 11 | exchangeRate: '0', 12 | usdExchangeRate: 0, 13 | verification: 'none', 14 | totalSupply: '0', 15 | fdv: 0 16 | }; 17 | 18 | export const getAsset = (address: string, assetsRecord: AssetsRecord): Asset => 19 | assetsRecord[address] != null 20 | ? assetsRecord[address] 21 | : {...EMPTY_ASSET, address}; 22 | 23 | export const findAssetBySlug = ( 24 | slug: string | undefined, 25 | assetsRecord: AssetsRecord 26 | ) => { 27 | if (!isDefined(slug)) { 28 | return undefined; 29 | } 30 | 31 | const uppercaseSlug = slug.toUpperCase(); 32 | 33 | const assetBySlug = Object.values(assetsRecord).find( 34 | item => item.slug === uppercaseSlug 35 | ); 36 | 37 | if (isDefined(assetBySlug)) { 38 | return assetBySlug; 39 | } 40 | 41 | return assetsRecord[slug]; 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/lottie/lottie.tsx: -------------------------------------------------------------------------------- 1 | import {DotLottieReact, DotLottie} from '@lottiefiles/dotlottie-react'; 2 | import {FC, useEffect, useState} from 'react'; 3 | 4 | import {LottieProps} from './lottie.props'; 5 | import {BASE_URL} from '../../globals'; 6 | import {Skeleton} from '../skeleton/skeleton'; 7 | 8 | const Lottie: FC = ({src, speed, className}) => { 9 | const [dotLottie, setDotLottie] = useState(null); 10 | const [isLoading, setIsLoading] = useState(true); 11 | 12 | useEffect(() => { 13 | const onLoad = () => setIsLoading(false); 14 | 15 | dotLottie?.addEventListener('load', onLoad); 16 | 17 | return () => { 18 | dotLottie?.removeEventListener('load', onLoad); 19 | }; 20 | }, [dotLottie]); 21 | 22 | return ( 23 | 24 | 32 | 33 | ); 34 | }; 35 | 36 | export default Lottie; 37 | -------------------------------------------------------------------------------- /src/store/swap-routes/swap-routes-state.ts: -------------------------------------------------------------------------------- 1 | import {BestRouteResponse} from 'rainbow-swap-sdk'; 2 | 3 | import {EMPTY_ASSET} from '../../utils/asset.utils'; 4 | import {LoadableEntityState} from '../types'; 5 | import {createEntity} from '../utils/create-entity'; 6 | 7 | export interface SwapRoutesState { 8 | bestRouteResponse: LoadableEntityState; 9 | lastRequestId?: string; 10 | } 11 | 12 | export const emptyBestRouteResponse: BestRouteResponse = { 13 | displayData: { 14 | inputAsset: EMPTY_ASSET, 15 | inputAssetAmount: 0, 16 | inputAssetUsdAmount: 0, 17 | outputAsset: EMPTY_ASSET, 18 | outputAssetAmount: 0, 19 | outputAssetUsdAmount: 0, 20 | minOutputAssetAmount: 0, 21 | exchangeRate: 0, 22 | maxSlippage: 0, 23 | routingFeePercent: 0, 24 | priceImprovementPercent: 0, 25 | priceImpact: 0, 26 | roughGasFee: 0, 27 | roughGasUsdFee: 0, 28 | routes: [] 29 | }, 30 | swapMessages: [], 31 | messageCount: 0 32 | }; 33 | 34 | export const swapRouteInitialState: SwapRoutesState = { 35 | bestRouteResponse: createEntity(emptyBestRouteResponse), 36 | lastRequestId: undefined 37 | }; 38 | -------------------------------------------------------------------------------- /src/assets/icons/HistoryIcon/HistoryIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const HistoryIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/history-modal/swaps-history/history-data/history-data.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | gap: 4px; 6 | } 7 | 8 | .row { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | gap: var(--step); 13 | } 14 | 15 | .status { 16 | display: flex; 17 | align-items: center; 18 | gap: var(--half-step); 19 | font-size: 15px; 20 | } 21 | 22 | .timestamp { 23 | font-size: 12px; 24 | color: var(--hint-color); 25 | } 26 | 27 | .loader_spinner { 28 | border: 2px solid rgba(var(--attention-color), 0.3); 29 | border-top: 2px solid var(--button-color); 30 | border-radius: 50%; 31 | width: 12px; 32 | height: 12px; 33 | animation: spin 1s linear infinite; 34 | } 35 | 36 | @keyframes spin { 37 | 0% { transform: rotate(0deg); } 38 | 100% { transform: rotate(360deg); } 39 | } 40 | 41 | .link_button { 42 | height: unset; 43 | padding: var(--half-step); 44 | border-radius: var(--step); 45 | font-size: 12px; 46 | line-height: unset; 47 | } 48 | 49 | .link_icon { 50 | width: var(--2-step); 51 | height: var(--2-step); 52 | fill: var(--button-color); 53 | } -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/route-info/route-info.module.css: -------------------------------------------------------------------------------- 1 | .route { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-evenly; 5 | } 6 | 7 | .dots { 8 | display: flex; 9 | overflow: hidden; 10 | word-wrap: break-word; 11 | color: var(--text-color); 12 | flex: 1; 13 | margin-bottom: 7px; 14 | letter-spacing: 5px; 15 | } 16 | 17 | .dots:after { 18 | content: '..................................................'; 19 | color: var(--text-color); 20 | opacity: var(--text-opacity); 21 | } 22 | 23 | 24 | .route_info { 25 | display: flex; 26 | flex-direction: column; 27 | color: var(--text-color); 28 | flex-shrink: 0; 29 | } 30 | 31 | .route_info p { 32 | overflow: hidden; 33 | white-space: nowrap; 34 | text-overflow: ellipsis; 35 | font-size: 12px; 36 | } 37 | 38 | .route_info :first-child { 39 | opacity: var(--text-opacity); 40 | } 41 | 42 | .route_info :last-child { 43 | color: var(--button-color); 44 | } 45 | 46 | .route > :first-child { 47 | margin-right: var(--half-step); 48 | align-items: flex-start; 49 | } 50 | 51 | .route > :last-child { 52 | margin-left: var(--half-step); 53 | align-items: flex-end; 54 | } -------------------------------------------------------------------------------- /src/shared/form-button/form-button.tsx: -------------------------------------------------------------------------------- 1 | import {FC, useEffect} from 'react'; 2 | 3 | import {MainButton} from './main-button/main-button'; 4 | import {MainButtonProps} from './main-button/main-button.props'; 5 | import {Button} from '../../components/button/button'; 6 | import {useIsMainButtonAvailable} from '../../hooks/use-is-main-button-available.hook'; 7 | 8 | interface Props extends MainButtonProps { 9 | containerClassName?: string; 10 | } 11 | 12 | export const FormButton: FC = ({text, containerClassName, onClick}) => { 13 | const isMainButtonAvailable = useIsMainButtonAvailable(); 14 | 15 | useEffect(() => { 16 | if (isMainButtonAvailable) { 17 | window.Telegram.WebApp.MainButton.show(); 18 | } else { 19 | window.Telegram.WebApp.MainButton.hide(); 20 | } 21 | }, [isMainButtonAvailable]); 22 | 23 | if (isMainButtonAvailable) { 24 | return ; 25 | } 26 | 27 | return ( 28 |
29 | 32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/swap-form/swap-disabled/swap-disabled.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './swap-disabled.module.css'; 4 | import {LottieWithSuspense} from '../../lottie/lottie-with-suspense'; 5 | 6 | interface Props { 7 | message: string; 8 | } 9 | 10 | export const SwapDisabled: FC = () => ( 11 |
12 |

Service temporary disabled

13 |
14 |
15 |
16 |

We are updating the serves

17 |
18 |
19 |

Wait a little bit, please

20 |
21 |
22 | 23 |
24 | 29 |
30 |
31 |
32 | ); 33 | -------------------------------------------------------------------------------- /src/store/settings/settings-reducers.ts: -------------------------------------------------------------------------------- 1 | import {createReducer} from '@reduxjs/toolkit'; 2 | 3 | import { 4 | setExplorerAction, 5 | setMaxSlippageAction, 6 | setMaxSplitsAction, 7 | setRiskToleranceAction, 8 | setThemeAction 9 | } from './settings-actions'; 10 | import {settingsInitialState, SettingsState} from './settings-state'; 11 | 12 | export const settingsReducers = createReducer( 13 | settingsInitialState, 14 | builder => { 15 | builder.addCase(setMaxSlippageAction, (state, {payload}) => ({ 16 | ...state, 17 | maxSlippage: payload 18 | })); 19 | 20 | builder.addCase(setRiskToleranceAction, (state, {payload}) => ({ 21 | ...state, 22 | riskTolerance: payload 23 | })); 24 | 25 | builder.addCase(setMaxSplitsAction, (state, {payload}) => ({ 26 | ...state, 27 | maxSplits: payload 28 | })); 29 | 30 | builder.addCase(setThemeAction, (state, {payload}) => ({ 31 | ...state, 32 | theme: payload 33 | })); 34 | 35 | builder.addCase(setExplorerAction, (state, {payload}) => ({ 36 | ...state, 37 | explorer: payload 38 | })); 39 | } 40 | ); 41 | -------------------------------------------------------------------------------- /src/components/settings-modal/settings-modal.module.css: -------------------------------------------------------------------------------- 1 | .content_container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | padding: var(--2-step); 6 | overflow-y: scroll; 7 | } 8 | 9 | .settings_icon { 10 | margin-left: var(--step); 11 | color: var(--hint-color); 12 | transition: color 0.2s ease-in-out; 13 | cursor: pointer; 14 | } 15 | 16 | .settings_icon:hover { 17 | color: var(--text-color); 18 | } 19 | 20 | .title_container { 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | } 25 | 26 | .title { 27 | color: var(--text-color); 28 | font-size: 16px; 29 | } 30 | 31 | .selector_container { 32 | display: flex; 33 | justify-content: space-around; 34 | padding: var(--half-step); 35 | border: 1px solid var(--border-color); 36 | border-radius: var(--2-step); 37 | } 38 | 39 | .selector_divider { 40 | padding: 0 var(--half-step); 41 | } 42 | 43 | .description { 44 | color: var(--hint-color); 45 | font-size: 14px; 46 | margin-top: var(--step); 47 | } 48 | 49 | .footer_container { 50 | margin-top: auto; 51 | padding: var(--2-step); 52 | background-color: var(--header-bg-color); 53 | border-top: 1px solid var(--border-color); 54 | } -------------------------------------------------------------------------------- /src/hooks/use-theme-styles.hook.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | import {IS_TMA} from '../globals'; 4 | import {useThemeSelector} from '../store/settings/settings-selectors'; 5 | 6 | export const useThemeStyles = () => { 7 | const theme = useThemeSelector(); 8 | 9 | useEffect(() => { 10 | document.documentElement.setAttribute('data-theme', theme); 11 | 12 | const computedStyle = getComputedStyle(document.documentElement); 13 | const bgColor = computedStyle.getPropertyValue('--bg-color').trim(); 14 | 15 | let themeMetaTag = document.querySelector('meta[name="theme-color"]'); 16 | if (!themeMetaTag) { 17 | themeMetaTag = document.createElement('meta'); 18 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 19 | // @ts-expect-error 20 | themeMetaTag.name = 'theme-color'; 21 | document.head.appendChild(themeMetaTag); 22 | } 23 | themeMetaTag.setAttribute('content', bgColor); 24 | 25 | if (IS_TMA) { 26 | window.Telegram.WebApp.setHeaderColor(bgColor); 27 | window.Telegram.WebApp.setBackgroundColor(bgColor); 28 | window.Telegram.WebApp.setBottomBarColor(bgColor); 29 | } 30 | }, [theme]); 31 | }; 32 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rainbow Swap - Aggregator on TON", 3 | "short_name": "Rainbow", 4 | "theme_color": "#fff", 5 | "background_color": "#fff", 6 | "display": "browser", 7 | "orientation": "portrait", 8 | "scope": "/", 9 | "start_url": "/", 10 | "icons": [ 11 | { 12 | "src": "icons/icon-72x72.png", 13 | "sizes": "72x72", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "icons/icon-96x96.png", 18 | "sizes": "96x96", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "icons/icon-128x128.png", 23 | "sizes": "128x128", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "icons/icon-144x144.png", 28 | "sizes": "144x144", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "icons/icon-152x152.png", 33 | "sizes": "152x152", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "icons/icon-192x192.png", 38 | "sizes": "192x192", 39 | "type": "image/png" 40 | }, 41 | { 42 | "src": "icons/icon-384x384.png", 43 | "sizes": "384x384", 44 | "type": "image/png" 45 | }, 46 | { 47 | "src": "icons/icon-512x512.png", 48 | "sizes": "512x512", 49 | "type": "image/png" 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /src/components/swap-form/swap-disabled/swap-disabled.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | overflow: hidden; 4 | margin-bottom: var(--2-step); 5 | border-radius: var(--border-radius); 6 | display: flex; 7 | flex-direction: column; 8 | box-sizing: border-box; 9 | padding: var(--2-step); 10 | gap: var(--2-step); 11 | background: rgba(var(--danger-color), 0.1); 12 | } 13 | 14 | .inner_container { 15 | width: 100%; 16 | display: flex; 17 | align-items: flex-end; 18 | gap: var(--step); 19 | } 20 | 21 | .animationContainer { 22 | position: absolute; 23 | right: 4%; 24 | bottom: -28px; 25 | height: 90%; 26 | aspect-ratio: 1 / 1; 27 | transform: rotate(-5deg); 28 | } 29 | 30 | .duck_airdrop { 31 | width: 100%; 32 | scale: 2; 33 | } 34 | 35 | .title { 36 | font-size: 16px; 37 | color: rgb(var(--danger-color)); 38 | } 39 | 40 | .list_container { 41 | display: flex; 42 | flex-direction: column; 43 | justify-content: center; 44 | gap: var(--half-step); 45 | } 46 | 47 | .message_container { 48 | display: flex; 49 | align-items: center; 50 | white-space: nowrap; 51 | text-overflow: ellipsis; 52 | } 53 | 54 | .button { 55 | font-size: 14px; 56 | color: var(--text-color); 57 | } -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-icon.tsx: -------------------------------------------------------------------------------- 1 | import {isDefined} from '@rnw-community/shared'; 2 | import {FC, useState} from 'react'; 3 | 4 | import {Tooltip} from './tooltip'; 5 | import {TooltipContent} from './tooltip-content'; 6 | import styles from './tooltip-icon.module.css'; 7 | import {TooltipTrigger} from './tooltip-triget'; 8 | import {InfoIcon} from '../../assets/icons/InfoIcon/InfoIcon'; 9 | 10 | interface Props { 11 | text?: string; 12 | } 13 | 14 | export const TooltipIcon: FC = ({text}) => { 15 | const [open, setOpen] = useState(false); 16 | 17 | const onTouchEnd = () => setOpen(v => !v); 18 | const onMouseEnter = () => setOpen(true); 19 | const onMouseLeave = () => setOpen(false); 20 | 21 | if (!isDefined(text)) { 22 | return null; 23 | } 24 | 25 | return ( 26 | 27 | 33 | 34 | 35 | {text} 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/enums/task-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum TaskTypeEnum { 2 | Telegram = 'telegram', 3 | Twitter = 'twitter', 4 | TonApp = 'tonApp', 5 | Intract_500 = 'intract500', 6 | 7 | TorchFinance_Telegram = 'torchFinanceTelegram', 8 | TorchFinance_Twitter = 'torchFinanceTwitter', 9 | 10 | SnapX_Telegram = 'snapXTelegram', 11 | SnapX_Twitter = 'snapXTwitter', 12 | 13 | AppsCenter_Telegram = 'appsCenterTelegram', 14 | AppsCenter_Bot = 'appsCenterBot', 15 | 16 | JVault_Telegram = 'jvaultTelegram', 17 | JVault_Staking = 'jvaultStaking', 18 | 19 | TonHedge_Telegram = 'tonHedgeTelegram', 20 | TonHedge_Bot = 'tonHedgeBot', 21 | 22 | Parraton_Telegram = 'parratonTelegram', 23 | Parraton_Bot = 'parratonBot', 24 | 25 | GemsWall_Bot = 'gemsWallBot', 26 | GemsWall_Twitter = 'gemsWallTwitter', 27 | 28 | Ton2k_Telegram = 'ton2kTelegram', 29 | Ton2k_TelegramRu = 'ton2kTelegramRu', 30 | 31 | TonStation_Bot = 'tonStationBot', 32 | Clayton_Bot = 'claytonBot', 33 | notPixel_Bot = 'notPixelBot', 34 | Terminal_Bot = 'terminalBot', 35 | BlockLabs_Bot = 'blockLabsBot', 36 | 37 | DaoLama_Swap = 'daoLamaSwap', 38 | DaoLama_Borrow = 'daoLamaBorrow', 39 | 40 | KukuCoin_Telegram = 'kukuCoinTelegram', 41 | KukuCoin_Twitter = 'kukuCoinTwitter' 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/use-update-assets-list.hook.ts: -------------------------------------------------------------------------------- 1 | import {getQueryId} from 'rainbow-swap-sdk'; 2 | import {useEffect, useRef} from 'react'; 3 | import {useParams} from 'react-router-dom'; 4 | 5 | import {useDispatch} from '../store'; 6 | import {loadAssetsListActions} from '../store/assets/assets-actions'; 7 | import {useAssetsSearchValueSelector} from '../store/initialized/runtime-selectors'; 8 | import {useUserAssetsSelector} from '../store/wallet/wallet-selectors'; 9 | 10 | export const useUpdateAssetsList = () => { 11 | const dispatch = useDispatch(); 12 | 13 | const params = useParams(); 14 | const initialInputAssetSlug = useRef(params.inputAssetSlug); 15 | const initialOutputAssetSlug = useRef(params.outputAssetSlug); 16 | 17 | const userAssets = useUserAssetsSelector(); 18 | const searchValue = useAssetsSearchValueSelector(); 19 | 20 | useEffect(() => { 21 | dispatch( 22 | loadAssetsListActions.submit({ 23 | userAssets: [ 24 | initialInputAssetSlug.current ?? '', 25 | initialOutputAssetSlug.current ?? '', 26 | ...userAssets 27 | ], 28 | searchValue, 29 | requestId: getQueryId().toString() 30 | }) 31 | ); 32 | }, [dispatch, userAssets, searchValue]); 33 | }; 34 | -------------------------------------------------------------------------------- /src/assets/icons/ExternalLinkIcon/ExternalLinkIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const ExternalLinkIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/assets/icons/XIcon/XIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | interface Props { 4 | width?: string; 5 | height?: string; 6 | className?: string; 7 | onClick?: () => void; 8 | } 9 | 10 | export const XIcon: FC = ({ 11 | width = '16px', 12 | height = '16px', 13 | className = '', 14 | onClick 15 | }): JSX.Element => ( 16 | 24 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/hooks/use-analytics.hook.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {useEffect} from 'react'; 3 | import ReactGA from 'react-ga4'; 4 | 5 | import {INIT_DATA, isProd} from '../globals'; 6 | 7 | export const useTrackPageView = (name: string, isOpen = true) => 8 | useEffect(() => { 9 | if (isProd && isOpen) { 10 | ReactGA.send({ 11 | hitType: 'pageview', 12 | page: '/' + name.toLowerCase().replace(/\s+/g, '-'), 13 | title: name 14 | }); 15 | } 16 | }, [name, isOpen]); 17 | 18 | export const trackButtonClick = (name: string) => 19 | isProd && 20 | ReactGA.event({ 21 | category: 'General', 22 | action: 'Clicked ' + name, 23 | label: name 24 | }); 25 | 26 | const API = axios.create({ 27 | baseURL: 'https://api.rainbow.ag/analytics' 28 | }); 29 | 30 | export const trackSwapConfirmation = (params: { 31 | walletAddress: string; 32 | bocHash: string; 33 | usdValue: number; 34 | inputAssetAddress: string; 35 | inputAssetSymbol: string; 36 | inputAssetAmount: number; 37 | outputAssetAddress: string; 38 | outputAssetSymbol: string; 39 | outputAssetAmount: number; 40 | }) => 41 | API.post('/track-event', { 42 | initData: INIT_DATA, 43 | type: 'swap-confirmed', 44 | ...params 45 | }); 46 | -------------------------------------------------------------------------------- /src/shared/tooltip/tooltip-triget.tsx: -------------------------------------------------------------------------------- 1 | import {useMergeRefs} from '@floating-ui/react'; 2 | import {cloneElement, forwardRef, isValidElement} from 'react'; 3 | 4 | import {useTooltipContext} from './use-tooltip-context.hook'; 5 | 6 | /* eslint-disable @typescript-eslint/no-explicit-any */ 7 | export const TooltipTrigger = forwardRef< 8 | HTMLElement, 9 | React.HTMLProps & {asChild?: boolean} 10 | >(({children, asChild = false, ...props}, propRef) => { 11 | const context = useTooltipContext(); 12 | const childrenRef = (children as any).ref; 13 | const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]); 14 | 15 | // `asChild` allows the user to pass any element as the anchor 16 | if (asChild && isValidElement(children)) { 17 | return cloneElement( 18 | children, 19 | context.getReferenceProps({ 20 | ref, 21 | ...props, 22 | ...(children.props as any), 23 | 'data-state': context.open ? 'open' : 'closed' 24 | }) 25 | ); 26 | } 27 | 28 | return ( 29 | 36 | ); 37 | }); 38 | -------------------------------------------------------------------------------- /src/assets/icons/TwitterIcon/TwitterIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const TwitterIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/ReactToastify.css: -------------------------------------------------------------------------------- 1 | .Toastify__toast-container { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | gap: var(--step); 6 | margin-top: var(--app-safe-area-inset-top); 7 | padding: 0 var(--2-step); 8 | min-width: var(--app-min-width); 9 | max-width: var(--app-max-width); 10 | } 11 | 12 | .Toastify__toast { 13 | border-radius: var(--step); 14 | min-height: unset; 15 | color: var(--button-text-color); 16 | } 17 | 18 | .Toastify__toast--success { 19 | background-color: rgb(var(--green-color)); 20 | } 21 | 22 | .Toastify__toast--error { 23 | background-color: #FF3B30; 24 | } 25 | 26 | .Toastify__toast--info { 27 | background-color: var(--button-color); 28 | } 29 | 30 | .Toastify__toast--default { 31 | background-color: var(--button-color); 32 | } 33 | 34 | .Toastify__toast-body { 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | .Toastify__close-button { 40 | display: none; 41 | } 42 | 43 | .Toastify__spinner { 44 | border-color: var(--spinner-color-empty-area); 45 | border-right-color: var(--spinner-color); 46 | } 47 | 48 | .Toastify__progress-bar--wrp { 49 | height: 3px; 50 | } 51 | 52 | :root { 53 | --toastify-color-progress-success: white; 54 | --toastify-color-progress-error: white; 55 | --toastify-color-progress-info: white; 56 | --toastify-icon-color-info: white; 57 | } -------------------------------------------------------------------------------- /src/components/history-modal/swaps-history/swaps-history.tsx: -------------------------------------------------------------------------------- 1 | import {EmptyFn} from '@rnw-community/shared'; 2 | import {FC, Fragment} from 'react'; 3 | 4 | import {HistoryData} from './history-data/history-data'; 5 | import styles from './swaps-history.module.css'; 6 | import {useSwapHistoryDataSelector} from '../../../store/wallet/wallet-selectors'; 7 | import {Button} from '../../button/button'; 8 | import {Divider} from '../../points-modal/social-tasks/divider/divider'; 9 | 10 | interface Props { 11 | onSwap: EmptyFn; 12 | } 13 | 14 | export const SwapsHistory: FC = ({onSwap}) => { 15 | const state = useSwapHistoryDataSelector(); 16 | 17 | return ( 18 |
19 | {state.data.length === 0 && ( 20 | 23 | )} 24 | 25 | {state.data.map((historyData, index) => ( 26 | 27 | 31 | {index !== state.data.length - 1 && } 32 | 33 | ))} 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/assets/icons/toast-success-icon/toast-success-icon.tsx: -------------------------------------------------------------------------------- 1 | export const ToastSuccessIcon = () => ( 2 | 9 | 10 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/earn-fees/earn-fees.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | } 7 | 8 | .inner_container { 9 | width: 100%; 10 | display: flex; 11 | align-items: flex-end; 12 | gap: var(--step); 13 | } 14 | 15 | .animationContainer { 16 | position: absolute; 17 | right: 5%; 18 | bottom: -4%; 19 | width: 30%; 20 | aspect-ratio: 1 / 1; 21 | transform: rotate(0deg); 22 | } 23 | 24 | .duck_airdrop { 25 | width: 100%; 26 | aspect-ratio: 1 / 1; 27 | } 28 | 29 | .title { 30 | font-size: 16px; 31 | color: rgb(var(--green-color)); 32 | } 33 | 34 | .list_container { 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: center; 38 | gap: var(--half-step); 39 | z-index: 1; 40 | } 41 | 42 | .message_container { 43 | display: flex; 44 | align-items: center; 45 | white-space: nowrap; 46 | text-overflow: ellipsis; 47 | } 48 | 49 | .message { 50 | font-size: 14px; 51 | color: rgb(var(--green-color)); 52 | } 53 | 54 | .button { 55 | margin-left: var(--step); 56 | background-color: rgba(var(--green-color), 0.25); 57 | font-size: 14px; 58 | color: var(--text-color); 59 | border-radius: var(--4-step); 60 | padding: var(--half-step) var(--2-step); 61 | cursor: pointer; 62 | } -------------------------------------------------------------------------------- /src/store/trading-competition/trading-competition-reducers.ts: -------------------------------------------------------------------------------- 1 | import {createReducer} from '@reduxjs/toolkit'; 2 | 3 | import {loadTradingCompetitionDataActions} from './trading-competition-actions'; 4 | import { 5 | tradingCompetitionInitialState, 6 | TradingCompetitionState 7 | } from './trading-competition-state'; 8 | import {createEntity} from '../utils/create-entity'; 9 | 10 | export const tradingCompetitionReducers = 11 | createReducer( 12 | tradingCompetitionInitialState, 13 | builder => { 14 | builder.addCase( 15 | loadTradingCompetitionDataActions.submit, 16 | state => ({ 17 | ...state, 18 | data: createEntity(state.data.data, true) 19 | }) 20 | ); 21 | builder.addCase( 22 | loadTradingCompetitionDataActions.success, 23 | (state, {payload}) => ({ 24 | ...state, 25 | data: createEntity(payload, false) 26 | }) 27 | ); 28 | builder.addCase( 29 | loadTradingCompetitionDataActions.fail, 30 | (state, {payload: error}) => ({ 31 | ...state, 32 | data: createEntity(state.data.data, false, error) 33 | }) 34 | ); 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /src/components/settings-modal/risk-tolerance/risk-tolerance.tsx: -------------------------------------------------------------------------------- 1 | import {RiskToleranceButton} from './risk-tolerance-button'; 2 | import {RiskToleranceInfo} from './risk-tolerance-info'; 3 | import {RiskTolerance} from '../../../enums/risk-tolerance.enum'; 4 | import sharedStyles from '../settings-modal.module.css'; 5 | 6 | export const RiskToleranceSetting = () => ( 7 | <> 8 |
9 |

Risk Tolerance

10 |
11 | 12 |
13 | 17 |
18 | 22 |
23 |
24 | 25 |

26 | Higher risk may offer better prices but also carries a greater 27 | chance of transaction failure and receiving intermediate tokens. 28 |

29 | 30 | ); 31 | -------------------------------------------------------------------------------- /src/store/create-store.ts: -------------------------------------------------------------------------------- 1 | import {configureStore} from '@reduxjs/toolkit'; 2 | import {createEpicMiddleware} from 'redux-observable'; 3 | import { 4 | FLUSH, 5 | PAUSE, 6 | PERSIST, 7 | persistStore, 8 | PURGE, 9 | REGISTER, 10 | REHYDRATE 11 | } from 'redux-persist'; 12 | 13 | import {rootEpic} from './root-state/root-state-epics'; 14 | import {persistedReducer} from './root-state/root-state.reducers'; 15 | 16 | const epicMiddleware = createEpicMiddleware(); 17 | const middlewares = [epicMiddleware]; 18 | 19 | export const createStore = () => { 20 | const store = configureStore({ 21 | reducer: persistedReducer, 22 | middleware: getDefaultMiddleware => { 23 | return getDefaultMiddleware({ 24 | serializableCheck: { 25 | ignoredActions: [ 26 | FLUSH, 27 | REHYDRATE, 28 | PAUSE, 29 | PERSIST, 30 | PURGE, 31 | REGISTER 32 | ], 33 | warnAfter: 400 34 | }, 35 | immutableCheck: { 36 | warnAfter: 600 37 | } 38 | }).concat(middlewares); 39 | } 40 | }); 41 | 42 | const persistor = persistStore(store); 43 | 44 | epicMiddleware.run(rootEpic); 45 | 46 | return {store, persistor}; 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/settings-modal/risk-tolerance/risk-tolerance-info.tsx: -------------------------------------------------------------------------------- 1 | import styles from './risk-tolerance.module.css'; 2 | import {RiskTolerance} from '../../../enums/risk-tolerance.enum'; 3 | import {useRiskToleranceSelector} from '../../../store/settings/settings-selectors'; 4 | 5 | const TitleRecord: Record = { 6 | [RiskTolerance.Safe]: 'Safe', 7 | [RiskTolerance.Normal]: 'Normal', 8 | [RiskTolerance.Risky]: 'Risky' 9 | }; 10 | 11 | const DescriptionRecord: Record = { 12 | [RiskTolerance.Safe]: 'Lower risk, minimal route length.', 13 | [RiskTolerance.Normal]: 'Balanced risk and potential rewards.', 14 | [RiskTolerance.Risky]: 15 | 'Better prices but increased chance of transaction failure.' 16 | }; 17 | 18 | const ColorRecord: Record = { 19 | [RiskTolerance.Safe]: '#34CC4E', 20 | [RiskTolerance.Normal]: '#3E88F7', 21 | [RiskTolerance.Risky]: '#FF5B00' 22 | }; 23 | 24 | export const RiskToleranceInfo = () => { 25 | const riskTolerance = useRiskToleranceSelector(); 26 | 27 | const title = TitleRecord[riskTolerance]; 28 | const description = DescriptionRecord[riskTolerance]; 29 | const color = ColorRecord[riskTolerance]; 30 | 31 | return ( 32 |

33 | {title}: 34 | {description} 35 |

36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import './polyfill'; 2 | 3 | import ReactDOM from 'react-dom/client'; 4 | import ReactGA from 'react-ga4'; 5 | import {Provider} from 'react-redux'; 6 | import {PersistGate} from 'redux-persist/integration/react'; 7 | 8 | import {App} from './app/app'; 9 | import {GA_MEASUREMENT_ID, IS_TMA, isProd, UNSAFE_INIT_DATA} from './globals'; 10 | import {TELEGRAM_ANALYTICS_APP_NAME, TELEGRAM_ANALYTICS_TOKEN} from './secrets'; 11 | import {persistor, store} from './store'; 12 | import TonConnectUIProvider from './tonconnect/TonConnectUIProvider'; 13 | 14 | import 'react-toastify/dist/ReactToastify.css'; 15 | import './ReactToastify.css'; 16 | import './index.css'; 17 | 18 | if (isProd) { 19 | ReactGA.initialize(GA_MEASUREMENT_ID, { 20 | gaOptions: { 21 | userId: UNSAFE_INIT_DATA.userId 22 | } 23 | }); 24 | } 25 | 26 | if (IS_TMA) { 27 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 28 | // @ts-expect-error 29 | window.telegramAnalytics?.init({ 30 | token: TELEGRAM_ANALYTICS_TOKEN, 31 | appName: TELEGRAM_ANALYTICS_APP_NAME 32 | }); 33 | } 34 | 35 | ReactDOM.createRoot(document.getElementById('root')!).render( 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | -------------------------------------------------------------------------------- /src/assets/icons/toast-error-icon/toast-error-icon.tsx: -------------------------------------------------------------------------------- 1 | export const ToastErrorIcon = () => ( 2 | 9 | 16 | 23 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/farm-volume/farm-volume.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | } 7 | 8 | .header_container { 9 | width: 100%; 10 | display: flex; 11 | justify-content: space-between; 12 | } 13 | 14 | .inner_container { 15 | width: 100%; 16 | display: flex; 17 | align-items: flex-end; 18 | gap: var(--step); 19 | } 20 | 21 | .animationContainer { 22 | position: absolute; 23 | right: 0%; 24 | bottom: -5%; 25 | width: 30%; 26 | aspect-ratio: 1 / 1; 27 | transform: rotate(0deg); 28 | } 29 | 30 | .duck_airdrop { 31 | width: 100%; 32 | aspect-ratio: 1 / 1; 33 | } 34 | 35 | .title { 36 | font-size: 16px; 37 | color: var(--button-color); 38 | } 39 | 40 | .list_container { 41 | display: flex; 42 | flex-direction: column; 43 | justify-content: center; 44 | gap: var(--half-step); 45 | z-index: 1; 46 | } 47 | 48 | .message_container { 49 | display: flex; 50 | align-items: center; 51 | white-space: nowrap; 52 | text-overflow: ellipsis; 53 | } 54 | 55 | .message { 56 | font-size: 14px; 57 | color: var(--button-color); 58 | } 59 | 60 | .button { 61 | margin-left: var(--step); 62 | background-color: var(--attention-bg); 63 | font-size: 14px; 64 | color: var(--text-color); 65 | border-radius: var(--4-step); 66 | padding: var(--half-step) var(--2-step); 67 | cursor: pointer; 68 | } -------------------------------------------------------------------------------- /src/assets/icons/ChatIcon/ChatIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const ChatIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2020": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:react-hooks/recommended", 11 | "plugin:import/recommended", 12 | "plugin:import/typescript", 13 | "plugin:prettier/recommended" 14 | ], 15 | "ignorePatterns": [ 16 | "dist", 17 | ".eslintrc.cjs" 18 | ], 19 | "parser": "@typescript-eslint/parser", 20 | "plugins": [ 21 | "react-refresh", 22 | "prettier" 23 | ], 24 | "rules": { 25 | "react-refresh/only-export-components": [ 26 | "warn", 27 | { 28 | "allowConstantExport": true 29 | } 30 | ], 31 | "import/order": [ 32 | "error", 33 | { 34 | "groups": [ 35 | [ 36 | "external", 37 | "builtin" 38 | ], 39 | "internal", 40 | [ 41 | "parent", 42 | "sibling", 43 | "index" 44 | ] 45 | ], 46 | "alphabetize": { 47 | "order": "asc", 48 | "caseInsensitive": true 49 | }, 50 | "newlines-between": "always" 51 | } 52 | ], 53 | "import/extensions": [ 54 | "error", 55 | "never", 56 | { 57 | "ts": "never", 58 | "tsx": "never" 59 | } 60 | ], 61 | "import/no-unresolved": [ 62 | "error", 63 | { 64 | "ignore": ["^swiper/"] 65 | } 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/trading-competition/trading-competition.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | } 7 | 8 | .header_container { 9 | width: 100%; 10 | display: flex; 11 | justify-content: space-between; 12 | } 13 | 14 | .inner_container { 15 | width: 100%; 16 | display: flex; 17 | align-items: flex-end; 18 | gap: var(--step); 19 | } 20 | 21 | .animationContainer { 22 | position: absolute; 23 | right: 5%; 24 | bottom: -4%; 25 | width: 30%; 26 | aspect-ratio: 1 / 1; 27 | transform: rotate(0deg); 28 | } 29 | 30 | .duck_airdrop { 31 | width: 100%; 32 | aspect-ratio: 1 / 1; 33 | } 34 | 35 | .title { 36 | font-size: 16px; 37 | color: rgb(var(--purple-color)); 38 | } 39 | 40 | .list_container { 41 | display: flex; 42 | flex-direction: column; 43 | justify-content: center; 44 | gap: var(--half-step); 45 | z-index: 1; 46 | } 47 | 48 | .message_container { 49 | display: flex; 50 | align-items: center; 51 | white-space: nowrap; 52 | text-overflow: ellipsis; 53 | } 54 | 55 | .message { 56 | font-size: 14px; 57 | color: rgb(var(--purple-color)); 58 | } 59 | 60 | .button { 61 | margin-left: var(--step); 62 | background-color: rgba(var(--purple-color), 0.25); 63 | font-size: 14px; 64 | color: var(--text-color); 65 | border-radius: var(--4-step); 66 | padding: var(--half-step) var(--2-step); 67 | cursor: pointer; 68 | } -------------------------------------------------------------------------------- /src/components/swap-form/swap-details/swap-details-header/swap-details-header.tsx: -------------------------------------------------------------------------------- 1 | import {FC} from 'react'; 2 | 3 | import styles from './swap-details-header.module.css'; 4 | import {SwapDetailsHeaderProps} from './swap-details-header.props'; 5 | import {TooltipIcon} from '../../../../shared/tooltip/tooltip-icon'; 6 | import {useExchangeRate} from '../../hooks/use-exchange-rate.hook'; 7 | 8 | export const SwapDetailsHeader: FC = ({ 9 | inputAsset, 10 | outputAsset, 11 | routesLength 12 | }) => { 13 | const exchangeRate = useExchangeRate(inputAsset, outputAsset); 14 | 15 | if (routesLength === 0) { 16 | return

No routes available

; 17 | } 18 | 19 | if (inputAsset.address === outputAsset.address) { 20 | return ( 21 |
22 |

Arbitrage mode!

23 | 28 |
29 | ); 30 | } 31 | 32 | return ( 33 |

34 | {exchangeRate.text} 35 | {exchangeRate.usdText} 36 |

37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /contracts/rainbow-wallet/storage.fc: -------------------------------------------------------------------------------- 1 | #include "stdlib.fc"; 2 | #include "utils/dict.utils.fc"; 3 | 4 | ;; storage variables 5 | 6 | global int storage::shard_offset; 7 | global slice storage::owner_address; 8 | global cell storage::effects_dict; 9 | 10 | () load_storage() impure { 11 | slice cs::storage = get_data().begin_parse(); 12 | 13 | storage::shard_offset = cs::storage~load_uint(32); 14 | storage::owner_address = cs::storage~load_msg_addr(); 15 | storage::effects_dict = cs::storage~load_dict(); 16 | 17 | cs::storage.end_parse(); 18 | } 19 | 20 | () save_storage() impure { 21 | set_data( 22 | begin_cell() 23 | .store_uint(storage::shard_offset, 32) 24 | .store_slice(storage::owner_address) 25 | .store_dict(storage::effects_dict) 26 | .end_cell() 27 | ); 28 | } 29 | 30 | (int, slice, cell) get_storage() method_id { 31 | load_storage(); 32 | 33 | return ( 34 | storage::shard_offset, 35 | storage::owner_address, 36 | storage::effects_dict 37 | ); 38 | } 39 | 40 | (slice, int) get_effect_dict(slice jetton_sender_address_to_listen, int query_id) method_id { 41 | load_storage(); 42 | 43 | int key_len = 267 + 64; 44 | slice index = begin_cell() 45 | .store_slice(jetton_sender_address_to_listen) 46 | .store_uint(query_id, 64) 47 | .end_cell() 48 | .begin_parse(); 49 | 50 | var (value, success?) = storage::effects_dict.dict_get?(key_len, index); 51 | 52 | return ( 53 | value, 54 | success? 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/icons/TelegramIcon/TelegramIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const TelegramIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/assets/icons/DiamondIcon/DiamondIcon.tsx: -------------------------------------------------------------------------------- 1 | import {FC, type JSX} from 'react'; 2 | 3 | import {IconProps} from '../../../interfaces/icon-props.interface'; 4 | 5 | export const DiamondIcon: FC = ({ 6 | width = 20, 7 | height = 20, 8 | className, 9 | onClick 10 | }): JSX.Element => ( 11 | 20 | 26 | 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /src/components/swap-form/hooks/use-exchange-rate.hook.ts: -------------------------------------------------------------------------------- 1 | import {Asset} from 'rainbow-swap-sdk'; 2 | import {useMemo, useState} from 'react'; 3 | 4 | import {useSwapDisplayDataSelector} from '../../../store/swap-routes/swap-routes-selectors'; 5 | import {formatNumber} from '../../../utils/format-number.utils'; 6 | 7 | export const useExchangeRate = (inputAsset: Asset, outputAsset: Asset) => { 8 | const [isDirect, setIsDirect] = useState(true); 9 | const swapDisplayData = useSwapDisplayDataSelector(); 10 | 11 | return useMemo(() => { 12 | const firstSymbol = isDirect ? inputAsset.symbol : outputAsset.symbol; 13 | const secondSymbol = isDirect ? outputAsset.symbol : inputAsset.symbol; 14 | 15 | const adjustedExchangeRate = isDirect 16 | ? swapDisplayData.exchangeRate 17 | : swapDisplayData.exchangeRate === 0 18 | ? 0 19 | : 1 / swapDisplayData.exchangeRate; 20 | 21 | const text = `1 ${firstSymbol} = ${formatNumber(adjustedExchangeRate, 5)} ${secondSymbol}`; 22 | 23 | const usdValue = isDirect 24 | ? inputAsset.usdExchangeRate 25 | : outputAsset.usdExchangeRate; 26 | 27 | const usdText = `($${usdValue.toFixed(2)})`; 28 | 29 | const toggleRate = () => setIsDirect(state => !state); 30 | 31 | return {text, usdText, toggleRate}; 32 | }, [ 33 | inputAsset.symbol, 34 | inputAsset.usdExchangeRate, 35 | outputAsset.symbol, 36 | outputAsset.usdExchangeRate, 37 | isDirect, 38 | swapDisplayData.exchangeRate 39 | ]); 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/swap-form/custom-input/asset-selector/asset-list/asset-list-item/asset-list-item.module.css: -------------------------------------------------------------------------------- 1 | .search_hint_text { 2 | height: 60px; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | font-size: 14px; 7 | color: var(--hint-color); 8 | } 9 | 10 | .container { 11 | display: flex; 12 | gap: var(--2-step); 13 | cursor: pointer; 14 | align-items: center; 15 | padding: var(--step) var(--2-step); 16 | } 17 | 18 | .container:hover { 19 | background-color: var(--secondary-bg-color); 20 | } 21 | 22 | .img { 23 | width: var(--5-step); 24 | height: var(--5-step); 25 | align-self: center; 26 | border-radius: 50%; 27 | object-fit: cover; 28 | } 29 | 30 | .info_container { 31 | flex: 1; 32 | display: flex; 33 | flex-direction: column; 34 | gap: var(--half-step); 35 | overflow: hidden; 36 | } 37 | 38 | .info_container_row { 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | gap: var(--step); 43 | overflow: hidden; 44 | } 45 | 46 | .asset_symbol { 47 | font-size: 18px; 48 | color: var(--text-color); 49 | overflow: hidden; 50 | white-space: nowrap; 51 | text-overflow: ellipsis; 52 | } 53 | 54 | .asset_name { 55 | font-size: 14px; 56 | color: var(--hint-color); 57 | overflow: hidden; 58 | white-space: nowrap; 59 | text-overflow: ellipsis; 60 | } 61 | 62 | .coin_balance { 63 | font-size: 18px; 64 | color: var(--text-color); 65 | } 66 | 67 | .dollar_balance { 68 | font-size: 14px; 69 | color: var(--hint-color); 70 | } 71 | -------------------------------------------------------------------------------- /src/store/assets/assets-epics.ts: -------------------------------------------------------------------------------- 1 | import {getAssetsList} from 'rainbow-swap-sdk'; 2 | import {combineEpics, Epic} from 'redux-observable'; 3 | import {concatMap, debounceTime, from, of, switchMap} from 'rxjs'; 4 | import {Action} from 'ts-action'; 5 | import {ofType, toPayload} from 'ts-action-operators'; 6 | 7 | import {loadAssetsListActions} from './assets-actions'; 8 | import {DEBOUNCE_DUE_TIME} from '../../globals'; 9 | import {sentryCatchError} from '../../utils/sentry.utils'; 10 | import {assetsInitializedAction} from '../initialized/runtime-actions'; 11 | 12 | const loadAssetsListEpic: Epic = action$ => 13 | action$.pipe( 14 | ofType(loadAssetsListActions.submit), 15 | toPayload(), 16 | debounceTime(DEBOUNCE_DUE_TIME), 17 | switchMap(payload => 18 | from(getAssetsList(payload)).pipe( 19 | concatMap(assetsList => [ 20 | loadAssetsListActions.success({ 21 | list: assetsList, 22 | requestId: payload.requestId 23 | }), 24 | assetsInitializedAction() 25 | ]), 26 | sentryCatchError(err => 27 | of( 28 | loadAssetsListActions.fail({ 29 | error: err.message, 30 | requestId: payload.requestId 31 | }), 32 | assetsInitializedAction() 33 | ) 34 | ) 35 | ) 36 | ) 37 | ); 38 | 39 | export const assetsEpics = combineEpics(loadAssetsListEpic); 40 | -------------------------------------------------------------------------------- /src/components/swap-form/ads-swiper/ads-swiper.tsx: -------------------------------------------------------------------------------- 1 | import {Pagination, Autoplay} from 'swiper/modules'; 2 | import {Swiper, SwiperSlide} from 'swiper/react'; 3 | 4 | import './custom-swiper.css'; 5 | import 'swiper/css'; 6 | import 'swiper/css/pagination'; 7 | import 'swiper/css/autoplay'; 8 | 9 | import styles from './ads-swiper.module.css'; 10 | // import {EarnFees} from './earn-fees/earn-fees'; 11 | // import {FarmVolume} from './farm-volume/farm-volume'; 12 | import {TradingCompetition} from './trading-competition/trading-competition'; 13 | import {getClassName} from '../../../utils/style.utils'; 14 | 15 | const AdsSwiper = () => ( 16 | 26 | {/**/} 29 | {/* */} 30 | {/**/} 31 | 34 | 35 | 36 | {/**/} 42 | {/* */} 43 | {/**/} 44 | 45 | ); 46 | 47 | export default AdsSwiper; 48 | --------------------------------------------------------------------------------