├── .easignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── user-story-and-definition-of-done.md ├── config.yaml └── workflows │ ├── build-apk.yaml │ ├── check-sdk-version.yaml │ ├── deploy.yaml │ └── tests.yaml ├── .gitignore ├── .prettierrc.json ├── .releaserc ├── .vscode ├── extensions.json └── settings.json ├── .yarnrc.yml ├── App.tsx ├── ERROR_HANDLING.md ├── LICENSE ├── README.md ├── android.manifest.plugin.js ├── babel.config.js ├── desc.d.ts ├── eas.json ├── index.js ├── jest.config.ts ├── jest.setup.js ├── metro.config.js ├── package.json ├── prepare.app.config.ts ├── src ├── assets │ ├── animations │ │ ├── privacy-policy.gif │ │ ├── qr-code.gif │ │ ├── security-splash.gif │ │ ├── security.gif │ │ └── transparency.gif │ ├── ethereumLogo │ │ ├── eth-logo.png │ │ └── polygon-logo.png │ ├── fonts │ │ ├── Inter.ttf │ │ └── Roboto-Regular.ttf │ ├── icons │ │ ├── ArrowForwardIcon.tsx │ │ ├── CloseIcon.tsx │ │ ├── CompassSolid.tsx │ │ ├── CopyIcon.tsx │ │ ├── DiscordIcon.tsx │ │ ├── FaceIdIcon.tsx │ │ ├── FingerprintIcon.tsx │ │ ├── FlashIcon.tsx │ │ ├── GridPlusSolid.tsx │ │ ├── MenuIcon.tsx │ │ ├── PrivacyIcon.tsx │ │ ├── ScanIcon.tsx │ │ ├── SecurityIcon.tsx │ │ ├── TelegramIcon.tsx │ │ ├── TransactionSuccess.tsx │ │ ├── TransparencyIcon.tsx │ │ ├── UserCircleSolid.tsx │ │ └── eth-img.png │ ├── images │ │ ├── QrScannerBorders.tsx │ │ ├── VestedAssetIcon.png │ │ ├── anchor-codes.png │ │ ├── apps │ │ │ ├── hypha-logo.jpeg │ │ │ ├── pangea-block-explorer.png │ │ │ ├── pangea-build.png │ │ │ ├── pangea-dao.png │ │ │ ├── pangea-gov.png │ │ │ ├── pangean-bankless.png │ │ │ └── sales-platform.png │ │ ├── citizenship │ │ │ ├── 1-slide.png │ │ │ ├── 2-slide.png │ │ │ ├── 3-slide.png │ │ │ ├── 4-slide.png │ │ │ ├── citizenship-identity-image.png │ │ │ ├── login-webapps.png │ │ │ └── manage-crypto.png │ │ ├── crypto-transaction.png │ │ ├── explore │ │ │ ├── discord-Our-Socials.png │ │ │ ├── earth-globe-network-connection.png │ │ │ ├── explore-video.png │ │ │ ├── git-Our-Socials.png │ │ │ ├── icon-social-discord.tsx │ │ │ ├── icon-social-github.tsx │ │ │ ├── icon-social-linkedin.tsx │ │ │ ├── icon-social-telegram.tsx │ │ │ ├── icon-social-x.tsx │ │ │ ├── join-community-discord.png │ │ │ ├── linkedin-Our-Socials.png │ │ │ ├── news-1.png │ │ │ ├── news-3.jpg │ │ │ ├── telegram-Our-Socials.png │ │ │ └── x-Our-Socials.png │ │ ├── hcaptcha.png │ │ ├── jack-tanner.png │ │ ├── loading-gif.json │ │ ├── onboarding │ │ │ ├── 1.svg │ │ │ ├── 2.svg │ │ │ ├── 3.svg │ │ │ ├── 4.svg │ │ │ └── 5.svg │ │ ├── pangea-web-platform.png │ │ ├── phil-patterson.jpeg │ │ ├── refresh-ccw.png │ │ ├── staking │ │ │ ├── success-stake.png │ │ │ └── success-unstake.png │ │ └── vesting │ │ │ ├── bg1.png │ │ │ ├── bg2.png │ │ │ └── vested-success.png │ ├── index.tsx │ ├── telos │ │ ├── Telos.png │ │ ├── telos-logo1024.png │ │ └── telos-logo48.png │ ├── tonomy │ │ ├── Agreement-amico.png │ │ ├── Privacypolicy-amico.png │ │ ├── connecting.png │ │ ├── tonomy-logo1024.png │ │ ├── tonomy-logo48.png │ │ └── tonomy-splash.png │ └── tonomyProduction │ │ ├── asset-icon.svg │ │ ├── favicon.png │ │ ├── logo1024x1024.png │ │ ├── logo48x48.png │ │ ├── pangea-splash.png │ │ ├── tono-splash.png │ │ └── tonomy-splash.png ├── components │ ├── AccountDetails.tsx │ ├── AllocationDetails.tsx │ ├── AutoCompletePassphraseWord.tsx │ ├── CustomDrawer.tsx │ ├── HowStakingWorks.tsx │ ├── LearnMoreAutonomous.tsx │ ├── NegligibleTransactionFees.tsx │ ├── PassphraseBox.tsx │ ├── PassphraseInput.tsx │ ├── QRCodeScanner.tsx │ ├── ReceiverAccountScanner.tsx │ ├── StakingAllocationDetails.tsx │ ├── TCard.tsx │ ├── TError.tsx │ ├── TErrorModal.tsx │ ├── TIconButton.tsx │ ├── TInfoBox.tsx │ ├── TInputTextBox.tsx │ ├── TList.tsx │ ├── TModal.tsx │ ├── TNavigationButton.tsx │ ├── TPin.tsx │ ├── Transaction.tsx │ ├── atoms │ │ ├── TA.tsx │ │ ├── TBadge.tsx │ │ ├── TButton.tsx │ │ ├── THeadings.tsx │ │ └── TSpinner.tsx │ ├── layout.tsx │ └── molecules │ │ ├── TCheckbox.tsx │ │ └── TTextInput.tsx ├── config │ ├── config.json │ ├── config.production.json │ ├── config.staging.json │ └── config.testnet.json ├── containers │ ├── AppsContainer.tsx │ ├── AssetManagerContainer.tsx │ ├── AssetsContainer.tsx │ ├── CitienshipContainer.tsx │ ├── ConfirmPassphraseContainer.tsx │ ├── ConfirmStakingContainer.tsx │ ├── ConfirmUnStakingContainer.tsx │ ├── CreateAccountUsernameContainer.tsx │ ├── CreateEthereumKeyContainer.tsx │ ├── CreatePassphraseContainer.tsx │ ├── ExploreContainer.tsx │ ├── HcaptchaContainer.tsx │ ├── HomeScreenContainer.tsx │ ├── LoginPassphraseContainer.tsx │ ├── LoginUsernameContainer.tsx │ ├── MainSplashContainer.tsx │ ├── OnboardingContainer.tsx │ ├── PrivacyAndPolicyContainer.tsx │ ├── ProfilePreviewContainer.tsx │ ├── ReceiveAssetContainer.tsx │ ├── SSOLoginContainer.tsx │ ├── ScanQRCodeContainer.tsx │ ├── SelectAssetContainer.tsx │ ├── SendAssetContainer.tsx │ ├── SettingsContainer.tsx │ ├── SignTransactionConsentContainer.tsx │ ├── SignTransactionConsentSuccessContainer.tsx │ ├── StakeAssetContainer.tsx │ ├── StakeAssetDetailContainer.tsx │ ├── SuccessUnstakeContainer.tsx │ ├── SupportContainer.tsx │ ├── TermsAndConditionContainer.tsx │ ├── VestedAssetsContainer.tsx │ ├── VestedSuccessContainer.tsx │ ├── WalletConnectLoginContainer.tsx │ ├── WithDrawVestedContainer.tsx │ └── unused │ │ ├── FingerprintUpdateContainer.tsx │ │ ├── LoginPinScreenContainer.tsx │ │ └── PinScreenContainer.tsx ├── navigation │ ├── BottomTabNavigator.tsx │ ├── Drawer.tsx │ ├── Root.tsx │ └── Settings.tsx ├── providers │ ├── AppInstruction.tsx │ ├── Communication.ts │ ├── ErrorHandler.tsx │ ├── InitializeApp.tsx │ └── Notifications.ts ├── screens │ ├── AppsScreen.tsx │ ├── AssetListingScreen.tsx │ ├── AssetManagerScreen.tsx │ ├── CitizenshipScreen.tsx │ ├── ConfirmPassphraseScreen.tsx │ ├── ConfirmStakingScreen.tsx │ ├── ConfirmUnstakingScreen.tsx │ ├── CreateAccountUsernameScreen.tsx │ ├── CreateEthereumKeyScreen.tsx │ ├── CreatePassphraseScreen.tsx │ ├── ExploreScreen.tsx │ ├── HcaptchaScreen.tsx │ ├── HomeScreen.tsx │ ├── LoginPassphraseScreen.tsx │ ├── LoginUsernameScreen.tsx │ ├── MainSplashScreen.tsx │ ├── OnboardingScreen.tsx │ ├── PrivacyAndPolicyScreen.tsx │ ├── ProfilePreviewScreen.tsx │ ├── ReceiveAssetScreen.tsx │ ├── SSOLoginScreen.tsx │ ├── ScanQRScreen.tsx │ ├── SelectAssetScreen.tsx │ ├── SendAssetScreen.tsx │ ├── SettingsScreen.tsx │ ├── SignTransactionConsentScreen.tsx │ ├── SignTransactionConsentSuccessScreen.tsx │ ├── StakeAssetDetailScreen.tsx │ ├── StakeAssetScreen.tsx │ ├── SuccessUnstakeScreen.tsx │ ├── SupportScreen.tsx │ ├── TermsAndConditionScreen.tsx │ ├── VestedAssetsScreen.tsx │ ├── VestedSuccessScreen.tsx │ ├── WalletConnectLoginScreen.tsx │ └── WithdrawVestedScreen.tsx ├── settings.ts ├── store │ ├── errorStore.ts │ ├── passphraseStore.ts │ ├── sessionStore.ts │ ├── useAppSettings.ts │ ├── useWalletStore.ts │ └── userStore.ts ├── types │ └── images.d.ts └── utils │ ├── RNKeyManager.ts │ ├── StorageManager │ ├── entities │ │ ├── appSettings.ts │ │ ├── assetStorage.ts │ │ └── keyStorage.ts │ ├── migrations │ │ └── assetNameMigration.ts │ ├── repositories │ │ ├── KeyStorageRepository.ts │ │ ├── appStorageManager.ts │ │ ├── appStorageRepository.ts │ │ ├── assetStorageManager.ts │ │ ├── assetStorageRepository.ts │ │ └── keyStorageManager.ts │ └── setup.ts │ ├── chain │ ├── antelope.ts │ ├── common.ts │ ├── etherum.ts │ └── types.ts │ ├── debug.ts │ ├── errors.ts │ ├── exceptions.ts │ ├── keys.ts │ ├── navigate.ts │ ├── network.ts │ ├── numbers.ts │ ├── polyfill.ts │ ├── runtimeTests.ts │ ├── sentry.ts │ ├── session │ ├── antelope.ts │ └── walletConnect.ts │ ├── setSettings.ts │ ├── storage.ts │ ├── strings.ts │ ├── theme.ts │ ├── time.ts │ ├── tokenRegistry.ts │ └── username.ts ├── test ├── utils │ ├── RNKeymanager.test.ts │ ├── antelope.test.ts │ ├── ethereum.test.ts │ └── mocks.ts └── veramo.test.ts ├── tsconfig.json ├── update_sdk_version.sh └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:prettier/recommended", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "plugins": [ 10 | "prettier", 11 | "react", 12 | "react-native", 13 | "react-hooks", 14 | "@typescript-eslint" 15 | ], 16 | "rules": { 17 | "eqeqeq": "error", 18 | "no-console": "warn", 19 | "indent": [ 20 | "error", 21 | 4, 22 | { 23 | "SwitchCase": 1 24 | } 25 | ], 26 | "camelcase": "error", 27 | "prettier/prettier": "error", 28 | "@typescript-eslint/ban-ts-comment": "warn", 29 | "react/display-name": "off", 30 | "react/no-children-prop": "off", 31 | "react/react-in-jsx-scope": "off", 32 | "react-hooks/rules-of-hooks": "error", 33 | "react-hooks/exhaustive-deps": "warn", 34 | "padding-line-between-statements": [ 35 | "warn", 36 | { 37 | "blankLine": "always", 38 | "prev": "block-like", 39 | "next": "*" 40 | }, 41 | { 42 | "blankLine": "always", 43 | "prev": "block", 44 | "next": "*" 45 | }, 46 | { 47 | "blankLine": "always", 48 | "prev": "*", 49 | "next": [ 50 | "block", 51 | "block-like" 52 | ] 53 | }, 54 | { 55 | "blankLine": "always", 56 | "prev": [ 57 | "const", 58 | "let", 59 | "var" 60 | ], 61 | "next": "*" 62 | }, 63 | { 64 | "blankLine": "any", 65 | "prev": [ 66 | "const", 67 | "let", 68 | "var" 69 | ], 70 | "next": [ 71 | "const", 72 | "let", 73 | "var" 74 | ] 75 | }, 76 | { 77 | "blankLine": "always", 78 | "prev": [ 79 | "export", 80 | "import" 81 | ], 82 | "next": "*" 83 | }, 84 | { 85 | "blankLine": "any", 86 | "prev": "import", 87 | "next": "import" 88 | }, 89 | { 90 | "blankLine": "any", 91 | "prev": "export", 92 | "next": "export" 93 | } 94 | ] 95 | }, 96 | "parserOptions": { 97 | "ecmaVersion": 6, 98 | "sourceType": "module", 99 | "ecmaFeatures": { 100 | "jsx": true 101 | } 102 | }, 103 | "env": { 104 | "browser": true, 105 | "node": true, 106 | "react-native/react-native": true, 107 | "es6": true, 108 | "jest": true 109 | }, 110 | "ignorePatterns": [ 111 | "node_modules", 112 | "build", 113 | "dist", 114 | "public" 115 | ] 116 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report an issue in Tonomy ID 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Testing and reporting process: 11 | 12 | Please replace the example bug report below with the information about your bug 13 | 14 | "it doesn't work" is not a bug report! 15 | 16 | Be very concise! Say what you did on which screen! 17 | 18 | ## Steps to replicate the issue 19 | 20 | 1. I open app for the first time 21 | 2. I click through the **three splash screens** 22 | 3. I press on "Create Account" button on the **Create account screen** 23 | 24 | Recorded video link: (optional - make one with the [Loom app](https://play.google.com/store/apps/details?id=com.loom.android) ) 25 | 26 | ## What do you expect to happen? 27 | 28 | 1. I am sent to a screen where I can create an account 29 | 30 | ## What actually happens 31 | 32 | 1. The button shows a spinner, and nothing else happens 33 | 34 | ## Your environment 35 | 36 | - Operating system: Android 5.0 37 | - Tonomy ID version: 0.1.6 (click [here](https://play.google.com/store/apps/details?id=foundation.tonomy.projects.tonomyidstaging) on your phone) 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story-and-definition-of-done.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User story and definition of done 3 | about: User story with a definition of done 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **User story** 11 | 12 | As a X 13 | I can Y 14 | So that Z 15 | 16 | **API Design planning** 17 | 18 | **Definition of done** 19 | 20 | - [ ] X 21 | - [ ] Y 22 | - [ ] Z 23 | 24 | **Hints** 25 | 26 | 1. 27 | -------------------------------------------------------------------------------- /.github/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Community Support (Discord) 4 | url: https://www.discord.gg/QqVJz5XF8d 5 | about: Please ask and answer questions here. -------------------------------------------------------------------------------- /.github/workflows/check-sdk-version.yaml: -------------------------------------------------------------------------------- 1 | name: Test - Check SDK version 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | check-sdk-version: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - name: 🏗 Setup repo 11 | uses: actions/checkout@v4 12 | 13 | - name: Enable Corepack before setting up Node 14 | run: corepack enable 15 | 16 | - name: 📦 Check it is using the latest version of the SDK 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 22.3.0 20 | - run: yarn install --immutable 21 | - run: yarn run updateSdkVersion $GITHUB_BASE_REF check -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - testnet 8 | - development 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-24.04 13 | env: 14 | EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} 15 | # https://expo.dev/accounts/[account]/settings/access-tokens 16 | GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }} 17 | # https://github.com/expo/fyi/blob/main/creating-google-service-account.md 18 | APPLE_STORE_CONNECT_API_KEY: ${{ secrets.APPLE_STORE_CONNECT_API_KEY }} 19 | # https://github.com/expo/fyi/blob/main/creating-asc-api-key.md 20 | GITHUB_TOKEN: ${{secrets.MY_GITHUB_PERSONAL_ACCESS_TOKEN}} 21 | # https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic 22 | 23 | steps: 24 | - name: 🏗 Setup repo 25 | uses: actions/checkout@v4 26 | with: 27 | token: ${{secrets.MY_GITHUB_PERSONAL_ACCESS_TOKEN}} 28 | # https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic 29 | 30 | - name: Enable Corepack before setting up Node 31 | run: corepack enable 32 | 33 | - name: 🚀 Build and publish staging app 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: 22.3.0 37 | - run: npm install -g eas-cli@12.6.2 38 | - run: node -v && npm -v && yarn -v && eas -v 39 | - run: echo "${GOOGLE_SERVICE_ACCOUNT_KEY}" > ./google-service-key.json 40 | - run: echo "${APPLE_STORE_CONNECT_API_KEY}" > ./apple-asc-api-key.p8 41 | - run: yarn --immutable 42 | - run: yarn release 43 | - run: | 44 | if [[ ${GITHUB_REF#refs/*/} == "master" ]]; then 45 | echo "Branch is 'master'" 46 | yarn run deploy:production 47 | fi 48 | 49 | if [[ ${GITHUB_REF#refs/*/} == "testnet" ]]; then 50 | echo "Branch is 'testnet'" 51 | yarn run deploy:testnet 52 | fi 53 | 54 | if [[ ${GITHUB_REF#refs/*/} == "development" ]]; then 55 | echo "Branch is 'development'" 56 | yarn run deploy:staging 57 | fi 58 | env: 59 | GITHUB_TOKEN: ${{secrets.MY_GITHUB_PERSONAL_ACCESS_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - name: 🏗 Setup repo 11 | uses: actions/checkout@v4 12 | 13 | - name: Enable Corepack before setting up Node 14 | run: corepack enable 15 | 16 | - name: 📦 Build, test and lint 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 22.3.0 20 | - run: yarn --immutable 21 | - run: yarn run test 22 | - run: yarn run lint 23 | - run: yarn run typeCheck -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "bracketSpacing": true, 6 | "singleQuote": true, 7 | "useTabs": false, 8 | "printWidth": 120 9 | } -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "tagFormat": "${version}", 3 | "branches": [ 4 | { 5 | "name": "master", 6 | "prerelease": false 7 | }, 8 | { 9 | "name": "testnet", 10 | "prerelease": false 11 | }, 12 | { 13 | "name": "development", 14 | "prerelease": false 15 | } 16 | ], 17 | "plugins": [ 18 | [ 19 | "@semantic-release/commit-analyzer", 20 | { 21 | "preset": "angular" 22 | } 23 | ], 24 | [ 25 | "@semantic-release/npm", 26 | { 27 | "npmPublish": false 28 | } 29 | ], 30 | [ 31 | "@semantic-release/git", 32 | { 33 | "assets": [ 34 | "CHANGELOG.md", 35 | "package.json", 36 | "yarn.lock" 37 | ], 38 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 39 | } 40 | ], 41 | "@semantic-release/github" 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "DavidAnson.vscode-markdownlint", 4 | "dbaeumer.vscode-eslint", 5 | "streetsidesoftware.code-spell-checker", 6 | "Orta.vscode-jest", 7 | "wayou.vscode-todo-highlight" 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | "typescript", 6 | "typescriptreact" 7 | ], 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit" 10 | }, 11 | "jest.autoRun": { 12 | "onSave": false 13 | }, 14 | "jest.debugMode": false, 15 | "jest.jestCommandLine": "yarn run test", 16 | "jest.testExplorer": { 17 | "enabled": true 18 | }, 19 | "git.branchPrefix": "feature/", 20 | "git.branchProtection": [ 21 | "master", 22 | "main", 23 | "testnet", 24 | "development" 25 | ], 26 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | // IMPORTANT: The following 2 packages should be imported in this order: 2 | import './src/utils/polyfill'; 3 | import './src/utils/setSettings'; 4 | // NOTE: The rest can be imported in any order 5 | import React from 'react'; 6 | import { Provider as PaperProvider } from 'react-native-paper'; 7 | import 'expo-dev-client'; 8 | import theme from './src/utils/theme'; 9 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 10 | import ErrorHandlerProvider from './src/providers/ErrorHandler'; 11 | import Debug from 'debug'; 12 | import { wrap } from './src/utils/sentry'; 13 | import InitializeAppProvider from './src/providers/InitializeApp'; 14 | import { StatusBar } from 'react-native'; 15 | 16 | Debug.enable(process.env.DEBUG); 17 | console.log('DEBUG:', process.env.DEBUG); 18 | 19 | function App() { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | export default wrap(App); -------------------------------------------------------------------------------- /android.manifest.plugin.js: -------------------------------------------------------------------------------- 1 | // https://docs.expo.dev/config-plugins/plugins-and-mods/ 2 | // https://chafikgharbi.com/expo-android-manifest/ 3 | // import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins'; 4 | const { withAndroidManifest } = require('expo/config-plugins'); 5 | 6 | // Update AndroidManifest.xml found in ./android/app/src/main/AndroidManifest.xml 7 | // Run `yarn run build:prepare && npx expo prebuild` to check the file 8 | const updateAndroidManifest = function (config, customName) { 9 | return withAndroidManifest(config, async (config) => { 10 | console.log('Updating AndroidManifest.xml'); 11 | const androidManifest = config.modResults.manifest; 12 | 13 | androidManifest['uses-permission'] = androidManifest['uses-permission'].map((perm) => { 14 | // Make permissions less open, to fix security vulnerability 15 | // https://github.com/Tonomy-Foundation/Tonomy-ID/pull/826#issuecomment-1690020984 16 | // TODO: a better approach would be to change the tag to use android:exported="false" 17 | if ( 18 | (perm.$['android:name'] === 'com.google.android.c2dm.permission.SEND') | 19 | (perm.$['android:name'] === 'android.permission.DUMP') 20 | ) { 21 | perm.$['android:protectionLevel'] = 'signature'; 22 | } 23 | 24 | return perm; 25 | }); 26 | 27 | return config; 28 | }); 29 | }; 30 | 31 | module.exports = updateAndroidManifest; 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (api) { 4 | api.cache(true); 5 | return { 6 | presets: ['babel-preset-expo'], 7 | plugins: [ 8 | 'react-native-paper/babel', 9 | 'transform-inline-environment-variables', 10 | 'react-native-reanimated/plugin', 11 | '@babel/transform-react-jsx-source', 12 | 'babel-plugin-transform-typescript-metadata', 13 | ['@babel/plugin-proposal-decorators', { version: 'legacy' }], 14 | ['@babel/plugin-transform-class-properties', { loose: true }], 15 | ['@babel/plugin-transform-private-methods', { loose: true }], 16 | [ 17 | 'babel-plugin-transform-builtin-extend', 18 | { 19 | globals: ['Error', 'Array'], 20 | }, 21 | // this will help with extending Error and Array classes 22 | ], 23 | [ 24 | 'module-resolver', 25 | { 26 | alias: { 27 | // fix for https://github.com/decentralized-identity/ethr-did-resolver/issues/186 28 | 'ethr-did-resolver': path.resolve(__dirname, 'node_modules/ethr-did-resolver/src/index.ts'), 29 | // fix for https://github.com/Shopify/flash-list/issues/896 30 | tslib: path.resolve(__dirname, 'node_modules/tslib/tslib.es6.js'), 31 | }, 32 | }, 33 | ], 34 | [ 35 | 'babel-plugin-rewrite-require', 36 | { 37 | aliases: { 38 | crypto: 'crypto-browserify', 39 | stream: 'stream-browserify', 40 | }, 41 | }, 42 | ], 43 | ], 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /desc.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-argon2' { 2 | var argon2: any; 3 | export default argon2; 4 | } 5 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": "^12.6.2", 4 | "appVersionSource": "remote" 5 | }, 6 | "build": { 7 | "production": { 8 | "env": { 9 | "EXPO_NODE_ENV": "production" 10 | }, 11 | "autoIncrement": true, 12 | "ios":{ 13 | "image": "macos-sonoma-14.6-xcode-16.0" 14 | }, 15 | "node": "22.3.0" 16 | }, 17 | "testnet": { 18 | "env": { 19 | "EXPO_NODE_ENV": "testnet", 20 | "DEBUG": "tonomy*" 21 | }, 22 | "autoIncrement": true, 23 | "ios":{ 24 | "image": "macos-sonoma-14.6-xcode-16.0" 25 | }, 26 | "node": "22.3.0" 27 | }, 28 | "testnet-internal": { 29 | "env": { 30 | "EXPO_NODE_ENV": "testnet", 31 | "DEBUG": "tonomy*" 32 | }, 33 | "distribution": "internal", 34 | "node": "22.3.0" 35 | }, 36 | "staging": { 37 | "env": { 38 | "EXPO_NODE_ENV": "staging", 39 | "DEBUG": "tonomy*" 40 | }, 41 | "ios":{ 42 | "image": "macos-sonoma-14.6-xcode-16.0" 43 | }, 44 | "autoIncrement": true, 45 | "node": "22.3.0" 46 | }, 47 | "staging-internal": { 48 | "env": { 49 | "EXPO_NODE_ENV": "staging", 50 | "DEBUG": "tonomy*" 51 | }, 52 | "distribution": "internal", 53 | "node": "22.3.0" 54 | }, 55 | "development": { 56 | "env": { 57 | "EXPO_NODE_ENV": "development", 58 | "DEBUG": "tonomy*" 59 | }, 60 | "developmentClient": true, 61 | "distribution": "internal", 62 | "node": "22.3.0" 63 | } 64 | }, 65 | "submit": { 66 | "production": { 67 | "android": { 68 | "serviceAccountKeyPath": "./google-service-key.json", 69 | "track": "production" 70 | }, 71 | "ios": { 72 | "ascAppId": "1663471436", 73 | "appleTeamId": "6BLD42QR78", 74 | "ascApiKeyId": "83XQ25UWSK", 75 | "ascApiKeyPath": "./apple-asc-api-key.p8", 76 | "ascApiKeyIssuerId": "ba87a564-cdb7-4a94-9e58-dbcb7d2e8309", 77 | } 78 | }, 79 | "appleTestnet": { 80 | "ios": { 81 | "ascAppId": "6476114575", 82 | "appleTeamId": "6BLD42QR78", 83 | "ascApiKeyId": "83XQ25UWSK", 84 | "ascApiKeyPath": "./apple-asc-api-key.p8", 85 | "ascApiKeyIssuerId": "ba87a564-cdb7-4a94-9e58-dbcb7d2e8309" 86 | } 87 | }, 88 | "appleProduction": { 89 | "ios": { 90 | "ascAppId": "6482296993", 91 | "appleTeamId": "6BLD42QR78", 92 | "ascApiKeyId": "83XQ25UWSK", 93 | "ascApiKeyPath": "./apple-asc-api-key.p8", 94 | "ascApiKeyIssuerId": "ba87a564-cdb7-4a94-9e58-dbcb7d2e8309" 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler'; 2 | 3 | import { registerRootComponent } from 'expo'; 4 | 5 | import App from './App'; 6 | 7 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 8 | // It also ensures that whether you load the app in Expo Go or in a native build, 9 | // the environment is set up appropriately 10 | registerRootComponent(App); 11 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest'; 2 | 3 | // List of node_module packages that will be transformed by jest 4 | const packagesRegexToTransform = [ 5 | // Existing packages migrated from package.json::jest.transformIgnorePatterns 6 | '@?(jest-)?react-native(-community)?', 7 | '@?expo(nent)?', 8 | '@expo-google-fonts', 9 | '@react-navigation', 10 | '@unimodules', 11 | 'native-base', 12 | 'react-native-svg', 13 | 'argon2', 14 | /* 15 | /home/dev/Documents/Git/Tonomy/Tonomy-ID/node_modules/multiformats/dist/src/basics.js:1 16 | ({"Object.":function(module,exports,require,__dirname,__filename,jest){import * as base10 from './bases/base10.js'; 17 | ^^^^^^ 18 | 19 | SyntaxError: Cannot use import statement outside a module 20 | */ 21 | '@veramo', 22 | '@ipld/dag-pb', 23 | 'multiformats', 24 | 'ipfs-unixfs', 25 | 'protons-runtime', 26 | 'uint8-varint', 27 | 'uint8arrays', 28 | 'expo-modules-core', 29 | /* 30 | /home/dev/Documents/Git/Tonomy/Tonomy-ID/node_modules/expo-sqlite/build/index.js:1 31 | ({"Object.":function(module,exports,require,__dirname,__filename,jest){export * from './SQLite'; 32 | ^^^^^^ 33 | 34 | SyntaxError: Unexpected token 'export' 35 | */ 36 | 'expo-sqlite', 37 | ]; 38 | 39 | // One big regex string to ignore several packages from not being transformed 40 | // Aka these ARE transformed (despite normally being ignored because they are in node_modules) 41 | const ignoreRegexString = 'node_modules/(?!(' + packagesRegexToTransform.join('|') + ')/.*)'; 42 | 43 | console.info('jest.transformIgnorePatterns:', ignoreRegexString); 44 | 45 | const config: Config = { 46 | preset: 'jest-expo', 47 | transformIgnorePatterns: [ignoreRegexString], 48 | testEnvironment: 'node', 49 | moduleNameMapper: { 50 | // Cannot find module ... from ... 51 | '^@ipld/dag-pb$': '/node_modules/@ipld/dag-pb/src/index.js', 52 | '^multiformats/(.*)$': '/node_modules/multiformats/esm/src/$1', 53 | '^ipfs-unixfs$': '/node_modules/ipfs-unixfs/dist/src/index.js', 54 | '^protons-runtime$': '/node_modules/protons-runtime/dist/src/index.js', 55 | '^uint8-varint$': '/node_modules/uint8-varint/dist/src/index.js', 56 | '^uint8arrays$': '/node_modules/uint8arrays/index.js', 57 | }, 58 | setupFiles: ['./jest.setup.js'], 59 | }; 60 | 61 | export default config; 62 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Bug fix: https://github.com/getsentry/sentry-react-native/issues/668#issuecomment-838085905 2 | jest.mock('@sentry/react-native', () => ({ init: () => jest.fn() })); 3 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const { getSentryExpoConfig } = require('@sentry/react-native/metro'); 3 | const path = require('path'); 4 | 5 | const config = getSentryExpoConfig(__dirname); 6 | 7 | // Needed to resolve pure ESM packages that are within Tonomy-ID-SDK 8 | config.resolver.unstable_enablePackageExports = true; 9 | 10 | // Turn on symlinks for local development 11 | config.resolver.unstable_enableSymlinks = true; 12 | 13 | if (process.env.EXPO_NODE_ENV === 'local') { 14 | console.log('Setting up local development environment. Using local Tonomy-ID-SDK.'); 15 | // see https://medium.com/@alielmajdaoui/linking-local-packages-in-react-native-the-right-way-2ac6587dcfa2 16 | 17 | const sdkPath = path.resolve(__dirname, '../Tonomy-ID-SDK'); 18 | 19 | config.resolver.nodeModulesPaths.push(sdkPath); 20 | config.watchFolders.push(sdkPath); 21 | 22 | // Need to resolve these babel-plugin-rewrite-require locally as Tonomy-ID-SDK tries to import them from it's directory 23 | config.resolver.extraNodeModules['crypto-browserify'] = path.resolve(__dirname); 24 | config.resolver.extraNodeModules['stream-browserify'] = path.resolve(__dirname); 25 | } 26 | 27 | const debugModulePath = path.resolve(__dirname, 'src/utils/debug.ts'); 28 | 29 | // For all app code, and imported libraries 30 | // if they import the "debug" package 31 | // this should be instead use the ./src/utils/debugAndLog.ts 32 | // so that we can send the logs to Sentry 33 | config.resolver.resolveRequest = (context, moduleName, platform) => { 34 | // if the module is debug and not importing from the src/utils/debug.ts file 35 | if (moduleName === 'debug') { 36 | console.log(`Resolving debug module from ${context.originModulePath}`); 37 | 38 | if (context.originModulePath !== debugModulePath) { 39 | return context.resolveRequest(context, debugModulePath, platform); 40 | } else { 41 | console.log(`>> Skipping debug module resolution from ${context.originModulePath}`); 42 | } 43 | } 44 | 45 | return context.resolveRequest(context, moduleName, platform); 46 | }; 47 | // config.resolver.extraNodeModules.debug = path.resolve(__dirname, 'src/utils/debugAndLog.ts'); 48 | 49 | console.log('Metro config resolver', config.resolver); 50 | 51 | config.transformer.babelTransformerPath = require.resolve('react-native-svg-transformer'); 52 | config.resolver.assetExts = config.resolver.assetExts.filter((ext) => ext !== 'svg'); 53 | config.resolver.sourceExts.push('svg'); 54 | module.exports = config; 55 | -------------------------------------------------------------------------------- /src/assets/animations/privacy-policy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/animations/privacy-policy.gif -------------------------------------------------------------------------------- /src/assets/animations/qr-code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/animations/qr-code.gif -------------------------------------------------------------------------------- /src/assets/animations/security-splash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/animations/security-splash.gif -------------------------------------------------------------------------------- /src/assets/animations/security.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/animations/security.gif -------------------------------------------------------------------------------- /src/assets/animations/transparency.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/animations/transparency.gif -------------------------------------------------------------------------------- /src/assets/ethereumLogo/eth-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/ethereumLogo/eth-logo.png -------------------------------------------------------------------------------- /src/assets/ethereumLogo/polygon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/ethereumLogo/polygon-logo.png -------------------------------------------------------------------------------- /src/assets/fonts/Inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/fonts/Inter.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/assets/icons/ArrowForwardIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function ArrowForwardIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function CloseIcon(props: SvgProps) { 6 | let color = theme.colors.white; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/CompassSolid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function CompassSolid(props: SvgProps) { 6 | let color = theme.colors.black; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | `; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/icons/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function CopyIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | `; 18 | 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/icons/DiscordIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function DiscordIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/FlashIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function FlashOnIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | `; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/icons/GridPlusSolid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function GridPlusSolid(props: SvgProps) { 6 | let color = theme.colors.black; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | `; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/icons/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function MenuIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/PrivacyIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function PrivacyIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/ScanIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function ScanIcon(props: SvgProps) { 6 | let color = theme.colors.white; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/SecurityIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function SecurityIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/TelegramIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function TelegramIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | `; 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/TransparencyIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function TransparencyIcon(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | `; 25 | 26 | return ; 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/icons/UserCircleSolid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function UserCircleSolid(props: SvgProps) { 6 | let color = theme.colors.black; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | `; 19 | 20 | return ; 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/icons/eth-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/icons/eth-img.png -------------------------------------------------------------------------------- /src/assets/images/QrScannerBorders.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../utils/theme'; 4 | 5 | export default function QrScannerBorders(props: SvgProps) { 6 | let color = theme.colors.primary; 7 | 8 | if (props.color && typeof props.color === 'string') { 9 | color = props.color; 10 | } 11 | 12 | const xml = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | `; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/images/VestedAssetIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/VestedAssetIcon.png -------------------------------------------------------------------------------- /src/assets/images/anchor-codes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/anchor-codes.png -------------------------------------------------------------------------------- /src/assets/images/apps/hypha-logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/hypha-logo.jpeg -------------------------------------------------------------------------------- /src/assets/images/apps/pangea-block-explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/pangea-block-explorer.png -------------------------------------------------------------------------------- /src/assets/images/apps/pangea-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/pangea-build.png -------------------------------------------------------------------------------- /src/assets/images/apps/pangea-dao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/pangea-dao.png -------------------------------------------------------------------------------- /src/assets/images/apps/pangea-gov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/pangea-gov.png -------------------------------------------------------------------------------- /src/assets/images/apps/pangean-bankless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/pangean-bankless.png -------------------------------------------------------------------------------- /src/assets/images/apps/sales-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/apps/sales-platform.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/1-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/1-slide.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/2-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/2-slide.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/3-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/3-slide.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/4-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/4-slide.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/citizenship-identity-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/citizenship-identity-image.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/login-webapps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/login-webapps.png -------------------------------------------------------------------------------- /src/assets/images/citizenship/manage-crypto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/citizenship/manage-crypto.png -------------------------------------------------------------------------------- /src/assets/images/crypto-transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/crypto-transaction.png -------------------------------------------------------------------------------- /src/assets/images/explore/discord-Our-Socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/discord-Our-Socials.png -------------------------------------------------------------------------------- /src/assets/images/explore/earth-globe-network-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/earth-globe-network-connection.png -------------------------------------------------------------------------------- /src/assets/images/explore/explore-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/explore-video.png -------------------------------------------------------------------------------- /src/assets/images/explore/git-Our-Socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/git-Our-Socials.png -------------------------------------------------------------------------------- /src/assets/images/explore/icon-social-github.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../../utils/theme'; 4 | 5 | export default function SocialIconGithub(props: SvgProps) { 6 | let color = theme.colors.white; 7 | const backgroundColor = theme.colors.primary; 8 | 9 | if (props.color && typeof props.color === 'string') { 10 | color = props.color; 11 | } 12 | 13 | const xml = ` 14 | 15 | 16 | 17 | `; 18 | 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/images/explore/icon-social-linkedin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../../utils/theme'; 4 | 5 | export default function SocialIconLinkedIn(props: SvgProps) { 6 | let color = theme.colors.white; 7 | const backgroundColor = theme.colors.primary; 8 | 9 | if (props.color && typeof props.color === 'string') { 10 | color = props.color; 11 | } 12 | 13 | const xml = ` 14 | 15 | 16 | 17 | `; 18 | 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/images/explore/icon-social-telegram.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../../utils/theme'; 4 | 5 | export default function SocialIconTelegram(props: SvgProps) { 6 | let color = theme.colors.white; 7 | const backgroundColor = theme.colors.primary; 8 | 9 | if (props.color && typeof props.color === 'string') { 10 | color = props.color; 11 | } 12 | 13 | const xml = ` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | `; 27 | 28 | return ; 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/images/explore/icon-social-x.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SvgProps, SvgXml } from 'react-native-svg'; 3 | import theme from '../../../utils/theme'; 4 | 5 | export default function SocialIconX(props: SvgProps) { 6 | let color = theme.colors.white; 7 | const backgroundColor = theme.colors.primary; 8 | 9 | if (props.color && typeof props.color === 'string') { 10 | color = props.color; 11 | } 12 | 13 | const xml = ` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | `; 25 | 26 | return ; 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/images/explore/join-community-discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/join-community-discord.png -------------------------------------------------------------------------------- /src/assets/images/explore/linkedin-Our-Socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/linkedin-Our-Socials.png -------------------------------------------------------------------------------- /src/assets/images/explore/news-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/news-1.png -------------------------------------------------------------------------------- /src/assets/images/explore/news-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/news-3.jpg -------------------------------------------------------------------------------- /src/assets/images/explore/telegram-Our-Socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/telegram-Our-Socials.png -------------------------------------------------------------------------------- /src/assets/images/explore/x-Our-Socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/explore/x-Our-Socials.png -------------------------------------------------------------------------------- /src/assets/images/hcaptcha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/hcaptcha.png -------------------------------------------------------------------------------- /src/assets/images/jack-tanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/jack-tanner.png -------------------------------------------------------------------------------- /src/assets/images/loading-gif.json: -------------------------------------------------------------------------------- 1 | {"v":"4.10.1","fr":30,"ip":0,"op":41,"w":300,"h":300,"nm":"合成 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"形状图层 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[180,156.5,0],"e":[100,156.5,0],"to":[-13.3333330154419,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":19,"s":[100,156.5,0],"e":[180,156.5,0],"to":[0,0,0],"ti":[-13.3333330154419,0,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[40.916,40.916],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.114865059946,0.659905586991,0.980438112745,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[4.534,-3.114],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[100,156.5,0],"e":[180,156.5,0],"to":[13.3333330154419,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":19,"s":[180,156.5,0],"e":[100,156.5,0],"to":[0,0,0],"ti":[13.3333330154419,0,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[40.916,40.916],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145097994337,0.847059003045,0.654901960784,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[4.534,-3.114],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /src/assets/images/pangea-web-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/pangea-web-platform.png -------------------------------------------------------------------------------- /src/assets/images/phil-patterson.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/phil-patterson.jpeg -------------------------------------------------------------------------------- /src/assets/images/refresh-ccw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/refresh-ccw.png -------------------------------------------------------------------------------- /src/assets/images/staking/success-stake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/staking/success-stake.png -------------------------------------------------------------------------------- /src/assets/images/staking/success-unstake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/staking/success-unstake.png -------------------------------------------------------------------------------- /src/assets/images/vesting/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/vesting/bg1.png -------------------------------------------------------------------------------- /src/assets/images/vesting/bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/vesting/bg2.png -------------------------------------------------------------------------------- /src/assets/images/vesting/vested-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/images/vesting/vested-success.png -------------------------------------------------------------------------------- /src/assets/index.tsx: -------------------------------------------------------------------------------- 1 | import settings from '../settings'; 2 | 3 | interface Image { 4 | name: string; 5 | image: any; 6 | } 7 | export class Images { 8 | private static tonomyImages: Array = [ 9 | { 10 | name: 'home', 11 | image: require('./tonomy/tonomy-splash.png'), 12 | }, 13 | { 14 | name: 'logo48', 15 | image: require('./tonomy/tonomy-logo48.png'), 16 | }, 17 | { 18 | name: 'logo1024', 19 | image: require('./tonomy/tonomy-logo1024.png'), 20 | }, 21 | { 22 | name: 'favicon', 23 | image: require('./tonomy/tonomy-logo48.png'), 24 | }, 25 | { 26 | name: 'splash', 27 | image: require('./tonomy/tonomy-splash.png'), 28 | }, 29 | ]; 30 | 31 | private static pangeaImages: Array = [ 32 | { 33 | name: 'home', 34 | image: require('./tonomyProduction/tono-splash.png'), 35 | }, 36 | { 37 | name: 'logo48', 38 | image: require('./tonomyProduction/logo48x48.png'), 39 | }, 40 | { 41 | name: 'logo1024', 42 | image: require('./tonomyProduction/logo1024x1024.png'), 43 | }, 44 | { 45 | name: 'favicon', 46 | image: require('./tonomyProduction/favicon.png'), 47 | }, 48 | { 49 | name: 'splash', 50 | image: require('./tonomyProduction/tonomy-splash.png'), 51 | }, 52 | ]; 53 | 54 | static GetImage = (name: string) => { 55 | let found; 56 | 57 | if (settings.env === 'staging') { 58 | found = Images.tonomyImages.find((e) => e.name === name); 59 | } else { 60 | found = Images.pangeaImages.find((e) => e.name === name); 61 | } 62 | 63 | return found ? found.image : null; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/assets/telos/Telos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/telos/Telos.png -------------------------------------------------------------------------------- /src/assets/telos/telos-logo1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/telos/telos-logo1024.png -------------------------------------------------------------------------------- /src/assets/telos/telos-logo48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/telos/telos-logo48.png -------------------------------------------------------------------------------- /src/assets/tonomy/Agreement-amico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/Agreement-amico.png -------------------------------------------------------------------------------- /src/assets/tonomy/Privacypolicy-amico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/Privacypolicy-amico.png -------------------------------------------------------------------------------- /src/assets/tonomy/connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/connecting.png -------------------------------------------------------------------------------- /src/assets/tonomy/tonomy-logo1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/tonomy-logo1024.png -------------------------------------------------------------------------------- /src/assets/tonomy/tonomy-logo48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/tonomy-logo48.png -------------------------------------------------------------------------------- /src/assets/tonomy/tonomy-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomy/tonomy-splash.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/favicon.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/logo1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/logo1024x1024.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/logo48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/logo48x48.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/pangea-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/pangea-splash.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/tono-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/tono-splash.png -------------------------------------------------------------------------------- /src/assets/tonomyProduction/tonomy-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonomy-Foundation/Tonomy-ID/c3e48a5e27da9b805b6178c680f7acae9cba56e1/src/assets/tonomyProduction/tonomy-splash.png -------------------------------------------------------------------------------- /src/components/NegligibleTransactionFees.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View, Text, TouchableOpacity, ScrollView } from 'react-native'; 3 | import RBSheet from 'react-native-raw-bottom-sheet'; 4 | import TIconButton from '../components/TIconButton'; 5 | import theme, { commonStyles } from '../utils/theme'; 6 | import { TransactionFeeData } from './Transaction'; 7 | import { formatCurrencyValue } from '../utils/numbers'; 8 | 9 | export type props = { 10 | refMessage: React.RefObject; 11 | onClose: () => void; 12 | transactionFee: TransactionFeeData; 13 | precision: number; 14 | }; 15 | 16 | const NegligibleTransactionFees = (props: props) => { 17 | return ( 18 | 25 | 26 | Transaction fee 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {props.transactionFee.fee.toString(props.precision)} 36 | 37 | 38 | ${formatCurrencyValue(props.transactionFee.usdFee)} 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | const styles = StyleSheet.create({ 48 | content: { 49 | flex: 1, 50 | paddingHorizontal: 16, 51 | paddingBottom: 50, 52 | }, 53 | rawTransactionDrawer: { 54 | padding: 15, 55 | flexDirection: 'row', 56 | justifyContent: 'space-between', 57 | alignItems: 'flex-start', 58 | }, 59 | drawerHead: { 60 | fontSize: 19, 61 | fontWeight: '600', 62 | marginTop: 8, 63 | }, 64 | drawerTitle: { 65 | fontSize: 16, 66 | fontWeight: '600', 67 | lineHeight: 18.75, 68 | }, 69 | darwerContent: { 70 | fontSize: 15.5, 71 | fontWeight: '400', 72 | lineHeight: 21, 73 | marginTop: 8, 74 | }, 75 | drawerInnerContent: { 76 | fontWeight: '700', 77 | }, 78 | }); 79 | 80 | export default NegligibleTransactionFees; 81 | -------------------------------------------------------------------------------- /src/components/PassphraseBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | 4 | type PassphraseBoxProps = { 5 | number: string; 6 | text: string; 7 | }; 8 | 9 | export default function PassphraseBox(props: PassphraseBoxProps) { 10 | const styles = StyleSheet.create({ 11 | squareContainer: { 12 | flexDirection: 'row', 13 | alignItems: 'flex-start', 14 | marginRight: 15, 15 | marginBottom: 10, 16 | }, 17 | square: { 18 | backgroundColor: '#F9F9F9', 19 | borderRadius: 8, 20 | borderColor: '#5B6261', 21 | borderWidth: 1, 22 | width: 120, 23 | height: 42, 24 | marginTop: 22, 25 | justifyContent: 'center', 26 | }, 27 | numberText: { 28 | color: '#5B6261', 29 | fontSize: 14, 30 | fontWeight: '400', 31 | marginRight: -15, 32 | marginLeft: 10, 33 | }, 34 | squareText: { 35 | textAlign: 'center', 36 | fontSize: 14, 37 | }, 38 | }); 39 | 40 | return ( 41 | 42 | {props.number} 43 | 44 | {props.text} 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/ReceiverAccountScanner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'; 3 | import RBSheet from 'react-native-raw-bottom-sheet'; 4 | import TIconButton from './TIconButton'; 5 | import theme from '../utils/theme'; 6 | import { BarCodeScannerResult } from 'expo-barcode-scanner'; 7 | import Debug from 'debug'; 8 | import QRCodeScanner from './QRCodeScanner'; 9 | import { ChainType, IChain } from '../utils/chain/types'; 10 | import useErrorStore from '../store/errorStore'; 11 | 12 | const debug = Debug('tonomy-id:components:ReceiverAccountScanner'); 13 | 14 | export type ReceiverAccountScannerProps = { 15 | refMessage: React.RefObject<{ open: () => void; close: () => void }>; 16 | onScanQR: (accountName: string) => void; 17 | chain: IChain; 18 | }; 19 | 20 | const ReceiverAccountScanner = (props: ReceiverAccountScannerProps) => { 21 | const errorStore = useErrorStore(); 22 | 23 | async function onScan({ data }: BarCodeScannerResult) { 24 | debug('send scan data: ', data); 25 | const chainType = props.chain.getChainType(); 26 | let account = data; 27 | 28 | if (chainType === ChainType.ETHEREUM) { 29 | account = data.replace(/^.*?(0x[a-fA-F0-9]{40})$/, '$1'); 30 | } 31 | 32 | if (!props.chain.isValidAccountName(account)) { 33 | props.refMessage.current?.close(); 34 | errorStore.setError({ 35 | error: new Error('The account you entered is invalid!'), 36 | expected: true, 37 | }); 38 | return; 39 | } 40 | 41 | props.onScanQR(account); 42 | onClose(); 43 | } 44 | 45 | const onClose = () => { 46 | props.refMessage.current?.close(); 47 | }; 48 | 49 | return ( 50 | 57 | 58 | Scan QR code 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | }; 69 | 70 | const styles = StyleSheet.create({ 71 | content: { 72 | flex: 1, 73 | paddingHorizontal: 16, 74 | paddingBottom: 50, 75 | }, 76 | rawTransactionDrawer: { 77 | padding: 15, 78 | flexDirection: 'row', 79 | justifyContent: 'space-between', 80 | alignItems: 'flex-start', 81 | }, 82 | drawerHead: { 83 | fontSize: 20, 84 | fontWeight: '600', 85 | marginTop: 8, 86 | }, 87 | }); 88 | 89 | export default ReceiverAccountScanner; 90 | -------------------------------------------------------------------------------- /src/components/TCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'react-native-paper'; 3 | import TBadge from './atoms/TBadge'; 4 | import { useAppTheme } from '../utils/theme'; 5 | 6 | type TCardPropType = React.ComponentProps & { style?: any }; 7 | const radius = 5; 8 | 9 | function TCard({ children, ...props }: TCardPropType) { 10 | const theme = useAppTheme(); 11 | const style = { 12 | borderWidth: 1, 13 | borderColor: theme.colors.grey5, 14 | borderRadius: radius, 15 | }; 16 | 17 | const subComponents = React.Children.map(children, (child) => { 18 | return React.isValidElement(child) ? React.cloneElement(child) : child; 19 | }); 20 | 21 | return ( 22 | 23 | {subComponents?.map((component) => component)} 24 | 25 | ); 26 | } 27 | 28 | const Title = (props) => ; 29 | 30 | Title.displayName = 'Title'; 31 | TCard.Title = Title; 32 | 33 | const Cover = (props: any) => { 34 | const style = { 35 | width: 190, 36 | height: 130, 37 | borderTopEndRadius: radius, 38 | borderTopStartRadius: radius, 39 | }; 40 | 41 | return ; 42 | }; 43 | 44 | Cover.displayName = 'Title'; 45 | TCard.Cover = Cover; 46 | 47 | const Content = (props: any) => { 48 | const theme = useAppTheme(); 49 | const style = { 50 | backgroundColor: theme.colors.grey4, 51 | borderBottomEndRadius: radius, 52 | borderBottomStartRadius: radius, 53 | paddingHorizontal: 8, 54 | paddingVertical: 5, 55 | borderTopWidth: 1, 56 | borderColor: theme.colors.grey5, 57 | }; 58 | 59 | return ; 60 | }; 61 | 62 | Content.displayName = 'Title'; 63 | TCard.Content = Content; 64 | 65 | const Action = (props) => ; 66 | 67 | Action.displayName = 'Title'; 68 | TCard.Action = Action; 69 | 70 | const Badge = (props: any) => { 71 | const style = { position: 'absolute', top: 8, left: 8, textAlign: 'center' }; 72 | 73 | return ; 74 | }; 75 | 76 | Badge.displayName = 'Title'; 77 | TCard.Badge = Badge; 78 | 79 | export default TCard; 80 | -------------------------------------------------------------------------------- /src/components/TError.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | import { TCaption } from './atoms/THeadings'; 4 | import theme from '../utils/theme'; 5 | 6 | interface Props { 7 | style?: object; 8 | children: React.ReactNode; 9 | } 10 | 11 | export const TError = ({ style, children }: Props) => ( 12 | {children} 13 | ); 14 | 15 | const styles = StyleSheet.create({ 16 | errorStyles: { 17 | textAlign: 'right', 18 | color: theme.colors.error, 19 | fontSize: 14, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/TIconButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IconButton } from 'react-native-paper'; 3 | import { StyleSheet } from 'react-native'; 4 | import theme from '../utils/theme'; 5 | 6 | export type IconButtonProps = React.ComponentProps & { color?: string; iconColor?: string }; 7 | 8 | export default function TButton(props: IconButtonProps) { 9 | const styles = StyleSheet.create({ 10 | icon: { 11 | backgroundColor: props.color ? props.color : theme.colors.primary, 12 | }, 13 | }); 14 | 15 | // https://materialdesignicons.com/ 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/TInfoBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import { SvgProps } from 'react-native-svg'; 4 | import PrivacyIcon from '../assets/icons/PrivacyIcon'; 5 | import SecurityIcon from '../assets/icons/SecurityIcon'; 6 | import TransparencyIcon from '../assets/icons/TransparencyIcon'; 7 | import { TP } from './atoms/THeadings'; 8 | import theme from '../utils/theme'; 9 | 10 | export type TInfoBoxProps = { 11 | icon: string; 12 | align: 'left' | 'center'; 13 | description: string; 14 | linkUrl: string; 15 | linkUrlText: string; 16 | }; 17 | 18 | function IconComponent(props: SvgProps & { icon: string }) { 19 | switch (props.icon) { 20 | case 'security': 21 | return ; 22 | case 'privacy': 23 | return ; 24 | case 'transparency': 25 | return ; 26 | default: 27 | throw new Error('Invalid icon name'); 28 | } 29 | } 30 | 31 | export default function TInfoBox(props: TInfoBoxProps) { 32 | const styles = StyleSheet.create({ 33 | infoContainer: { 34 | alignContent: 'stretch', 35 | flexDirection: props.align === 'center' ? 'column' : 'row', 36 | alignSelf: 'center', 37 | backgroundColor: theme.colors.info, 38 | borderRadius: 8, 39 | padding: 14, 40 | width: '100%', 41 | }, 42 | icon: { 43 | alignSelf: 'center', 44 | marginBottom: props.align === 'center' ? 5 : 0, 45 | marginRight: props.align === 'center' ? 0 : 5, 46 | }, 47 | description: { 48 | textAlign: props.align === 'center' ? 'center' : 'left', 49 | alignSelf: props.align === 'center' ? 'center' : 'auto', 50 | justifyContent: props.align === 'center' ? 'center' : 'flex-start', 51 | flex: props.align === 'center' ? 0 : 1, 52 | }, 53 | link: { 54 | color: theme.colors.primary, 55 | }, 56 | paragraph: { 57 | textAlign: props.align, 58 | textAlignVertical: 'center', 59 | letterSpacing: 0.2, 60 | lineHeight: 19, 61 | paddingLeft: 5, 62 | fontSize: 14, 63 | fontWeight: '400', 64 | color: theme.colors.text, 65 | }, 66 | }); 67 | 68 | return ( 69 | 70 | 71 | 72 | 73 | {props.description} {/* TODO: uncomment link */} 74 | {/* 75 | {props.linkUrlText} 76 | */} 77 | 78 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/components/TInputTextBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import { TextInput } from 'react-native-paper'; 4 | import theme from '../utils/theme'; 5 | import { TCaption } from './atoms/THeadings'; 6 | 7 | export type TTextInputProps = React.ComponentProps & { errorText?: string }; 8 | 9 | export default function TInputTextBox(props: TTextInputProps) { 10 | const showError: boolean = !!props.errorText && props.errorText.length > 0; 11 | 12 | return ( 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | {showError && {props.errorText}} 26 | 27 | ); 28 | } 29 | 30 | const styles = StyleSheet.create({ 31 | username: { 32 | flexDirection: 'row', 33 | alignItems: 'center', 34 | }, 35 | usernameInput: { 36 | backgroundColor: 'transparent', 37 | width: '60%', 38 | height: 45, 39 | flex: 1, 40 | }, 41 | inputContainer: { 42 | borderWidth: 1, 43 | borderColor: theme.colors.disabled, 44 | borderRadius: 8, 45 | }, 46 | accountSuffix: { 47 | width: '40%', 48 | marginLeft: 14, 49 | textAlignVertical: 'bottom', 50 | fontSize: 16, 51 | }, 52 | errorStyles: { 53 | textAlign: 'right', 54 | color: theme.colors.error, 55 | fontSize: 14, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/TList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable prettier/prettier */ 2 | import React from 'react'; 3 | import { StyleSheet, View, Text } from 'react-native'; 4 | import theme from '../utils/theme'; 5 | 6 | export type TListProps = React.ComponentProps & { bulletIcon?: string; item?: JSX.Element }; 7 | 8 | export default function TList(props: TListProps) { 9 | return ( 10 | 11 | 12 | {props.bulletIcon} 13 | 14 | 15 | {props.item} 16 | 17 | 18 | ); 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | inputContainer: { 23 | flexDirection: 'row', 24 | alignItems: 'flex-start', 25 | paddingRight: 4, 26 | marginRight: 4, 27 | paddingLeft: 2, 28 | }, 29 | iconBullet: { 30 | marginRight: 5, 31 | }, 32 | textColor: { 33 | color: theme.colors.textGray, 34 | fontWeight: '400', 35 | fontSize: 16, 36 | lineHeight: 23, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/TModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import { Text } from 'react-native-paper'; 4 | import theme from '../utils/theme'; 5 | import { TButtonText } from './atoms/TButton'; 6 | import TIconButton from './TIconButton'; 7 | import { Modal } from 'react-native'; 8 | 9 | export type ModalProps = React.ComponentProps & { 10 | icon?: string; 11 | iconColor?: string; 12 | title?: string; 13 | visible?: boolean; 14 | enableLinkButton?: boolean; 15 | linkButtonText?: string; 16 | linkOnPress?: () => void; 17 | footer?: JSX.Element; 18 | buttonLabel?: string; 19 | onPress?: () => void; 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | modal: { 24 | flex: 1, 25 | justifyContent: 'center', 26 | alignItems: 'center', 27 | padding: 50, 28 | backgroundColor: 'rgba(0,0,0,0.5)', 29 | }, 30 | modalContent: { 31 | justifyContent: 'center', 32 | alignItems: 'center', 33 | backgroundColor: theme.colors.background, 34 | borderRadius: 4, 35 | padding: 26, 36 | }, 37 | title: { 38 | fontSize: 16, 39 | fontWeight: 'bold', 40 | marginBottom: 16, 41 | }, 42 | buttonView: { 43 | marginTop: 16, 44 | textAlign: 'center', 45 | }, 46 | }); 47 | 48 | export default function TModal(props: ModalProps) { 49 | return ( 50 | 51 | 52 | 53 | {props.icon && ( 54 | 55 | 56 | 57 | )} 58 | {props.title && ( 59 | 60 | {props.title} 61 | 62 | )} 63 | {props.children} 64 | 65 | {props.enableLinkButton && ( 66 | 67 | {props.linkButtonText} 68 | 69 | )} 70 | {props.footer ? ( 71 | <>{props.footer} 72 | ) : ( 73 | 74 | 75 | {props.buttonLabel ? props.buttonLabel : 'OK'} 76 | 77 | 78 | )} 79 | 80 | 81 | 82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/components/TNavigationButton.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import React from 'react'; 3 | import { View, TouchableOpacity, StyleSheet } from 'react-native'; 4 | import { IconButton } from 'react-native-paper'; 5 | import { useAppTheme } from '../utils/theme'; 6 | import { TCaption, TP } from './atoms/THeadings'; 7 | 8 | export type NavigationButtonProps = { 9 | onPress: () => void; 10 | icon?: string | React.ReactNode; 11 | title: string; 12 | description?: string; 13 | disabled?: boolean; 14 | color?: string; 15 | textColor?: string; 16 | }; 17 | 18 | export default function TNavigationButton(props: NavigationButtonProps) { 19 | const theme = useAppTheme(); 20 | const getAlignmentBasenOnDescription = () => { 21 | return props.description ? 'flex-start' : 'center'; 22 | }; 23 | 24 | return ( 25 | 26 | {props.icon && typeof props.icon === 'string' && ( 27 | 28 | 29 | 30 | )} 31 | {props.icon && typeof props.icon === 'object' && ( 32 | {props.icon} 33 | )} 34 | 35 | 36 | 47 | {props.title} 48 | 49 | {props.description && {props.description}} 50 | 51 | 52 | 53 | 54 | ); 55 | } 56 | 57 | const styles = StyleSheet.create({ 58 | button: { 59 | flexDirection: 'row', 60 | justifyContent: 'space-between', 61 | alignItems: 'center', 62 | marginVertical: 12, 63 | }, 64 | chevronStyle: { 65 | alignSelf: 'center', 66 | marginLeft: 20, 67 | }, 68 | textContainer: { 69 | width: '75%', 70 | }, 71 | 72 | icons: { 73 | margin: 0, 74 | }, 75 | titleContainer: { 76 | flex: 1, 77 | textAlignVertical: 'top', 78 | }, 79 | }); 80 | -------------------------------------------------------------------------------- /src/components/atoms/TA.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, StyleSheet, Linking } from 'react-native'; 3 | import theme from '../../utils/theme'; 4 | 5 | type AProps = { 6 | href: string; 7 | }; 8 | 9 | export default function TLink(props: AProps & any) { 10 | const to = props.to ? props.to : props.href; 11 | 12 | return ( 13 | Linking.openURL(to)}> 14 | {props.children} 15 | 16 | ); 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | a: { 21 | color: theme.colors.linkColor, 22 | textDecorationLine: 'underline', 23 | fontSize: 14, 24 | fontWeight: '400', 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /src/components/atoms/TBadge.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Badge } from 'react-native-paper'; 3 | import { useAppTheme } from '../../utils/theme'; 4 | import { Text, View } from 'react-native'; 5 | 6 | type TBadgeProps = React.ComponentProps & { style?: any }; 7 | 8 | export default function TBadge(props: TBadgeProps) { 9 | const theme = useAppTheme(); 10 | const style = { 11 | backgroundColor: theme.colors.accent2, 12 | color: theme.colors.accent, 13 | padding: 4, 14 | borderRadius: 20, 15 | fontSize: 10, 16 | }; 17 | 18 | return ( 19 | 20 | {props.children} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/atoms/THeadings.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | import { Caption, Paragraph, Text } from 'react-native-paper'; 4 | import { useAppTheme } from '../../utils/theme'; 5 | 6 | type THProps = React.ComponentProps; 7 | 8 | export function TH1(props: THProps) { 9 | return ( 10 | // eslint-disable-next-line react/prop-types 11 | 12 | {props.children} 13 | 14 | ); 15 | } 16 | 17 | export function TH2(props: THProps) { 18 | return ( 19 | // eslint-disable-next-line react/prop-types 20 | 21 | {props.children} 22 | 23 | ); 24 | } 25 | 26 | export function TH4(props: THProps) { 27 | return ( 28 | // eslint-disable-next-line react/prop-types 29 | 30 | {props.children} 31 | 32 | ); 33 | } 34 | 35 | type TPProps = React.ComponentProps & { size?: 1 | 2 | 3 }; 36 | 37 | export function TP(props: TPProps) { 38 | return ( 39 | // eslint-disable-next-line react/prop-types 40 | 41 | {props.children} 42 | 43 | ); 44 | } 45 | 46 | type TCaptionProps = React.ComponentProps; 47 | 48 | export function TCaption(props: TCaptionProps) { 49 | const theme = useAppTheme(); 50 | const style = { 51 | color: theme.colors.textGray, 52 | }; 53 | 54 | // eslint-disable-next-line react/prop-types 55 | return {props.children}; 56 | } 57 | 58 | const styles = StyleSheet.create({ 59 | h1: { 60 | fontSize: 24, 61 | marginBottom: 8, 62 | fontWeight: 'bold', 63 | }, 64 | h2: { 65 | fontSize: 24, 66 | fontWeight: '600', 67 | }, 68 | h4: { 69 | fontSize: 16, 70 | }, 71 | s4: { 72 | fontSize: 20, 73 | }, 74 | s3: { 75 | fontSize: 18, 76 | }, 77 | s2: { 78 | fontSize: 16, 79 | }, 80 | s1: { 81 | fontSize: 14, 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /src/components/atoms/TSpinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LottieView from 'lottie-react-native'; 3 | import { StyleSheet, View } from 'react-native'; 4 | 5 | const TSpinner = ({ size = 70 }: { size?: number }) => { 6 | return ( 7 | 8 | 14 | 15 | ); 16 | }; 17 | 18 | const styles = StyleSheet.create({ 19 | animationContainer: { 20 | alignItems: 'center', 21 | justifyContent: 'center', 22 | flexDirection: 'row', 23 | }, 24 | }); 25 | 26 | export default TSpinner; 27 | -------------------------------------------------------------------------------- /src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Keyboard, StyleSheet, Text, View } from 'react-native'; 3 | import { SafeAreaView } from 'react-native-safe-area-context'; 4 | 5 | type layoutProps = { 6 | body: JSX.Element; 7 | footerHint?: JSX.Element; 8 | footer?: JSX.Element; 9 | noFooterHintLayout?: boolean; 10 | }; 11 | 12 | export default function LayoutComponent(props: layoutProps) { 13 | const [keyboardStatusShown, setKeyboardStatusShown] = useState(); 14 | 15 | useEffect(() => { 16 | const showSubscription = Keyboard.addListener('keyboardDidShow', () => { 17 | setKeyboardStatusShown(true); 18 | }); 19 | const hideSubscription = Keyboard.addListener('keyboardDidHide', () => { 20 | setKeyboardStatusShown(false); 21 | }); 22 | 23 | return () => { 24 | showSubscription.remove(); 25 | hideSubscription.remove(); 26 | }; 27 | }, []); 28 | return ( 29 | 30 | {props.body && {props.body}} 31 | {props.footerHint && !keyboardStatusShown ? ( 32 | {props.footerHint} 33 | ) : ( 34 | <> 35 | {!props.noFooterHintLayout && ( 36 | 37 |   38 | 39 | )} 40 | 41 | )} 42 | {props.footer && {props.footer}} 43 | 44 | ); 45 | } 46 | 47 | const layoutStyles = StyleSheet.create({ 48 | container: { 49 | flex: 1, 50 | flexDirection: 'column', 51 | justifyContent: 'space-between', 52 | padding: 16, 53 | backgroundColor: '#FDFEFF', 54 | }, 55 | body: { flex: 3 }, 56 | footerHint: { flex: 1, justifyContent: 'flex-end' }, 57 | nofooterHint: { flex: 1.3, justifyContent: 'flex-end' }, 58 | footer: { 59 | flex: 1.2, 60 | flexDirection: 'column', 61 | justifyContent: 'flex-start', 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/molecules/TCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'react-native-paper'; 3 | 4 | type TCheckboxType = React.ComponentProps; 5 | 6 | export default function TCheckbox(props: TCheckboxType) { 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/molecules/TTextInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import { Text, TextInput } from 'react-native-paper'; 4 | import theme from '../../utils/theme'; 5 | 6 | export type TTextInputProps = React.ComponentProps & { errorText?: string }; 7 | 8 | export default function TTextInput(props: TTextInputProps) { 9 | const showError: boolean = !!props.errorText && props.errorText.length > 0; 10 | 11 | return ( 12 | <> 13 | 14 | {showError && {props.errorText}} 15 | 16 | ); 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | errorText: { 21 | color: theme.colors.error, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": { 3 | "primaryColor": "#000000", 4 | "disabled": "#939393", 5 | "secondaryColor": "#EDF2F9", 6 | "secondaryColor2": "#6E84A3", 7 | "tertiaryColor": "#7660E7", 8 | "tertiaryColor2": "#CFC8F7", 9 | "text": "#000000", 10 | "textGray": "#000000", 11 | "linkColor": "#4CAF50", 12 | "infoBackground": "#F6F9FB", 13 | "lightBackground": "#F6F9FB" 14 | }, 15 | "appLogoUrl": "./src/assets/pangea/favicon.png", 16 | "appName": "Tonomy ID Development", 17 | "ecosystemName": "Tonomy Development", 18 | "appSlogan": "Building Ecosystem Of Trust", 19 | "images": { 20 | "splash": "./src/assets/pangea/pangea-splash.png", 21 | "logo48": "./src/assets/pangea/favicon.png", 22 | "logo1024": "./src/assets/pangea/pangea-large-logo.png" 23 | }, 24 | "links": { 25 | "privacyPolicyLearnMore": "#", 26 | "termsAndConditionsLearnMore": "#", 27 | "usernameLearnMore": "#", 28 | "passwordLearnMore": "#", 29 | "privacyLearnMore": "#", 30 | "transparencyLearnMore": "#", 31 | "securityLearnMore": "#" 32 | }, 33 | "expoProjectId": "8b58b886-eaa6-4a7e-bded-3804f4402490", 34 | "accountSuffix": ".stag.tonomy.id", 35 | "blockchainUrl": "https://blockchain-api-staging.tonomy.foundation", 36 | "ssoWebsiteOrigin": "https://accounts.staging.tonomy.foundation", 37 | "communicationUrl": "wss://communication.staging.tonomy.foundation", 38 | "accountsServiceUrl": "https://communication.staging.tonomy.foundation", 39 | "blockExplorerUrl": "https://local.bloks.io", 40 | "captchaSiteKey": "95008dde-06bd-4713-bb8d-4b1e578dad6a", 41 | "tonomyIdSlug": "tonomy-id-development://", 42 | "infuraKey": "77422fa49acc4b4eb189abd416350cd8", 43 | "etherscanApiKey": "ESTDTCEUZWS9R56V3FYTXBNAFVQE3EZVIT", 44 | "walletConnectProjectId": "850cdd095217e599acfd5f9f01d7b1ba", 45 | "currencySymbol": "TONO" 46 | } -------------------------------------------------------------------------------- /src/config/config.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": { 3 | "primaryColor": "#5833BC", 4 | "disabled": "#C2B8E6", 5 | "secondaryColor": "#EDF2F9", 6 | "secondaryColor2": "#1ACFD3", 7 | "tertiaryColor": "#7660E7", 8 | "tertiaryColor2": "#CFC8F7", 9 | "text": "#000000", 10 | "textGray": "#000000", 11 | "linkColor": "#5833BC", 12 | "infoBackground": "#F6F9FB", 13 | "lightBackground": "#F6F9FB" 14 | }, 15 | "appLogoUrl": "./src/assets/tonomyProduction/favicon.png", 16 | "appName": "Tonomy ID", 17 | "ecosystemName": "Tonomy", 18 | "appSlogan": "One Identity. Infinite Access", 19 | "images": { 20 | "splash": "./src/assets/tonomyProduction/tonomy-splash.png", 21 | "logo48": "./src/assets/tonomyProduction/favicon.png", 22 | "logo1024": "./src/assets/tonomyProduction/logo1024x1024.png" 23 | }, 24 | "links": { 25 | "privacyPolicyLearnMore": "#", 26 | "termsAndConditionsLearnMore": "#", 27 | "usernameLearnMore": "#", 28 | "passwordLearnMore": "#", 29 | "privacyLearnMore": "#", 30 | "transparencyLearnMore": "#", 31 | "securityLearnMore": "#" 32 | }, 33 | "expoProjectId": "fdea0cd3-2296-4fd8-a79e-7ccc583196e7", 34 | "accountSuffix": ".pangea.id", 35 | "blockchainUrl": "https://blockchain-api.tonomy.io", 36 | "ssoWebsiteOrigin": "https://accounts.tonomy.io", 37 | "communicationUrl": "wss://communication.tonomy.io", 38 | "accountsServiceUrl": "https://communication.tonomy.io", 39 | "captchaSiteKey": "0caf1023-edb9-466c-b848-6f2e0fe975eb", 40 | "blockExplorerUrl": "https://explorer.tonomy.io", 41 | "tonomyIdSlug": "united-wallet://", 42 | "infuraKey": "77422fa49acc4b4eb189abd416350cd8", 43 | "etherscanApiKey": "ID6DBNPAV79QFD8M784Y7SH58HNDDP25PZ", 44 | "walletConnectProjectId": "850cdd095217e599acfd5f9f01d7b1ba", 45 | "currencySymbol": "TONO" 46 | } -------------------------------------------------------------------------------- /src/config/config.staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": { 3 | "primaryColor": "#67D7ED", 4 | "disabled": "#D2F3FA", 5 | "secondaryColor": "#EDF2F9", 6 | "secondaryColor2": "#6E84A3", 7 | "tertiaryColor": "#7660E7", 8 | "tertiaryColor2": "#CFC8F7", 9 | "text": "#474D4C", 10 | "textGray": "#5B6261", 11 | "linkColor": "#67D7ED", 12 | "infoBackground": "#e1f2e2", 13 | "lightBackground": "#F6F9FB" 14 | }, 15 | "appLogoUrl": "./src/assets/tonomy/tonomy-logo48.png", 16 | "appName": "Tonomy ID Staging", 17 | "ecosystemName": "Tonomy Staging", 18 | "appSlogan": "Building Ecosystem Of Trust", 19 | "images": { 20 | "splash": "./src/assets/tonomy/tonomy-splash.png", 21 | "logo48": "./src/assets/tonomy/tonomy-logo48.png", 22 | "logo1024": "./src/assets/tonomy/tonomy-logo1024.png" 23 | }, 24 | "links": { 25 | "privacyPolicyLearnMore": "#", 26 | "termsAndConditionsLearnMore": "#", 27 | "usernameLearnMore": "#", 28 | "passwordLearnMore": "#", 29 | "privacyLearnMore": "#", 30 | "transparencyLearnMore": "#", 31 | "securityLearnMore": "#" 32 | }, 33 | "expoProjectId": "b7d1c77f-3535-492c-8f55-0ba569ccdc93", 34 | "accountSuffix": ".stag.tonomy.id", 35 | "blockchainUrl": "https://blockchain-api-staging.tonomy.foundation", 36 | "ssoWebsiteOrigin": "https://accounts.staging.tonomy.foundation", 37 | "communicationUrl": "wss://communication.staging.tonomy.foundation", 38 | "accountsServiceUrl": "https://communication.staging.tonomy.foundation", 39 | "captchaSiteKey": "95008dde-06bd-4713-bb8d-4b1e578dad6a", 40 | "blockExplorerUrl": "https://local.bloks.io", 41 | "tonomyIdSlug": "tonomy-id-staging://", 42 | "infuraKey": "77422fa49acc4b4eb189abd416350cd8", 43 | "etherscanApiKey": "8V6DEHQ64R18FFNX2B5S345IXXNZRJVVQX", 44 | "walletConnectProjectId": "650e5b61be5fadae5d817ff12daa1283", 45 | "currencySymbol": "TONO" 46 | } -------------------------------------------------------------------------------- /src/config/config.testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": { 3 | "primaryColor": "#5833BC", 4 | "disabled": "#C2B8E6", 5 | "secondaryColor": "#EDF2F9", 6 | "secondaryColor2": "#1ACFD3", 7 | "tertiaryColor": "#7660E7", 8 | "tertiaryColor2": "#CFC8F7", 9 | "text": "#000000", 10 | "textGray": "#000000", 11 | "linkColor": "#5833BC", 12 | "infoBackground": "#F6F9FB", 13 | "lightBackground": "#F6F9FB" 14 | }, 15 | "appLogoUrl": "./src/assets/tonomyProduction/favicon.png", 16 | "appName": "Tonomy ID Testnet", 17 | "ecosystemName": "Tonomy Testnet", 18 | "appSlogan": "Building Ecosystem Of Trust", 19 | "images": { 20 | "splash": "./src/assets/tonomyProduction/tonomy-splash.png", 21 | "logo48": "./src/assets/tonomyProduction/favicon.png", 22 | "logo1024": "./src/assets/tonomyProduction/logo1024x1024.png" 23 | }, 24 | "links": { 25 | "privacyPolicyLearnMore": "#", 26 | "termsAndConditionsLearnMore": "#", 27 | "usernameLearnMore": "#", 28 | "passwordLearnMore": "#", 29 | "privacyLearnMore": "#", 30 | "transparencyLearnMore": "#", 31 | "securityLearnMore": "#" 32 | }, 33 | "expoProjectId": "b227858a-74c6-4996-8645-33b7f28f8980", 34 | "accountSuffix": ".testnet.pangea", 35 | "blockchainUrl": "https://blockchain-api-testnet.tonomy.io", 36 | "ssoWebsiteOrigin": "https://accounts.testnet.tonomy.io", 37 | "communicationUrl": "wss://communication.testnet.tonomy.io", 38 | "accountsServiceUrl": "https://communication.testnet.tonomy.io", 39 | "blockExplorerUrl": "https://explorer.testnet.tonomy.io", 40 | "captchaSiteKey": "bf994ca7-3b1f-43c4-b35f-0cf27b0da3c8", 41 | "tonomyIdSlug": "pangea-testnet://", 42 | "infuraKey": "77422fa49acc4b4eb189abd416350cd8", 43 | "etherscanApiKey": "8V6DEHQ64R18FFNX2B5S345IXXNZRJVVQX", 44 | "walletConnectProjectId": "650e5b61be5fadae5d817ff12daa1283", 45 | "currencySymbol": "TONO" 46 | } -------------------------------------------------------------------------------- /src/containers/HomeScreenContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View, Image } from 'react-native'; 3 | import { TButtonContained, TButtonOutlined } from '../components/atoms/TButton'; 4 | import LayoutComponent from '../components/layout'; 5 | import { TH1, TH4 } from '../components/atoms/THeadings'; 6 | import theme, { commonStyles } from '../utils/theme'; 7 | import settings from '../settings'; 8 | import { Props } from '../screens/HomeScreen'; 9 | import { Images } from '../assets'; 10 | 11 | export default function HomeScreenContainer({ navigation }: { navigation: Props['navigation'] }) { 12 | return ( 13 | 16 | {/* TODO: uncomment link */} 17 | {/* Need Help? */} 18 | 19 | 20 | 21 | {settings.config.appSlogan} 22 | 23 | 24 | } 25 | footer={ 26 | 27 | navigation.navigate('CreateAccountUsername')} 30 | style={commonStyles.marginBottom} 31 | > 32 | Create Account 33 | 34 | navigation.navigate('LoginUsername')} 37 | style={commonStyles.marginBottom} 38 | > 39 | Login 40 | 41 | 42 | } 43 | > 44 | ); 45 | } 46 | 47 | const styles = StyleSheet.create({ 48 | header: { 49 | flex: 1, 50 | }, 51 | link: { 52 | color: settings.config.theme.primaryColor, 53 | textDecorationLine: 'underline', 54 | fontSize: 14, 55 | fontWeight: '400', 56 | }, 57 | headerButton: { 58 | alignSelf: 'flex-end', 59 | }, 60 | imgContainer: { 61 | flex: 1, 62 | justifyContent: 'flex-end', 63 | alignItems: 'center', 64 | }, 65 | logo: { 66 | alignSelf: 'center', 67 | height: '40%', 68 | resizeMode: 'contain', 69 | }, 70 | sloganText: { 71 | color: theme.colors.black, 72 | fontWeight: '600', 73 | fontSize: 15, 74 | marginTop: 10, 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /src/containers/SuccessUnstakeContainer.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Image, View } from 'react-native'; 2 | import { Props } from '../screens/SuccessUnstakeScreen'; 3 | import theme from '../utils/theme'; 4 | import { TButtonContained } from '../components/atoms/TButton'; 5 | import { TH1, TP } from '../components/atoms/THeadings'; 6 | import { IChain } from '../utils/chain/types'; 7 | import { useState } from 'react'; 8 | import TSpinner from '../components/atoms/TSpinner'; 9 | import settings from '../settings'; 10 | 11 | export type SuccessUnstakeProps = { 12 | navigation: Props['navigation']; 13 | chain: IChain; 14 | }; 15 | 16 | const SuccessUnstakeContainer = ({ navigation, chain }: SuccessUnstakeProps) => { 17 | const [loading, setLoading] = useState(false); 18 | 19 | const backToTONO = () => { 20 | setLoading(true); 21 | navigation.navigate('AssetManager', { chain }); 22 | setLoading(false); 23 | }; 24 | 25 | return ( 26 | 27 | 28 | {'Unstaking Completed'} 29 | Your assets have been unstaked and are no longer earning rewards 30 | 31 | backToTONO()} 37 | > 38 | Back to {settings.config.currencySymbol} 39 | 40 | 41 | 42 | ); 43 | }; 44 | const styles = StyleSheet.create({ 45 | container: { 46 | flex: 1, 47 | backgroundColor: '#fff', 48 | marginHorizontal: 15, 49 | alignItems: 'center', 50 | marginTop: 20, 51 | }, 52 | vestedHead: { marginTop: 8, marginBottom: 0, paddingHorizontal: 20 }, 53 | vestedSubHead: { 54 | paddingHorizontal: 25, 55 | fontSize: 17, 56 | textAlign: 'center', 57 | marginTop: 7, 58 | color: theme.colors.grey9, 59 | }, 60 | bottomView: { 61 | position: 'absolute', 62 | bottom: 40, 63 | width: '100%', 64 | alignItems: 'center', 65 | }, 66 | backBtn: { 67 | borderRadius: 6, 68 | backgroundColor: theme.colors.backgroundGray, 69 | width: '94%', 70 | paddingVertical: 15, 71 | }, 72 | }); 73 | 74 | export default SuccessUnstakeContainer; 75 | -------------------------------------------------------------------------------- /src/navigation/Drawer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDrawerNavigator, DrawerNavigationOptions } from '@react-navigation/drawer'; 3 | import CustomDrawer from '../components/CustomDrawer'; 4 | import { useAppTheme } from '../utils/theme'; 5 | import BottomTabNavigator from './BottomTabNavigator'; 6 | import SettingsScreen from '../screens/SettingsScreen'; 7 | import { TouchableOpacity } from 'react-native'; 8 | import MenuIcon from '../assets/icons/MenuIcon'; 9 | import SupportScreen from '../screens/SupportScreen'; 10 | 11 | export type DrawerStackParamList = { 12 | UserHome: { did?: string }; 13 | Settings: undefined; 14 | Support: undefined; 15 | Help: undefined; 16 | Logout: undefined; 17 | ChangePin: undefined; 18 | SSO: { payload: string; platform: 'mobile' | 'browser' }; 19 | BottomTabs: undefined; 20 | }; 21 | 22 | const Drawer = createDrawerNavigator(); 23 | 24 | export default function DrawerNavigation() { 25 | const theme = useAppTheme(); 26 | const defaultScreenOptions: DrawerNavigationOptions = { 27 | headerTitleStyle: { 28 | fontSize: 16, 29 | fontWeight: '500', 30 | color: theme.colors.text, 31 | }, 32 | headerTitleAlign: 'center', 33 | headerTintColor: theme.dark ? theme.colors.text : 'black', 34 | }; 35 | 36 | const drawerScreenOptions = ({ navigation }) => ({ 37 | headerLeft: () => ( 38 | navigation.toggleDrawer()} 41 | > 42 | 43 | 44 | ), 45 | }); 46 | 47 | return ( 48 | } 50 | initialRouteName="BottomTabs" 51 | screenOptions={defaultScreenOptions} 52 | > 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/navigation/Settings.tsx: -------------------------------------------------------------------------------- 1 | import { createNativeStackNavigator, NativeStackNavigationOptions } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import { Platform } from 'react-native'; 4 | import { useNavigation } from '@react-navigation/native'; 5 | import { IconButton, useTheme } from 'react-native-paper'; 6 | import SettingsScreen from '../screens/SettingsScreen'; 7 | import { TouchableOpacity, StyleSheet } from 'react-native'; 8 | import { AppTheme } from '../utils/theme'; 9 | 10 | export type SettingsStackParamList = { 11 | Splash: undefined; 12 | Settings: undefined; 13 | }; 14 | 15 | const Stack = createNativeStackNavigator(); 16 | 17 | export default function SettingsNavigation() { 18 | // Setup styles 19 | const theme = useTheme(); 20 | 21 | const navigation = useNavigation(); 22 | const backButton = () => { 23 | return ( 24 | // @ts-expect-error no overload matches this call 25 | // FIXME: fix type error 26 | navigation.navigate('Assets')}> 27 | 33 | 34 | ); 35 | }; 36 | const defaultScreenOptions: NativeStackNavigationOptions = { 37 | headerTitleStyle: { 38 | fontSize: 16, 39 | fontWeight: '500', 40 | color: theme.colors.text, 41 | }, 42 | headerTitleAlign: 'center', 43 | headerTintColor: theme.dark ? theme.colors.text : 'black', 44 | }; 45 | 46 | return ( 47 | 48 | 53 | 54 | ); 55 | } 56 | 57 | const styles = StyleSheet.create({ 58 | iosIcon: { 59 | paddingBottom: 10, 60 | marginLeft: -30, 61 | marginTop: -2, 62 | }, 63 | androidIcon: { 64 | paddingBottom: 10, 65 | marginLeft: -4, 66 | marginBottom: -10, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/providers/ErrorHandler.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import { useRef } from 'react'; 3 | import { useEffect } from 'react'; 4 | import useErrorStore from '../store/errorStore'; 5 | import TErrorModal from '../components/TErrorModal'; 6 | import setErrorHandlers from '../utils/exceptions'; 7 | 8 | export default function ErrorHandlerProvider() { 9 | const [showModal, setShowModal] = useState(false); 10 | 11 | const errorStore = useErrorStore(); 12 | const { onClose, unSetError } = errorStore; 13 | 14 | setErrorHandlers(errorStore); 15 | 16 | const onModalPress = useCallback(async () => { 17 | const oldOnClose = onClose; 18 | 19 | unSetError(); 20 | if (oldOnClose) await oldOnClose(); 21 | setShowModal(false); 22 | }, [onClose, unSetError]); 23 | 24 | // gets the initial value of the error state 25 | const errorRef = useRef(useErrorStore.getState()); 26 | 27 | useEffect(() => { 28 | const unsubscribe = useErrorStore.subscribe((state) => { 29 | // Only update the modal if there's a change in the error state 30 | if (JSON.stringify(state.error) === JSON.stringify(errorRef.current.error)) return; 31 | 32 | if (state.error !== errorRef.current.error) { 33 | errorRef.current.error = state.error; 34 | errorRef.current.title = state.title; 35 | errorRef.current.expected = state.expected; 36 | 37 | if (state.error) { 38 | setShowModal(true); 39 | } else { 40 | setShowModal(false); 41 | } 42 | } 43 | }); 44 | 45 | return () => { 46 | unsubscribe(); 47 | }; 48 | }, []); 49 | 50 | return ( 51 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/providers/InitializeApp.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { View } from 'react-native'; 3 | import Debug from 'debug'; 4 | import { connect } from '../utils/StorageManager/setup'; 5 | import TSpinner from '../components/atoms/TSpinner'; 6 | import useErrorStore from '../store/errorStore'; 7 | import settings from '../settings'; 8 | import { runTests } from '../utils/runtimeTests'; 9 | 10 | const debug = Debug('tonomy-id:providers:InitializeApp'); 11 | 12 | const InitializeAppProvider: React.FC = () => { 13 | const [RootNavigation, setRootNavigation] = useState(null); 14 | 15 | const { setError } = useErrorStore(); 16 | 17 | useEffect(() => { 18 | const initialize = async () => { 19 | try { 20 | debug('App setup started'); 21 | await connect(); 22 | debug('Storage connected'); 23 | // need import dynamically, to ensure that it's sub-components do not call getSettings() 24 | // from @tonomy/tonomy-id-sdk before setSettings() is called, or try connect to storage before 25 | // it's connected 26 | // @ts-expect-error dynamic imports only supported in ESM 27 | const { default: Root } = await import('../navigation/Root'); 28 | 29 | debug('Root navigation loaded'); 30 | 31 | setRootNavigation(() => Root); 32 | } catch (error) { 33 | setError({ 34 | error, 35 | title: 'App Setup Failed', 36 | expected: false, 37 | }); 38 | } 39 | }; 40 | 41 | const runRuntimeTests = async () => { 42 | try { 43 | if (settings.isProduction()) return; 44 | await runTests(); 45 | } catch (error) { 46 | setError({ 47 | error, 48 | title: 'Runtime Test Failed', 49 | expected: false, 50 | }); 51 | } 52 | }; 53 | 54 | initialize(); 55 | runRuntimeTests(); 56 | }, [setError]); 57 | 58 | if (!RootNavigation) { 59 | return ( 60 | 61 | 62 | 63 | ); 64 | } 65 | 66 | return ; 67 | }; 68 | 69 | export default InitializeAppProvider; 70 | -------------------------------------------------------------------------------- /src/providers/Notifications.ts: -------------------------------------------------------------------------------- 1 | import * as Notifications from 'expo-notifications'; 2 | 3 | export default function NotificationsProvider() { 4 | Notifications.setNotificationHandler({ 5 | handleNotification: async () => ({ 6 | shouldShowAlert: true, 7 | shouldPlaySound: true, 8 | shouldSetBadge: true, 9 | }), 10 | }); 11 | 12 | return null; 13 | } 14 | -------------------------------------------------------------------------------- /src/screens/AppsScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppsContainer from '../containers/AppsContainer'; 3 | 4 | export default function AppsScreen() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /src/screens/AssetListingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AssetsContainer from '../containers/AssetsContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type AssetsScreenNavigationProp = NativeStackScreenProps; 7 | 8 | export default function AssetListingScreen(props: AssetsScreenNavigationProp) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/AssetManagerScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AssetManagerContainer from '../containers/AssetManagerContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function AssetManagerScreen(props: Props) { 9 | return ( 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/screens/CitizenshipScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import CitizenshipContainer from '../containers/CitienshipContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function CitizenshipScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/ConfirmPassphraseScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ConfirmPassphraseWordContainer from '../containers/ConfirmPassphraseContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function ConfirmPassphraseScreen(props: Props) { 9 | return ( 10 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/screens/ConfirmStakingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ConfirmStakingContainer from '../containers/ConfirmStakingContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function ConfirmStakingScreen(props: Props) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/screens/ConfirmUnstakingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ConfirmUnStakingContainer from '../containers/ConfirmUnStakingContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function ConfirmUnstakingScreen(props: Props) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/screens/CreateAccountUsernameScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CreateAccountUsernameContainer from '../containers/CreateAccountUsernameContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function CreateAccountUsernameScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/CreateEthereumKeyScreen.tsx: -------------------------------------------------------------------------------- 1 | import CreateEthereumKeyContainer from '../containers/CreateEthereumKeyContainer'; 2 | import React from 'react'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function CreateEthereumKeyScreen(props: Props) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/screens/CreatePassphraseScreen.tsx: -------------------------------------------------------------------------------- 1 | import CreatePassphraseContainer from '../containers/CreatePassphraseContainer'; 2 | import React from 'react'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function CreatePassphraseScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/ExploreScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import ExploreContainer from '../containers/ExploreContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function ExploreScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/HcaptchaScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HcaptchaContainer from '../containers/HcaptchaContainer'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function HcaptchaScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import HomeScreenContainer from '../containers/HomeScreenContainer'; 2 | import React from 'react'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function HomeScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/LoginPassphraseScreen.tsx: -------------------------------------------------------------------------------- 1 | import LoginPassphraseContainer from '../containers/LoginPassphraseContainer'; 2 | import React from 'react'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function LoginPassphraseScreen(props: Props) { 9 | return ( 10 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/screens/LoginUsernameScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import LoginUsernameContainer from '../containers/LoginUsernameContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function LoginUsernameScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/MainSplashScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MainSplashScreenContainer from '../containers/MainSplashContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function MainSplashScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/OnboardingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import OnboardingContainer from '../containers/OnboardingContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function OnboardingScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/PrivacyAndPolicyScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import PrivacyAndPolicyContainer from '../containers/PrivacyAndPolicyContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function PrivacyAndPolicyScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/ProfilePreviewScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import ProfilePreviewContainer from '../containers/ProfilePreviewContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function ProfilePreviewScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/ReceiveAssetScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReceiveAssetContainer from '../containers/ReceiveAssetContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type ReceiveAssetScreenNavigationProp = NativeStackScreenProps; 7 | 8 | export default function ReceiveAssetScreen(props: ReceiveAssetScreenNavigationProp) { 9 | return ( 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/screens/SSOLoginScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import SSOLoginContainer from '../containers/SSOLoginContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type SSOLoginScreenProps = NativeStackScreenProps; 7 | 8 | export default function SSOLoginScreen(props: SSOLoginScreenProps) { 9 | return ( 10 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/screens/ScanQRScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RouteStackParamList } from '../navigation/Root'; 3 | import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; 4 | import ScanQRCodeContainer from '../containers/ScanQRCodeContainer'; 5 | 6 | export type ScanQRScreenProps = BottomTabScreenProps; 7 | 8 | export default function ScanQRScreen(props: ScanQRScreenProps) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/SelectAssetScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SelectAssetContainer from '../containers/SelectAssetContainer'; 3 | 4 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 5 | import { RouteStackParamList } from '../navigation/Root'; 6 | 7 | export type SelectAssetScreenNavigationProp = NativeStackScreenProps; 8 | 9 | export default function SelectAssetScreen(props: SelectAssetScreenNavigationProp) { 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /src/screens/SendAssetScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SendAssetContainer from '../containers/SendAssetContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type SendAssetScreenNavigationProp = NativeStackScreenProps; 7 | 8 | export default function SendAssetScreen(props: SendAssetScreenNavigationProp) { 9 | return ( 10 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/screens/SettingsScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import SettingsContainer from '../containers/SettingsContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function SettingsScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/SignTransactionConsentScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import SignTransactionConsentContainer from '../containers/SignTransactionConsentContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function SignTransactionConsentScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/SignTransactionConsentSuccessScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import SignTransactionConsentSuccessContainer from '../containers/SignTransactionConsentSuccessContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function SignTransactionConsentSuccessScreen(props: Props) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/screens/StakeAssetDetailScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import StakeAssetDetailContainer from '../containers/StakeAssetDetailContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function StakeAssetDetailScreen(props: Props) { 9 | return ( 10 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/screens/StakeAssetScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import StakeAssetContainer from '../containers/StakeAssetContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function StakeAssetScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/SuccessUnstakeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SuccessUnstakeContainer from '../containers/SuccessUnstakeContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function SuccessUnstakeScreen(props: Props) { 9 | return ( 10 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/screens/SupportScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import SupportContainer from '../containers/SupportContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function SupportScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/TermsAndConditionScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3 | import { RouteStackParamList } from '../navigation/Root'; 4 | import TermsAndConditionContainer from '../containers/TermsAndConditionContainer'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function TermsAndConditionScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/VestedAssetsScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import VestedAssetsContainer from '../containers/VestedAssetsContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type VestedAssetscreenNavigationProp = NativeStackScreenProps; 7 | 8 | export default function VestedAssetsScreen(props: VestedAssetscreenNavigationProp) { 9 | return ( 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/screens/VestedSuccessScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import VestedSuccessContainer from '../containers/VestedSuccessContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function VestedSuccessScreen(props: Props) { 9 | return ( 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/screens/WalletConnectLoginScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 | import React from 'react'; 3 | import WalletConnectLoginContainer from '../containers/WalletConnectLoginContainer'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function WalletConnectLoginScreen(props: Props) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/screens/WithdrawVestedScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WithDrawVestedContainer from '../containers/WithDrawVestedContainer'; 3 | import { NativeStackScreenProps } from '@react-navigation/native-stack'; 4 | import { RouteStackParamList } from '../navigation/Root'; 5 | 6 | export type Props = NativeStackScreenProps; 7 | 8 | export default function WithdrawVestedScreen(props: Props) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/store/errorStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import Debug from 'debug'; 3 | import { serializeAny } from '../utils/strings'; 4 | 5 | const debug = Debug('tonomy-id:store:errorStore'); 6 | 7 | export interface ErrorState { 8 | error?: Error; 9 | title?: string; 10 | expected?: boolean; 11 | onClose?: () => Promise; 12 | setError: ({ 13 | error, 14 | title, 15 | expected, 16 | onClose, 17 | }: { 18 | error: Error; 19 | title?: string; 20 | expected?: boolean; 21 | onClose?: () => Promise; 22 | }) => void; 23 | unSetError: () => void; 24 | } 25 | 26 | const useErrorStore = create((set, get) => ({ 27 | error: undefined, 28 | title: undefined, 29 | expected: undefined, 30 | onClose: undefined, 31 | setError: async ({ 32 | error, 33 | title = 'Something went wrong', 34 | expected, 35 | onClose, 36 | }: { 37 | error: Error | unknown; 38 | title?: string; 39 | expected?: boolean; 40 | onClose?: () => Promise; 41 | }) => { 42 | debug('setError', error instanceof Error ? error.message : error); 43 | const currentState = get(); 44 | 45 | // Only show an error if it is not already shown 46 | if (currentState.error || currentState.title) { 47 | debug('Error already set', currentState); 48 | return; 49 | } 50 | 51 | const newError = error instanceof Error ? error : new Error(serializeAny(error)); 52 | 53 | set((state) => { 54 | if ( 55 | state.error?.message !== newError.message || 56 | state.title !== title || 57 | state.expected !== expected || 58 | state.onClose !== onClose 59 | ) { 60 | return { error: newError, title, expected, onClose }; 61 | } 62 | 63 | return state; // No change, do not update state 64 | }); 65 | }, 66 | unSetError: () => { 67 | debug('unSetError'); 68 | set({ error: undefined, title: undefined, expected: undefined, onClose: undefined }); 69 | }, 70 | })); 71 | 72 | export default useErrorStore; 73 | -------------------------------------------------------------------------------- /src/store/passphraseStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import settings from '../settings'; 3 | import { util } from '@tonomy/tonomy-id-sdk'; 4 | import { ApplicationErrors, throwError } from '../utils/errors'; 5 | 6 | interface PassphraseStoreState { 7 | passphraseList: string[]; 8 | randomWordIndexes: number[]; 9 | confirmPassphraseWords: string[]; 10 | } 11 | 12 | interface PassphraseStoreActions { 13 | getPassphrase: () => string; 14 | generatePassphraseList: () => void; 15 | unsetPassphraseList: () => void; 16 | checkWordAtIndex: (index: number, word: string) => boolean; 17 | setConfirmPassphraseWord: (index: number, word: string) => void; 18 | unsetConfirmPassphraseWord: () => void; 19 | } 20 | 21 | type PassphraseStore = PassphraseStoreState & PassphraseStoreActions; 22 | 23 | function generate3PassphraseIndexes(): number[] { 24 | const randomWordIndexesList = [] as number[]; 25 | 26 | while (randomWordIndexesList.length < 3) { 27 | const randomValue = util.randomNumber(0, 5); 28 | 29 | if (!randomWordIndexesList.includes(randomValue)) { 30 | randomWordIndexesList.push(randomValue); 31 | } 32 | } 33 | 34 | return randomWordIndexesList; 35 | } 36 | 37 | export const DEFAULT_DEV_PASSPHRASE_LIST = ['above', 'day', 'fever', 'lemon', 'piano', 'sport']; 38 | 39 | const usePassphraseStore = create((set, get) => ({ 40 | passphraseList: settings.isProduction() ? util.generateRandomKeywords() : DEFAULT_DEV_PASSPHRASE_LIST, 41 | randomWordIndexes: generate3PassphraseIndexes(), 42 | confirmPassphraseWords: ['', '', ''], 43 | getPassphrase: () => { 44 | const list = get().passphraseList; 45 | 46 | if (list.length === 0) { 47 | throwError('Passphrase list is empty', ApplicationErrors.NoDataFound); 48 | } 49 | 50 | return list.join(' '); 51 | }, 52 | generatePassphraseList: () => { 53 | set({ passphraseList: util.generateRandomKeywords() }); 54 | const randomWordIndexesList = generate3PassphraseIndexes(); 55 | 56 | set({ randomWordIndexes: randomWordIndexesList }); 57 | }, 58 | unsetPassphraseList: () => { 59 | set({ passphraseList: settings.isProduction() ? [] : DEFAULT_DEV_PASSPHRASE_LIST }); 60 | }, 61 | unsetConfirmPassphraseWord: () => { 62 | set({ confirmPassphraseWords: [] }); 63 | }, 64 | checkWordAtIndex: (index, word) => { 65 | const { passphraseList } = get(); 66 | 67 | return passphraseList[index] === word; 68 | }, 69 | setConfirmPassphraseWord: (index, word) => { 70 | const { confirmPassphraseWords } = get(); 71 | const updatedPassphraseWord = [...confirmPassphraseWords]; 72 | 73 | updatedPassphraseWord[index] = word; 74 | set({ confirmPassphraseWords: updatedPassphraseWord }); 75 | }, 76 | })); 77 | 78 | export default usePassphraseStore; 79 | -------------------------------------------------------------------------------- /src/store/sessionStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { WalletConnectSession } from '../utils/session/walletConnect'; 3 | import { AntelopeSession } from '../utils/session/antelope'; 4 | 5 | interface SessionState { 6 | walletConnectSession: WalletConnectSession | null; 7 | antelopeSession: AntelopeSession | null; 8 | initializeSession: () => Promise; 9 | } 10 | 11 | export const useSessionStore = create((set, get) => ({ 12 | walletConnectSession: null, 13 | antelopeSession: null, 14 | initializeSession: async () => { 15 | const walletConnectSession = new WalletConnectSession(); 16 | 17 | await walletConnectSession.initialize(); 18 | walletConnectSession.onEvent(); 19 | 20 | const antelopeSession = new AntelopeSession(); 21 | 22 | // Set the session in the store 23 | set({ walletConnectSession, antelopeSession }); 24 | }, 25 | })); 26 | -------------------------------------------------------------------------------- /src/store/useAppSettings.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | import { appStorage } from '../utils/StorageManager/setup'; 3 | import { useFocusEffect } from '@react-navigation/native'; 4 | import useErrorStore from './errorStore'; 5 | 6 | function useAppSettings() { 7 | const [developerMode, setDeveloperMode] = useState(false); 8 | const errorStore = useErrorStore(); 9 | 10 | useFocusEffect( 11 | useCallback(() => { 12 | const fetchData = async () => { 13 | try { 14 | const mode = await appStorage.getDeveloperMode(); 15 | 16 | setDeveloperMode(mode); 17 | } catch (error) { 18 | errorStore.setError({ error, expected: false }); 19 | } 20 | }; 21 | 22 | fetchData(); 23 | }, [errorStore]) 24 | ); 25 | 26 | const setDeveloperModeSettings = async (mode: boolean) => { 27 | try { 28 | setDeveloperMode(mode); 29 | await appStorage.setDeveloperMode(mode); 30 | } catch (error) { 31 | errorStore.setError({ error, expected: false }); 32 | } 33 | }; 34 | 35 | return { developerMode, setDeveloperModeSettings }; 36 | } 37 | 38 | export default useAppSettings; 39 | -------------------------------------------------------------------------------- /src/types/images.d.ts: -------------------------------------------------------------------------------- 1 | // types/images.d.ts 2 | declare module '*.png' { 3 | const value: any; 4 | export default value; 5 | } 6 | 7 | declare module '*.svg' { 8 | import * as React from 'react'; 9 | import { SvgProps } from 'react-native-svg'; 10 | const content: React.FC; 11 | export default content; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/StorageManager/entities/appSettings.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import { Entity, Unique, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; 3 | 4 | @Entity('AppStorage') 5 | @Unique(['name']) 6 | export class AppStorage { 7 | @PrimaryGeneratedColumn() 8 | id!: number; 9 | 10 | @Column({ 11 | unique: true, 12 | type: 'varchar', 13 | }) 14 | @Index() 15 | name!: string; 16 | 17 | @Column({ type: 'varchar' }) 18 | value!: string; 19 | 20 | @Column({ type: 'datetime' }) 21 | createdAt!: Date; 22 | 23 | @Column({ type: 'datetime' }) 24 | updatedAt!: Date; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/StorageManager/entities/assetStorage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import { Entity, Unique, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; 3 | 4 | @Entity('AssetStorage') 5 | @Unique(['assetName']) 6 | export class AssetStorage { 7 | @PrimaryGeneratedColumn() 8 | id!: number; 9 | 10 | @Column({ 11 | unique: true, 12 | type: 'varchar', 13 | }) 14 | @Column() 15 | @Index() 16 | assetName!: string; 17 | 18 | @Column({ type: 'varchar' }) 19 | accountName!: string; 20 | 21 | @Column({ type: 'varchar' }) 22 | balance!: string; 23 | 24 | @Column({ type: 'int' }) 25 | usdBalance!: number; 26 | 27 | @Column({ type: 'datetime' }) 28 | createdAt!: Date; 29 | 30 | @Column({ type: 'datetime' }) 31 | updatedAt!: Date; 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/StorageManager/entities/keyStorage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import { Entity, Unique, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; 3 | 4 | @Entity('KeyStorage') 5 | @Unique(['name']) 6 | export class KeyStorage { 7 | @PrimaryGeneratedColumn() 8 | id!: number; 9 | 10 | @Column({ unique: true, type: 'varchar' }) 11 | @Index() 12 | name!: string; 13 | 14 | @Column({ type: 'varchar' }) 15 | value!: string; 16 | 17 | @Column({ type: 'datetime' }) 18 | createdAt!: Date; 19 | 20 | @Column({ type: 'datetime' }) 21 | updatedAt!: Date; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/StorageManager/migrations/assetNameMigration.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | import settings from '../../../settings'; 3 | import Debug from 'debug'; 4 | import { checkReposExists } from '../setup'; 5 | 6 | const debug = Debug('tonomy-id:utils:storageManager:migrations:assetNameMigration'); 7 | 8 | export class AssetNameMigration163837490194410 implements MigrationInterface { 9 | public async up(queryRunner: QueryRunner): Promise { 10 | const isExists = await checkReposExists(); 11 | 12 | if (isExists) { 13 | const chainName = settings.config.ecosystemName; 14 | const name = chainName + '-' + 'LEOS'; 15 | const newName = chainName + '-' + 'TONO'; 16 | 17 | // First check if any records match 18 | const result = await queryRunner.query( 19 | `SELECT COUNT(*) FROM AssetStorage WHERE assetName LIKE '%${name}%'` 20 | ); 21 | 22 | const count = parseInt(result[0]['COUNT(*)'], 10); 23 | 24 | debug(`Found ${count} records to update.`); 25 | 26 | if (count > 0) { 27 | try { 28 | const updateQuery = ` 29 | UPDATE AssetStorage 30 | SET assetName = REPLACE(assetName, '${name}', '${newName}') 31 | WHERE assetName LIKE '%${name}%'`; 32 | 33 | debug(`Executing update query: ${updateQuery}`); 34 | 35 | await queryRunner.query(updateQuery); 36 | await queryRunner.query(` 37 | DELETE FROM AssetStorage 38 | WHERE assetName LIKE '%${name}%' 39 | `); 40 | } catch (error) { 41 | console.error('❌ Update query failed:', error); 42 | } 43 | } 44 | } 45 | } 46 | 47 | public async down(queryRunner: QueryRunner): Promise { 48 | debug('down function not implemented yet.'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/KeyStorageRepository.ts: -------------------------------------------------------------------------------- 1 | import { Repository, DataSource } from 'typeorm'; 2 | import { KeyStorage } from '../entities/keyStorage'; 3 | 4 | export class KeyStorageRepository { 5 | private ormRepository: Repository; 6 | 7 | constructor(dataSource: DataSource) { 8 | this.ormRepository = dataSource.getRepository(KeyStorage); 9 | } 10 | 11 | public async storeNewKey(name: string, value: string): Promise { 12 | const keyStorageEntity = this.ormRepository.create({ 13 | name, 14 | value, 15 | createdAt: new Date(), 16 | updatedAt: new Date(), 17 | }); 18 | 19 | const storage = this.ormRepository.save(keyStorageEntity); 20 | 21 | return storage; 22 | } 23 | 24 | public async updateKey(key: KeyStorage): Promise { 25 | const findDoc = await this.ormRepository.findOne({ where: { name: key.name, id: key.id } }); 26 | 27 | if (findDoc) return await this.ormRepository.save(key); 28 | else throw new Error('Name not exists '); 29 | } 30 | 31 | public async findByName(name: string): Promise { 32 | return this.ormRepository.findOne({ where: { name } }); 33 | } 34 | 35 | public async deleteAll(): Promise { 36 | await this.ormRepository.delete({}); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/appStorageManager.ts: -------------------------------------------------------------------------------- 1 | import { AppStorageRepository } from './appStorageRepository'; 2 | 3 | export abstract class AppStorageManager { 4 | protected repository: AppStorageRepository; 5 | 6 | constructor(repository: AppStorageRepository) { 7 | this.repository = repository; 8 | } 9 | 10 | public async setCryptoSeed(seed: string): Promise { 11 | const existingValue = await this.repository.findByName('seed'); 12 | 13 | if (existingValue) { 14 | existingValue.value = seed; 15 | existingValue.updatedAt = new Date(); 16 | await this.repository.updateSetting(existingValue); 17 | } else { 18 | await this.repository.addNewSetting('seed', seed); 19 | } 20 | } 21 | 22 | public async getCryptoSeed(): Promise { 23 | const seed = await this.repository.findByName('seed'); 24 | 25 | return seed ? seed.value : null; 26 | } 27 | 28 | public async deleteAll(): Promise { 29 | await this.repository.deleteAll(); 30 | } 31 | 32 | //-- Setting >> Developer Mode 33 | public async setDeveloperMode(mode: boolean): Promise { 34 | const existingValue = await this.repository.findByName('developerMode'); 35 | 36 | if (existingValue) { 37 | existingValue.value = mode.toString(); 38 | existingValue.updatedAt = new Date(); 39 | await this.repository.updateSetting(existingValue); 40 | } else { 41 | await this.repository.addNewSetting('developerMode', mode.toString()); 42 | } 43 | } 44 | public async getDeveloperMode(): Promise { 45 | const mode = await this.repository.findByName('developerMode'); 46 | 47 | return mode?.value === 'true' ? true : false; 48 | } 49 | 50 | //-- Splace Screen >> Onboarding 51 | public async setSplashOnboarding(value: boolean): Promise { 52 | const existingValue = await this.repository.findByName('splashOnboarding'); 53 | 54 | if (existingValue) { 55 | existingValue.value = value.toString(); 56 | existingValue.updatedAt = new Date(); 57 | await this.repository.updateSetting(existingValue); 58 | } else { 59 | await this.repository.addNewSetting('splashOnboarding', value.toString()); 60 | } 61 | } 62 | public async getSplashOnboarding(): Promise { 63 | const onboarding = await this.repository.findByName('splashOnboarding'); 64 | 65 | return onboarding?.value === 'false' ? false : true; 66 | } 67 | 68 | //-- Home >> App Instructions 69 | public async setAppInstruction(value: boolean): Promise { 70 | const existingValue = await this.repository.findByName('appInstruction'); 71 | 72 | if (existingValue) { 73 | existingValue.value = value.toString(); 74 | existingValue.updatedAt = new Date(); 75 | await this.repository.updateSetting(existingValue); 76 | } else { 77 | await this.repository.addNewSetting('appInstruction', value.toString()); 78 | } 79 | } 80 | public async getAppInstruction(): Promise { 81 | const instructions = await this.repository.findByName('appInstruction'); 82 | 83 | return instructions?.value === 'false' ? false : true; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/appStorageRepository.ts: -------------------------------------------------------------------------------- 1 | import { Repository, DataSource } from 'typeorm'; 2 | import { AppStorage } from '../entities/appSettings'; 3 | 4 | export class AppStorageRepository { 5 | private ormRepository: Repository; 6 | 7 | constructor(dataSource: DataSource) { 8 | this.ormRepository = dataSource.getRepository(AppStorage); 9 | } 10 | 11 | public async addNewSetting(name: string, value: string): Promise { 12 | const appStorageEntity = this.ormRepository.create({ 13 | name, 14 | value, 15 | createdAt: new Date(), 16 | updatedAt: new Date(), 17 | }); 18 | 19 | return this.ormRepository.save(appStorageEntity); 20 | } 21 | 22 | public async findByName(name: string): Promise { 23 | const settings = this.ormRepository.findOne({ where: { name } }); 24 | 25 | return settings; 26 | } 27 | 28 | public async deleteAll(): Promise { 29 | await this.ormRepository.delete({}); 30 | } 31 | 32 | public async updateSetting(settings: AppStorage): Promise { 33 | const findDoc = await this.ormRepository.findOne({ where: { name: settings.name, id: settings.id } }); 34 | 35 | if (findDoc) return await this.ormRepository.save(settings); 36 | else throw new Error('Name not exists '); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/assetStorageManager.ts: -------------------------------------------------------------------------------- 1 | import { IAccount, IAsset, IToken } from '../../chain/types'; 2 | import { AssetStorageRepository } from './assetStorageRepository'; 3 | import Debug from '../../debug'; 4 | 5 | const debug = Debug('tonomy-id:utils:storage:assetStorageManager'); 6 | 7 | export interface AssetStorage { 8 | accountName: string; 9 | balance: string; 10 | usdBalance: number; 11 | assetName?: string; 12 | } 13 | 14 | export abstract class AssetStorageManager { 15 | protected repository: AssetStorageRepository; 16 | 17 | constructor(repository: AssetStorageRepository) { 18 | this.repository = repository; 19 | } 20 | public async createAsset(asset: IAsset, value: IAccount): Promise { 21 | const token = asset.getToken(); 22 | const symbol = token.getSymbol(); 23 | const name = token.getChain().getName() + '-' + symbol; 24 | 25 | await this.repository.createAsset(name, value.getName()); 26 | } 27 | public async updateAccountBalance(asset: IAsset): Promise { 28 | const name = asset.getToken().getChain().getName() + '-' + asset.getToken().getSymbol(); 29 | const existingAsset = await this.repository.findAssetByName(name); 30 | 31 | if (existingAsset) { 32 | const balance = asset.toString(); 33 | 34 | debug(`updateAccountBalance() updating ${name} balance to ${balance}`); 35 | 36 | try { 37 | const usdBalance = await asset.getUsdValue(); 38 | 39 | if (usdBalance >= 0 && usdBalance !== existingAsset.usdBalance) { 40 | existingAsset.usdBalance = usdBalance; 41 | } 42 | } catch (error) { 43 | debug(`updateAccountBalance() error fetching ${name} usd balance`, error); 44 | } 45 | 46 | existingAsset.balance = balance.split(' ')[0]; 47 | existingAsset.updatedAt = new Date(); 48 | await this.repository.updateAccountBalance(existingAsset); 49 | } else { 50 | throw new Error('Asset not found'); 51 | } 52 | } 53 | 54 | public async findAssetByName(token: IToken): Promise { 55 | const name = token.getChain().getName() + '-' + token.getSymbol(); 56 | 57 | const existingAsset = await this.repository.findAssetByName(name); 58 | 59 | if (existingAsset) { 60 | return existingAsset; 61 | } 62 | 63 | return null; 64 | } 65 | 66 | public async deleteAll(): Promise { 67 | await this.repository.deleteAll(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/assetStorageRepository.ts: -------------------------------------------------------------------------------- 1 | import { Repository, DataSource } from 'typeorm'; 2 | import { AssetStorage } from '../entities/assetStorage'; 3 | 4 | export class AssetStorageRepository { 5 | private ormRepository: Repository; 6 | 7 | constructor(dataSource: DataSource) { 8 | this.ormRepository = dataSource.getRepository(AssetStorage); 9 | } 10 | 11 | public async createAsset(assetName: string, accountName: string): Promise { 12 | const assetStorageEntity = this.ormRepository.create({ 13 | assetName, 14 | accountName, 15 | balance: '0', 16 | usdBalance: 0, 17 | createdAt: new Date(), 18 | updatedAt: new Date(), 19 | }); 20 | 21 | const storage = this.ormRepository.save(assetStorageEntity); 22 | 23 | return storage; 24 | } 25 | 26 | public async updateAccountBalance(key: AssetStorage): Promise { 27 | const findDoc = await this.ormRepository.findOne({ where: { assetName: key.assetName, id: key.id } }); 28 | 29 | if (findDoc) return await this.ormRepository.save(key); 30 | else throw new Error('Name not exists '); 31 | } 32 | 33 | public async findAssetByName(name: string): Promise { 34 | const findDoc = await this.ormRepository.findOne({ where: { assetName: name } }); 35 | 36 | return findDoc; 37 | } 38 | 39 | public async deleteAll(): Promise { 40 | await this.ormRepository.delete({}); 41 | } 42 | 43 | public async findAll(): Promise { 44 | return await this.ormRepository.find({}); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/StorageManager/repositories/keyStorageManager.ts: -------------------------------------------------------------------------------- 1 | import { KeyStorageRepository } from './KeyStorageRepository'; 2 | import { ChainType, IChain, IPrivateKey } from '../../chain/types'; 3 | import { EthereumChain, EthereumPrivateKey } from '../../chain/etherum'; 4 | import { AntelopeChain, AntelopePrivateKey } from '../../chain/antelope'; 5 | 6 | export abstract class KeyManager { 7 | protected repository: KeyStorageRepository; 8 | 9 | constructor(repository: KeyStorageRepository) { 10 | this.repository = repository; 11 | } 12 | 13 | public async emplaceKey(name: string, privateKey: IPrivateKey): Promise { 14 | const existingKey = await this.repository.findByName(name); 15 | const value = await privateKey.exportPrivateKey(); 16 | 17 | if (existingKey) { 18 | existingKey.value = value; 19 | existingKey.updatedAt = new Date(); 20 | await this.repository.updateKey(existingKey); 21 | } else { 22 | await this.repository.storeNewKey(name, value); 23 | } 24 | } 25 | 26 | public async findByName(name: string, chain: IChain): Promise { 27 | const key = await this.repository.findByName(name); 28 | 29 | if (key) { 30 | if (chain.getChainType() === ChainType.ETHEREUM) { 31 | return new EthereumPrivateKey(key.value, chain as EthereumChain); 32 | } else { 33 | return AntelopePrivateKey.fromPrivateKeyHex(key.value, chain as AntelopeChain); 34 | } 35 | } else return null; 36 | } 37 | 38 | public async deleteAll(): Promise { 39 | await this.repository.deleteAll(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/chain/common.ts: -------------------------------------------------------------------------------- 1 | const CACHE_DURATION = 30 * 1000; // 30 seconds 2 | let cachedPrice: number | null = null; 3 | let lastFetchTime = 0; 4 | 5 | export async function getPriceCoinGecko(token: string, currency: string): Promise { 6 | const now = Date.now(); 7 | 8 | // Return cached value if within 30 seconds 9 | if (cachedPrice !== null && now - lastFetchTime < CACHE_DURATION) { 10 | return cachedPrice; 11 | } 12 | 13 | // Fetch new price from CoinGecko 14 | const res = await fetch( 15 | `https://api.coingecko.com/api/v3/simple/price?ids=${token}&vs_currencies=${currency}` 16 | ).then((res) => res.json()); 17 | 18 | if (!res || res === null) { 19 | throw new Error('Failed to fetch price from CoinGecko'); 20 | } 21 | 22 | const price = res.ethereum?.usd; 23 | 24 | if (typeof price === 'number') { 25 | cachedPrice = price; 26 | lastFetchTime = now; 27 | } 28 | 29 | return price; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/debug.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | import { serializeAny } from './strings'; 3 | 4 | type LogEntry = { 5 | dateTime: Date; 6 | namespace: string; 7 | message: string; 8 | }; 9 | 10 | export const debugLog: LogEntry[] = []; 11 | 12 | const MAX_LOG_ENTRIES = 100; 13 | 14 | const DebugAndLog = (namespace: string) => { 15 | const debug = Debug(namespace); 16 | 17 | return (...args: any[]) => { 18 | debugLog.push({ dateTime: new Date(), namespace, message: args.map((x) => serializeAny(x, true)).join(' ') }); 19 | 20 | while (debugLog.length > MAX_LOG_ENTRIES) { 21 | debugLog.shift(); 22 | } 23 | 24 | debug(...args); 25 | }; 26 | }; 27 | 28 | DebugAndLog.enable = Debug.enable; 29 | DebugAndLog.enabled = Debug.enabled; 30 | DebugAndLog.log = Debug.log; 31 | DebugAndLog.formatArgs = Debug.formatArgs; 32 | DebugAndLog.save = Debug.save; 33 | DebugAndLog.load = Debug.load; 34 | DebugAndLog.useColors = Debug.useColors; 35 | DebugAndLog.colors = Debug.colors; 36 | DebugAndLog.disable = Debug.disable; 37 | DebugAndLog.disabled = Debug.disabled; 38 | DebugAndLog.storage = Debug.storage; 39 | DebugAndLog.destroy = Debug.destroy; 40 | 41 | export default DebugAndLog; 42 | -------------------------------------------------------------------------------- /src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | export class ApplicationError extends Error { 2 | code?: ApplicationErrors; 3 | 4 | constructor(message: string) { 5 | super(message); 6 | // Ensure the name of this error is the same as the class name 7 | this.name = this.constructor.name; 8 | 9 | // This clips the constructor invocation from the stack trace. 10 | // It's not absolutely essential, but it does make the stack trace a little nicer. 11 | // @see Node.js reference (bottom) 12 | Error.captureStackTrace(this, this.constructor); 13 | } 14 | } 15 | 16 | export function throwError(message: string, code?: ApplicationErrors) { 17 | let error = new ApplicationError(message); 18 | 19 | if (code) { 20 | error = new ApplicationError(code + ': ' + message); 21 | error.code = code; 22 | } 23 | 24 | throw error; 25 | } 26 | 27 | enum ApplicationErrors { 28 | UsernameTaken = 'UsernameTaken', 29 | NoKeyFound = 'NoKeyFound', 30 | NoDataFound = 'NoDataFound', // No Data found in the storage 31 | NoRequestData = 'NoRequestData', 32 | InvalidJwt = 'InvalidJwt', 33 | MissingParams = 'MissingParams', 34 | InvalidLinkAuthRequest = 'InvalidLinkAuthRequest', 35 | NotEnoughCoins = 'NotEnoughCoins', 36 | IncorrectTransactionAuthorization = 'IncorrectTransactionAuthorization', 37 | } 38 | 39 | // eslint-disable-next-line @typescript-eslint/no-namespace 40 | namespace ApplicationErrors { 41 | /* 42 | * Returns the index of the enum value 43 | * 44 | * @param value The value to get the index of 45 | */ 46 | export function indexFor(value: ApplicationErrors): number { 47 | return Object.keys(ApplicationErrors).indexOf(value); 48 | } 49 | 50 | /* 51 | * Creates an ApplicationErrors from a string or index of the level 52 | * 53 | * @param value The string or index 54 | */ 55 | export function from(value: number | string): ApplicationErrors { 56 | let index: number; 57 | 58 | if (typeof value !== 'number') { 59 | index = ApplicationErrors.indexFor(value as ApplicationErrors); 60 | } else { 61 | index = value; 62 | } 63 | 64 | return Object.values(ApplicationErrors)[index] as ApplicationErrors; 65 | } 66 | } 67 | 68 | export { ApplicationErrors }; 69 | 70 | export const NETWORK_ERROR_MESSAGE = 'Network request failed'; 71 | export function isNetworkError(error: unknown): boolean { 72 | return error instanceof Error && error.message === NETWORK_ERROR_MESSAGE; 73 | } 74 | export const NETWORK_ERROR_RESPONSE = 'Please check your connection and try again.'; 75 | export const createNetworkErrorState = (expected = true) => ({ 76 | error: new Error(NETWORK_ERROR_RESPONSE), 77 | expected: expected ?? false, 78 | }); 79 | -------------------------------------------------------------------------------- /src/utils/exceptions.ts: -------------------------------------------------------------------------------- 1 | import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler'; 2 | import { ErrorState } from '../store/errorStore'; 3 | import Debug from 'debug'; 4 | import { captureError } from './sentry'; 5 | import { serializeAny } from './strings'; 6 | 7 | const debug = Debug('tonomy-id:utils:exceptions'); 8 | 9 | export default function setErrorHandlers(errorStore: ErrorState) { 10 | // http://bluebirdjs.com/docs/api/error-management-configuration.html#global-rejection-events 11 | global.onunhandledrejection = function (reason: any) { 12 | debug( 13 | 'Unhandled Promise Rejection Error', 14 | reason, 15 | typeof reason, 16 | reason instanceof Error, 17 | serializeAny(reason, true) 18 | ); 19 | const errorObject = reason instanceof Error ? reason : new Error(serializeAny(reason)); 20 | 21 | errorStore.setError({ error: errorObject, title: 'Unhandled Promise Rejection Error', expected: false }); 22 | }; 23 | 24 | setJSExceptionHandler((e: any, isFatal: boolean) => { 25 | debug('Unexpected JS Error Logs', isFatal, e, typeof e, e instanceof Error, serializeAny(e, true)); 26 | const error = e instanceof Error ? e : new Error(serializeAny(e)); 27 | 28 | if (isFatal) { 29 | captureError('JS Exception Handler', error, 'fatal'); 30 | errorStore.setError({ error, title: 'Unexpected Fatal JS Error', expected: false }); 31 | } else { 32 | if (e?.context?.startsWith('core') && e?.time && e?.level) { 33 | // Network connection issue with the WalletConnect Core Relay. It will resolve again once internet returns. 34 | debug('Ignoring WalletConnect Core Relay error', error); 35 | captureError('WalletConnect Core Relay Error', error, 'debug'); 36 | return; 37 | } 38 | 39 | if (e?.context === 'client') { 40 | debug('Ignoring WalletConnect Core Client error', error); 41 | // getting async error throw by the WalletConnect Core client. when the key is MISSING_OR_INVALID or NO_MATCHING_KEY 42 | // https://github.com/WalletConnect/walletconnect-monorepo/blob/v2.0/packages/core/src/controllers/store.ts#L160 43 | captureError('Ignoring WalletConnect Core Client error', e, 'debug'); 44 | return; 45 | } 46 | 47 | errorStore.setError({ error: e, title: 'Unexpected JS Error', expected: false }); 48 | } 49 | }, false); 50 | 51 | setNativeExceptionHandler((errorString: string) => { 52 | debug( 53 | 'Native Exception Handler', 54 | typeof errorString, 55 | // @ts-expect-error errorString is a string not Error 56 | errorString instanceof Error, 57 | serializeAny(errorString, true) 58 | ); 59 | captureError('Native Exception Handler', new Error(errorString), 'fatal'); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/keys.ts: -------------------------------------------------------------------------------- 1 | import { Bytes, Checksum256, KeyType, PrivateKey } from '@wharfkit/antelope'; 2 | import argon2 from 'react-native-argon2'; 3 | import { randomBytes } from '@tonomy/tonomy-id-sdk'; 4 | import { appStorage, keyStorage } from './StorageManager/setup'; 5 | import { getKeyOrNullFromChain, tokenRegistry } from './tokenRegistry'; 6 | 7 | export async function generatePrivateKeyFromPassword( 8 | password: string, 9 | salt?: Checksum256 10 | ): Promise<{ privateKey: PrivateKey; salt: Checksum256 }> { 11 | const { seed, salt: saltString } = await generateSeedFromPassword(password, salt?.hexString); 12 | 13 | const bytes = Bytes.from(seed, 'hex'); 14 | const privateKey = new PrivateKey(KeyType.K1, bytes); 15 | 16 | return { 17 | privateKey, 18 | salt: Checksum256.from(saltString), 19 | }; 20 | } 21 | 22 | export async function generateSeedFromPassword( 23 | password: string, 24 | salt?: string 25 | ): Promise<{ seed: string; salt: string }> { 26 | if (!salt) salt = Checksum256.from(randomBytes(32)).hexString; 27 | const result = await argon2(password, salt, { 28 | mode: 'argon2id', 29 | iterations: 40, 30 | memory: 64 * 1024, 31 | parallelism: 1, 32 | hashLength: 32, 33 | }); 34 | 35 | return { seed: result.rawHash as string, salt }; 36 | } 37 | 38 | export async function savePrivateKeyToStorage(passphrase: string, salt?: string): Promise { 39 | // Generate the seed data from the password and salt (computationally expensive) 40 | const { seed } = await generateSeedFromPassword(passphrase, salt); 41 | 42 | await appStorage.setCryptoSeed(seed); 43 | 44 | for (const tokenEntry of tokenRegistry) { 45 | const { chain, keyName } = tokenEntry; 46 | let key = await getKeyOrNullFromChain(tokenEntry); 47 | 48 | if (!key) { 49 | // Generate the key from seed data (computationally cheap) 50 | key = await chain.createKeyFromSeed(seed); 51 | 52 | await keyStorage.emplaceKey(keyName, key); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/navigate.ts: -------------------------------------------------------------------------------- 1 | import { Linking, Platform } from 'react-native'; 2 | import { createNavigationContainerRef, ParamListBase } from '@react-navigation/native'; 3 | 4 | export const navigationRef = createNavigationContainerRef(); 5 | 6 | export function navigate(name: string, params: any) { 7 | if (navigationRef.isReady()) { 8 | navigationRef.navigate(name, params); 9 | } 10 | } 11 | 12 | export async function redirectToMobileBrowser(url: string): Promise { 13 | // Detect if the user is on an iOS or Android device using React Native's Platform module 14 | const isIOS = Platform.OS === 'ios'; 15 | const isAndroid = Platform.OS === 'android'; 16 | 17 | // console.log('Device', Device.deviceType); 18 | // Detect if the user is on a mobile browser 19 | const userAgent = navigator.userAgent || navigator.vendor; 20 | 21 | const isMobileBrowser = /android|iPad|iPhone|iPod/.test(userAgent); 22 | 23 | console.log('isMobileBrowser'); 24 | 25 | if ((isIOS || isAndroid) && isMobileBrowser) { 26 | // Redirect to the device's default browser by opening the URL 27 | await Linking.openURL(url); 28 | } else { 29 | console.log('No need to redirect. Not on a mobile device.'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/network.ts: -------------------------------------------------------------------------------- 1 | import settings from '../settings'; 2 | import Debug from 'debug'; 3 | import { sleep } from './time'; 4 | import { isNetworkError } from './errors'; 5 | import { captureError } from './sentry'; 6 | 7 | const debug = Debug('tonomy-id:utils:network'); 8 | 9 | export function extractHostname(url): string { 10 | const urlObject = new URL(url); 11 | 12 | if (urlObject.protocol !== 'https:' && urlObject.protocol !== 'http:') { 13 | return 'Invalid URL'; 14 | } 15 | 16 | if (settings.isProduction() && !urlObject) { 17 | return 'Invalid URL - Must use HTTPS'; 18 | } 19 | 20 | return urlObject.hostname; 21 | } 22 | 23 | export async function progressiveRetryOnNetworkError( 24 | fn: () => Promise, 25 | initialDelay = 10000, 26 | maxDelay = 3600000 27 | ) { 28 | let condition = true; 29 | let delay = initialDelay; 30 | 31 | while (condition) { 32 | try { 33 | await fn(); // Try the function 34 | condition = false; 35 | break; // If it succeeds, exit the loop 36 | } catch (error) { 37 | debug('progressiveRetryOnNetworkError()', fn.name + '()', error, typeof error); 38 | 39 | if (isNetworkError(error)) { 40 | debug(`Retrying ${fn.name}() in ${delay / 1000} seconds...`); 41 | await sleep(delay); 42 | delay = Math.min(delay * 2, maxDelay); // Exponential backoff 43 | } else { 44 | captureError(`progressiveRetryOnNetworkError() Non-network error occurred: ${fn.name}`, error); 45 | throw error; 46 | } 47 | } 48 | } 49 | } 50 | 51 | export const debounce = any>( 52 | func: T, 53 | wait: number 54 | ): ((...args: Parameters) => void) => { 55 | let timeout: ReturnType; 56 | 57 | return (...args: Parameters): void => { 58 | clearTimeout(timeout); 59 | timeout = setTimeout(() => func(...args), wait); 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/utils/numbers.ts: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | 3 | export function numberToOrdinal(value: number) { 4 | let screenNumber; 5 | 6 | if (value === 1) screenNumber = '1st'; 7 | else if (value === 2) screenNumber = '2nd'; 8 | else if (value === 3) screenNumber = '3rd'; 9 | else screenNumber = `${value}th`; 10 | 11 | return screenNumber; 12 | } 13 | 14 | export function formatCurrencyValue(value: number, decimalPlaces = 2): string { 15 | if (value) { 16 | return value.toLocaleString('en-US', { 17 | minimumFractionDigits: decimalPlaces, 18 | maximumFractionDigits: decimalPlaces, 19 | }); 20 | } 21 | 22 | return '0.00'; 23 | } 24 | 25 | export function formatTokenValue(amount: Decimal, maxDecimals = 4): string { 26 | let formattedAmount: string; 27 | const decimalPart = amount.toFixed().split('.')[1] || ''; 28 | 29 | if (amount.equals(amount.floor())) { 30 | formattedAmount = amount.toFixed(2); 31 | } else if (decimalPart.length > maxDecimals) { 32 | // If the decimal part exceeds maxDecimals, display only maxDecimals decimal places 33 | formattedAmount = amount.toFixed(maxDecimals, Decimal.ROUND_DOWN); 34 | } else { 35 | // If the decimal part is maxDecimals digits or fewer, display as-is 36 | formattedAmount = amount.toString(); 37 | } 38 | 39 | // Add commas for thousands 40 | const [integerPart, fractionalPart] = formattedAmount.split('.'); 41 | 42 | formattedAmount = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); 43 | 44 | if (fractionalPart) { 45 | formattedAmount += `.${fractionalPart}`; 46 | } 47 | 48 | return formattedAmount; 49 | } 50 | 51 | export function formatAssetToNumber(asset: string): Decimal { 52 | const numericValue = asset.replace(/[^0-9.-]/g, ''); 53 | 54 | return new Decimal(numericValue); 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/polyfill.ts: -------------------------------------------------------------------------------- 1 | // Polyfills for React Native 2 | // reflect-metadata is for typeorm - https://typeorm.io/#installation 3 | import 'reflect-metadata'; 4 | // 3x packages are for Veramo - https://veramo.io/docs/react_native_tutorials/react_native_1_setup_identifiers/#prerequisite-configuration 5 | import '@sinonjs/text-encoding'; 6 | import 'react-native-get-random-values'; 7 | import '@ethersproject/shims'; 8 | // WalletConnect 9 | import '@walletconnect/react-native-compat'; 10 | 11 | // Manual polyfills 12 | import Bluebird from 'bluebird'; 13 | import BigInteger from 'big-integer'; 14 | import { NETWORK_ERROR_MESSAGE } from './errors'; 15 | 16 | // @ts-expect-error Promise library type mismatch 17 | global.Promise = Bluebird; 18 | 19 | // required for @tonomy/antelope-did-resolver update 20 | // https://github.com/Tonomy-Foundation/Tonomy-ID/pull/757/ 21 | if (typeof BigInt === 'undefined') { 22 | // @ts-expect-error BigInt global type mismatch 23 | global.BigInt = BigInteger; 24 | } 25 | 26 | /** 27 | * Mocks no internet connection by overriding fetch and XMLHttpRequest. 28 | * Call this function to simulate a network failure. 29 | */ 30 | function mockNoInternet() { 31 | // Save the original fetch function 32 | const originalFetch = global.fetch; 33 | 34 | global.fetch = async (url, options) => { 35 | await originalFetch(url, options); 36 | throw new Error(NETWORK_ERROR_MESSAGE); 37 | }; 38 | 39 | if (global.XMLHttpRequest) { 40 | const OriginalXMLHttpRequest = global.XMLHttpRequest; 41 | 42 | class CustomXMLHttpRequest extends OriginalXMLHttpRequest { 43 | constructor() { 44 | super(); 45 | throw new Error(NETWORK_ERROR_MESSAGE); 46 | } 47 | } 48 | 49 | global.XMLHttpRequest = CustomXMLHttpRequest; 50 | } else { 51 | console.warn('XMLHttpRequest is not available in this environment'); 52 | } 53 | } 54 | 55 | // To simulate no internet connection, call mockNoInternet(); 56 | // mockNoInternet(); 57 | -------------------------------------------------------------------------------- /src/utils/sentry.ts: -------------------------------------------------------------------------------- 1 | import { init, captureException, wrap as sentryWrap, setUser as sentrySetUser, User, Hub } from '@sentry/react-native'; 2 | import settings from '../settings'; 3 | import { ExclusiveEventHintOrCaptureContext } from '@sentry/core/types/utils/prepareEvent'; 4 | import { SeverityLevel } from '@sentry/types/types/severity'; 5 | import { debugLog } from './debug'; 6 | import { serializeAny } from './strings'; 7 | 8 | // TODO: 9 | // setup with https://docs.sentry.io/platforms/react-native/tracing/instrumentation/react-navigation/ 10 | // check how to set release version with commit hash 11 | 12 | if (settings.isProduction()) { 13 | init({ 14 | dsn: `https://${settings.config.sentryPublicKey}@${settings.config.sentrySecretKey}.ingest.de.sentry.io/${settings.config.sentryProjectId}`, 15 | debug: false, 16 | environment: settings.env, 17 | }); 18 | } 19 | 20 | export function captureError(message: string, error: any, level: SeverityLevel = 'error'): string { 21 | const errorObject = error instanceof Error ? error : new Error(serializeAny(error)); 22 | 23 | if (settings.isProduction()) { 24 | return sendToSentry(message, errorObject, level); 25 | } else { 26 | console[level](`captureError(): ${message}`, errorObject.stack || errorObject); 27 | return 'sentry-not-active'; 28 | } 29 | } 30 | 31 | export function wrap(component: React.ComponentType): React.ComponentType { 32 | if (settings.isProduction()) { 33 | return sentryWrap(component); 34 | } else { 35 | return component; 36 | } 37 | } 38 | 39 | export function setUser(user: User | null): ReturnType | null { 40 | if (settings.isProduction()) { 41 | return sentrySetUser(user); 42 | } else { 43 | return null; 44 | } 45 | } 46 | 47 | /** 48 | * Creates a readable text blog from the debug logs and sends it to Sentry 49 | */ 50 | export function createBlobFromDebugLogs(): string { 51 | const now = new Date(); 52 | const blobs: string[] = []; 53 | 54 | // Get log strings 55 | debugLog.forEach((log) => { 56 | const diff = now.getTime() - log.dateTime.getTime(); 57 | const blobString = 58 | log.dateTime.toISOString() + `(-${diff / 1000}s)` + ' ' + log.namespace + ': ' + log.message + '\n'; 59 | 60 | blobs.push(blobString); 61 | }); 62 | 63 | // Truncate to 16kB (16267 characters) as per size limit in sentry 64 | while (blobs.join('').length > 16267) { 65 | blobs.shift(); 66 | } 67 | 68 | return blobs.join(''); 69 | } 70 | 71 | function sendToSentry(message: string, error: Error, level: SeverityLevel): string { 72 | const hint: ExclusiveEventHintOrCaptureContext = { 73 | extra: { 74 | // NOTE: For the following line to work, in the Sentry project settings, 75 | // under "Data Scrubbing", we need to add the following (multiple lines) as Safe Fields: 76 | // extra.errorJson 77 | // extra.errorJson.** 78 | // extra.debugLog 79 | // extra.debugLog.** 80 | errorJson: JSON.stringify(error, null, 2), 81 | debugLog: createBlobFromDebugLogs(), 82 | }, 83 | tags: { 84 | message, 85 | }, 86 | level, 87 | }; 88 | 89 | return captureException(error, hint); 90 | } 91 | -------------------------------------------------------------------------------- /src/utils/setSettings.ts: -------------------------------------------------------------------------------- 1 | import { setSettings } from '@tonomy/tonomy-id-sdk'; 2 | import settings from '../settings'; 3 | 4 | const env = settings.env; 5 | 6 | setSettings({ 7 | environment: env, 8 | blockchainUrl: settings.config.blockchainUrl, 9 | accountSuffix: settings.config.accountSuffix, 10 | communicationUrl: settings.config.communicationUrl, 11 | tonomyIdSchema: settings.config.tonomyIdSlug, 12 | accountsServiceUrl: settings.config.accountsServiceUrl, 13 | ssoWebsiteOrigin: settings.config.ssoWebsiteOrigin, 14 | currencySymbol: settings.config.currencySymbol, 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | import { PersistentStorage } from '@tonomy/tonomy-id-sdk'; 3 | 4 | export default class Storage implements PersistentStorage { 5 | [x: string]: any; 6 | scope: string; 7 | 8 | constructor(scope: string) { 9 | this.scope = scope; 10 | } 11 | 12 | async retrieve(key: string): Promise { 13 | const data = await AsyncStorage.getItem(key); 14 | 15 | if (!data) return null; 16 | return JSON.parse(data); 17 | } 18 | 19 | async store(key: string, value: any): Promise { 20 | const str = JSON.stringify(value); 21 | 22 | await AsyncStorage.setItem(key, str); 23 | } 24 | 25 | async clear(): Promise { 26 | await AsyncStorage.clear(); 27 | } 28 | } 29 | 30 | export function storageFactory(scope: string): PersistentStorage { 31 | return new Storage(scope); 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | export const capitalizeFirstLetter = (string) => { 2 | return string.charAt(0).toUpperCase() + string.slice(1); 3 | }; 4 | 5 | export function getQueryParam(url: string, name: string): string { 6 | const urlObject = new URL(url); 7 | const query = urlObject.searchParams; 8 | const param = query.get(name); 9 | 10 | if (!param) throw new Error(`Query param ${name} not found in URL ${url}`); 11 | return param; 12 | } 13 | 14 | export function createUrl(baseUrl: string, params: KeyValue): string { 15 | const url = new URL(baseUrl); 16 | 17 | Object.entries(params).forEach(([key, value]) => { 18 | url.searchParams.set(key, value); 19 | }); 20 | 21 | return url.toString(); 22 | } 23 | 24 | export function serializeAny(value: any, verbose = false): string { 25 | if (value === null) { 26 | return 'null'; 27 | } else if (value === undefined) { 28 | return 'undefined'; 29 | } else if (value instanceof Error) { 30 | let res = value.message || value.toString(); 31 | 32 | if (verbose) { 33 | res += value.stack ? '\n' + value.stack : ''; 34 | const object = JSON.stringify(value, null, 2); 35 | 36 | res += object === '{}' ? '' : '\n' + object; 37 | } 38 | 39 | return res; 40 | } else if (Array.isArray(value)) { 41 | return '[' + value.map((x) => serializeAny(x, verbose)).join(', ') + ']'; 42 | } else if (typeof value === 'object') { 43 | try { 44 | return JSON.stringify(value); 45 | } catch (e) { 46 | return '[Circular]'; 47 | } 48 | } else if (value.toString) { 49 | return value.toString(); 50 | } else { 51 | try { 52 | return JSON.stringify(value); 53 | } catch (e) { 54 | return '[Circular]'; 55 | } 56 | } 57 | } 58 | 59 | export type KeyValue = Record; 60 | -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | import { StakingContract } from '@tonomy/tonomy-id-sdk'; 2 | 3 | export function formatDate(date: Date): string { 4 | const day = date.getUTCDate(); 5 | const month = date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' }); 6 | const year = date.getUTCFullYear(); 7 | 8 | return `${day} ${month} ${year}`; 9 | } 10 | 11 | export function sleep(milliseconds: number) { 12 | return new Promise((resolve) => setTimeout(resolve, milliseconds)); 13 | } 14 | 15 | export const getStakeUntilDate = () => { 16 | const date = new Date(); 17 | 18 | date.setDate(date.getDate() + StakingContract.getLockedDays()); 19 | return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); 20 | }; 21 | 22 | export const getUnstakeTime = (unstakeTime) => { 23 | const now = new Date(); 24 | 25 | const unstakeableTime = new Date(unstakeTime); 26 | const daysUntilUnlockable = Math.ceil((unstakeableTime.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); 27 | 28 | return daysUntilUnlockable; 29 | }; 30 | 31 | export const getStakeReleaseTime = (stakeReleaseTime) => { 32 | const now = new Date(); 33 | 34 | const stakeReleaseDate = new Date(stakeReleaseTime); 35 | const daysUntilRelease = Math.ceil((stakeReleaseDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); 36 | 37 | return daysUntilRelease; 38 | }; 39 | -------------------------------------------------------------------------------- /src/utils/username.ts: -------------------------------------------------------------------------------- 1 | export function formatUsername(value: string) { 2 | return replaceIllegalCharacters(value); 3 | } 4 | 5 | function replaceIllegalCharacters(value: string) { 6 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'; 7 | let result = ''; 8 | 9 | for (let i = 0; i < value.length; i++) { 10 | const char = value.charAt(i); 11 | 12 | if (characters.includes(char)) { 13 | result += char; 14 | } 15 | } 16 | 17 | return result; 18 | } 19 | -------------------------------------------------------------------------------- /test/utils/RNKeymanager.test.ts: -------------------------------------------------------------------------------- 1 | import './mocks'; 2 | import { PublicKey } from '@wharfkit/antelope'; 3 | import { KeyManagerLevel } from '@tonomy/tonomy-id-sdk'; 4 | import RNKeyManager from '../../src/utils/RNKeyManager'; 5 | import { generatePrivateKeyFromPassword } from '../../src/utils/keys'; 6 | 7 | describe('RN Key Manager', () => { 8 | const rn = new RNKeyManager(); 9 | 10 | it('can store a key password', async () => { 11 | const { privateKey, salt } = await generatePrivateKeyFromPassword('test'); 12 | const publicKey = await rn.storeKey({ 13 | // @ts-ignore PrivateKey type error 14 | privateKey, 15 | level: KeyManagerLevel.PASSWORD, 16 | challenge: 'test', 17 | }); 18 | 19 | expect(publicKey).toBeInstanceOf(PublicKey); 20 | }); 21 | 22 | // get key password 23 | it('can get a key password', async () => { 24 | const { privateKey } = await generatePrivateKeyFromPassword('test'); 25 | const publicKey = await rn.storeKey({ 26 | // @ts-ignore PrivateKey type error 27 | privateKey, 28 | level: KeyManagerLevel.PASSWORD, 29 | challenge: 'test', 30 | }); 31 | const key = await rn.getKey({ 32 | level: KeyManagerLevel.PASSWORD, 33 | }); 34 | 35 | expect(key).toBeInstanceOf(PublicKey); 36 | expect(key.toString()).toEqual(publicKey.toString()); 37 | }, 10000); 38 | }); 39 | -------------------------------------------------------------------------------- /test/utils/ethereum.test.ts: -------------------------------------------------------------------------------- 1 | import './mocks'; 2 | import { EthereumAccount, EthereumPrivateKey, EthereumSepoliaChain } from '../../src/utils/chain/etherum'; 3 | import { ethers, TransactionRequest } from 'ethers'; 4 | 5 | describe('Ethereum sign transaction', () => { 6 | //generate key and sign transaction 7 | it('generate private key and sign transaction', async () => { 8 | const ethereumKey = await EthereumSepoliaChain.createKeyFromSeed('test'); 9 | const exportPrivateKey = await ethereumKey.exportPrivateKey(); 10 | const ethereumPrivateKey = new EthereumPrivateKey(exportPrivateKey, EthereumSepoliaChain); 11 | 12 | const ethereumAccount = await EthereumAccount.fromPublicKey( 13 | EthereumSepoliaChain, 14 | await ethereumPrivateKey.getPublicKey() 15 | ); 16 | const transactionRequest: TransactionRequest = { 17 | to: ethereumAccount.getName(), 18 | from: ethereumAccount.getName(), 19 | value: ethers.parseEther('0'), 20 | data: '0x00', 21 | }; 22 | 23 | const signedTransaction = await ethereumPrivateKey.signTransaction(transactionRequest); 24 | 25 | // Check if signedTransaction is defined and not empty 26 | expect(signedTransaction).toBeDefined(); 27 | expect(signedTransaction).not.toEqual(''); 28 | 29 | // Check if signedTransaction is a string in hexadecimal format 30 | expect(signedTransaction).toMatch(/^0x[a-fA-F0-9]+$/); 31 | }, 30000); 32 | }); 33 | -------------------------------------------------------------------------------- /test/utils/mocks.ts: -------------------------------------------------------------------------------- 1 | import mockArg from 'argon2'; 2 | 3 | jest.mock('react-native-argon2', () => { 4 | return { 5 | __esModule: true, 6 | default: jest.fn(async (passowrd: string, salt: string, options?) => { 7 | return mockArg 8 | .hash(passowrd, { 9 | raw: true, 10 | salt: Buffer.from(salt), 11 | type: mockArg.argon2id, 12 | hashLength: 32, 13 | memoryCost: 64 * 1024, 14 | parallelism: 1, 15 | timeCost: 40, 16 | }) 17 | .then((hash) => { 18 | return { 19 | rawHash: hash.toString('hex'), 20 | encoded: 'test value', 21 | }; 22 | }); 23 | }), 24 | }; 25 | }); 26 | 27 | jest.mock('expo-secure-store', () => { 28 | const storage: any = {}; 29 | 30 | return { 31 | setItemAsync: jest.fn(async (key: string, value: string, options) => { 32 | storage[key] = value; 33 | }), 34 | getItemAsync: jest.fn(async (key: string, options) => { 35 | return storage[key]; 36 | }), 37 | deleteItemAsync: jest.fn(async (key: string, options) => { 38 | delete storage[key]; 39 | }), 40 | }; 41 | }); 42 | 43 | jest.mock('@react-native-async-storage/async-storage', () => { 44 | const storage: any = {}; 45 | 46 | return { 47 | getItem: jest.fn(async (key: string, callback?) => { 48 | return storage[key]; 49 | }), 50 | 51 | setItem: jest.fn(async (key: string, value: string, callback?) => { 52 | storage[key] = value; 53 | }), 54 | 55 | removeItem: jest.fn(async (key: string, callback?) => { 56 | delete storage[key]; 57 | }), 58 | }; 59 | }); 60 | // } 61 | -------------------------------------------------------------------------------- /test/veramo.test.ts: -------------------------------------------------------------------------------- 1 | import { dbConnection, setupDatabase, veramo, veramo2 } from '@tonomy/tonomy-id-sdk'; 2 | 3 | describe('veramo', () => { 4 | beforeAll(async () => { 5 | await setupDatabase(); 6 | }); 7 | 8 | afterEach(async () => { 9 | const entities = dbConnection.entityMetadatas; 10 | 11 | for (const entity of entities) { 12 | const repository = dbConnection.getRepository(entity.name); 13 | 14 | await repository.clear(); // This clears all entries from the entity's table. 15 | } 16 | }); 17 | 18 | test('1', async () => { 19 | await veramo(); 20 | }); 21 | 22 | test('2', async () => { 23 | await veramo2(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "noImplicitAny": false, 6 | "skipLibCheck": true, 7 | "skipDefaultLibCheck": true, 8 | "experimentalDecorators": true, // needed for @wharfkit/antelope 9 | "useUnknownInCatchVariables": false, // needed for @wharfkit/antelope 10 | "emitDecoratorMetadata": true, 11 | "strictPropertyInitialization": false 12 | }, 13 | "include": [ 14 | "App.tsx", 15 | "src/**/*", 16 | "test/**/*", 17 | "types" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | ] 22 | } -------------------------------------------------------------------------------- /update_sdk_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BRANCH=${1-default} 4 | CHECK=${2-default} 5 | 6 | echo "yarn run updateSdkVersion $@" 7 | 8 | set +e 9 | 10 | # Get the latest version of the SDK for the correct npmjs tag based on branch 11 | if [ "${BRANCH}" == "master" ]; then 12 | VERSION=$(npm view @tonomy/tonomy-id-sdk@latest version) 13 | elif [ "${BRANCH}" == "testnet" ]; then 14 | VERSION=$(npm view @tonomy/tonomy-id-sdk@testnet version) 15 | elif [ "${BRANCH}" == "development" ]; then 16 | VERSION=$(npm view @tonomy/tonomy-id-sdk@development version) 17 | else 18 | # Print help 19 | echo "Usage: yarn run updateSdkVersion master|testnet|development [check]" 20 | echo "" 21 | echo "Example: yarn run updateSdkVersion development" 22 | echo "Example: yarn run updateSdkVersion master check" 23 | echo "" 24 | exit 1 25 | fi 26 | 27 | # Get the sha256sum of yarn.lock before updating the SDK 28 | YARNLOCK_SHA256_OLD=$(sha256sum yarn.lock) 29 | 30 | # Update the SDK 31 | echo "Updating package @tonomy/tonomy-id-sdk@${VERSION}" 32 | yarn up "@tonomy/tonomy-id-sdk@${VERSION}" 33 | 34 | # Get the sha256sum of yarn.lock after updating the SDK 35 | YARNLOCK_SHA256_NEW=$(sha256sum yarn.lock) 36 | 37 | if [ "${CHECK}" == "check" ]; then 38 | echo "Checking if yarn.lock has changed" 39 | if [ "${YARNLOCK_SHA256_NEW}" != "${YARNLOCK_SHA256_OLD}" ]; then 40 | echo "yarn.lock has changed while updating @tonomy/tonomy-id-sdk@${VERSION}" 41 | echo "Please run \"yarn run installSdkVersion\" to fix" 42 | exit 1 43 | fi 44 | fi --------------------------------------------------------------------------------