├── .commitlintrc.json ├── .editorconfig ├── .eslintrc ├── .github ├── auto_assign.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── e2e.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .pre-commit-config.yaml ├── .prettierrc ├── .swcrc ├── .vercelignore ├── .vscode └── launch.json ├── LICENSE.header.md ├── LICENSE.md ├── README.md ├── api ├── README.md ├── storage │ ├── package.json │ ├── save-configurations.ts │ ├── tsconfig.json │ ├── validate-hash.ts │ └── yarn.lock └── swaps │ ├── compile-asa-to-asa.py │ ├── compile-asas-to-algo.py │ ├── compile-swap-proxy.py │ ├── get-swap-configurations.py │ └── requirements.txt ├── api_utils └── utils.py ├── e2e ├── common.ts ├── pages │ ├── dashboard.spec.ts │ ├── public-swaps.spec.ts │ └── swappers │ │ ├── asa-to-asa.spec.ts │ │ └── asas-to-algo.spec.ts └── tsconfig.json ├── jest.config.js ├── jest.setup.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── playwright.config.ts ├── postcss.config.js ├── public ├── algoworld_logo.svg ├── favicon.ico ├── icon-192x192.png ├── icon-256x256.png ├── icon-384x384.png ├── icon-512x512.png ├── manifest.json ├── myalgowallet_logo.svg ├── perawallet_logo.svg └── vercel.svg ├── renovate.json ├── src ├── __utils__ │ └── renderWithProviders.tsx ├── common │ ├── constants.ts │ └── public-swaps.spec.ts ├── components │ ├── Backdrops │ │ ├── Backdrop.test.tsx │ │ └── Backdrop.tsx │ ├── Cards │ │ ├── FromSwapCard.tsx │ │ ├── SwapTypePickerCard.tsx │ │ ├── ToSwapCard.tsx │ │ └── constants.ts │ ├── Dialogs │ │ ├── AboutDialog.test.tsx │ │ ├── AboutDialog.tsx │ │ ├── ConfirmDialog.test.tsx │ │ ├── ConfirmDialog.tsx │ │ ├── ConnectProviderDialog.tsx │ │ ├── FromAssetPickerDialog.tsx │ │ ├── InfoDialog.tsx │ │ ├── ManageSwapDialog.tsx │ │ ├── ShareSwapDialog.tsx │ │ ├── ToAlgoPickerDialog.tsx │ │ ├── ToAssetPickerDialog.tsx │ │ └── constants.ts │ ├── Footers │ │ ├── Footer.test.tsx │ │ ├── Footer.tsx │ │ ├── TelegramFooter.tsx │ │ └── constants.ts │ ├── Grids │ │ └── PublicSwapsGrid.tsx │ ├── Headers │ │ ├── NavBar.test.tsx │ │ ├── NavBar.tsx │ │ ├── PageHeader.test.tsx │ │ ├── PageHeader.tsx │ │ └── constants.ts │ ├── Layouts │ │ ├── Layout.test.tsx │ │ └── Layout.tsx │ ├── Lists │ │ └── AssetListView.tsx │ ├── Misc │ │ ├── ParticlesContainer.test.tsx │ │ └── ParticlesContainer.tsx │ ├── Select │ │ └── ValueSelect.tsx │ ├── Tables │ │ ├── AssetsTable.test.tsx │ │ ├── AssetsTable.tsx │ │ ├── MySwapsTable.test.tsx │ │ ├── MySwapsTable.tsx │ │ ├── PublicSwapAssetsTable.test.tsx │ │ ├── PublicSwapAssetsTable.tsx │ │ └── constants.ts │ └── TextFields │ │ ├── CryptoTextField.tsx │ │ └── constants.ts ├── models │ ├── AlloExplorerUrlType.ts │ ├── Asset.ts │ ├── Chain.ts │ ├── CoinType.ts │ ├── Gateway.ts │ ├── LoadingIndicator.ts │ ├── Swap.ts │ └── Transaction.ts ├── pages │ ├── _app.page.tsx │ ├── _document.page.tsx │ ├── dashboard.page.tsx │ ├── dashboard.test.tsx │ ├── index.page.tsx │ ├── index.test.tsx │ ├── public-swaps.page.tsx │ ├── public-swaps.test.tsx │ ├── swap │ │ └── [proxy] │ │ │ ├── [escrow].page.tsx │ │ │ └── [escrow].test.tsx │ └── swaps │ │ ├── asa-to-asa.page.tsx │ │ ├── asa-to-asa.test.tsx │ │ ├── asas-to-algo.page.tsx │ │ ├── asas-to-algo.test.tsx │ │ ├── my-swaps.page.tsx │ │ └── my-swaps.test.tsx ├── redux │ ├── helpers │ │ ├── types.ts │ │ ├── utilities.test.ts │ │ └── utilities.ts │ ├── hooks │ │ └── useLoadingIndicator.ts │ ├── slices │ │ └── applicationSlice.ts │ ├── store │ │ ├── hooks.ts │ │ └── index.ts │ └── theme │ │ └── darkTheme.ts ├── types │ └── globals.d.ts └── utils │ ├── api │ ├── accounts │ │ ├── accountExists.test.ts │ │ ├── accountExists.ts │ │ ├── getAccountInfo.test.ts │ │ ├── getAccountInfo.ts │ │ ├── getAssetsForAccount.test.ts │ │ ├── getAssetsForAccount.ts │ │ ├── getLogicSignature.ts │ │ ├── getNFDsForAddress.test.ts │ │ ├── getNFDsForAddress.ts │ │ ├── getPublicSwapCreators.test.ts │ │ ├── getPublicSwapCreators.ts │ │ ├── getSwapConfigurationsForAccount.ts │ │ ├── optAssetsForAccount.test.ts │ │ ├── optAssetsForAccount.ts │ │ └── recoverSwapConfigurationsForAccount.ts │ ├── algorand.ts │ ├── assets │ │ ├── assetsToRawString.test.ts │ │ ├── assetsToRawString.ts │ │ ├── getAssetBalance.test.ts │ │ ├── getAssetBalance.ts │ │ ├── getAssetsToOptIn.test.ts │ │ └── getAssetsToOptIn.ts │ ├── swaps │ │ ├── __utils__ │ │ │ └── testUtils.ts │ │ ├── createInitSwapTxns.test.ts │ │ ├── createInitSwapTxns.ts │ │ ├── createPerformSwapTxns.test.ts │ │ ├── createPerformSwapTxns.ts │ │ ├── createSaveSwapConfigTxns.test.ts │ │ ├── createSaveSwapConfigTxns.ts │ │ ├── createSwapDeactivateTxns.ts │ │ ├── createSwapDepositTxns.ts │ │ ├── getCompiledMultiSwap.ts │ │ ├── getCompiledProxy.ts │ │ ├── getCompiledSwap.ts │ │ ├── getSwapConfigurations.ts │ │ ├── getSwapUrl.test.ts │ │ ├── getSwapUrl.ts │ │ ├── loadSwapConfigurations.ts │ │ ├── saveSwapConfigurations.test.ts │ │ ├── saveSwapConfigurations.ts │ │ ├── swapExists.test.ts │ │ └── swapExists.ts │ └── transactions │ │ ├── createTransactionToSign.test.ts │ │ ├── createTransactionToSign.ts │ │ ├── getTransactionParams.ts │ │ ├── processTransactions.test.ts │ │ ├── processTransactions.ts │ │ ├── submitTransactions.test.ts │ │ ├── submitTransactions.ts │ │ ├── waitForTransaction.test.ts │ │ └── waitForTransaction.ts │ ├── createAlloExplorerUrl.test.ts │ ├── createAlloExplorerUrl.ts │ ├── createEmotionCache.test.ts │ ├── createEmotionCache.ts │ ├── filterAsync.test.ts │ ├── filterAsync.ts │ ├── formatAmount.test.ts │ ├── formatAmount.ts │ ├── ipfsToProxyUrl.test.ts │ ├── ipfsToProxyUrl.ts │ ├── isSafeVersion.ts │ └── paginate.ts ├── tsconfig.json ├── vercel.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": ["next", "next/core-web-vitals", "prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "camelcase": "off", 7 | "import/prefer-default-export": "off", 8 | "react/jsx-filename-extension": "off", 9 | "react/jsx-props-no-spreading": "off", 10 | "react/no-unused-prop-types": "off", 11 | "react/require-default-props": "off", 12 | "import/extensions": [ 13 | "error", 14 | "ignorePackages", 15 | { 16 | "ts": "never", 17 | "tsx": "never", 18 | "js": "never", 19 | "jsx": "never" 20 | } 21 | ], 22 | "quotes": "off", 23 | "jsx-a11y/anchor-is-valid": [ 24 | "error", 25 | { 26 | "components": ["Link"], 27 | "specialLink": ["hrefLeft", "hrefRight"], 28 | "aspects": ["invalidHref", "preferButton"] 29 | } 30 | ] 31 | }, 32 | "overrides": [ 33 | { 34 | "files": "**/*.+(ts|tsx)", 35 | "parser": "@typescript-eslint/parser", 36 | "plugins": ["@typescript-eslint/eslint-plugin"], 37 | "extends": ["plugin:@typescript-eslint/recommended", "prettier"], 38 | "rules": { 39 | "@typescript-eslint/explicit-function-return-type": "off", 40 | "@typescript-eslint/explicit-module-boundary-types": "off", 41 | "no-use-before-define": [0], 42 | "@typescript-eslint/no-use-before-define": [1], 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/no-var-requires": "off", 45 | "@typescript-eslint/quotes": [ 46 | 2, 47 | "backtick", 48 | { 49 | "avoidEscape": true 50 | } 51 | ] 52 | } 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # REF: https://probot.github.io/apps/auto-assign/ 2 | 3 | # Set to true to add reviewers to pull requests 4 | addReviewers: true 5 | 6 | # Set to true to add assignees to pull requests 7 | addAssignees: 'author' 8 | 9 | # A list of reviewers to be added to pull requests (GitHub user name) 10 | reviewers: 11 | - aorumbayev 12 | 13 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 14 | #skipKeywords: 15 | # - wip 16 | 17 | # A number of reviewers added to the pull request 18 | # Set 0 to add all the reviewers (default: 0) 19 | numberOfReviewers: 0 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | > _Please explain the changes you made here_ 4 | 5 | ### Checklist 6 | 7 | > _Please, make sure to comply with the checklist below before expecting review_ 8 | 9 | - [ ] Code compiles correctly 10 | - [ ] Created tests which fail without the change (if possible) 11 | - [ ] All tests passing 12 | - [ ] Extended the README / documentation, if necessary 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - 'main' 10 | 11 | jobs: 12 | run-ci: 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | name: Run Type Check & Linters 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up Node 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: 18 29 | 30 | - name: Install dependencies (with cache) 31 | uses: bahmutov/npm-install@v1 32 | 33 | - name: Run TypeScript type check 34 | run: yarn type-check 35 | 36 | - name: Run TypeScript lint 37 | run: yarn lint 38 | 39 | - uses: pre-commit/action@v3.0.0 40 | name: 'Run pre-commit checks' 41 | with: 42 | extra_args: --all-files --show-diff-on-failure 43 | 44 | - name: Check commits messages 45 | uses: wagoid/commitlint-github-action@v5 46 | 47 | - name: Run unit tests 48 | run: yarn test:ci 49 | 50 | - name: Upload coverage to Codecov 51 | uses: codecov/codecov-action@v3 52 | with: 53 | fail_ci_if_error: true 54 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: ['main'] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ['main'] 20 | schedule: 21 | - cron: '15 21 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ['javascript', 'python'] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v2 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | on: 3 | # pull_request: 4 | # branches: [main] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | e2e_test: 9 | env: 10 | PR_NUMBER: ${{ github.event.number }} 11 | E2E_TESTS_BASE_URL: 'https://pr${{ github.event.number }}-${{ github.run_number }}.vercel-action.millionalgos.com' 12 | E2E_TESTS_BASE_URL_ALIAS: 'pr${{ github.event.number }}-${{ github.run_number }}.vercel-action.millionalgos.com' 13 | 14 | timeout-minutes: 60 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Cancel Previous Runs 18 | uses: styfle/cancel-workflow-action@0.11.0 19 | with: 20 | access_token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - uses: actions/checkout@v3 23 | 24 | - name: Expose e2e base url 25 | run: | 26 | echo "${{ env.E2E_TESTS_BASE_URL }}" 27 | shell: bash 28 | 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: '16.x' 32 | 33 | - name: Install dependencies (with cache) 34 | uses: bahmutov/npm-install@v1 35 | 36 | - name: Install Playwright Browsers 37 | run: npx playwright install chromium 38 | 39 | - name: Deploy preview 40 | uses: amondnet/vercel-action@v25.1.1 41 | with: 42 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required 43 | github-token: ${{ secrets.GITHUB_TOKEN }} #Optional 44 | vercel-org-id: ${{ secrets.VERCEL_ORG_ID}} #Required 45 | vercel-project-id: ${{ secrets.VERCEL_STAGING_PROJECT_ID}} #Required 46 | working-directory: ./ 47 | alias-domains: ${{ env.E2E_TESTS_BASE_URL_ALIAS }} 48 | 49 | - name: Run Playwright tests 50 | env: 51 | E2E_TESTS_BOB_MNEMONIC: ${{ secrets.E2E_TESTS_BOB_MNEMONIC }} 52 | E2E_TESTS_ALICE_MNEMONIC: ${{ secrets.E2E_TESTS_ALICE_MNEMONIC }} 53 | run: | 54 | E2E_TESTS_BASE_URL=${{ env.E2E_TESTS_BASE_URL }} DEBUG=pw:api yarn run test:e2e:ci 55 | 56 | - uses: actions/upload-artifact@v3 57 | if: always() 58 | with: 59 | name: playwright-report 60 | path: playwright-report/ 61 | retention-days: 30 62 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Trigger release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | jobs: 9 | run-ci: 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | 13 | name: Run Type Check & Linters 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Deploy new release 23 | uses: amondnet/vercel-action@v25 24 | with: 25 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required 26 | github-token: ${{ secrets.GITHUB_TOKEN }} #Optional 27 | vercel-args: '--prod' #Optional 28 | vercel-org-id: ${{ secrets.VERCEL_ORG_ID}} #Required 29 | vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}} #Required 30 | working-directory: ./ 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # JetBrains IDE files 9 | .idea/ 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | tsconfig.tsbuildinfo 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | /api/storage/node_modules 42 | 43 | #python 44 | .venv 45 | 46 | # Sentry 47 | .sentryclirc 48 | /test-results/ 49 | /playwright-report/ 50 | /playwright/.cache/ 51 | 52 | # PWA 53 | workbox-*.js.map 54 | workbox-*.js 55 | *sw.js.map 56 | *sw.js 57 | 58 | .python-version 59 | 60 | .env.test 61 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.16 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-yaml 6 | stages: [commit] 7 | - id: end-of-file-fixer 8 | stages: [commit] 9 | - id: trailing-whitespace 10 | stages: [commit] 11 | 12 | - repo: https://github.com/psf/black 13 | rev: 23.1.0 14 | hooks: 15 | - id: black 16 | stages: [commit] 17 | 18 | - repo: https://github.com/pycqa/isort 19 | rev: 5.12.0 20 | hooks: 21 | - id: isort 22 | args: ['--profile', 'black'] 23 | stages: [commit] 24 | 25 | - repo: https://github.com/myint/autoflake 26 | rev: v2.0.1 27 | hooks: 28 | - id: autoflake 29 | args: 30 | - --in-place 31 | - --remove-unused-variables 32 | - --remove-all-unused-imports 33 | - --expand-star-imports 34 | - --ignore-init-module-imports 35 | stages: [commit] 36 | 37 | - repo: https://github.com/pre-commit/mirrors-prettier 38 | rev: v2.7.1 39 | hooks: 40 | - id: prettier 41 | stages: [commit] 42 | 43 | - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook 44 | rev: v9.4.0 45 | hooks: 46 | - id: commitlint 47 | stages: [commit-msg] 48 | - id: commitlint-travis 49 | stages: [manual] 50 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "targets": "Chrome >= 60, Safari >= 10.1, iOS >= 10.3, Firefox >= 54, Edge >= 15" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | # Ignore everything (folders and files) on root only 2 | /* 3 | *.test.ts 4 | *.test.tsx 5 | *.spec.ts 6 | *.spec.tsx 7 | 8 | !api 9 | !public 10 | !api_utils 11 | !vercel.json 12 | !package.json 13 | !yarn.lock 14 | !src 15 | !tsconfig.json 16 | !postcss.config.js 17 | !.prettierrc 18 | !next.config.js 19 | !.swcrc 20 | !vercel.json 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Next: Chrome", 11 | "sourceMaps": true, 12 | "url": "http://localhost:3000", 13 | "webRoot": "${workspaceFolder}" 14 | }, 15 | { 16 | "type": "node", 17 | "request": "attach", 18 | "name": "Next: Node", 19 | // "program": "${workspaceFolder}/node_modules/.bin/next", 20 | // "args": ["dev"], 21 | "sourceMaps": true, 22 | "autoAttachChildProcesses": true, 23 | "skipFiles": ["/**"] 24 | // "console": "integratedTerminal" 25 | } 26 | ], 27 | "compounds": [ 28 | { 29 | "name": "Next: Full", 30 | "configurations": ["Next: Node", "Next: Chrome"] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.header.md: -------------------------------------------------------------------------------- 1 | AlgoWorld Swapper 2 | Copyright (C) 2022 AlgoWorld 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

687474703a2f2f6936332e74696e797069632e636f6d2f333031336c67342e706e67

2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |

14 | 15 | ## 📃 About 16 | 17 | The following repository hosts the source codes for `AlgoWorld Swapper`. Free and open-source swapper that allows for trustless transfers of assets on Algorand blockchain and extensibility. 18 | 19 | > _**⚠️ NOTE: [algoworld-contracts](https://github.com/AlgoWorldNFT/algoworld-contracts) used by the swapper are formally audited by [Tenset Security](https://www.tenset.io/en/smart-contract-audits). Contracts are a basis for certain functionality on the [explorer](https://explorer.algoworld.io) platform and were developed in collaboration with Solution Architect from Algorand (credits @cusma).**_ 20 | 21 | > **⚠️ REPOSITORY IS NO LONGER ACTIVELY MAINTAINED, swapper is planned to be revamped in v4 (most likely to reside in a new repo) with a simplified functionality removing dependency on ipfs providers, existing implementation is to be kept available in archived mode. Audited stateless contracts are available for anyone to use or be used as a base for their own implementations.** 22 | 23 | --- 24 | 25 | ## 💁‍♂️ Docs 26 | 27 | Refer to [docs.algoworld.io](https://docs.algoworld.io/swapper) for detailed documentation. 28 | 29 | --- 30 | 31 | ## 📜 License 32 | 33 | This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for more information. 34 | 35 | --- 36 | 37 | ## ⭐️ Stargazers 38 | 39 | Special thanks to everyone who forked or starred the repository ❤️ 40 | 41 | [![Stargazers repo roster for @AlgoWorldNFT/algoworld-swapper](https://reporoster.com/stars/dark/AlgoWorldNFT/algoworld-swapper)](https://github.com/AlgoWorldNFT/algoworld-swapper/stargazers) 42 | 43 | [![Forkers repo roster for @AlgoWorldNFT/algoworld-swapper](https://reporoster.com/forks/dark/AlgoWorldNFT/algoworld-swapper)](https://github.com/AlgoWorldNFT/algoworld-swapper/network/members) 44 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 |

687474703a2f2f6936332e74696e797069632e636f6d2f333031336c67342e706e67

2 | 3 | ## Serverless Functions 4 | 5 | The following folder is hosting a set of serverless functions used by the swapper. Vercel allows using multiple runtime environments hence: 6 | 7 | 1. `swaps` - contains `python` functions that execute `pyteal` code and compile stateless contracts against required `algod` instance. 8 | 2. `storage` - a simple function that works with `POST` calls and stores `SwapperConfiguration` objects on ipfs. 9 | 10 | ### Further notes 11 | 12 | Dependencies for `storage` functions are not listed in subfolder because they are inherited from root `package.json` file. 13 | Dependencies for `swaps` are listed in `requirements.txt` and simply fetch latest `algoworld-contracts` version via release tag. 14 | -------------------------------------------------------------------------------- /api/storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algoworld-swapper-vercel-storage", 3 | "description": "vercel function for storing ipfs files ⚡️", 4 | "version": "0.1.0", 5 | "private": true, 6 | "author": "AlgoWorld ", 7 | "license": "GPL-3.0", 8 | "dependencies": { 9 | "typescript": "5.1.6", 10 | "web3.storage": "4.5.4" 11 | }, 12 | "devDependencies": { 13 | "@vercel/node": "2.15.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/storage/save-configurations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type { VercelRequest, VercelResponse } from '@vercel/node'; 20 | 21 | import { File, Web3Storage } from 'web3.storage'; 22 | 23 | function getAccessToken() { 24 | // If you're just testing, you can paste in a token 25 | // and uncomment the following line: 26 | return process.env.AW_WEB_STORAGE_API_KEY as string; 27 | 28 | // In a real app, it's better to read an access token from an 29 | // environement variable or other configuration that's kept outside of 30 | // your code base. For this to work, you need to set the 31 | // WEB3STORAGE_TOKEN environment variable before you run your code. 32 | // return process.env.WEB3STORAGE_TOKEN; 33 | } 34 | 35 | function makeStorageClient() { 36 | return new Web3Storage({ 37 | token: getAccessToken(), 38 | endpoint: new URL(`https://api.web3.storage`), 39 | }); 40 | } 41 | 42 | const makeFileObjects = (configuration: any) => { 43 | const buffer = Buffer.from(JSON.stringify(configuration)); 44 | 45 | const files = [new File([buffer], `aw_swaps.json`)]; 46 | return files; 47 | }; 48 | 49 | const saveConfigurations = async ( 50 | request: VercelRequest, 51 | response: VercelResponse, 52 | ) => { 53 | if (request.method === `POST`) { 54 | const content = request.body; 55 | const files = makeFileObjects(content); 56 | const cid = await makeStorageClient().put(files); 57 | response.status(200).send(cid); 58 | } else { 59 | response.status(500).send(`Request unsupported`); 60 | } 61 | }; 62 | 63 | export default saveConfigurations; 64 | -------------------------------------------------------------------------------- /api/storage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["ES2020"], 6 | "strict": true, 7 | "noImplicitAny": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "strictBindCallApply": true, 11 | "strictPropertyInitialization": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/storage/validate-hash.ts: -------------------------------------------------------------------------------- 1 | import { webcrypto } from 'crypto'; 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | 4 | type Data = { ok: boolean } | { error: string }; 5 | 6 | async function isHashValid(data: Record, botToken: string) { 7 | const encoder = new TextEncoder(); 8 | 9 | const checkString = Object.keys(data) 10 | .filter((key) => key !== `hash`) 11 | .map((key) => `${key}=${data[key]}`) 12 | .sort() 13 | .join(`\n`); 14 | 15 | const secretKey = await webcrypto.subtle.importKey( 16 | `raw`, 17 | encoder.encode(`WebAppData`), 18 | { name: `HMAC`, hash: `SHA-256` }, 19 | true, 20 | [`sign`], 21 | ); 22 | 23 | const secret = await webcrypto.subtle.sign( 24 | `HMAC`, 25 | secretKey, 26 | encoder.encode(botToken), 27 | ); 28 | 29 | const signatureKey = await webcrypto.subtle.importKey( 30 | `raw`, 31 | secret, 32 | { name: `HMAC`, hash: `SHA-256` }, 33 | true, 34 | [`sign`], 35 | ); 36 | 37 | const signature = await webcrypto.subtle.sign( 38 | `HMAC`, 39 | signatureKey, 40 | encoder.encode(checkString), 41 | ); 42 | 43 | const hex = Buffer.from(signature).toString(`hex`); 44 | 45 | return data.hash === hex; 46 | } 47 | 48 | export default async function handler( 49 | req: NextApiRequest, 50 | res: NextApiResponse, 51 | ) { 52 | if (req.method !== `POST`) { 53 | return res.status(405).json({ error: `Method not allowed` }); 54 | } 55 | 56 | if (!req.body.hash) { 57 | return res.status(400).json({ 58 | error: `Missing required field hash`, 59 | }); 60 | } 61 | 62 | if (!process.env.BOT_TOKEN) { 63 | return res.status(500).json({ error: `Internal server error` }); 64 | } 65 | 66 | const data = Object.fromEntries(new URLSearchParams(req.body.hash)); 67 | const isValid = await isHashValid(data, process.env.BOT_TOKEN); 68 | 69 | if (isValid) { 70 | return res.status(200).json({ ok: true }); 71 | } 72 | 73 | return res.status(403).json({ error: `Invalid hash` }); 74 | } 75 | -------------------------------------------------------------------------------- /api/swaps/compile-asa-to-asa.py: -------------------------------------------------------------------------------- 1 | # AlgoWorld Swapper 2 | # Copyright (C) 2022 AlgoWorld 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import json 18 | from dataclasses import dataclass 19 | from http.server import BaseHTTPRequestHandler 20 | from urllib import parse 21 | 22 | from algoworld_contracts import contracts 23 | 24 | from api_utils.utils import INCENTIVE_WALLET, get_algod, get_incentive_fee 25 | 26 | 27 | @dataclass 28 | class SwapQueryParams: 29 | creator_address: str 30 | offered_asa_id: int 31 | offered_asa_amount: int 32 | requested_asa_id: int 33 | requested_asa_amount: int 34 | chain_type: str 35 | version: str 36 | 37 | 38 | def compileSwap(input_params: SwapQueryParams): 39 | swapper = contracts.get_swapper_teal( 40 | input_params.creator_address, 41 | input_params.offered_asa_id, 42 | input_params.offered_asa_amount, 43 | input_params.requested_asa_id, 44 | input_params.requested_asa_amount, 45 | INCENTIVE_WALLET, 46 | get_incentive_fee(input_params.version), 47 | ) 48 | 49 | response = get_algod(input_params.chain_type).compile(swapper) 50 | return response 51 | 52 | 53 | class handler(BaseHTTPRequestHandler): 54 | def do_GET(self): 55 | s = self.path 56 | 57 | raw_params = dict(parse.parse_qsl(parse.urlsplit(s).query)) 58 | 59 | params = SwapQueryParams( 60 | **{ 61 | "creator_address": raw_params["creator_address"], 62 | "offered_asa_id": int(raw_params["offered_asa_id"]), 63 | "offered_asa_amount": int(raw_params["offered_asa_amount"]), 64 | "requested_asa_id": int(raw_params["requested_asa_id"]), 65 | "requested_asa_amount": int(raw_params["requested_asa_amount"]), 66 | "chain_type": raw_params["chain_type"], 67 | "version": raw_params["version"], 68 | } 69 | ) 70 | 71 | self.send_response(200) 72 | self.send_header("Content-type", "text/plain") 73 | self.end_headers() 74 | 75 | response = json.dumps(compileSwap(params)) 76 | self.wfile.write(response.encode()) 77 | return 78 | -------------------------------------------------------------------------------- /api/swaps/compile-asas-to-algo.py: -------------------------------------------------------------------------------- 1 | # AlgoWorld Swapper 2 | # Copyright (C) 2022 AlgoWorld 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import json 18 | from dataclasses import dataclass 19 | from http.server import BaseHTTPRequestHandler 20 | 21 | from algoworld_contracts import contracts 22 | 23 | from api_utils.utils import INCENTIVE_WALLET, get_algod, get_incentive_fee 24 | 25 | 26 | @dataclass 27 | class SwapConfig: 28 | creator_address: str 29 | offered_asa_amounts: dict 30 | requested_algo_amount: int 31 | max_fee: int 32 | optin_funding_amount: int 33 | chain_type: str 34 | version: str 35 | 36 | 37 | def compileMultiSwap(inputParams: SwapConfig): 38 | swapper = contracts.get_multi_swapper_teal( 39 | inputParams.creator_address, 40 | inputParams.offered_asa_amounts, 41 | inputParams.requested_algo_amount, 42 | inputParams.max_fee, 43 | inputParams.optin_funding_amount, 44 | INCENTIVE_WALLET, 45 | get_incentive_fee(inputParams.version), 46 | ) 47 | 48 | response = get_algod(inputParams.chain_type).compile(swapper) 49 | return response 50 | 51 | 52 | class handler(BaseHTTPRequestHandler): 53 | def do_POST(self): 54 | content_len = int(self.headers["Content-Length"]) 55 | post_body = json.loads(self.rfile.read(content_len)) 56 | 57 | params = SwapConfig( 58 | **{ 59 | "creator_address": post_body["creator_address"], 60 | "offered_asa_amounts": post_body["offered_asa_amounts"], 61 | "requested_algo_amount": post_body["requested_algo_amount"], 62 | "max_fee": post_body["max_fee"], 63 | "optin_funding_amount": post_body["optin_funding_amount"], 64 | "chain_type": post_body["chain_type"], 65 | "version": post_body["version"], 66 | } 67 | ) 68 | 69 | self.send_response(200) 70 | self.send_header("Content-type", "text/plain") 71 | self.end_headers() 72 | 73 | response = json.dumps(compileMultiSwap(params)) 74 | 75 | self.wfile.write(response.encode()) 76 | return 77 | -------------------------------------------------------------------------------- /api/swaps/compile-swap-proxy.py: -------------------------------------------------------------------------------- 1 | # AlgoWorld Swapper 2 | # Copyright (C) 2022 AlgoWorld 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import json 18 | from http.server import BaseHTTPRequestHandler 19 | from urllib import parse 20 | 21 | from algoworld_contracts import contracts 22 | 23 | from api_utils.utils import SwapProxyConfig, get_algod 24 | 25 | 26 | def compileSwapProxy(cfg: SwapProxyConfig): 27 | swapper = contracts.get_swapper_proxy_teal( 28 | swap_creator=cfg.swap_creator, version=cfg.version 29 | ) 30 | response = get_algod(cfg.chain_type).compile(swapper) 31 | return response 32 | 33 | 34 | class handler(BaseHTTPRequestHandler): 35 | def do_GET(self): 36 | s = self.path 37 | raw_params = dict(parse.parse_qsl(parse.urlsplit(s).query)) 38 | dic = SwapProxyConfig( 39 | **{ 40 | "swap_creator": raw_params["swap_creator"], 41 | "version": raw_params["version"], 42 | "chain_type": raw_params["chain_type"], 43 | } 44 | ) 45 | self.send_response(200) 46 | self.send_header("Content-type", "text/plain") 47 | self.end_headers() 48 | 49 | response = json.dumps(compileSwapProxy(dic)) 50 | 51 | self.wfile.write(response.encode()) 52 | return 53 | -------------------------------------------------------------------------------- /api/swaps/requirements.txt: -------------------------------------------------------------------------------- 1 | algoworld-contracts==1.4.0 2 | requests==2.28.1 3 | cffi==1.15.0 4 | -------------------------------------------------------------------------------- /api_utils/utils.py: -------------------------------------------------------------------------------- 1 | # AlgoWorld Swapper 2 | # Copyright (C) 2022 AlgoWorld 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import base64 18 | from dataclasses import dataclass 19 | 20 | from algosdk.future.transaction import LogicSig 21 | from algosdk.v2client.algod import AlgodClient 22 | from algosdk.v2client.indexer import IndexerClient 23 | 24 | INCENTIVE_WALLET = "RJVRGSPGSPOG7W3V7IMZZ2BAYCABW3YC5MWGKEOPAEEI5ZK5J2GSF6Y26A" 25 | INCENTIVE_FEE = 50_000 26 | INCENTIVE_FEES = { 27 | "0.0.1": 500_000, 28 | "0.0.2": 500_000, 29 | "0.0.3": 50_000, 30 | } 31 | 32 | 33 | ALGOD_URL = "https://mainnet-api.algonode.cloud" 34 | TESTNET_ALGOD_URL = "https://testnet-api.algonode.cloud" 35 | 36 | INDEXER_URL = "https://mainnet-idx.algonode.cloud" 37 | TESTNET_INDEXER_URL = "https://testnet-idx.algonode.cloud" 38 | 39 | 40 | def get_incentive_fee(version: str): 41 | try: 42 | return INCENTIVE_FEES[version] 43 | except Exception: 44 | return INCENTIVE_FEE 45 | 46 | 47 | def get_algod(chain_type: str): 48 | return AlgodClient( 49 | "", 50 | TESTNET_ALGOD_URL if chain_type == "testnet" else ALGOD_URL, 51 | headers={"User-Agent": "algosdk"}, 52 | ) 53 | 54 | 55 | def get_indexer(chain_type: str): 56 | return IndexerClient( 57 | "", 58 | TESTNET_INDEXER_URL if chain_type == "testnet" else INDEXER_URL, 59 | headers={"User-Agent": "algosdk"}, 60 | ) 61 | 62 | 63 | def get_logic_signature(compiled_response: dict): 64 | """Create and return logic signature for provided `teal_source`.""" 65 | return LogicSig(base64.b64decode(compiled_response["result"])) 66 | 67 | 68 | def get_account_txns(chain_type: str, account: str): 69 | indexer = get_indexer(chain_type) 70 | response = indexer.search_transactions_by_address(account) 71 | return response["transactions"] if response and "transactions" in response else [] 72 | 73 | 74 | def get_decoded_note_from_txn(txn: dict): 75 | return base64.b64decode(txn["note"]).decode() 76 | 77 | 78 | def account_exists(chain_type: str, account: str): 79 | try: 80 | indexer = get_indexer(chain_type) 81 | indexer.account_info(account) 82 | return True 83 | except Exception: 84 | return False 85 | 86 | 87 | @dataclass 88 | class SwapProxyConfig: 89 | swap_creator: str 90 | version: str 91 | chain_type: str 92 | -------------------------------------------------------------------------------- /e2e/common.ts: -------------------------------------------------------------------------------- 1 | export const AW_SWAPPER_BASE_URL = 2 | process.env.E2E_TESTS_BASE_URL ?? `http://localhost:3000`; 3 | 4 | export const BOB_ADDRESS = `YYO24YP3BGCOB2QYZFSU6OOEMLWH6BYP4D5VTMYJFX5TC4UNJV6QBHCYIQ`; 5 | export const ALICE_ADDRESS = `DOGEWXLAK4PEY2B47W45IZIDFOOYDGDE3KQDTHJUHN42KLQGCRAAZEYEKQ`; 6 | 7 | export function delay(time: number) { 8 | return new Promise(function (resolve) { 9 | setTimeout(resolve, time); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /e2e/pages/dashboard.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { AW_SWAPPER_BASE_URL } from '../common'; 3 | import pJson from '@/package.json'; 4 | 5 | test(`should navigate to the about page`, async ({ page }) => { 6 | await page.goto(AW_SWAPPER_BASE_URL); 7 | await expect(page.locator(`h1`)).toContainText(`🏠 Dashboard`); 8 | 9 | await page.locator(`#AWNavigationBar >> text=About`).click(); 10 | await expect( 11 | page.locator(`text=AlgoWorld Swapper v${pJson.version}`).first(), 12 | ).toBeVisible(); 13 | 14 | await page.locator(`button:has-text("Close")`).click(); 15 | await expect( 16 | page.locator(`text=AlgoWorld Swapper v${pJson.version}`).first(), 17 | ).toBeHidden(); 18 | }); 19 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "exclude": ["node_modules", "api"], 4 | "include": [ 5 | "next-env.d.ts", 6 | "desc.d.ts", 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*page.tsx", 10 | "src/pages/_error.js", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | 3 | const nextJest = require(`next/jest`); 4 | 5 | const createJestConfig = nextJest({ 6 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 7 | dir: `./`, 8 | }); 9 | 10 | // Add any custom config to be passed to Jest 11 | const customJestConfig = { 12 | preset: `ts-jest`, 13 | roots: [`/src/`], 14 | setupFilesAfterEnv: [`/jest.setup.js`], 15 | moduleNameMapper: { 16 | // Handle module aliases (this will be automatically configured for you soon) 17 | '^@/(.*)$': `/src/$1`, 18 | }, 19 | testPathIgnorePatterns: [ 20 | `/.vercel`, 21 | '/__fixtures__/', 22 | '/__utils__/', 23 | ], 24 | coveragePathIgnorePatterns: [ 25 | '/src/redux/theme/', 26 | '_app.page.tsx', 27 | '_document.page.tsx', 28 | 'constants.ts', 29 | ], 30 | testEnvironment: `jest-environment-jsdom`, 31 | collectCoverageFrom: [`**/*.{ts,tsx}`], 32 | coverageReporters: ['text', 'cobertura'], 33 | }; 34 | 35 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 36 | module.exports = createJestConfig(customJestConfig); 37 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | import '@testing-library/jest-dom/extend-expect'; 7 | import fetch from 'node-fetch'; 8 | 9 | var localStorageMock = (function () { 10 | var store = {}; 11 | 12 | return { 13 | getItem: function (key) { 14 | return store[key] || null; 15 | }, 16 | setItem: function (key, value) { 17 | store[key] = value.toString(); 18 | }, 19 | removeItem(key) { 20 | delete store[key]; 21 | }, 22 | clear: function () { 23 | store = {}; 24 | }, 25 | }; 26 | })(); 27 | 28 | if (typeof window !== 'undefined') { 29 | Object.defineProperty(window, 'localStorage', { 30 | value: localStorageMock, 31 | }); 32 | } 33 | 34 | global.fetch = fetch; 35 | global.dummyContract = 36 | 'BiAH6AcBBJmH0wcDAGQmASB6zQMKOJBtxFB4ILNYiiRQngRy5XJoFC96k36qJavhHDIEgQISMwAQIxIQMwEQJBIQQAENMgQhBBIzABAkEhAzARAkEhAzAhAjEhBAAG8yBCEEEjMAECQSEDMBECMSEDMCECMSEEAAAQAzAAEiDjMAIDIDEhAzABMyAxIQMwEBIg4zASAyAxIQEDMAESUSEDMAFCgSEDMAFSgSEDMBBygSEDMBCSgSEDMCACgSEDMCBygSEDMCCCEFEhBCAN4zAAEiDjMAIDIDEhAzABMyAxIQMwAVMgMSEDMBEzIDEhAzABElEhAzABIhBhIQMwERJRIQMwESIQYSEDMAFDMBABIQMwEUKBIQMwIHgCCKaxNJ5pPcb9t1+hmc6CDAgBtvAussZRHPAQiO5V1OjRIQMwIAMwEAEhAzAgiBoMIeEhBCAFozAAEiDjMAIDIDEhAzAAkyAxIQMwEBIg4zASAyAxIQMwETMgMSEDMBFTIDEhAQMwAAKBIQMwAHMwEAEhAzAAiB0OgMDxAzARElEhAzAQAzARQSEDMBEiEFEhBD'; 37 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // This file sets a custom webpack configuration to use your Next.js app 2 | // with Sentry. 3 | // https://nextjs.org/docs/api-reference/next.config.js/introduction 4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 5 | 6 | const runtimeCaching = require('next-pwa/cache'); 7 | const withPWA = require('next-pwa')({ 8 | dest: 'public', 9 | runtimeCaching, 10 | }); 11 | 12 | const plugins = []; 13 | 14 | plugins.push(withPWA); 15 | 16 | const nextConfig = { 17 | reactStrictMode: true, 18 | pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'], 19 | images: { 20 | domains: [ 21 | 'ipfs.algonode.xyz', 22 | 'cf-ipfs.com', 23 | 'dweb.link', 24 | 'cloudflare-ipfs.com', 25 | 'ipfs-gateway.cloud', 26 | '*.nf.domains', 27 | 'images.nf.domains', 28 | 29 | 'images.unsplash.com', 30 | 'vitals.vercel-insights.com', 31 | 'google-analytics.com', 32 | '*.google-analytics.com', 33 | 'gist.githubusercontent.com', 34 | ], 35 | }, 36 | }; 37 | 38 | module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig); 39 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { PlaywrightTestConfig, devices } from '@playwright/test'; 2 | 3 | import dotenv from 'dotenv'; 4 | 5 | dotenv.config(); 6 | 7 | const BASE_URL = process.env.E2E_TESTS_BASE_URL ?? `http://localhost:3000`; 8 | const VERCEL_TOKEN = process.env.VERCEL_TOKEN ?? null; 9 | const config: PlaywrightTestConfig = { 10 | projects: [ 11 | { 12 | name: `chromium`, 13 | use: { ...devices[`Desktop Chrome`] }, 14 | }, 15 | ], 16 | timeout: 5 * 60 * 1000, 17 | testDir: `e2e`, 18 | reporter: `html`, 19 | use: { 20 | baseURL: BASE_URL, 21 | }, 22 | }; 23 | 24 | if (BASE_URL.includes(`localhost:3000`)) { 25 | config[`webServer`] = { 26 | command: 27 | VERCEL_TOKEN === null 28 | ? `vercel dev` 29 | : `vercel dev --token ${VERCEL_TOKEN} --yes`, 30 | url: `http://localhost:3000`, 31 | timeout: 120 * 1000, 32 | reuseExistingServer: !process.env.CI, 33 | }; 34 | } 35 | 36 | export default config; 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoWorldNFT/algoworld-swapper/c119473fb1077606ab64542e7118903167a871c9/public/favicon.ico -------------------------------------------------------------------------------- /public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoWorldNFT/algoworld-swapper/c119473fb1077606ab64542e7118903167a871c9/public/icon-192x192.png -------------------------------------------------------------------------------- /public/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoWorldNFT/algoworld-swapper/c119473fb1077606ab64542e7118903167a871c9/public/icon-256x256.png -------------------------------------------------------------------------------- /public/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoWorldNFT/algoworld-swapper/c119473fb1077606ab64542e7118903167a871c9/public/icon-384x384.png -------------------------------------------------------------------------------- /public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoWorldNFT/algoworld-swapper/c119473fb1077606ab64542e7118903167a871c9/public/icon-512x512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#44475a", 3 | "background_color": "#282a36", 4 | "display": "fullscreen", 5 | "scope": "/", 6 | "start_url": "/", 7 | "name": "AlgoWorld Swapper", 8 | "short_name": "AW Swapper", 9 | "description": "⚡️ Free and trustless ASA swapper, powered by Algorand", 10 | "icons": [ 11 | { 12 | "src": "/icon-192x192.png", 13 | "sizes": "192x192", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/icon-256x256.png", 18 | "sizes": "256x256", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "/icon-384x384.png", 23 | "sizes": "384x384", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "/icon-512x512.png", 28 | "sizes": "512x512", 29 | "type": "image/png" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /public/perawallet_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "semanticCommits": true, 4 | "ignoreDeps": [], 5 | "schedule": "before 3am on the first day of the month", 6 | "assignees": ["aorumbayev"], 7 | "baseBranches": ["main"], 8 | "separateMajorMinor": true, 9 | "rebaseStalePrs": true, 10 | "lockFileMaintenance": { 11 | "enabled": true, 12 | "extends": "schedule:monthly", 13 | "automerge": true 14 | }, 15 | "packageRules": [ 16 | { 17 | "matchPackagePatterns": ["*"], 18 | "matchUpdateTypes": ["minor", "patch"], 19 | "groupName": "all non-major dependencies", 20 | "groupSlug": "all-minor-patch", 21 | "automerge": true 22 | } 23 | ], 24 | "docker": { 25 | "enabled": true 26 | }, 27 | "python": { 28 | "enabled": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/__utils__/renderWithProviders.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import React, { PropsWithChildren } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { setupStore } from '@/redux/store'; 5 | 6 | export default function renderWithProviders( 7 | ui: React.ReactElement, 8 | { 9 | preloadedState = {}, 10 | // Automatically create a store instance if no store was passed in 11 | store = setupStore(preloadedState), 12 | ...renderOptions 13 | } = {}, 14 | ) { 15 | // eslint-disable-next-line @typescript-eslint/ban-types 16 | function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element { 17 | return {children}; 18 | } 19 | 20 | // Return an object with the store and all of RTL's query functions 21 | return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Backdrops/Backdrop.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import LoadingBackdrop from './Backdrop'; 4 | 5 | describe(`Backdrop`, () => { 6 | it(`should render the loading message and progress bar when isLoading is true`, () => { 7 | const { getByText, queryByText } = render( 8 | , 9 | ); 10 | expect(getByText(`Loading...`)).toBeInTheDocument(); 11 | expect(queryByText(`Custom message`)).not.toBeInTheDocument(); 12 | expect( 13 | queryByText(`Custom message`, { exact: false }), 14 | ).not.toBeInTheDocument(); 15 | }); 16 | 17 | it(`should not render the loading message or progress bar when isLoading is false`, () => { 18 | const { queryByText } = render( 19 | , 20 | ); 21 | expect(queryByText(`Loading...`)).not.toBeInTheDocument(); 22 | expect(queryByText(/linear/i)).not.toBeInTheDocument(); 23 | }); 24 | 25 | it(`should render a custom message if provided`, () => { 26 | const { getByText } = render( 27 | , 28 | ); 29 | expect(getByText(`Custom message`)).toBeInTheDocument(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/Backdrops/Backdrop.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react'; 20 | import Backdrop from '@mui/material/Backdrop'; 21 | import LinearProgress from '@mui/material/LinearProgress'; 22 | import { Stack, Typography } from '@mui/material'; 23 | 24 | type Props = { 25 | isLoading: boolean; 26 | message?: string; 27 | }; 28 | 29 | const LoadingBackdrop = ({ 30 | isLoading = false, 31 | message = `Loading...`, 32 | }: Props) => { 33 | return ( 34 |
35 | theme.zIndex.drawer + 1, 39 | opacity: 0.1, 40 | }} 41 | open={isLoading} 42 | > 43 | 44 | {message && ( 45 | 46 | {message} 47 | 48 | )} 49 | 50 | 51 | 52 |
53 | ); 54 | }; 55 | 56 | export default LoadingBackdrop; 57 | -------------------------------------------------------------------------------- /src/components/Cards/SwapTypePickerCard.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react'; 20 | import Card from '@mui/material/Card'; 21 | import CardContent from '@mui/material/CardContent'; 22 | import Typography from '@mui/material/Typography'; 23 | import { CardActionArea, Grid } from '@mui/material'; 24 | import Link from 'next/link'; 25 | import { SWAP_TYPE_PICKER_CARD_ID } from './constants'; 26 | 27 | type Props = { 28 | title: string; 29 | description: string; 30 | emojiContent: string; 31 | swapPageUrl: string; 32 | disabled?: boolean; 33 | }; 34 | 35 | const SwapTypePickerCard = ({ 36 | title, 37 | description, 38 | emojiContent, 39 | swapPageUrl, 40 | disabled = false, 41 | }: Props) => { 42 | return ( 43 | 50 | 51 | 52 | 53 | 61 | 62 | 67 | {emojiContent} 68 | 69 | 70 | 71 | 72 | 79 | {title} 80 | 81 | 82 | {description} 83 | 84 | 85 | 86 | 87 | 88 | ); 89 | }; 90 | 91 | export default SwapTypePickerCard; 92 | -------------------------------------------------------------------------------- /src/components/Cards/constants.ts: -------------------------------------------------------------------------------- 1 | export const SWAP_TYPE_PICKER_CARD_ID = (name: string) => { 2 | return `AWSwapTypePickerCard_${name}`; 3 | }; 4 | 5 | export const FROM_SWAP_OFFERING_ASSET_BTN_ID = `AWFromSwapOfferingAssetButton`; 6 | 7 | export const TO_SWAP_REQUESTING_BTN_ID = `AWToSwapRequestingAssetButton`; 8 | -------------------------------------------------------------------------------- /src/components/Dialogs/AboutDialog.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, fireEvent } from '@testing-library/react'; 3 | import AboutDialog from './AboutDialog'; 4 | 5 | jest.mock(`react-markdown`, () => jest.fn()); 6 | 7 | describe(`AboutDialog`, () => { 8 | it(`renders the correct content and handles the close button correctly`, () => { 9 | const changeStateMock = jest.fn(); 10 | const { getByText } = render( 11 | , 12 | ); 13 | 14 | // Check that the dialog renders with the correct title and content 15 | expect( 16 | getByText( 17 | `AlgoWorld Swapper is a free and open-source tool for swapping assets on Algorand blockchain.`, 18 | { exact: false }, 19 | ), 20 | ).toBeInTheDocument(); 21 | 22 | // Check that clicking the close button calls the changeState function with the correct argument 23 | fireEvent.click(getByText(`Close`)); 24 | expect(changeStateMock).toHaveBeenCalledWith(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/components/Dialogs/ConfirmDialog.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import ConfirmDialog from './ConfirmDialog'; 3 | import renderWithProviders from '@/__utils__/renderWithProviders'; 4 | 5 | describe(`ConfirmDialog`, () => { 6 | it(`renders the correct content and handles the buttons correctly`, () => { 7 | const setOpenMock = jest.fn(); 8 | const onConfirmMock = jest.fn(); 9 | const onSwapVisibilityChangeMock = jest.fn(); 10 | const { getByText } = renderWithProviders( 11 | 20 |

Test content

21 |
, 22 | ); 23 | 24 | // Check that the dialog renders with the correct title and content 25 | expect(getByText(`Test Dialog`)).toHaveTextContent(`Test Dialog`); 26 | expect(getByText(`Test content`)).toBeInTheDocument(); 27 | expect(getByText(`Transaction fees: ~10 Algo`)).toBeInTheDocument(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/Dialogs/InfoDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { 20 | Dialog, 21 | DialogTitle, 22 | DialogContent, 23 | DialogActions, 24 | Button, 25 | } from '@mui/material'; 26 | import { INFO_DIALOG_CLOSE_BTN_ID, INFO_DIALOG_ID } from './constants'; 27 | 28 | type Props = { 29 | title: string; 30 | children: React.ReactNode; 31 | open: boolean; 32 | setOpen: (open: boolean) => void; 33 | onClose: () => void; 34 | }; 35 | 36 | const InfoDialog = ({ title, children, open, setOpen, onClose }: Props) => { 37 | return ( 38 | setOpen(false)} 42 | aria-labelledby="confirm-dialog" 43 | > 44 | {title} 45 | {children} 46 | 47 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default InfoDialog; 62 | -------------------------------------------------------------------------------- /src/components/Dialogs/ShareSwapDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { SwapConfiguration } from '@/models/Swap'; 20 | import { useAppSelector } from '@/redux/store/hooks'; 21 | import getSwapUrl from '@/utils/api/swaps/getSwapUrl'; 22 | import { 23 | Dialog, 24 | DialogTitle, 25 | DialogContent, 26 | DialogActions, 27 | Button, 28 | } from '@mui/material'; 29 | 30 | import { toast } from 'react-toastify'; 31 | import { useCopyToClipboard } from 'react-use'; 32 | import { 33 | DIALOG_CANCEL_BTN_ID, 34 | SHARE_SWAP_COPY_BTN_ID, 35 | SHARE_SWAP_DIALOG_ID, 36 | } from './constants'; 37 | 38 | type Props = { 39 | children: React.ReactNode; 40 | open: boolean; 41 | swapConfiguration?: SwapConfiguration; 42 | title?: string; 43 | setOpen?: (open: boolean) => void; 44 | onClose?: () => void; 45 | onConfirm?: () => void; 46 | showManageSwapBtn?: boolean; 47 | }; 48 | 49 | const ShareSwapDialog = ({ 50 | children, 51 | open, 52 | swapConfiguration, 53 | title = `Share AlgoWorld Swap`, 54 | setOpen, 55 | onClose, 56 | }: Props) => { 57 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 58 | const [_result, copyToClipboard] = useCopyToClipboard(); 59 | const selectedChain = useAppSelector((state) => state.application.chain); 60 | return ( 61 | { 66 | if (setOpen) setOpen(false); 67 | }} 68 | aria-labelledby="confirm-dialog" 69 | > 70 | {title} 71 | {children} 72 | 73 | 86 | 97 | 98 | 99 | ); 100 | }; 101 | 102 | export default ShareSwapDialog; 103 | -------------------------------------------------------------------------------- /src/components/Dialogs/ToAlgoPickerDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * AlgoWorld Swapper 3 | * Copyright (C) 2022 AlgoWorld 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react'; 20 | import Button from '@mui/material/Button'; 21 | import Dialog from '@mui/material/Dialog'; 22 | import DialogActions from '@mui/material/DialogActions'; 23 | import DialogContent from '@mui/material/DialogContent'; 24 | import DialogContentText from '@mui/material/DialogContentText'; 25 | import DialogTitle from '@mui/material/DialogTitle'; 26 | import { Asset } from '@/models/Asset'; 27 | import { useState } from 'react'; 28 | import CryptoTextField from '../TextFields/CryptoTextField'; 29 | import { CoinType } from '@/models/CoinType'; 30 | import { selectAssets } from '@/redux/slices/applicationSlice'; 31 | import { useAppSelector } from '@/redux/store/hooks'; 32 | import { 33 | DIALOG_CANCEL_BTN_ID, 34 | DIALOG_SELECT_BTN_ID, 35 | TO_ALGO_PICKER_DIALOG_ID, 36 | } from './constants'; 37 | 38 | type Props = { 39 | open: boolean; 40 | onAlgoAmoutSelected: (algo: Asset, amount: number) => void; 41 | onCancel: () => void; 42 | selectedAssets?: Asset[]; 43 | coinType?: CoinType; 44 | }; 45 | 46 | export const ToAlgoPickerDialog = ({ 47 | open, 48 | onAlgoAmoutSelected, 49 | onCancel, 50 | }: Props) => { 51 | const [selectedAlgoAmount, setSelectedAlgoAmount] = useState< 52 | number | undefined 53 | >(undefined); 54 | 55 | const existingAssets = useAppSelector(selectAssets); 56 | const algoAsset = existingAssets.find( 57 | (asset: Asset) => asset.index === 0, 58 | ) as Asset; 59 | 60 | return ( 61 |
62 | 63 | Input amount 64 | 65 | Enter the requested Algo amount 66 | 67 | { 72 | setSelectedAlgoAmount(value); 73 | }} 74 | coinType={CoinType.ALGO} 75 | decimals={6} 76 | /> 77 | 78 | 79 | 88 | 89 | 100 | 101 | 102 |
103 | ); 104 | }; 105 | -------------------------------------------------------------------------------- /src/components/Dialogs/constants.ts: -------------------------------------------------------------------------------- 1 | export const CONNECT_PROVIDER_DIALOG_ID = `AWConnectProviderDialog`; 2 | 3 | export const ABOUT_DIALOG_ID = `AWAboutDialog`; 4 | 5 | export const DIALOG_SELECT_BTN_ID = `AWDialogSelectButton`; 6 | export const DIALOG_CANCEL_BTN_ID = `AWDialogCancelButton`; 7 | 8 | export const FROM_ASSET_PICKER_DIALOG_ID = `AWFromAssetPickerDialog`; 9 | export const FROM_ASSET_PICKER_DIALOG_SEARCH_ID = `AWFromAssetPickerDialogSearch`; 10 | 11 | export const TO_ASSET_PICKER_DIALOG_ID = `AWToAssetPickerDialog`; 12 | export const TO_ASSET_PICKER_DIALOG_SEARCH_ID = `AWToAssetPickerDialogSearch`; 13 | 14 | export const TO_ALGO_PICKER_DIALOG_ID = `AWToAlgoPickerDialog`; 15 | 16 | export const CONFIRM_DIALOG_ID = `AWConfirmDialog`; 17 | export const CONFIRM_DIALOG_PUBLIC_SWAP_SWITCH_ID = `AWConfirmDialogPublicSwapSwitch`; 18 | 19 | export const SHARE_SWAP_DIALOG_ID = `AWShareSwapDialog`; 20 | export const SHARE_SWAP_COPY_BTN_ID = `AWShareSwapCopyButton`; 21 | 22 | export const SWAP_DEACTIVATION_PERFORMED_MESSAGE = `Deactivation of swap performed..`; 23 | export const SWAP_REMOVED_FROM_PROXY_MESSAGE = `Swap removed from proxy configuration...`; 24 | export const SIGN_DEPOSIT_TXN_MESSAGE = `Open your wallet to sign the deposit transactions...`; 25 | export const SWAP_DEPOSIT_PERFORMED_MESSAGE = `Deposit of offering asset performed...`; 26 | 27 | export const MNEMONIC_DIALOG_ID = `AWMnemonicDialog`; 28 | export const MNEMONIC_DIALOG_TEXT_INPUT_ID = `AWMnemonicDialogTextInput`; 29 | export const MNEMONIC_DIALOG_SELECT_BUTTON_ID = `AWMnemonicSelectButton`; 30 | 31 | export const INFO_DIALOG_ID = `AWInfoDialog`; 32 | export const INFO_DIALOG_CLOSE_BTN_ID = `AWInfoDialogCloseButton`; 33 | -------------------------------------------------------------------------------- /src/components/Footers/Footer.test.tsx: -------------------------------------------------------------------------------- 1 | import renderWithProviders from '@/__utils__/renderWithProviders'; 2 | import Footer from './Footer'; 3 | 4 | describe(`Footer`, () => { 5 | it(`should render nav bar items`, () => { 6 | const { container } = renderWithProviders(