├── .babelrc ├── .buckconfig ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .prettierrc ├── .watchmanconfig ├── App.tsx ├── CHANGELOG.md ├── README.md ├── SMOKE_TESTS.md ├── android ├── app │ ├── _BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── res │ │ │ └── xml │ │ │ └── react_native_config.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── AntDesign.ttf │ │ │ ├── Entypo.ttf │ │ │ ├── EvilIcons.ttf │ │ │ ├── Feather.ttf │ │ │ ├── FontAwesome.ttf │ │ │ ├── FontAwesome5_Brands.ttf │ │ │ ├── FontAwesome5_Regular.ttf │ │ │ ├── FontAwesome5_Solid.ttf │ │ │ ├── Fontisto.ttf │ │ │ ├── Foundation.ttf │ │ │ ├── Ionicons.ttf │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ ├── MaterialIcons.ttf │ │ │ ├── Octicons.ttf │ │ │ ├── SimpleLineIcons.ttf │ │ │ └── Zocial.ttf │ │ ├── ic_launcher-web.png │ │ ├── java │ │ └── com │ │ │ └── badgermobile │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── api ├── api.bitcoin.com.ts ├── bcash.ts └── pay.badger.ts ├── app.json ├── assets ├── fonts │ └── SpaceMono-Regular.ttf ├── images │ ├── bch-full.png │ ├── icon-full.png │ ├── icon.png │ ├── slp-logo.png │ └── token-icons │ │ ├── 0f3f223902c44dc2bee6d3f77d565904d8501affba5ee0c56f7b32e8080ce14b.png │ │ ├── 1074bafb678b85f90bca79fa201a26011e09bfc6f723b95c770c0850f8d44fe8.png │ │ ├── 10db44eb221797d8015d55855edbec656b73559f2afb28da3f7d5d19529ae624.png │ │ ├── 29d353a3d19cdd7324f1c14b3fe289293976842869fed1bea3f9510558f6f006.png │ │ ├── 3f83fa9f168f01d68933ef5fdb77143b2376ba7bf3a78175258861982d90d500.png │ │ ├── 49be89bbbe018bcfaebcb41cac8340bc555f022b47b922599e510b143603f4b6.png │ │ ├── 4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf.png │ │ ├── 527a337f34e04b1974cb8a1edc7ca30b2e444bea111afc122259552243c1dbe3.png │ │ ├── 7853218e23fdabb103b4bccbe6e987da8974c7bc775b7e7e64722292ac53627f.png │ │ ├── 7f8889682d57369ed0e32336f8b7e0ffec625a35cca183f4e81fde4e71a538a1.png │ │ ├── 959a6818cba5af8aba391d3f7649f5f6a5ceb6cdcd2c2a3dcb5d2fbfc4b08e98.png │ │ ├── 9fc89d6b7d5be2eac0b3787c5b8236bca5de641b5bafafc8f450727b63615c11.png │ │ ├── c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479.png │ │ ├── eebaa04d0e715b7bd21901cb60e10d7f71d219626daf24c57ce6ea9584333149.png │ │ ├── f35007140e40c4b6ce4ecc9ad166101ad94562b3e4f650a30de10b8a80c0b987.png │ │ └── f66c6d0ac6b8c5c4ed469234ec9734f6d3499b0351b22349f40e617d22254fec.png └── store-images │ ├── Badger-Banner.png │ ├── Badger-Banner.psd │ ├── ios-splash.png │ └── ios-splash.psd ├── atoms ├── Button │ ├── Button.tsx │ ├── README.md │ └── index.ts ├── H1 │ ├── H1.ts │ ├── README.md │ └── index.ts ├── H2 │ ├── H2.ts │ ├── README.md │ └── index.ts ├── README.md ├── Spacer │ ├── README.md │ ├── Spacer.ts │ └── index.ts ├── SwipeButton │ ├── README.md │ ├── SwipeButton.tsx │ └── index.ts ├── T │ ├── README.md │ ├── T.ts │ └── index.ts └── index.ts ├── babel.config.js ├── components ├── CoinRow │ ├── CoinRow.tsx │ ├── README.md │ └── index.ts ├── README.md ├── TransactionRow │ ├── README.md │ ├── TransactionRow.tsx │ └── index.ts └── index.ts ├── constants └── Layout.ts ├── d.ts ├── data ├── README.md ├── accounts │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts ├── prices │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts ├── selectors.test.ts ├── selectors.ts ├── settings │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts ├── store.ts ├── tokens │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts ├── transactions │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts └── utxos │ ├── README.md │ ├── actions.test.ts │ ├── actions.ts │ ├── constants.test.ts │ ├── constants.ts │ ├── reducer.test.ts │ ├── reducer.ts │ ├── selectors.test.ts │ └── selectors.ts ├── hooks ├── useBlockheight.ts └── useSimpleledgerFormat.ts ├── index.js ├── ios ├── Podfile ├── Podfile.lock ├── badgerMobile-tvOS │ └── Info.plist ├── badgerMobile-tvOSTests │ └── Info.plist ├── badgerMobile.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── badgerMobile-tvOS.xcscheme │ │ └── badgerMobile.xcscheme ├── badgerMobile.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── badgerMobile │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon.png │ │ │ ├── icon_20pt@2x.png │ │ │ ├── icon_20pt@3x.png │ │ │ ├── icon_29pt@2x.png │ │ │ ├── icon_29pt@3x.png │ │ │ ├── icon_40pt@2x.png │ │ │ ├── icon_40pt@3x.png │ │ │ ├── icon_60pt@2x.png │ │ │ └── icon_60pt@3x.png │ │ ├── Contents.json │ │ └── LaunchImage-fits.launchimage │ │ │ ├── Contents.json │ │ │ ├── LaunchImage-1125@3x~iphoneX-portrait_1125x2436.png │ │ │ ├── LaunchImage-1242@3x~iphone6s-landscape_2208x1242.png │ │ │ ├── LaunchImage-1242@3x~iphone6s-portrait_1242x2208.png │ │ │ ├── LaunchImage-1242@3x~iphoneXsMax-portrait_1242x2688.png │ │ │ ├── LaunchImage-1792@2x~iphoneXr-landscape_1792x828.png │ │ │ ├── LaunchImage-2436@3x~iphoneX-landscape_2436x1125.png │ │ │ ├── LaunchImage-2688@3x~iphoneXsMax-landscape_2688x1242.png │ │ │ ├── LaunchImage-568h@2x~iphone_640x1136-1.png │ │ │ ├── LaunchImage-568h@2x~iphone_640x1136.png │ │ │ ├── LaunchImage-750@2x~iphone6-portrait_750x1334.png │ │ │ ├── LaunchImage-828@2x~iphoneXr-portrait_828x1792.png │ │ │ ├── LaunchImage~iphone-320x480.png │ │ │ ├── LaunchImage~iphone_640x960-1.png │ │ │ └── LaunchImage~iphone_640x960.png │ ├── Info.plist │ └── main.m └── badgerMobileTests │ ├── Info.plist │ └── badgerMobileTests.m ├── license ├── metro.config.js ├── navigation ├── AuthLoadingScreen.tsx └── MainTabNavigator.tsx ├── package.json ├── screens ├── Bip70ConfirmScreen.tsx ├── Bip70SuccessScreen.tsx ├── ContactUsScreen.tsx ├── CreateWalletScreen.tsx ├── FAQScreen.tsx ├── HomeScreen.tsx ├── KeySweepScreen.tsx ├── LogoutScreen.tsx ├── MenuScreen.tsx ├── PrivacyNoticeScreen.tsx ├── ReceiveScreen.tsx ├── RequestScreen.tsx ├── RestoreWalletScreen.tsx ├── SelectCurrencyScreen.tsx ├── SendConfirmScreen.tsx ├── SendSetupScreen.tsx ├── SendSuccessScreen.tsx ├── TermsOfUseScreen.tsx ├── ViewSeedScreen.tsx ├── WalletDetailScreen.tsx └── WelcomeScreen.tsx ├── shim.js ├── themes ├── README.md └── spaceBadger.ts ├── tsconfig.json ├── utils ├── account-utils.test.ts ├── account-utils.ts ├── balance-utils.test.ts ├── balance-utils.ts ├── bip-70-utils.test.ts ├── bip70-utils.ts ├── currency-utils.test.ts ├── currency-utils.ts ├── schemeParser-utils.test.ts ├── schemeParser-utils.ts ├── token-utils.test.ts ├── token-utils.ts ├── transaction-utils.test.ts └── transaction-utils.ts ├── yarn └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "typescript", 4 | extends: ["@react-native-community", "prettier"], 5 | plugins: ["prettier"], 6 | rules: { 7 | "prettier/prettier": "error" 8 | } 9 | }; 10 | 11 | // module.exports = { 12 | // root: true, 13 | // parser: "@typescript-eslint/parser", 14 | // extends: ["@react-native-community", "prettier"], 15 | // plugins: ["prettier", "@typescript-eslint/eslint-plugin"], 16 | // rules: { 17 | // "prettier/prettier": "error" 18 | // } 19 | // }; 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | yarn-error.log 37 | 38 | # BUCK 39 | buck-out/ 40 | \.buckd/ 41 | *.keystore 42 | !debug.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | 61 | 62 | **/app/*.keystore 63 | test_wallets.md 64 | 65 | android/app/release/* 66 | 67 | android/.gradle/* 68 | android/.idea/* 69 | android/local.properties 70 | android/gradle.properties 71 | 72 | ios/badgerMobile.xcworkspace/xcuserdata/ 73 | ios/badgerMobile.xcodeproj/xcuserdata/ 74 | 75 | coverage/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "printWidth": 80, 4 | "arrowParens": "avoid", 5 | "trailingComma": "none", 6 | "bracketSpacing": true 7 | } 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { NavigationContainer } from "@react-navigation/native"; 4 | import { createStackNavigator } from "@react-navigation/stack"; 5 | 6 | import styled, { ThemeProvider } from "styled-components"; 7 | import { Provider } from "react-redux"; 8 | import { View, LogBox } from "react-native"; 9 | LogBox.ignoreLogs(["Require cycle"]); 10 | import { PersistGate } from "redux-persist/integration/react"; 11 | 12 | import { getStore } from "./data/store"; 13 | import { spaceBadger } from "./themes/spaceBadger"; 14 | import MainAppStack from "./navigation/MainTabNavigator"; 15 | import AuthLoadingScreen from "./navigation/AuthLoadingScreen"; 16 | // Auth Screens 17 | import WelcomeScreen from "./screens/WelcomeScreen"; 18 | import TermsOfUseScreen from "./screens/TermsOfUseScreen"; 19 | import PrivacyNoticeScreen from "./screens/PrivacyNoticeScreen"; 20 | import CreateWalletScreen from "./screens/CreateWalletScreen"; 21 | 22 | import RestoreWalletScreen from "./screens/RestoreWalletScreen"; 23 | 24 | const { store, persistor } = getStore(); 25 | const Stack = createStackNavigator(); 26 | 27 | const AuthStack = () => { 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | const AppWrapper = styled(View)` 40 | flex: 1; 41 | `; 42 | 43 | const bchPrefix = "bitcoincash"; 44 | const slpPrefix = "simpleledger"; 45 | 46 | const App = () => { 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | {/* */} 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | }; 69 | 70 | export default App; 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Badger Wallet - Changelog 2 | 3 | ## 0.14.1 4 | 5 | - Optionally hide specific SLP tokens from the main screen 6 | 7 | ## 0.14.0 8 | 9 | - Now in TypeScript, the code is much more maintainable now 10 | - Account recovery and transaction history speed improvements 11 | - Many minor bugs fixed 12 | 13 | ## 0.13.0 14 | 15 | - Sweep SLP tokens from paper wallets, enabling more real life tipping options 16 | - Speed improvements when creating and restoring wallets, no longer freezing old phones 17 | - More token icons 18 | - Fixing missing error message issue 19 | 20 | ## 0.12.6 21 | 22 | - Restrict sending BCH to cash addresses 23 | - Restrict sending SLP to simpleledger addresses 24 | - Confirmation counts in transaction history 25 | - Adding more token icons 26 | - GOC, LEAD 27 | - Some BIP70 improvements for optional fields 28 | 29 | ## 0.12.5 30 | 31 | - Updated receive screen 32 | - Removed largest cause of slowdown - repeated history updates 33 | 34 | ## 0.12.0 35 | 36 | - BIP-70 support 37 | - Single & multi-output BCH 38 | - Single & multi-output SLP 39 | - Open from a URI, scan, or paste the payment request to access. 40 | - History improvements, especially for multi-output 41 | - Minor UI & UX fixes 42 | 43 | ## 0.11.0 44 | 45 | - Larger QR codes 46 | - Slide to send improvements 47 | - Activates earlier in swipe 48 | - FAQ Page to address common questions 49 | - Direct link to explorer from each transaction 50 | - Paper Wallet Sweeping screen added 51 | 52 | ## 0.10.3 53 | 54 | - React Native 0.60 upgrade 55 | - Smaller builds 56 | - Faster app 57 | - Improved history screen 58 | - Detects self send 59 | - Basic payout detection 60 | - SLP unknown types un-spendable 61 | 62 | ## 0.9.0 63 | 64 | - Request amount of any token from the detail screen 65 | - Scan and parse URI's and QR codes for SLP tokens 66 | - Open Badger Wallet directly from any valid URI 67 | - Adding in more token icons 68 | - Icon is now centered correctly 69 | 70 | ## 0.8.0 71 | 72 | - Re-branding full app and BCH references from the previous orange colour to the new exciting green colour. 73 | 74 | ## 0.7.1 75 | 76 | - Fixing an edge case app crash 77 | 78 | ## 0.7.0 79 | 80 | ### Features / Improvements 81 | 82 | - Currency select screen from Menu 83 | - Change to any base currency to display your BCH balance as. 84 | - At first enabling a whitelist of 24 top used currencies 85 | - Number formatting improvements throughout to better match selected currency 86 | - Improved fee calculations when sending SLP 87 | 88 | ### Bug fixes 89 | 90 | - Updating bitbox-sdk to reduce server load 91 | - Fixed SLP issue sending tokens when user has a large number of SLP related UTXO 92 | - Fixed bug where new users see negative balance in sending flow 93 | - Allow sending non fungible tokens without ticker symbols 94 | - Fixed bug where app crashes when accessing an SLP token before it's data is loaded 95 | 96 | ## 0.6.2 and earlier 97 | 98 | - Initial versions of Badger Wallet for both iOS and Android 99 | - Android Beta release 100 | - iOS Beta release 101 | -------------------------------------------------------------------------------- /SMOKE_TESTS.md: -------------------------------------------------------------------------------- 1 | # Smoke Tests 2 | 3 | Set of manual tests to complete and verify before each major release. 4 | Update the tests as issues are found and new workflows are created. 5 | 6 | ## Account Setup 7 | 8 | - [ ] After update, ensure the existing account is still logged in without issue 9 | - [ ] Delete the app and do a fresh install, recover an account 10 | - [ ] Create a new account 11 | - [ ] Clear seed phrase backup prompt 12 | 13 | ## Basic Send Flows 14 | 15 | - [ ] Send BCH to `bitcoincash` address 16 | - [ ] Ensure history and amounts update correct 17 | - [ ] Send SLP to `simpleledger` address 18 | - [ ] Ensure history and amounts update correct 19 | - [ ] Attempt sending to an invalid address 20 | - [ ] Attempt sending an invalid amount 21 | - [ ] Attempt sending a token from a wallet with 0 BCH balance 22 | - [ ] Send Max of an SLP token 23 | - [ ] Send Max of BCH balance, from a wallet which contains 1+ SLP tokens 24 | - [ ] Send a small amount of an SLP token (< 50) 25 | - [ ] Send a large amount of an SLP token (> 5000) 26 | 27 | ## Open from URI's 28 | 29 | - [ ] Click on a `bitcoincash` URI with no amount 30 | - [ ] Click on a `bitcoincash` URI with amount specified 31 | - [ ] Click on a `simpleledger` URI with no amount 32 | - [ ] Click on a `simpleledger` URI with an amount for a specific token 33 | - [ ] Click on a URI with a malformed address 34 | - [ ] Click on a BIP-70 URI request 35 | 36 | ## Request Screen 37 | 38 | - [ ] Request a BCH amount 39 | - [ ] Scan QR code 40 | - [ ] Paste into send flow 41 | - [ ] Request a SLP token amount 42 | - [ ] Scan QR ode 43 | - [ ] Paste into send flow 44 | 45 | ## Menu & Settings 46 | 47 | - [ ] Change currency, ensure app looks right after change 48 | - [ ] View Seed Phrase screen 49 | - [ ] Ensure logout works 50 | 51 | ## History 52 | 53 | - [ ] BCH history appears and updates 54 | - [ ] SLP history appears and updates 55 | 56 | ## BIP-70 57 | 58 | - [ ] BCH single output - Multiple payment providers (2+) 59 | - [ ] BCH multi output 60 | - [ ] SLP single output - Low amount 61 | - [ ] SLP multi output - Low amount 62 | - [ ] BCH single output - More than the wallet has funds for 63 | - [ ] SLP - More than the wallet has funds for 64 | - [ ] SLP - Token which the wallet does not have or is aware of 65 | - [ ] Invalid BIP70 payment request 66 | - [ ] Scan BIP70 request 67 | - [ ] Open BIP70 request from link 68 | - [ ] Paste BIP70 request 69 | 70 | ## Paper Sweeping 71 | 72 | - [ ] Sweep BCH empty paper wallet 73 | - [ ] Sweep BCH from paper wallet 74 | - [ ] Sweep BCH + SLP from paper wallet same time 75 | - [ ] Sweep SLP from paper wallet - No local funds - should error 76 | - [ ] Sweep SLP from paper wallet - use own funds 77 | - [ ] Sweep SLP from paper wallet with multiple tokens 78 | -------------------------------------------------------------------------------- /android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.badgermobile", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.badgermobile", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/debug/res/xml/react_native_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost 5 | 10.0.2.2 6 | 10.0.3.2 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/AntDesign.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/AntDesign.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Fontisto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Fontisto.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/badgermobile/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.badgermobile; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. This is used to schedule 9 | * rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "badgerMobile"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/badgermobile/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.badgermobile; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.facebook.react.PackageList; 7 | import com.facebook.hermes.reactexecutor.HermesExecutorFactory; 8 | import com.facebook.react.bridge.JavaScriptExecutorFactory; 9 | import com.facebook.react.ReactApplication; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactPackage; 12 | 13 | import com.facebook.soloader.SoLoader; 14 | 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.util.List; 17 | 18 | public class MainApplication extends Application implements ReactApplication { 19 | 20 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 21 | @Override 22 | public boolean getUseDeveloperSupport() { 23 | return BuildConfig.DEBUG; 24 | } 25 | 26 | @Override 27 | protected List getPackages() { 28 | @SuppressWarnings("UnnecessaryLocalVariable") 29 | List packages = new PackageList(this).getPackages(); 30 | // Packages that cannot be autolinked yet can be added manually here, for example: 31 | // packages.add(new MyReactNativePackage()); 32 | return packages; 33 | } 34 | 35 | @Override 36 | protected String getJSMainModuleName() { 37 | return "index"; 38 | } 39 | }; 40 | 41 | @Override 42 | public ReactNativeHost getReactNativeHost() { 43 | return mReactNativeHost; 44 | } 45 | 46 | @Override 47 | public void onCreate() { 48 | super.onCreate(); 49 | SoLoader.init(this, /* native exopackage */ false); 50 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled 51 | } 52 | 53 | /** 54 | * Loads Flipper in React Native templates. 55 | * 56 | * @param context 57 | */ 58 | private static void initializeFlipper(Context context) { 59 | if (BuildConfig.DEBUG) { 60 | try { 61 | /* 62 | We use reflection here to pick up the class that initializes Flipper, 63 | since Flipper library is not available in release mode 64 | */ 65 | Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); 66 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); 67 | } catch (ClassNotFoundException e) { 68 | e.printStackTrace(); 69 | } catch (NoSuchMethodException e) { 70 | e.printStackTrace(); 71 | } catch (IllegalAccessException e) { 72 | e.printStackTrace(); 73 | } catch (InvocationTargetException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Badger Wallet 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = '32.0.0-rc1' 6 | minSdkVersion = 21 7 | compileSdkVersion = 30 8 | targetSdkVersion = 30 9 | ndkVersion = "20.1.5948944" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath('com.android.tools.build:gradle:4.2.0') 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url("$rootDir/../node_modules/react-native/android") 31 | } 32 | maven { 33 | // Android JSC is installed from npm 34 | url("$rootDir/../node_modules/jsc-android/dist") 35 | } 36 | google() 37 | jcenter() 38 | maven { url 'https://www.jitpack.io' } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useAndroidX=true 21 | android.enableJetifier=true 22 | 23 | org.gradle.jvmargs=-Xmx4608M 24 | 25 | MYAPP_RELEASE_STORE_FILE=badger-mobile-release.keystore 26 | MYAPP_RELEASE_KEY_ALIAS=badger-mobile-release 27 | MYAPP_RELEASE_STORE_PASSWORD=android 28 | MYAPP_RELEASE_KEY_PASSWORD=android -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jun 06 16:28:58 ChST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 17 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 18 | 19 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 20 | set DEFAULT_JVM_OPTS= 21 | 22 | @rem Find java.exe 23 | if defined JAVA_HOME goto findJavaFromJavaHome 24 | 25 | set JAVA_EXE=java.exe 26 | %JAVA_EXE% -version >NUL 2>&1 27 | if "%ERRORLEVEL%" == "0" goto execute 28 | 29 | echo. 30 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 31 | echo. 32 | echo Please set the JAVA_HOME variable in your environment to match the 33 | echo location of your Java installation. 34 | 35 | goto fail 36 | 37 | :findJavaFromJavaHome 38 | set JAVA_HOME=%JAVA_HOME:"=% 39 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 40 | 41 | if exist "%JAVA_EXE%" goto execute 42 | 43 | echo. 44 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 45 | echo. 46 | echo Please set the JAVA_HOME variable in your environment to match the 47 | echo location of your Java installation. 48 | 49 | goto fail 50 | 51 | :execute 52 | @rem Setup the command line 53 | 54 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 55 | 56 | @rem Execute Gradle 57 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 58 | 59 | :end 60 | @rem End local scope for the variables with windows NT shell 61 | if "%ERRORLEVEL%"=="0" goto mainEnd 62 | 63 | :fail 64 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 65 | rem the _cmd.exe /c_ return code! 66 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 67 | exit /b 1 68 | 69 | :mainEnd 70 | if "%OS%"=="Windows_NT" endlocal 71 | 72 | :omega 73 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'badgerMobile' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | -------------------------------------------------------------------------------- /api/api.bitcoin.com.ts: -------------------------------------------------------------------------------- 1 | // import axios, { AxiosResponse } from "axios" 2 | 3 | // export class Price { 4 | // public async current(currency: string = "usd"): Promise { 5 | // try { 6 | // const response: AxiosResponse = await axios.get( 7 | // `https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}` 8 | // ) 9 | // return response.data.price 10 | // } catch (error) { 11 | // if (error.response && error.response.data) throw error.response.data 12 | // else throw error 13 | // } 14 | // } 15 | // } 16 | 17 | const API = `https://index-api.bitcoin.com/api`; 18 | 19 | const priceEndpoint = `${API}/v0/cash/price/`; 20 | 21 | const getPrice = async (currency: string = "usd"): Promise => { 22 | try { 23 | const req = await fetch(`${priceEndpoint}${currency.toLowerCase()}`); 24 | const resp = await req.json(); 25 | return resp.price; 26 | } catch (e) { 27 | console.warn(e); 28 | throw e; 29 | } 30 | }; 31 | 32 | export { priceEndpoint, getPrice }; 33 | -------------------------------------------------------------------------------- /api/pay.badger.ts: -------------------------------------------------------------------------------- 1 | // Helper methods to communicate with the Post Office REST API 2 | 3 | const API = `https://pay.badger.cash`; 4 | 5 | const postageEndpoint = `${API}/postage`; 6 | 7 | const getPostageRates = async () => { 8 | try { 9 | const req = await fetch(postageEndpoint); 10 | const resp = await req.json(); 11 | return resp; 12 | } catch (e) { 13 | console.warn(e); 14 | throw e; 15 | } 16 | }; 17 | 18 | export { postageEndpoint, getPostageRates }; 19 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "badgerMobile", 3 | "displayName": "Badger Wallet" 4 | } 5 | -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/images/bch-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/bch-full.png -------------------------------------------------------------------------------- /assets/images/icon-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/icon-full.png -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/slp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/slp-logo.png -------------------------------------------------------------------------------- /assets/images/token-icons/0f3f223902c44dc2bee6d3f77d565904d8501affba5ee0c56f7b32e8080ce14b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/0f3f223902c44dc2bee6d3f77d565904d8501affba5ee0c56f7b32e8080ce14b.png -------------------------------------------------------------------------------- /assets/images/token-icons/1074bafb678b85f90bca79fa201a26011e09bfc6f723b95c770c0850f8d44fe8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/1074bafb678b85f90bca79fa201a26011e09bfc6f723b95c770c0850f8d44fe8.png -------------------------------------------------------------------------------- /assets/images/token-icons/10db44eb221797d8015d55855edbec656b73559f2afb28da3f7d5d19529ae624.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/10db44eb221797d8015d55855edbec656b73559f2afb28da3f7d5d19529ae624.png -------------------------------------------------------------------------------- /assets/images/token-icons/29d353a3d19cdd7324f1c14b3fe289293976842869fed1bea3f9510558f6f006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/29d353a3d19cdd7324f1c14b3fe289293976842869fed1bea3f9510558f6f006.png -------------------------------------------------------------------------------- /assets/images/token-icons/3f83fa9f168f01d68933ef5fdb77143b2376ba7bf3a78175258861982d90d500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/3f83fa9f168f01d68933ef5fdb77143b2376ba7bf3a78175258861982d90d500.png -------------------------------------------------------------------------------- /assets/images/token-icons/49be89bbbe018bcfaebcb41cac8340bc555f022b47b922599e510b143603f4b6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/49be89bbbe018bcfaebcb41cac8340bc555f022b47b922599e510b143603f4b6.png -------------------------------------------------------------------------------- /assets/images/token-icons/4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf.png -------------------------------------------------------------------------------- /assets/images/token-icons/527a337f34e04b1974cb8a1edc7ca30b2e444bea111afc122259552243c1dbe3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/527a337f34e04b1974cb8a1edc7ca30b2e444bea111afc122259552243c1dbe3.png -------------------------------------------------------------------------------- /assets/images/token-icons/7853218e23fdabb103b4bccbe6e987da8974c7bc775b7e7e64722292ac53627f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/7853218e23fdabb103b4bccbe6e987da8974c7bc775b7e7e64722292ac53627f.png -------------------------------------------------------------------------------- /assets/images/token-icons/7f8889682d57369ed0e32336f8b7e0ffec625a35cca183f4e81fde4e71a538a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/7f8889682d57369ed0e32336f8b7e0ffec625a35cca183f4e81fde4e71a538a1.png -------------------------------------------------------------------------------- /assets/images/token-icons/959a6818cba5af8aba391d3f7649f5f6a5ceb6cdcd2c2a3dcb5d2fbfc4b08e98.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/959a6818cba5af8aba391d3f7649f5f6a5ceb6cdcd2c2a3dcb5d2fbfc4b08e98.png -------------------------------------------------------------------------------- /assets/images/token-icons/9fc89d6b7d5be2eac0b3787c5b8236bca5de641b5bafafc8f450727b63615c11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/9fc89d6b7d5be2eac0b3787c5b8236bca5de641b5bafafc8f450727b63615c11.png -------------------------------------------------------------------------------- /assets/images/token-icons/c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479.png -------------------------------------------------------------------------------- /assets/images/token-icons/eebaa04d0e715b7bd21901cb60e10d7f71d219626daf24c57ce6ea9584333149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/eebaa04d0e715b7bd21901cb60e10d7f71d219626daf24c57ce6ea9584333149.png -------------------------------------------------------------------------------- /assets/images/token-icons/f35007140e40c4b6ce4ecc9ad166101ad94562b3e4f650a30de10b8a80c0b987.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/f35007140e40c4b6ce4ecc9ad166101ad94562b3e4f650a30de10b8a80c0b987.png -------------------------------------------------------------------------------- /assets/images/token-icons/f66c6d0ac6b8c5c4ed469234ec9734f6d3499b0351b22349f40e617d22254fec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/images/token-icons/f66c6d0ac6b8c5c4ed469234ec9734f6d3499b0351b22349f40e617d22254fec.png -------------------------------------------------------------------------------- /assets/store-images/Badger-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/store-images/Badger-Banner.png -------------------------------------------------------------------------------- /assets/store-images/Badger-Banner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/store-images/Badger-Banner.psd -------------------------------------------------------------------------------- /assets/store-images/ios-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/store-images/ios-splash.png -------------------------------------------------------------------------------- /assets/store-images/ios-splash.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/assets/store-images/ios-splash.psd -------------------------------------------------------------------------------- /atoms/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import styled, { css } from "styled-components"; 4 | import { TouchableOpacity, StyleSheet } from "react-native"; 5 | import T from "../T"; 6 | 7 | interface ButtonProps { 8 | nature?: "primary" | "caution" | "cautionGhost" | "ghost" | "inverse"; 9 | } 10 | 11 | const StyledButton = styled(TouchableOpacity)` 12 | border-width: ${StyleSheet.hairlineWidth}; 13 | min-width: 135px; 14 | 15 | padding: 8px 12px; 16 | border-radius: 3px; 17 | justify-content: center; 18 | ${props => 19 | props.nature === "primary" 20 | ? css` 21 | background-color: ${props.theme.primary500}; 22 | ` 23 | : props.nature === "cautionGhost" 24 | ? css` 25 | background-color: transparent; 26 | border-color: ${props.theme.accent500}; 27 | ` 28 | : props.nature === "ghost" 29 | ? css` 30 | background-color: transparent; 31 | border-color: ${props.theme.primary500}; 32 | ` 33 | : props.nature === "inverse" 34 | ? css` 35 | background-color: ${props => props.theme.bg900}; 36 | border-color: ${props.theme.primary300}; 37 | ` 38 | : css` 39 | border-color: ${props.theme.primary500}; 40 | background-color: ${props.theme.primary500}; 41 | `} 42 | `; 43 | 44 | interface Props { 45 | text?: string; 46 | children?: React.ReactNode; 47 | nature?: "primary" | "caution" | "cautionGhost" | "ghost" | "inverse"; 48 | onPress(): void; 49 | style?: any; 50 | } 51 | 52 | const Button = ({ text, children, nature, ...rest }: Props) => { 53 | const textType = 54 | nature === "cautionGhost" 55 | ? "accent" 56 | : nature === "inverse" 57 | ? "primary" 58 | : "inverse"; 59 | return ( 60 | 61 | {children ? ( 62 | children 63 | ) : ( 64 | 65 | {text} 66 | 67 | )} 68 | 69 | ); 70 | }; 71 | 72 | export default Button; 73 | -------------------------------------------------------------------------------- /atoms/Button/README.md: -------------------------------------------------------------------------------- 1 | # Button 2 | 3 | Standardised button to use throughout the app. 4 | 5 | ## Props 6 | 7 | - nature - The type of button it can be 8 | - primary 9 | - danger 10 | - cautionGhost 11 | - ghost 12 | - inverse 13 | - default 14 | -------------------------------------------------------------------------------- /atoms/Button/index.ts: -------------------------------------------------------------------------------- 1 | import Button from "./Button"; 2 | 3 | export default Button; 4 | -------------------------------------------------------------------------------- /atoms/H1/H1.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | 3 | import { Text } from "react-native"; 4 | 5 | import { BASE_SIZE, textBase } from "../T"; 6 | 7 | const H1 = styled(Text)` 8 | ${textBase}; 9 | font-size: ${BASE_SIZE * 2}; 10 | ${props => 11 | props.spacing === "loose" && 12 | css` 13 | letter-spacing: 1.4; 14 | `} 15 | `; 16 | 17 | export default H1; 18 | -------------------------------------------------------------------------------- /atoms/H1/README.md: -------------------------------------------------------------------------------- 1 | # H1 2 | 3 | Standard heading component. Not too big, not too small. Just right, usually. 4 | -------------------------------------------------------------------------------- /atoms/H1/index.ts: -------------------------------------------------------------------------------- 1 | import H1 from "./H1"; 2 | 3 | export default H1; 4 | -------------------------------------------------------------------------------- /atoms/H2/H2.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | import { Text } from "react-native"; 4 | 5 | import { BASE_SIZE, textBase } from "../T"; 6 | 7 | const H2 = styled(Text)` 8 | ${textBase} 9 | font-size: ${BASE_SIZE * 1.5}; 10 | `; 11 | 12 | export default H2; 13 | -------------------------------------------------------------------------------- /atoms/H2/README.md: -------------------------------------------------------------------------------- 1 | # H2 2 | 3 | Standard Subheading. Example - Ticker in a wallet detail page. 4 | -------------------------------------------------------------------------------- /atoms/H2/index.ts: -------------------------------------------------------------------------------- 1 | import H2 from "./H2"; 2 | 3 | export default H2; 4 | -------------------------------------------------------------------------------- /atoms/README.md: -------------------------------------------------------------------------------- 1 | # Atoms 2 | 3 | Atoms are the basic building blocks of the UI. Simple text, buttons, styling, etc should be build as Atoms. 4 | Any UI used within a screen or a Component - except for layout related things - should be built as an `Atom` for consistency and maintainability throughout the application. 5 | 6 | ## Available Atoms 7 | 8 | - T 9 | - Basic text and common variants 10 | - H1 11 | - Largest header, and some commno variants 12 | - H2 13 | - Subheader, shares variants with H1 14 | - Spacer 15 | - Adjustable size spacing component. Prefer the use of this over adding margins components in most cases 16 | - Button 17 | - Basic tapable button and common variants 18 | - SwipeButton 19 | - Button which activates by swiping to the right 20 | -------------------------------------------------------------------------------- /atoms/Spacer/README.md: -------------------------------------------------------------------------------- 1 | # Spacer 2 | 3 | Component to provide simple and consistent vertical margin/spacing between elements. 4 | Prefer the use of Spacer over margins in most cases 5 | -------------------------------------------------------------------------------- /atoms/Spacer/Spacer.ts: -------------------------------------------------------------------------------- 1 | import { View } from "react-native"; 2 | import styled, { css } from "styled-components"; 3 | 4 | // Todo - Refactor all size related props into a single prop. 5 | interface Props { 6 | large?: boolean; 7 | small?: boolean; 8 | tiny?: boolean; 9 | minimal?: boolean; 10 | fill?: boolean; 11 | } 12 | const Spacer = styled(View)` 13 | margin-bottom: 28px; 14 | ${props => 15 | props.large && 16 | css` 17 | margin-bottom: 48px; 18 | `} 19 | ${props => 20 | props.small && 21 | css` 22 | margin-bottom: 16px; 23 | `} 24 | ${props => 25 | props.tiny && 26 | css` 27 | margin-bottom: 8px; 28 | `} 29 | ${props => 30 | props.minimal && 31 | css` 32 | margin-bottom: 2px; 33 | `} 34 | ${props => 35 | props.fill && 36 | css` 37 | margin-bottom: 0px; 38 | flex: 1; 39 | `} 40 | `; 41 | 42 | export default Spacer; 43 | -------------------------------------------------------------------------------- /atoms/Spacer/index.ts: -------------------------------------------------------------------------------- 1 | import Spacer from "./Spacer"; 2 | 3 | export default Spacer; 4 | -------------------------------------------------------------------------------- /atoms/SwipeButton/README.md: -------------------------------------------------------------------------------- 1 | # SwipeButton 2 | 3 | Button which activates by swiping to the right. 4 | -------------------------------------------------------------------------------- /atoms/SwipeButton/SwipeButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { ActivityIndicator, View, StyleSheet } from "react-native"; 3 | import { Slider } from "@miblanchard/react-native-slider"; 4 | import styled from "styled-components"; 5 | import { spaceBadger } from "../../themes/spaceBadger"; 6 | 7 | import T from "../T"; 8 | import Spacer from "../Spacer"; 9 | import Ionicons from "react-native-vector-icons/Ionicons"; 10 | 11 | const SWIPEABLE_WIDTH_PERCENT = 78; 12 | 13 | const SwipeButtonContainer = styled(View)` 14 | overflow: visible; 15 | width: ${SWIPEABLE_WIDTH_PERCENT}%; 16 | height: 70px; 17 | align-self: center; 18 | `; 19 | 20 | const SwipeContent = styled(View)` 21 | border-radius: 45px; 22 | background-color: ${props => props.theme.primary500}; 23 | `; 24 | 25 | const SwipeActivity = styled(View)` 26 | align-items: center; 27 | justify-content: center; 28 | align-self: center; 29 | `; 30 | 31 | const Swiper = styled(Slider)` 32 | flex: 1; 33 | margin-left: 10; 34 | margin-right: 10; 35 | align-items: stretch; 36 | justify-content: center; 37 | overflow: visible; 38 | `; 39 | 40 | type ButtonStates = 41 | | "neutral" 42 | | "activated" 43 | | "success" 44 | | "error" 45 | | "pending" 46 | | undefined; 47 | 48 | interface Props { 49 | swipeFn(): void; 50 | labelAction: string; 51 | } 52 | 53 | const SwipeButtonAtom = ({ swipeFn, labelAction }: Props) => { 54 | const [visible, setVisible] = useState(true); 55 | const [sliderValue, setSliderValue] = useState(0); 56 | 57 | const sliding = (value: any) => { 58 | if (value > 90) { 59 | swipeFn(); 60 | setVisible(false); 61 | } 62 | setSliderValue(value); 63 | }; 64 | 65 | const endSlide = () => { 66 | setSliderValue(0); 67 | }; 68 | 69 | const forwardCircle = () => ( 70 | 75 | ); 76 | 77 | return ( 78 | 79 | 85 | 86 | {visible ? `Slide ${labelAction}` : ""} 87 | 88 | 89 | {visible && ( 90 | 91 | sliding(value)} 99 | onSlidingComplete={() => endSlide()} 100 | /> 101 | 102 | )} 103 | 104 | ); 105 | }; 106 | 107 | export default SwipeButtonAtom; 108 | -------------------------------------------------------------------------------- /atoms/SwipeButton/index.ts: -------------------------------------------------------------------------------- 1 | import SwipeButton from "./SwipeButton"; 2 | 3 | export default SwipeButton; 4 | -------------------------------------------------------------------------------- /atoms/T/README.md: -------------------------------------------------------------------------------- 1 | # T 2 | 3 | Atom for common text throughout the app. Used for all types of text except titles. 4 | -------------------------------------------------------------------------------- /atoms/T/T.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | 3 | import { Text, Platform } from "react-native"; 4 | 5 | export interface BaseTextProps { 6 | type?: "muted" | "muted2" | "inverse" | "accent" | "primary" | "danger"; 7 | center?: boolean; 8 | right?: boolean; 9 | weight?: "bold"; 10 | spacing?: "loose"; 11 | monospace?: boolean; 12 | } 13 | 14 | interface TProps extends BaseTextProps { 15 | size?: "tiny" | "xsmall" | "small" | "large"; 16 | } 17 | 18 | export const BASE_SIZE = 16; 19 | 20 | export const textBase = css` 21 | color: ${props => 22 | props.type === "muted" 23 | ? props.theme.fg200 24 | : props.type === "muted2" 25 | ? props.theme.fg300 26 | : props.type === "inverse" 27 | ? props.theme.bg900 28 | : props.type === "accent" 29 | ? props.theme.accent500 30 | : props.type === "primary" 31 | ? props.theme.primary500 32 | : props.type === "danger" 33 | ? props.theme.danger300 34 | : props.theme.fg100}; 35 | 36 | ${props => 37 | props.center && 38 | css` 39 | text-align: center; 40 | `} 41 | ${props => 42 | props.right && 43 | css` 44 | text-align: right; 45 | `} 46 | 47 | ${props => 48 | props.weight === "bold" && 49 | css` 50 | font-weight: 700; 51 | `} 52 | ${props => 53 | props.spacing === "loose" && 54 | css` 55 | letter-spacing: 1; 56 | `} 57 | 58 | ${props => 59 | props.monospace && 60 | css` 61 | font-family: ${Platform.OS === "ios" ? "Courier" : "monospace"}; 62 | `} 63 | 64 | `; 65 | 66 | const T = styled(Text)` 67 | ${textBase}; 68 | font-size: ${BASE_SIZE}; 69 | 70 | ${props => 71 | props.size === "tiny" 72 | ? css` 73 | font-size: ${BASE_SIZE * 0.5}; 74 | ` 75 | : props.size === "xsmall" 76 | ? css` 77 | font-size: ${BASE_SIZE * 0.75}; 78 | ` 79 | : props.size === "small" 80 | ? css` 81 | font-size: ${BASE_SIZE * 0.9}; 82 | ` 83 | : props.size === "large" && 84 | css` 85 | font-size: ${BASE_SIZE * 1.2}; 86 | `} 87 | `; 88 | 89 | export default T; 90 | -------------------------------------------------------------------------------- /atoms/T/index.ts: -------------------------------------------------------------------------------- 1 | import T, { textBase, BASE_SIZE } from "./T"; 2 | 3 | export { BASE_SIZE, textBase }; 4 | 5 | export default T; 6 | -------------------------------------------------------------------------------- /atoms/index.ts: -------------------------------------------------------------------------------- 1 | import Button from "./Button"; 2 | import SwipeButton from "./SwipeButton"; 3 | import H1 from "./H1"; 4 | import H2 from "./H2"; 5 | import Spacer from "./Spacer"; 6 | import T from "./T"; 7 | 8 | export { T, H1, H2, Button, SwipeButton, Spacer }; 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /components/CoinRow/CoinRow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import { View, Image, TouchableOpacity } from "react-native"; 5 | import Ionicons from "react-native-vector-icons/Ionicons"; 6 | 7 | import { T, Spacer } from "../../atoms"; 8 | 9 | import { getTokenImage } from "../../utils/token-utils"; 10 | 11 | interface Props { 12 | amount: string; 13 | name: string; 14 | ticker: string; 15 | tokenId?: string | null; 16 | valueDisplay?: string | null; 17 | onPress(): void; 18 | } 19 | 20 | const Outer = styled(TouchableOpacity)<{ onPress(): void }>` 21 | padding: 16px 16px; 22 | flex-direction: row; 23 | border-bottom-color: ${props => props.theme.fg700}; 24 | border-bottom-width: 1px; 25 | `; 26 | 27 | const IconArea = styled(View)` 28 | justify-content: center; 29 | margin-right: 12px; 30 | `; 31 | 32 | const ArrowArea = styled(View)` 33 | justify-content: center; 34 | `; 35 | const IconImage = styled(Image)` 36 | width: 36; 37 | height: 36; 38 | border-radius: 18; 39 | overflow: hidden; 40 | `; 41 | 42 | const InfoArea = styled(View)` 43 | flex: 1; 44 | `; 45 | 46 | const CoinRow = ({ 47 | ticker, 48 | name, 49 | amount, 50 | tokenId, 51 | valueDisplay, 52 | onPress 53 | }: Props) => { 54 | const imageSource = getTokenImage(tokenId); 55 | 56 | let [amountWhole, amountDecimal] = (amount && amount.split(".")) || [ 57 | null, 58 | null 59 | ]; 60 | 61 | amountDecimal = 62 | amountDecimal && [...amountDecimal].every(v => v === "0") 63 | ? null 64 | : amountDecimal; 65 | 66 | return ( 67 | 68 | 69 | 70 | 71 | 72 | 73 | {ticker} 74 | - {name} 75 | 76 | 77 | 78 | {amountWhole} 79 | {amountDecimal && {`.${amountDecimal}`}} 80 | 81 | {valueDisplay && ( 82 | 83 | {valueDisplay} 84 | 85 | )} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | }; 95 | 96 | const HeaderWrapper = styled(View)` 97 | padding: 0px 16px; 98 | margin-top: 25px; 99 | `; 100 | 101 | const CoinRowHeader = ({ children }: { children: string }) => { 102 | return ( 103 | 104 | {children} 105 | 106 | ); 107 | }; 108 | 109 | export { CoinRowHeader }; 110 | export default CoinRow; 111 | -------------------------------------------------------------------------------- /components/CoinRow/README.md: -------------------------------------------------------------------------------- 1 | # CoinRow 2 | 3 | Component to display information about a coin on the wallet screen. 4 | 5 | Basic layout 6 | 7 | | symbol Ticker - Coin Name | 8 | | symbol Amount | 9 | | symbol extra | 10 | 11 | example 12 | 13 | | symbol BCH - Bitcoin Cash | 14 | | symbol 1.33333353 | 15 | | symbol \$175.23 | 16 | -------------------------------------------------------------------------------- /components/CoinRow/index.ts: -------------------------------------------------------------------------------- 1 | import CoinRow, { CoinRowHeader } from "./CoinRow"; 2 | 3 | export { CoinRowHeader }; 4 | export default CoinRow; 5 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | Components are relatively complex sections of the UI, often containing some local state and multiple `Atoms`. 4 | 5 | ## Available Components 6 | 7 | - CoinRow 8 | -------------------------------------------------------------------------------- /components/TransactionRow/README.md: -------------------------------------------------------------------------------- 1 | # Transaction Row 2 | 3 | A single transaction row in the transaction history page. 4 | -------------------------------------------------------------------------------- /components/TransactionRow/index.ts: -------------------------------------------------------------------------------- 1 | import TransactionRow from "./TransactionRow"; 2 | 3 | export default TransactionRow; 4 | -------------------------------------------------------------------------------- /components/index.ts: -------------------------------------------------------------------------------- 1 | import CoinRow, { CoinRowHeader } from "./CoinRow"; 2 | import TransactionRow from "./TransactionRow"; 3 | 4 | export { CoinRow, CoinRowHeader, TransactionRow }; 5 | -------------------------------------------------------------------------------- /constants/Layout.ts: -------------------------------------------------------------------------------- 1 | // unused currently, leaving as reference as it's probably a good idea. 2 | 3 | import { Dimensions } from "react-native"; 4 | 5 | const width = Dimensions.get("window").width; 6 | const height = Dimensions.get("window").height; 7 | 8 | export default { 9 | window: { 10 | width, 11 | height 12 | }, 13 | isSmallDevice: width < 375 14 | }; 15 | -------------------------------------------------------------------------------- /d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" { 2 | const value: any; 3 | export = value; 4 | } 5 | 6 | declare module "react-native-swipeable"; 7 | declare module "react-native-markdown-package"; 8 | declare module "bitcore-payment-protocol"; 9 | declare module "slp-sdk"; 10 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Redux Store 2 | 3 | The redux store which holds all data which is application wide or benefits from caching. 4 | Should not hold application specific data such as the status of menus and modals to allow for easier sharing between multiple implementation of a Badger wallet. (web, mobile, extension, etc). 5 | 6 | Data in reducers should always be `normalized` for consistency 7 | Data should always be requested through `selectors` for maintainability 8 | -------------------------------------------------------------------------------- /data/accounts/README.md: -------------------------------------------------------------------------------- 1 | # Accounts Data 2 | 3 | Address, mnemonic, and keypair information for accounts. 4 | 5 | Each account is referenced by their 145 derivation path, but each account also has an associated 245 derivation path address to be used with SLP tokens. 6 | -------------------------------------------------------------------------------- /data/accounts/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import thunk, { ThunkDispatch } from "redux-thunk"; 3 | import configureMockStore from "redux-mock-store"; 4 | 5 | import * as actions from "./actions"; 6 | import * as actionTypes from "./constants"; 7 | import { FullState } from "../store"; 8 | import { Account } from "./reducer"; 9 | 10 | type DispatchExts = ThunkDispatch; 11 | 12 | const middlewares = [thunk]; 13 | const mockStore = configureMockStore(middlewares); 14 | 15 | describe("accounts::action::creators", () => { 16 | it("should create action for - Get account start", () => { 17 | const expectedAction = { 18 | type: actionTypes.GET_ACCOUNT_START, 19 | payload: null 20 | }; 21 | expect(actions.getAccountStart()).toEqual(expectedAction); 22 | }); 23 | 24 | it("should create action for - Get account success", () => { 25 | // Only testing these values get passed, not the logic on them at this point. 26 | const account = ("anything" as unknown) as Account; 27 | const accountSlp = ("anything2" as unknown) as Account; 28 | const isNew = true; 29 | const expectedAction = { 30 | type: actionTypes.GET_ACCOUNT_SUCCESS, 31 | payload: { account, accountSlp, isNew } 32 | }; 33 | expect(actions.getAccountSuccess(account, accountSlp, isNew)).toEqual( 34 | expectedAction 35 | ); 36 | }); 37 | 38 | it("should create action for - Get account fail", () => { 39 | const expectedAction = { 40 | type: actionTypes.GET_ACCOUNT_FAIL, 41 | payload: null 42 | }; 43 | expect(actions.getAccountFail()).toEqual(expectedAction); 44 | }); 45 | 46 | it("should create action for - logout account", () => { 47 | const expectedAction = { 48 | type: actionTypes.LOGOUT_ACCOUNT, 49 | payload: null 50 | }; 51 | expect(actions.logoutAccount()).toEqual(expectedAction); 52 | }); 53 | 54 | it("should create action for - view seed", () => { 55 | const address = "someString"; 56 | const expectedAction = { 57 | type: actionTypes.VIEW_SEED, 58 | payload: { address } 59 | }; 60 | expect(actions.viewSeed(address)).toEqual(expectedAction); 61 | }); 62 | }); 63 | 64 | describe("accounts::actions::async", () => { 65 | it.todo("get account flow"); 66 | }); 67 | -------------------------------------------------------------------------------- /data/accounts/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GET_ACCOUNT_START, 3 | GET_ACCOUNT_SUCCESS, 4 | GET_ACCOUNT_FAIL, 5 | LOGOUT_ACCOUNT, 6 | VIEW_SEED 7 | } from "./constants"; 8 | 9 | import { Account } from "./reducer"; 10 | 11 | import { deriveAccount, generateMnemonic } from "../../utils/account-utils"; 12 | 13 | const getAccountStart = () => ({ 14 | type: GET_ACCOUNT_START, 15 | payload: null 16 | }); 17 | 18 | const getAccountSuccess = ( 19 | account: Account, 20 | accountSlp: Account, 21 | isNew: boolean 22 | ) => ({ 23 | type: GET_ACCOUNT_SUCCESS, 24 | payload: { 25 | account, 26 | accountSlp, 27 | isNew 28 | } 29 | }); 30 | 31 | const getAccountFail = () => ({ 32 | type: GET_ACCOUNT_FAIL, 33 | payload: null 34 | }); 35 | 36 | const getAccount = (mnemonic?: string, accountIndex: number = 0) => { 37 | const accountMnemonic = mnemonic ? mnemonic : generateMnemonic(); 38 | const isNew = !mnemonic; 39 | 40 | return async (dispatch: Function, getState: Function) => { 41 | dispatch(getAccountStart()); 42 | 43 | const derivationPathBCH = "m/44'/145'"; 44 | const derivationPathSLP = "m/44'/245'"; 45 | 46 | const childIndex = 0; 47 | // TODO - Error or fail state 48 | const account = deriveAccount( 49 | accountMnemonic, 50 | accountIndex, 51 | childIndex, 52 | derivationPathBCH 53 | ) as Account; 54 | 55 | const accountSlp = deriveAccount( 56 | accountMnemonic, 57 | accountIndex, 58 | childIndex, 59 | derivationPathSLP 60 | ) as Account; 61 | 62 | dispatch(getAccountSuccess(account, accountSlp, isNew)); 63 | }; 64 | }; 65 | 66 | const logoutAccount = () => { 67 | return { 68 | type: LOGOUT_ACCOUNT, 69 | payload: null 70 | }; 71 | }; 72 | 73 | const viewSeed = (address: string) => { 74 | return { 75 | type: VIEW_SEED, 76 | payload: { 77 | address 78 | } 79 | }; 80 | }; 81 | 82 | export { 83 | getAccount, 84 | getAccountStart, 85 | getAccountSuccess, 86 | getAccountFail, 87 | logoutAccount, 88 | viewSeed 89 | }; 90 | -------------------------------------------------------------------------------- /data/accounts/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_ACCOUNT, 3 | GET_ACCOUNT_START, 4 | GET_ACCOUNT_FAIL, 5 | GET_ACCOUNT_SUCCESS, 6 | LOGOUT_ACCOUNT, 7 | VIEW_SEED 8 | } from "./constants"; 9 | 10 | describe("accounts::constants", () => { 11 | it("ADD_ACCOUNT defined", () => { 12 | expect(ADD_ACCOUNT).toBeDefined(); 13 | }); 14 | 15 | it("GET_ACCOUNT_START defined", () => { 16 | expect(GET_ACCOUNT_START).toBeDefined(); 17 | }); 18 | 19 | it("GET_ACCOUNT_FAIL defined", () => { 20 | expect(GET_ACCOUNT_FAIL).toBeDefined(); 21 | }); 22 | 23 | it("GET_ACCOUNT_SUCCESS defined", () => { 24 | expect(GET_ACCOUNT_SUCCESS).toBeDefined(); 25 | }); 26 | 27 | it("LOGOUT_ACCOUNT defined", () => { 28 | expect(LOGOUT_ACCOUNT).toBeDefined(); 29 | }); 30 | 31 | it("VIEW_SEED defined", () => { 32 | expect(VIEW_SEED).toBeDefined(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /data/accounts/constants.ts: -------------------------------------------------------------------------------- 1 | // Add an additional account from HD 2 | export const ADD_ACCOUNT = "badger::accounts:ADD_ACCOUNT"; 3 | 4 | // Get or create an account from keywords 5 | export const GET_ACCOUNT_START = "badger:accounts:GET_ACCOUNT_START"; 6 | export const GET_ACCOUNT_SUCCESS = "badger:accounts:GET_ACCOUNT_SUCCESS"; 7 | export const GET_ACCOUNT_FAIL = "badger:accounts:GET_ACCOUNT_FAIL"; 8 | 9 | // Logout 10 | export const LOGOUT_ACCOUNT = "badger::accounts::LOGOUT_ACCOUNT"; 11 | 12 | export const VIEW_SEED = "badger::accounts::VIEW_SEED"; 13 | -------------------------------------------------------------------------------- /data/accounts/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { 3 | GET_ACCOUNT_START, 4 | GET_ACCOUNT_SUCCESS, 5 | GET_ACCOUNT_FAIL, 6 | LOGOUT_ACCOUNT, 7 | VIEW_SEED 8 | } from "./constants"; 9 | 10 | // Todo - Fill in this type as needed 11 | export interface ECPair { 12 | compressed: boolean; 13 | d: any; 14 | network: any; 15 | __Q: any; 16 | } 17 | 18 | export interface Account { 19 | address: string; 20 | addressSlp?: string; 21 | keypair?: ECPair; 22 | mnemonic: string; 23 | accountIndex: number; 24 | seedViewed?: boolean; 25 | } 26 | 27 | export type State = { 28 | byId: { 29 | [accountId: string]: Account; 30 | }; 31 | keypairsByAccount?: { 32 | [accountId: string]: { 33 | bch: ECPair; 34 | slp: ECPair; 35 | }; 36 | }; 37 | allIds: string[]; 38 | activeId?: string | null; 39 | }; 40 | 41 | export const initialState: State = { 42 | byId: {}, 43 | allIds: [], 44 | activeId: null, 45 | keypairsByAccount: {} 46 | }; 47 | 48 | const addAccount = ( 49 | state: State, 50 | payload: { 51 | account: Account; 52 | accountSlp: Account; 53 | isNew: boolean; 54 | } 55 | ) => { 56 | const { account, accountSlp, isNew } = payload; 57 | 58 | const { keypair, ...removedKeypair } = account; 59 | const { address } = account; 60 | 61 | const combinedAccount = { 62 | ...removedKeypair, 63 | addressSlp: accountSlp.address, 64 | seedViewed: !isNew 65 | }; 66 | 67 | const keypairSlp = accountSlp.keypair; 68 | 69 | const existingAcounts = state.allIds; 70 | 71 | // TODO - Investigate this early-exit, may not be needed anymore. 72 | if (existingAcounts.includes(address)) { 73 | return { 74 | ...state, 75 | keypairsByAccount: { 76 | ...state.keypairsByAccount, 77 | [address]: { 78 | bch: keypair, 79 | slp: keypairSlp 80 | } 81 | } 82 | }; 83 | } 84 | 85 | return { 86 | ...state, 87 | byId: { 88 | ...state.byId, 89 | [address]: combinedAccount 90 | }, 91 | keypairsByAccount: { 92 | ...state.keypairsByAccount, 93 | [address]: { 94 | bch: keypair, 95 | slp: keypairSlp 96 | } 97 | }, 98 | allIds: [...state.allIds, address], 99 | activeId: address 100 | }; 101 | }; 102 | 103 | const logoutAccount = (state: State) => { 104 | return initialState; 105 | }; 106 | 107 | const setSeedViewed = ( 108 | state: State, 109 | payload: { 110 | address: string; 111 | } 112 | ) => { 113 | const { address } = payload; 114 | const currentAccount = state.byId[address]; 115 | 116 | const updatedAccount = { 117 | ...currentAccount, 118 | seedViewed: true 119 | }; 120 | return { 121 | ...state, 122 | byId: { 123 | ...state.byId, 124 | [address]: updatedAccount 125 | } 126 | }; 127 | }; 128 | 129 | const accounts = (state: State = initialState, action: AnyAction): State => { 130 | switch (action.type) { 131 | case GET_ACCOUNT_START: 132 | return state; 133 | 134 | case GET_ACCOUNT_SUCCESS: 135 | return addAccount(state, action.payload); 136 | 137 | case LOGOUT_ACCOUNT: 138 | return logoutAccount(state); 139 | 140 | case VIEW_SEED: 141 | return setSeedViewed(state, action.payload); 142 | 143 | default: 144 | return state; 145 | } 146 | }; 147 | 148 | export default accounts; 149 | -------------------------------------------------------------------------------- /data/accounts/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import { FullState } from "../store"; 3 | 4 | const accountsSelector = (state: FullState) => state.accounts; 5 | 6 | const accountsByIdSelector = (state: FullState) => state.accounts.byId; 7 | 8 | const activeAccountIdSelector = (state: FullState) => state.accounts.activeId; 9 | 10 | const keypairsByAccountSelector = (state: FullState) => 11 | state.accounts.keypairsByAccount; 12 | 13 | const activeAccountSelector = createSelector( 14 | accountsByIdSelector, 15 | activeAccountIdSelector, 16 | (byId, activeId) => { 17 | return activeId ? byId[activeId] : null; 18 | } 19 | ); 20 | 21 | const hasMnemonicSelector = createSelector(activeAccountSelector, account => { 22 | if (account && account.mnemonic) { 23 | return true; 24 | } 25 | return false; 26 | }); 27 | 28 | const getMnemonicSelector = createSelector(activeAccountSelector, account => { 29 | return account ? account.mnemonic : ""; 30 | }); 31 | 32 | const getKeypairSelector = createSelector( 33 | keypairsByAccountSelector, 34 | activeAccountIdSelector, 35 | (keypairs, accountId) => { 36 | return keypairs && accountId ? keypairs[accountId] : null; 37 | } 38 | ); 39 | 40 | const getAddressSelector = createSelector(activeAccountSelector, account => { 41 | return account ? account.address : ""; 42 | }); 43 | 44 | const getAddressSlpSelector = createSelector(activeAccountSelector, account => { 45 | return account ? account.addressSlp : ""; 46 | }); 47 | 48 | const getSeedViewedSelector = createSelector(activeAccountSelector, account => { 49 | return account && account.seedViewed; 50 | }); 51 | 52 | export { 53 | activeAccountIdSelector, 54 | activeAccountSelector, 55 | accountsByIdSelector, 56 | getAddressSelector, 57 | getAddressSlpSelector, 58 | getKeypairSelector, 59 | getMnemonicSelector, 60 | hasMnemonicSelector, 61 | getSeedViewedSelector 62 | }; 63 | -------------------------------------------------------------------------------- /data/prices/README.md: -------------------------------------------------------------------------------- 1 | # Prices reducer 2 | 3 | Fetch store and calculate the spot and historic prices for BCH and SLP tokens 4 | -------------------------------------------------------------------------------- /data/prices/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import thunk, { ThunkDispatch } from "redux-thunk"; 3 | import configureMockStore from "redux-mock-store"; 4 | 5 | import * as actions from "./actions"; 6 | import * as actionTypes from "./constants"; 7 | import { FullState } from "../store"; 8 | 9 | type DispatchExts = ThunkDispatch; 10 | 11 | const middlewares = [thunk]; 12 | const mockStore = configureMockStore(middlewares); 13 | 14 | describe("prices::actions", () => { 15 | describe("prices::action creators", () => { 16 | it("should create action for - Set fiat currency", () => { 17 | const currencyCode = "CHF"; 18 | const expectedAction = { 19 | type: actionTypes.SET_FIAT_CURRENCY, 20 | payload: currencyCode 21 | }; 22 | expect(actions.setFiatCurrency(currencyCode)).toEqual(expectedAction); 23 | }); 24 | 25 | it("should create action for - Updating spot rate start", () => { 26 | const expectedAction = { 27 | type: actionTypes.UPDATE_BCH_SPOT_PRICE_START, 28 | payload: null 29 | }; 30 | expect(actions.updateSpotPriceStart()).toEqual(expectedAction); 31 | }); 32 | 33 | it("should create action for - Updating spot rate success", () => { 34 | const now = +new Date(); 35 | 36 | const expectedAction = { 37 | type: actionTypes.UPDATE_BCH_SPOT_PRICE_SUCCESS, 38 | payload: { currency: "CHF", rate: 5000, timestamp: now } 39 | }; 40 | 41 | expect(actions.updateSpotPriceSuccess("CHF", 5000, now)).toEqual( 42 | expectedAction 43 | ); 44 | }); 45 | 46 | it("should create action for - Updating spot rate fail", () => { 47 | const now = +new Date(); 48 | const expectedAction = { 49 | type: actionTypes.UPDATE_BCH_SPOT_PRICE_FAIL, 50 | payload: { currency: "CHF", timestamp: now } 51 | }; 52 | expect(actions.updateSpotPriceFail("CHF", now)).toEqual(expectedAction); 53 | }); 54 | }); 55 | 56 | describe("prices::actions async", () => { 57 | beforeEach(() => { 58 | // Todo - setup the price API mock 59 | }); 60 | 61 | it("fetches and passes on the new price", async () => { 62 | // Hits live price API currently, so this is an integration test. 63 | 64 | // fetchMock.getOnce(`https://index-api.bitcoin.com/api/v0/cash/price/chf`, { 65 | // data: { price: 500000 } 66 | // }); 67 | const expectedActions = [ 68 | { type: actionTypes.UPDATE_BCH_SPOT_PRICE_START }, 69 | { 70 | type: actionTypes.UPDATE_BCH_SPOT_PRICE_SUCCESS, 71 | payload: { rate: 5000, currency: "CHF" } 72 | } 73 | ]; 74 | const store = mockStore({ prices: { spot: {} } } as FullState); 75 | 76 | await store.dispatch(actions.updateSpotPrice("CHF")); 77 | 78 | const gotActions = store.getActions(); 79 | 80 | expect(gotActions.length).toEqual(2); 81 | expect(gotActions[0].type).toEqual(expectedActions[0].type); 82 | expect(gotActions[1].type).toEqual(expectedActions[1].type); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /data/prices/actions.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { ThunkDispatch } from "redux-thunk"; 3 | 4 | import { CurrencyCode } from "../../utils/currency-utils"; 5 | import { getPrice } from "../../api/api.bitcoin.com"; 6 | 7 | import { 8 | UPDATE_BCH_SPOT_PRICE_START, 9 | UPDATE_BCH_SPOT_PRICE_SUCCESS, 10 | UPDATE_BCH_SPOT_PRICE_FAIL, 11 | SET_FIAT_CURRENCY 12 | } from "./constants"; 13 | import { FullState } from "../store"; 14 | 15 | const setFiatCurrency = (currencyCode: string) => ({ 16 | type: SET_FIAT_CURRENCY, 17 | payload: currencyCode 18 | }); 19 | 20 | const updateSpotPriceStart = () => ({ 21 | type: UPDATE_BCH_SPOT_PRICE_START, 22 | payload: null 23 | }); 24 | 25 | const updateSpotPriceSuccess = ( 26 | currencyCode: CurrencyCode, 27 | rate: number, 28 | timestamp: number 29 | ) => ({ 30 | type: UPDATE_BCH_SPOT_PRICE_SUCCESS, 31 | payload: { 32 | currency: currencyCode, 33 | rate, 34 | timestamp 35 | } 36 | }); 37 | 38 | const updateSpotPriceFail = ( 39 | currencyCode: CurrencyCode, 40 | timestamp: number 41 | ) => ({ 42 | type: UPDATE_BCH_SPOT_PRICE_FAIL, 43 | payload: { 44 | currency: currencyCode, 45 | timestamp 46 | } 47 | }); 48 | 49 | // For now assume BCH 50 | const updateSpotPrice = (currencyCode: CurrencyCode) => { 51 | return async ( 52 | dispatch: ThunkDispatch, 53 | getState: () => FullState 54 | ) => { 55 | dispatch(updateSpotPriceStart()); 56 | try { 57 | const rate = await getPrice(currencyCode); 58 | 59 | // API always returns as if currency has 2 decimals, even if it has none such as the JPY 60 | const decimalAdjustedRate = rate / Math.pow(10, 2); 61 | const now = +new Date(); 62 | // const decimalAdjustedRate = 63 | // rate / Math.pow(10, currencyDecimalMap[currencyCode]); 64 | 65 | dispatch(updateSpotPriceSuccess(currencyCode, decimalAdjustedRate, now)); 66 | } catch { 67 | const now = +new Date(); 68 | dispatch(updateSpotPriceFail(currencyCode, now)); 69 | } 70 | }; 71 | }; 72 | 73 | export { 74 | updateSpotPrice, 75 | updateSpotPriceSuccess, 76 | updateSpotPriceStart, 77 | updateSpotPriceFail, 78 | setFiatCurrency 79 | }; 80 | -------------------------------------------------------------------------------- /data/prices/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UPDATE_BCH_SPOT_PRICE_START, 3 | UPDATE_BCH_SPOT_PRICE_SUCCESS, 4 | UPDATE_BCH_SPOT_PRICE_FAIL, 5 | SET_FIAT_CURRENCY 6 | } from "./constants"; 7 | 8 | describe("prices::constants", () => { 9 | it("UPDATE_BCH_SPOT_PRICE_START defined", () => { 10 | expect(UPDATE_BCH_SPOT_PRICE_START).toBeDefined(); 11 | }); 12 | 13 | it("UPDATE_BCH_SPOT_PRICE_SUCCESS defined", () => { 14 | expect(UPDATE_BCH_SPOT_PRICE_SUCCESS).toBeDefined(); 15 | }); 16 | 17 | it("UPDATE_BCH_SPOT_PRICE_FAIL defined", () => { 18 | expect(UPDATE_BCH_SPOT_PRICE_FAIL).toBeDefined(); 19 | }); 20 | 21 | it("SET_FIAT_CURRENCY defined", () => { 22 | expect(SET_FIAT_CURRENCY).toBeDefined(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /data/prices/constants.ts: -------------------------------------------------------------------------------- 1 | export const UPDATE_BCH_SPOT_PRICE_START = 2 | "badger::prices::UPDATE_BCH_SPOT_PRICE_START"; 3 | export const UPDATE_BCH_SPOT_PRICE_SUCCESS = 4 | "badger::prices::UPDATE_BCH_SPOT_PRICE_SUCCESS"; 5 | export const UPDATE_BCH_SPOT_PRICE_FAIL = 6 | "badger::prices::UPDATE_BCH_SPOT_PRICE_FAIL"; 7 | 8 | export const SET_FIAT_CURRENCY = "badger::prices:SET_FIAT_CURRENCY"; 9 | -------------------------------------------------------------------------------- /data/prices/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import pricesReducer, { initialState } from "./reducer"; 2 | import { 3 | updateSpotPriceSuccess, 4 | updateSpotPriceStart, 5 | updateSpotPriceFail, 6 | setFiatCurrency 7 | } from "./actions"; 8 | 9 | describe("prices::reducer", () => { 10 | it("should return the initial state", () => { 11 | expect(pricesReducer(undefined, { type: "__init", payload: null })).toEqual( 12 | initialState 13 | ); 14 | }); 15 | 16 | it("should handle starting price by doing nothing", () => { 17 | const stateBefore = { ...initialState }; 18 | const stateAfter = pricesReducer(stateBefore, updateSpotPriceStart()); 19 | 20 | const expectedState = { ...initialState }; 21 | expect(stateAfter).toEqual(expectedState); 22 | }); 23 | 24 | it("should handle updating spot price success - new currency", () => { 25 | const stateBefore = { ...initialState }; 26 | const now = +new Date(); 27 | const stateAfter = pricesReducer( 28 | stateBefore, 29 | updateSpotPriceSuccess("CHF", 500, now) 30 | ); 31 | 32 | const expectedState = { 33 | ...initialState, 34 | spot: { 35 | bch: { 36 | ...initialState.spot.bch, 37 | CHF: { 38 | rate: 500, 39 | lastUpdated: now 40 | } 41 | } 42 | } 43 | }; 44 | expect(stateAfter).toEqual(expectedState); 45 | }); 46 | 47 | it("should handle updating spot price success - over initial currency", () => { 48 | const stateBefore = { ...initialState }; 49 | const now = +new Date(); 50 | const stateAfter = pricesReducer( 51 | stateBefore, 52 | updateSpotPriceSuccess("USD", 500, now) 53 | ); 54 | 55 | // In this case no existing bch rate to preserve 56 | const expectedState = { 57 | ...initialState, 58 | spot: { 59 | bch: { 60 | USD: { 61 | rate: 500, 62 | lastUpdated: now 63 | } 64 | } 65 | } 66 | }; 67 | expect(stateAfter).toEqual(expectedState); 68 | }); 69 | 70 | it("should handle updating spot price fail", () => { 71 | const stateBefore = { ...initialState }; 72 | const now = +new Date(); 73 | const stateAfter = pricesReducer( 74 | stateBefore, 75 | updateSpotPriceFail("CHF", now) 76 | ); 77 | 78 | const expectedState = { 79 | ...initialState, 80 | spot: { 81 | bch: { 82 | ...initialState.spot.bch, 83 | CHF: { 84 | rate: null, 85 | lastUpdated: now 86 | } 87 | } 88 | } 89 | }; 90 | expect(stateAfter).toEqual(expectedState); 91 | }); 92 | 93 | it("should handle changing fiat currency", () => { 94 | const stateBefore = { ...initialState }; 95 | const stateAfter = pricesReducer(stateBefore, setFiatCurrency("CHF")); 96 | 97 | const expectedState = { 98 | ...initialState, 99 | currencySelected: "CHF" 100 | }; 101 | 102 | expect(stateAfter).toEqual(expectedState); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /data/prices/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { 3 | UPDATE_BCH_SPOT_PRICE_START, 4 | UPDATE_BCH_SPOT_PRICE_SUCCESS, 5 | UPDATE_BCH_SPOT_PRICE_FAIL, 6 | SET_FIAT_CURRENCY 7 | } from "./constants"; 8 | 9 | import { CurrencyCode } from "../../utils/currency-utils"; 10 | 11 | export type State = { 12 | currencySelected: CurrencyCode; 13 | spot: { 14 | [coinId: string]: { 15 | [currency: string]: { 16 | rate?: number | null; 17 | lastUpdated?: number | null; 18 | }; 19 | }; 20 | }; 21 | }; 22 | 23 | export const initialState: State = { 24 | currencySelected: "USD", 25 | spot: { 26 | bch: { 27 | USD: { 28 | rate: null, 29 | lastUpdated: null 30 | } 31 | } 32 | } 33 | }; 34 | 35 | const updateSpotRate = ( 36 | state: State, 37 | { 38 | currency, 39 | rate, 40 | timestamp 41 | }: { 42 | currency: CurrencyCode; 43 | rate: number | null; 44 | timestamp: number; 45 | } 46 | ) => { 47 | return { 48 | ...state, 49 | spot: { 50 | ...state.spot, 51 | bch: { 52 | ...state.spot.bch, 53 | [currency]: { 54 | rate, 55 | lastUpdated: timestamp 56 | } 57 | } 58 | } 59 | }; 60 | }; 61 | 62 | const updateFiatCurrency = (state: State, currencyCode: CurrencyCode) => { 63 | return { 64 | ...state, 65 | currencySelected: currencyCode 66 | }; 67 | }; 68 | 69 | const prices = (state = initialState, action: AnyAction) => { 70 | switch (action.type) { 71 | case UPDATE_BCH_SPOT_PRICE_START: 72 | return state; 73 | 74 | case UPDATE_BCH_SPOT_PRICE_SUCCESS: 75 | return updateSpotRate(state, action.payload); 76 | 77 | case UPDATE_BCH_SPOT_PRICE_FAIL: 78 | return updateSpotRate(state, { 79 | currency: action.payload.currency, 80 | rate: null, 81 | timestamp: action.payload.timestamp 82 | }); 83 | 84 | case SET_FIAT_CURRENCY: 85 | return updateFiatCurrency(state, action.payload); 86 | 87 | default: 88 | return state; 89 | } 90 | }; 91 | 92 | export default prices; 93 | -------------------------------------------------------------------------------- /data/prices/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { currencySelector, spotPricesSelector } from "./selectors"; 2 | import { FullState } from "../store"; 3 | 4 | describe("price::selectors", () => { 5 | describe("currencySelector", () => { 6 | it("returns the current active fiat currency", () => { 7 | const state = { prices: { currencySelected: "CHF" } } as FullState; 8 | expect(currencySelector(state)).toEqual("CHF"); 9 | }); 10 | }); 11 | 12 | describe("spotPricesSelector", () => { 13 | it("returns all of the spot price data", () => { 14 | const spotData = { 15 | bch: { 16 | USD: { 17 | rate: null, 18 | lastUpdated: null 19 | }, 20 | CHF: { 21 | rate: 500, 22 | lastUpdated: +new Date() 23 | } 24 | } 25 | }; 26 | 27 | const state = ({ 28 | prices: { 29 | spot: spotData 30 | } 31 | } as unknown) as FullState; 32 | 33 | expect(spotPricesSelector(state)).toEqual(spotData); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /data/prices/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | 3 | import { FullState } from "../store"; 4 | 5 | const pricesSelector = (state: FullState) => state.prices; 6 | 7 | const currencySelector = createSelector(pricesSelector, prices => { 8 | return prices.currencySelected; 9 | }); 10 | 11 | // TODO - Create selectors to get specific amounts for currencies and coins. Less logic in view layer. 12 | // This is a mapping of all the known spot prices 13 | const spotPricesSelector = createSelector(pricesSelector, prices => { 14 | return prices.spot; 15 | }); 16 | 17 | export { currencySelector, spotPricesSelector }; 18 | -------------------------------------------------------------------------------- /data/selectors.test.ts: -------------------------------------------------------------------------------- 1 | describe("data::selectors", () => { 2 | it.todo("to complete"); 3 | }); 4 | -------------------------------------------------------------------------------- /data/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import BigNumber from "bignumber.js"; 3 | import _ from "lodash"; 4 | 5 | import { activeAccountSelector } from "./accounts/selectors"; 6 | import { transactionsSelector } from "./transactions/selectors"; 7 | import { utxosByAccountSelector } from "./utxos/selectors"; 8 | 9 | // number, but actually BigNumber. 10 | export type Balances = { 11 | satoshisAvailable: BigNumber; 12 | satoshisLockedInMintingBaton: BigNumber; 13 | satoshisLockedInTokens: BigNumber; 14 | slpTokens: { 15 | [tokenId: string]: BigNumber; 16 | }; 17 | }; 18 | 19 | const transactionsActiveAccountSelector = createSelector( 20 | activeAccountSelector, 21 | transactionsSelector, 22 | (activeAccount, transactions) => { 23 | if (!activeAccount) return []; 24 | 25 | const { address } = activeAccount; 26 | const { byId, byAccount } = transactions; 27 | 28 | if (!address) return []; 29 | 30 | const accountTransactionIds = byAccount[address] || []; 31 | 32 | const accountTransactions = accountTransactionIds.map( 33 | txHash => byId[txHash] 34 | ); 35 | 36 | const sortedTransactions = _.sortBy(accountTransactions, [ 37 | "time" 38 | ]).reverse(); 39 | 40 | return sortedTransactions; 41 | } 42 | ); 43 | const transactionsLatestBlockSelector = createSelector( 44 | transactionsActiveAccountSelector, 45 | transactions => { 46 | const latestBlock = transactions.reduce( 47 | (acc, curr) => (curr.block > acc ? curr.block : acc), 48 | 0 49 | ); 50 | return latestBlock; 51 | } 52 | ); 53 | const balancesSelector = createSelector(utxosByAccountSelector, utxos => { 54 | const balancesInitial: Balances = { 55 | satoshisAvailable: new BigNumber(0), 56 | satoshisLockedInMintingBaton: new BigNumber(0), 57 | satoshisLockedInTokens: new BigNumber(0), 58 | slpTokens: {} 59 | }; 60 | if (!utxos) return balancesInitial; 61 | const balances: Balances = utxos.reduce((prev, utxo) => { 62 | if (!utxo) return prev; 63 | 64 | if (utxo.slp) { 65 | if (utxo.slp.type == "BATON") { 66 | return { 67 | ...prev, 68 | satoshisLockedInMintingBaton: prev.satoshisLockedInMintingBaton.plus( 69 | utxo.value 70 | ) 71 | }; 72 | } else { 73 | const { tokenId, value } = utxo.slp; 74 | const previousQuantity = prev.slpTokens[tokenId] || new BigNumber(0); 75 | return { 76 | ...prev, 77 | satoshisLockedInTokens: prev.satoshisLockedInTokens.plus(utxo.value), 78 | slpTokens: { 79 | ...prev.slpTokens, 80 | [tokenId]: previousQuantity.plus(value) 81 | } 82 | }; 83 | } 84 | } 85 | 86 | if (!utxo.slp) { 87 | return { 88 | ...prev, 89 | satoshisAvailable: prev.satoshisAvailable.plus(utxo.value) 90 | }; 91 | } 92 | 93 | return prev; 94 | }, balancesInitial); 95 | return balances; 96 | }); 97 | export { 98 | balancesSelector, 99 | transactionsActiveAccountSelector, 100 | transactionsLatestBlockSelector 101 | }; 102 | -------------------------------------------------------------------------------- /data/settings/README.md: -------------------------------------------------------------------------------- 1 | # Settings Data 2 | 3 | Things like darkmode theme, token whitelist, preferences, etc. 4 | -------------------------------------------------------------------------------- /data/settings/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { addTokenToFavorites, removeTokenFromFavorites } from "./actions"; 2 | import * as actionTypes from "./constants"; 3 | 4 | describe("settings::actions", () => { 5 | it("creates an action for adding a favorite token", () => { 6 | const tokenId = "tokenId"; 7 | const expectedAction = { 8 | type: actionTypes.ADD_TOKEN_TO_FAVORITES, 9 | payload: tokenId 10 | }; 11 | expect(addTokenToFavorites(tokenId)).toEqual(expectedAction); 12 | }); 13 | 14 | it("creates an action for removing a favorite token", () => { 15 | const tokenId = "tokenId"; 16 | const expectedAction = { 17 | type: actionTypes.REMOVE_TOKEN_FROM_FAVORITES, 18 | payload: tokenId 19 | }; 20 | expect(removeTokenFromFavorites(tokenId)).toEqual(expectedAction); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /data/settings/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | REMOVE_TOKEN_FROM_FAVORITES, 3 | ADD_TOKEN_TO_FAVORITES 4 | } from "./constants"; 5 | 6 | const addTokenToFavorites = (tokenId: string) => ({ 7 | type: ADD_TOKEN_TO_FAVORITES, 8 | payload: tokenId 9 | }); 10 | 11 | const removeTokenFromFavorites = (tokenId: string) => ({ 12 | type: REMOVE_TOKEN_FROM_FAVORITES, 13 | payload: tokenId 14 | }); 15 | 16 | export { addTokenToFavorites, removeTokenFromFavorites }; 17 | -------------------------------------------------------------------------------- /data/settings/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_TOKEN_TO_FAVORITES, 3 | REMOVE_TOKEN_FROM_FAVORITES 4 | } from "./constants"; 5 | 6 | describe("settings::constants", () => { 7 | it("ADD_TOKEN_TO_FAVORITES defined", () => { 8 | expect(ADD_TOKEN_TO_FAVORITES).toBeDefined(); 9 | }); 10 | 11 | it("REMOVE_TOKEN_FROM_FAVORITES defined", () => { 12 | expect(REMOVE_TOKEN_FROM_FAVORITES).toBeDefined(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /data/settings/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADD_TOKEN_TO_FAVORITES = "badger::accounts:ADD_TOKEN_TO_FAVORITES"; 2 | export const REMOVE_TOKEN_FROM_FAVORITES = 3 | "badger::accounts:REMOVE_TOKEN_FROM_FAVORITES"; 4 | -------------------------------------------------------------------------------- /data/settings/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import settingsReducer, { initialState } from "./reducer"; 2 | import { addTokenToFavorites, removeTokenFromFavorites } from "./actions"; 3 | 4 | describe("settings::reducer", () => { 5 | it("should return the initial state", () => { 6 | expect( 7 | settingsReducer(undefined, { type: "__init", payload: null }) 8 | ).toEqual(initialState); 9 | }); 10 | 11 | describe("handles adding tokens to favorites", () => { 12 | it("adds a token to favorites", () => { 13 | const stateBefore = { ...initialState }; 14 | const stateAfter = settingsReducer( 15 | stateBefore, 16 | addTokenToFavorites("tokenId") 17 | ); 18 | 19 | const expectedState = { ...initialState, tokenFavorites: ["tokenId"] }; 20 | expect(stateAfter).toEqual(expectedState); 21 | }); 22 | 23 | it("adds a token only once", () => { 24 | const stateBefore = { ...initialState }; 25 | const stateAfter1 = settingsReducer( 26 | stateBefore, 27 | addTokenToFavorites("tokenId") 28 | ); 29 | const stateAfter2 = settingsReducer( 30 | stateAfter1, 31 | addTokenToFavorites("tokenId") 32 | ); 33 | 34 | const expectedState = { ...initialState, tokenFavorites: ["tokenId"] }; 35 | expect(stateAfter2).toEqual(expectedState); 36 | }); 37 | }); 38 | 39 | describe("handles removing token from favorites", () => { 40 | it("removes a token from favorites", () => { 41 | const stateBefore = { 42 | ...initialState, 43 | tokenFavorites: ["tokenId1", "tokenId2"] 44 | }; 45 | const stateAfter = settingsReducer( 46 | stateBefore, 47 | removeTokenFromFavorites("tokenId2") 48 | ); 49 | 50 | const expectedState = { ...initialState, tokenFavorites: ["tokenId1"] }; 51 | expect(stateAfter).toEqual(expectedState); 52 | }); 53 | 54 | it("does nothing if the token is not in favorites", () => { 55 | const stateBefore = { 56 | ...initialState, 57 | tokenFavorites: ["tokenId1", "tokenId2"] 58 | }; 59 | const stateAfter = settingsReducer( 60 | stateBefore, 61 | removeTokenFromFavorites("tokenId3") 62 | ); 63 | 64 | const expectedState = { 65 | ...initialState, 66 | tokenFavorites: ["tokenId1", "tokenId2"] 67 | }; 68 | expect(stateAfter).toEqual(expectedState); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /data/settings/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { 3 | REMOVE_TOKEN_FROM_FAVORITES, 4 | ADD_TOKEN_TO_FAVORITES 5 | } from "./constants"; 6 | 7 | export interface SettingsState { 8 | tokenFavorites: string[] | undefined; 9 | } 10 | export const initialState: SettingsState = { 11 | tokenFavorites: [] 12 | }; 13 | 14 | const addTokenToFavorites = ( 15 | state: SettingsState, 16 | tokenId: string 17 | ): SettingsState => { 18 | const { tokenFavorites } = state; 19 | const updatedFavorites = tokenFavorites 20 | ? [...new Set([...tokenFavorites, tokenId])] 21 | : [tokenId]; 22 | 23 | return { 24 | ...state, 25 | tokenFavorites: updatedFavorites 26 | }; 27 | }; 28 | 29 | const removeTokenFromFavorites = ( 30 | state: SettingsState, 31 | tokenId: string 32 | ): SettingsState => { 33 | const { tokenFavorites } = state; 34 | const updatedFavorites = tokenFavorites 35 | ? tokenFavorites.filter(x => x !== tokenId) 36 | : []; 37 | 38 | return { 39 | ...state, 40 | tokenFavorites: updatedFavorites 41 | }; 42 | }; 43 | 44 | const settings = (state = initialState, action: AnyAction): SettingsState => { 45 | switch (action.type) { 46 | case ADD_TOKEN_TO_FAVORITES: 47 | return addTokenToFavorites(state, action.payload); 48 | 49 | case REMOVE_TOKEN_FROM_FAVORITES: 50 | return removeTokenFromFavorites(state, action.payload); 51 | 52 | default: 53 | return state; 54 | } 55 | }; 56 | 57 | export default settings; 58 | -------------------------------------------------------------------------------- /data/settings/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { settingsSelector, tokenFavoritesSelector } from "./selectors"; 2 | import { FullState } from "../store"; 3 | 4 | describe("settings::selectors", () => { 5 | it("selector for setting slice", () => { 6 | const settingsSlice = { anything: "here" }; 7 | const state = ({ settings: settingsSlice } as unknown) as FullState; 8 | 9 | expect(settingsSelector(state)).toEqual(settingsSlice); 10 | }); 11 | 12 | it("selector for favorite tokens", () => { 13 | const tokenFavorites = ["tokenId1", "tokenId2"]; 14 | const state = ({ settings: { tokenFavorites } } as unknown) as FullState; 15 | 16 | expect(tokenFavoritesSelector(state)).toEqual(tokenFavorites); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /data/settings/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import { FullState } from "../store"; 3 | 4 | const settingsSelector = (state: FullState) => state.settings; 5 | 6 | const tokenFavoritesSelector = createSelector(settingsSelector, settings => { 7 | return settings.tokenFavorites; 8 | }); 9 | 10 | export { settingsSelector, tokenFavoritesSelector }; 11 | -------------------------------------------------------------------------------- /data/store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | Middleware, 6 | AnyAction 7 | } from "redux"; 8 | import { persistStore, persistReducer, PersistState } from "redux-persist"; 9 | import AsyncStorage from "@react-native-community/async-storage"; 10 | import ReduxThunk, { ThunkMiddleware } from "redux-thunk"; 11 | 12 | import accountsReducer, { 13 | State as StateAccount, 14 | initialState as initialAccountState 15 | } from "./accounts/reducer"; 16 | 17 | import transactionsReducer, { 18 | State as StateTransactions, 19 | initialState as initialTransactionsState 20 | } from "./transactions/reducer"; 21 | 22 | import utxosReducer, { 23 | State as StateUTXOS, 24 | initialState as initialUTXOSState 25 | } from "./utxos/reducer"; 26 | 27 | import tokensReducer, { 28 | State as StateTokens, 29 | initialState as initialTokensState 30 | } from "./tokens/reducer"; 31 | 32 | import pricesReducer, { 33 | State as StatePrices, 34 | initialState as initialPricesState 35 | } from "./prices/reducer"; 36 | 37 | import settingsReducer, { 38 | SettingsState as StateSettings, 39 | initialState as initialSettingsState 40 | } from "./settings/reducer"; 41 | 42 | export type FullState = { 43 | accounts: StateAccount; 44 | prices: StatePrices; 45 | tokens: StateTokens; 46 | transactions: StateTransactions; 47 | utxos: StateUTXOS; 48 | settings: StateSettings; 49 | _persist?: PersistState; 50 | }; 51 | 52 | const initialState: FullState = { 53 | accounts: initialAccountState, 54 | prices: initialPricesState, 55 | tokens: initialTokensState, 56 | transactions: initialTransactionsState, 57 | settings: initialSettingsState, 58 | utxos: initialUTXOSState 59 | }; 60 | 61 | // TODO - Setup encryption on certain parts of the redux state 62 | const persistConfig = { 63 | key: "root", 64 | storage: AsyncStorage, 65 | whitelist: ["utxos", "tokens", "transactions", "settings"] 66 | }; 67 | 68 | // keypairs are re-computed each time the app launches, cannot persist complex objects easily. 69 | const accountsPersistConfig = { 70 | key: "accounts", 71 | storage: AsyncStorage, 72 | blacklist: ["keypairsByAccount"] 73 | }; 74 | 75 | const pricesPersistConfig = { 76 | key: "prices", 77 | storage: AsyncStorage, 78 | whitelist: ["currencySelected"] 79 | }; 80 | 81 | const rootReducer = combineReducers({ 82 | accounts: persistReducer(accountsPersistConfig, accountsReducer), 83 | prices: persistReducer(pricesPersistConfig, pricesReducer), 84 | tokens: tokensReducer, 85 | transactions: transactionsReducer, 86 | utxos: utxosReducer, 87 | settings: settingsReducer 88 | }); 89 | 90 | const persistedReducer = persistReducer(persistConfig, rootReducer); 91 | 92 | const Logger: Middleware = store => next => action => { 93 | if (__DEV__) { 94 | // Uncomment to enable debug logging 95 | // console.log("::LOG_ACTION::", action); 96 | } 97 | 98 | return next(action); 99 | }; 100 | 101 | const middleware = [ 102 | Logger, 103 | ReduxThunk as ThunkMiddleware 104 | ]; 105 | 106 | const getStore = () => { 107 | // The ignore here is because it wants initialState to have all of the persist information. 108 | // Try removing after updating libraries 109 | const store = createStore( 110 | persistedReducer, 111 | // @ts-ignore 112 | initialState, 113 | applyMiddleware(...middleware) 114 | ); 115 | const persistor = persistStore(store); 116 | return { 117 | store, 118 | persistor 119 | }; 120 | }; 121 | 122 | export { getStore }; 123 | -------------------------------------------------------------------------------- /data/tokens/README.md: -------------------------------------------------------------------------------- 1 | # Tokens Data 2 | 3 | Metadata slice for all SLP tokens. 4 | -------------------------------------------------------------------------------- /data/tokens/actions.ts: -------------------------------------------------------------------------------- 1 | import { chunk } from "lodash"; 2 | 3 | import { 4 | UPDATE_TOKENS_META_START, 5 | UPDATE_TOKENS_META_SUCCESS, 6 | UPDATE_TOKENS_META_FAIL 7 | } from "./constants"; 8 | 9 | import { TokenData } from "./reducer"; 10 | 11 | import { getTokenMetadata } from "../../utils/transaction-utils"; 12 | 13 | const updateTokensMetaStart = () => ({ 14 | type: UPDATE_TOKENS_META_START, 15 | payload: null 16 | }); 17 | 18 | const updateTokensMetaSuccess = (tokens: (TokenData | null)[]) => ({ 19 | type: UPDATE_TOKENS_META_SUCCESS, 20 | payload: { 21 | tokens 22 | } 23 | }); 24 | 25 | const updateTokensMetaFail = () => ({ 26 | type: UPDATE_TOKENS_META_FAIL, 27 | payload: null 28 | }); 29 | 30 | const updateTokensMeta = (tokenIds: string[]) => { 31 | return async (dispatch: Function, getState: Function): Promise => { 32 | dispatch(updateTokensMetaStart()); 33 | 34 | const tokenMetadataList: TokenData[] = await Promise.all( 35 | tokenIds.map((tokenIdChunk: string) => { 36 | return getTokenMetadata(tokenIdChunk); 37 | }) 38 | ); 39 | 40 | dispatch(updateTokensMetaSuccess(tokenMetadataList)); 41 | }; 42 | }; 43 | 44 | export { 45 | updateTokensMeta, 46 | updateTokensMetaStart, 47 | updateTokensMetaFail, 48 | updateTokensMetaSuccess 49 | }; 50 | -------------------------------------------------------------------------------- /data/tokens/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UPDATE_TOKENS_META_START, 3 | UPDATE_TOKENS_META_FAIL, 4 | UPDATE_TOKENS_META_SUCCESS 5 | } from "./constants"; 6 | 7 | describe("tokens::constants", () => { 8 | it("UPDATE_TOKENS_META_START defined", () => { 9 | expect(UPDATE_TOKENS_META_START).toBeDefined(); 10 | }); 11 | 12 | it("UPDATE_TOKENS_META_FAIL defined", () => { 13 | expect(UPDATE_TOKENS_META_FAIL).toBeDefined(); 14 | }); 15 | 16 | it("UPDATE_TOKENS_META_SUCCESS defined", () => { 17 | expect(UPDATE_TOKENS_META_SUCCESS).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /data/tokens/constants.ts: -------------------------------------------------------------------------------- 1 | export const UPDATE_TOKENS_META_START = 2 | "badger::tokens::UPDATE_TOKENS_META_START"; 3 | export const UPDATE_TOKENS_META_SUCCESS = 4 | "badger::tokens::UPDATE_TOKENS_META_SUCCESS"; 5 | export const UPDATE_TOKENS_META_FAIL = 6 | "badger::tokens::UPDATE_TOKENS_META_FAIL"; 7 | -------------------------------------------------------------------------------- /data/tokens/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | 3 | import { 4 | UPDATE_TOKENS_META_START, 5 | UPDATE_TOKENS_META_SUCCESS, 6 | UPDATE_TOKENS_META_FAIL 7 | } from "./constants"; 8 | 9 | export type TokenData = { 10 | tokenId: string; 11 | symbol: string; 12 | name: string; 13 | decimals: number; 14 | protocol: "slp"; 15 | protocolData?: { 16 | baton: boolean; 17 | }; 18 | }; 19 | 20 | export type State = { 21 | byId: { 22 | [tokenId: string]: TokenData; 23 | }; 24 | allIds: string[]; 25 | updating: boolean; 26 | }; 27 | 28 | export const initialState: State = { 29 | byId: {}, 30 | allIds: [], 31 | updating: false 32 | }; 33 | 34 | const updateTokens = (state: State, tokens: TokenData[]) => { 35 | if (!tokens.length) { 36 | return state; 37 | } 38 | const allIdsNext = tokens.map(val => val.tokenId); 39 | const byIdNext = tokens.reduce((prev, curr) => { 40 | return { 41 | ...prev, 42 | [curr.tokenId]: curr 43 | }; 44 | }, state.byId); 45 | return { 46 | byId: byIdNext, 47 | allIds: allIdsNext, 48 | updating: false 49 | }; 50 | }; 51 | 52 | const tokensReducer = (state = initialState, action: AnyAction) => { 53 | switch (action.type) { 54 | case UPDATE_TOKENS_META_START: 55 | return { 56 | ...state, 57 | updating: true 58 | }; 59 | 60 | case UPDATE_TOKENS_META_SUCCESS: 61 | return updateTokens(state, action.payload.tokens); 62 | 63 | case UPDATE_TOKENS_META_FAIL: 64 | return state; 65 | 66 | default: 67 | return state; 68 | } 69 | }; 70 | 71 | export default tokensReducer; 72 | -------------------------------------------------------------------------------- /data/tokens/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { tokensByIdSelector } from "./selectors"; 2 | import { FullState } from "../store"; 3 | 4 | describe("tokens::selectors", () => { 5 | it("returns all token metadata indexed by tokenId", () => { 6 | const tokenData = { a: { tokenId: "a" }, b: { tokenId: "b" } }; 7 | const state = ({ tokens: { byId: tokenData } } as unknown) as FullState; 8 | const result = tokensByIdSelector(state); 9 | 10 | expect(result["a"].tokenId).toEqual("a"); 11 | expect(result["b"].tokenId).toEqual("b"); 12 | expect(result).toEqual(tokenData); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /data/tokens/selectors.ts: -------------------------------------------------------------------------------- 1 | import { FullState } from "../store"; 2 | 3 | const tokensByIdSelector = (state: FullState) => { 4 | const { byId } = state.tokens; 5 | return byId; 6 | }; 7 | 8 | export { tokensByIdSelector }; 9 | -------------------------------------------------------------------------------- /data/transactions/README.md: -------------------------------------------------------------------------------- 1 | # Transactions 2 | 3 | Transaction history fetching and management 4 | -------------------------------------------------------------------------------- /data/transactions/actions.test.ts: -------------------------------------------------------------------------------- 1 | import configureMockStore from "redux-mock-store"; 2 | import thunk, { ThunkDispatch } from "redux-thunk"; 3 | import { AnyAction } from "redux"; 4 | 5 | import { 6 | getTransactionsStart, 7 | getTransactionsFail, 8 | getTransactionsSuccess, 9 | updateTransactions 10 | } from "./actions"; 11 | 12 | import * as actionTypes from "./constants"; 13 | import { initialState, Transaction } from "./reducer"; 14 | import { FullState } from "../store"; 15 | 16 | describe("transactions::action::creators", () => { 17 | it("get transactions start", () => { 18 | const expectedAction = { 19 | type: actionTypes.GET_TRANSACTIONS_START, 20 | payload: null 21 | }; 22 | expect(getTransactionsStart()).toEqual(expectedAction); 23 | }); 24 | 25 | it("get transactions success", () => { 26 | const tokenData = (["any", "values"] as unknown) as Transaction[]; 27 | const now = +new Date(); 28 | 29 | const expectedAction = { 30 | type: actionTypes.GET_TRANSACTIONS_SUCCESS, 31 | payload: { 32 | transactions: tokenData, 33 | address: "bchAddress", 34 | timestamp: now 35 | } 36 | }; 37 | 38 | expect(getTransactionsSuccess(tokenData, "bchAddress", now)).toEqual( 39 | expectedAction 40 | ); 41 | }); 42 | 43 | it("get transactions fail", () => { 44 | const expectedAction = { 45 | type: actionTypes.GET_TRANSACTIONS_FAIL, 46 | payload: null 47 | }; 48 | expect(getTransactionsFail()).toEqual(expectedAction); 49 | }); 50 | }); 51 | 52 | describe("transactions::action::async", () => { 53 | it.todo("update transactions - this one might be tricky"); 54 | }); 55 | -------------------------------------------------------------------------------- /data/transactions/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GET_TRANSACTIONS_START, 3 | GET_TRANSACTIONS_FAIL, 4 | GET_TRANSACTIONS_SUCCESS 5 | } from "./constants"; 6 | 7 | describe("transactions::constants", () => { 8 | it("GET_TRANSACTIONS_START defined", () => { 9 | expect(GET_TRANSACTIONS_START).toBeDefined(); 10 | }); 11 | 12 | it("GET_TRANSACTIONS_FAIL defined", () => { 13 | expect(GET_TRANSACTIONS_FAIL).toBeDefined(); 14 | }); 15 | 16 | it("GET_TRANSACTIONS_SUCCESS defined", () => { 17 | expect(GET_TRANSACTIONS_SUCCESS).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /data/transactions/constants.ts: -------------------------------------------------------------------------------- 1 | // Get all transactions for an account 2 | export const GET_TRANSACTIONS_START = 3 | "badger::transactions::GET_TRANSACTIONS_START"; 4 | export const GET_TRANSACTIONS_SUCCESS = 5 | "badger::transactions::GET_TRANSACTIONS_SUCCESS"; 6 | export const GET_TRANSACTIONS_FAIL = 7 | "badger::transactions::GET_TRANSACTIONS_FAIL"; 8 | -------------------------------------------------------------------------------- /data/transactions/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import transactionsReducer, { initialState, Transaction } from "./reducer"; 2 | import { 3 | getTransactionsStart, 4 | getTransactionsFail, 5 | getTransactionsSuccess 6 | } from "./actions"; 7 | 8 | describe("transactions::reducer", () => { 9 | it("should return the initial state", () => { 10 | expect( 11 | transactionsReducer(undefined, { type: "__init", payload: null }) 12 | ).toEqual(initialState); 13 | }); 14 | 15 | it("handles update start", () => { 16 | const stateBefore = { ...initialState }; 17 | const stateAfter = transactionsReducer(stateBefore, getTransactionsStart()); 18 | 19 | const expectedState = { ...initialState, updating: true }; 20 | expect(stateAfter).toEqual(expectedState); 21 | }); 22 | 23 | it("handles update fail by doing nothing", () => { 24 | const stateBefore = { ...initialState }; 25 | const stateAfter = transactionsReducer(stateBefore, getTransactionsFail()); 26 | 27 | const expectedState = { ...initialState }; 28 | expect(stateAfter).toEqual(expectedState); 29 | }); 30 | 31 | describe("transaction update success", () => { 32 | it("adds new transactions to the store, normalized", () => { 33 | const stateBefore = { ...initialState }; 34 | const now = +new Date(); 35 | 36 | const transaction1 = ({ 37 | hash: "someTxHash", 38 | time: now, 39 | block: 1001, 40 | networkId: "unit-test", 41 | txParams: { 42 | arbitrary: "values" 43 | } 44 | } as unknown) as Transaction; 45 | 46 | const newTransactions = [transaction1]; 47 | const address = "bchAddress"; 48 | const stateAfter = transactionsReducer( 49 | stateBefore, 50 | getTransactionsSuccess(newTransactions, address, now) 51 | ); 52 | 53 | const expectedState = { 54 | ...initialState, 55 | allIds: ["someTxHash"], 56 | byId: { 57 | someTxHash: transaction1 58 | }, 59 | byAccount: { 60 | bchAddress: ["someTxHash"] 61 | }, 62 | updating: false, 63 | lastUpdate: now 64 | }; 65 | 66 | expect(stateAfter).toEqual(expectedState); 67 | }); 68 | 69 | it("updates a transaction if it already exists, and adds multiple at a time", () => { 70 | const now = +new Date(); 71 | 72 | const transaction1 = ({ 73 | hash: "someTxHash", 74 | time: now, 75 | block: 1001, 76 | networkId: "unit-test", 77 | txParams: { 78 | arbitrary: "values" 79 | } 80 | } as unknown) as Transaction; 81 | 82 | const transaction1Updated = ({ 83 | ...transaction1, 84 | block: 1337 85 | } as unknown) as Transaction; 86 | 87 | const transaction2 = ({ 88 | hash: "someTxHash2", 89 | time: now, 90 | block: 1002, 91 | networkId: "unit-test", 92 | txParams: { 93 | arbitrary: "values" 94 | } 95 | } as unknown) as Transaction; 96 | 97 | const stateBefore = { 98 | ...initialState, 99 | allIds: ["someTxHash"], 100 | byId: { 101 | someTxHash: transaction1 102 | }, 103 | byAccount: { 104 | bchAddress: ["someTxHash"] 105 | }, 106 | updating: false, 107 | lastUpdate: now 108 | }; 109 | 110 | const newTransactions = [transaction1Updated, transaction2]; 111 | const address = "bchAddress"; 112 | const stateAfter = transactionsReducer( 113 | stateBefore, 114 | getTransactionsSuccess(newTransactions, address, now) 115 | ); 116 | 117 | const expectedState = { 118 | ...initialState, 119 | allIds: ["someTxHash", "someTxHash2"], 120 | byId: { 121 | someTxHash: transaction1Updated, 122 | someTxHash2: transaction2 123 | }, 124 | byAccount: { 125 | bchAddress: ["someTxHash", "someTxHash2"] 126 | }, 127 | updating: false, 128 | lastUpdate: now 129 | }; 130 | 131 | expect(stateAfter).toEqual(expectedState); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /data/transactions/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | 3 | import { 4 | GET_TRANSACTIONS_START, 5 | GET_TRANSACTIONS_SUCCESS, 6 | GET_TRANSACTIONS_FAIL 7 | } from "./constants"; 8 | 9 | // Transaction shape in redux store 10 | export type Transaction = { 11 | hash: string; 12 | txParams: { 13 | from?: string | null; 14 | to: string | null; 15 | transactionType?: string; 16 | fromAddresses: string[]; 17 | toAddresses: string[]; 18 | value?: string; 19 | valueBch: number; 20 | sendTokenData?: { 21 | tokenProtocol: "slp"; 22 | tokenId: string; 23 | valueToken: string; 24 | }; 25 | }; 26 | time: number; 27 | block: number; 28 | networkId: "mainnet" | "testnet"; 29 | }; 30 | 31 | export type State = { 32 | byId: { 33 | [transactionId: string]: Transaction; 34 | }; 35 | allIds: string[]; 36 | byAccount: { 37 | [accountId: string]: string[]; 38 | }; 39 | updating: boolean; 40 | lastUpdate: number; 41 | }; 42 | 43 | export const initialState: State = { 44 | byId: {}, 45 | allIds: [], 46 | byAccount: {}, 47 | updating: false, 48 | lastUpdate: +new Date() 49 | }; 50 | 51 | const addTransactions = ( 52 | state: State, 53 | payload: { 54 | transactions: Transaction[]; 55 | address: string; 56 | timestamp: number; 57 | } 58 | ) => { 59 | const { transactions, address, timestamp } = payload; 60 | 61 | const transactionsById = transactions.reduce((acc, tx) => { 62 | return { 63 | ...acc, 64 | [tx.hash]: tx 65 | }; 66 | }, {}); 67 | 68 | const txIds = transactions.map(tx => tx.hash); 69 | 70 | const existingAccountTxs = state.byAccount[address] || []; 71 | const nextAccountTxs = new Set([...existingAccountTxs, ...txIds]); 72 | 73 | return { 74 | ...state, 75 | byId: { 76 | ...state.byId, 77 | ...transactionsById 78 | }, 79 | byAccount: { 80 | ...state.byAccount, 81 | [address]: [...nextAccountTxs] 82 | }, 83 | allIds: [...new Set([...state.allIds, ...txIds])], 84 | updating: false, 85 | lastUpdate: timestamp 86 | }; 87 | }; 88 | 89 | const transactions = ( 90 | state: State = initialState, 91 | action: AnyAction 92 | ): State => { 93 | switch (action.type) { 94 | case GET_TRANSACTIONS_START: 95 | return { 96 | ...state, 97 | updating: true 98 | }; 99 | 100 | case GET_TRANSACTIONS_SUCCESS: 101 | return addTransactions(state, action.payload); 102 | 103 | case GET_TRANSACTIONS_FAIL: 104 | return state; 105 | 106 | default: 107 | return state; 108 | } 109 | }; 110 | 111 | export default transactions; 112 | -------------------------------------------------------------------------------- /data/transactions/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | transactionsSelector, 3 | isUpdatingTransactionsSelector 4 | } from "./selectors"; 5 | import { FullState } from "../store"; 6 | 7 | describe("transactions::selectors", () => { 8 | it("gets all transactions", () => { 9 | const transactionsData = { checking: "full slice" }; 10 | const state = ({ transactions: transactionsData } as unknown) as FullState; 11 | const result = transactionsSelector(state); 12 | 13 | expect(result).toEqual(transactionsData); 14 | }); 15 | 16 | it("gets the current update status", () => { 17 | const state = ({ 18 | transactions: { updating: true } 19 | } as unknown) as FullState; 20 | 21 | const result = isUpdatingTransactionsSelector(state); 22 | 23 | expect(result).toEqual(true); 24 | 25 | const state2 = ({ 26 | transactions: { updating: false } 27 | } as unknown) as FullState; 28 | const result2 = isUpdatingTransactionsSelector(state2); 29 | expect(result2).toEqual(false); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /data/transactions/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import _ from "lodash"; 3 | 4 | import { FullState } from "../store"; 5 | 6 | const transactionsSelector = (state: FullState) => state.transactions; 7 | 8 | const isUpdatingTransactionsSelector = createSelector( 9 | transactionsSelector, 10 | transactions => { 11 | return transactions.updating; 12 | } 13 | ); 14 | 15 | // const transactionsByAccountSelector = ( 16 | // state: FullState, 17 | // { address, tokenId }: { address: ?string, tokenId?: ?string } 18 | // ) => { 19 | // if (!address) return []; 20 | 21 | // const { byId, byAccount } = state.transactions; 22 | // const transactionTransactionsIds = byAccount[address]; 23 | 24 | // if (!transactionTransactionsIds) return []; 25 | 26 | // const transactions = transactionTransactionsIds 27 | // .map(txHash => byId[txHash]) 28 | // .filter(tx => { 29 | // const txTokenId = 30 | // tx.txParams.sendTokenData && tx.txParams.sendTokenData.tokenId; 31 | // if (tokenId) { 32 | // return tokenId === txTokenId; 33 | // } 34 | // return !tokenId; 35 | // }); 36 | 37 | // return _.sortBy(transactions, ["time"]).reverse(); 38 | // }; 39 | 40 | export { transactionsSelector, isUpdatingTransactionsSelector }; 41 | -------------------------------------------------------------------------------- /data/utxos/README.md: -------------------------------------------------------------------------------- 1 | # UTXO 2 | 3 | Information regarding each UTXO along with additional metadata injected to them for additional information such as SLP data, and spendability 4 | -------------------------------------------------------------------------------- /data/utxos/actions.test.ts: -------------------------------------------------------------------------------- 1 | import configureMockStore from "redux-mock-store"; 2 | import thunk from "redux-thunk"; 3 | import fetchMock from "fetch-mock"; 4 | 5 | import * as actions from "./actions"; 6 | import * as actionTypes from "./constants"; 7 | import { UTXO } from "./reducer"; 8 | 9 | const middlewares = [thunk]; 10 | const mockStore = configureMockStore(middlewares); 11 | 12 | describe("utxos::actions::creators", () => { 13 | it("should create action for - UPDATE_UTXO_SUCCESS_START", () => { 14 | const address = "testAddress"; 15 | 16 | const expectedAction = { 17 | type: actionTypes.UPDATE_UTXO_START, 18 | payload: null 19 | }; 20 | expect(actions.updateUtxoStart()).toEqual(expectedAction); 21 | }); 22 | 23 | it("should create action for - UPDATE_UTXO_SUCCESS", () => { 24 | const address = "testAddress"; 25 | const utxos = [] as UTXO[]; 26 | 27 | const expectedAction = { 28 | type: actionTypes.UPDATE_UTXO_SUCCESS, 29 | payload: { 30 | utxos, 31 | address 32 | } 33 | }; 34 | expect(actions.updateUtxoSuccess(utxos, address)).toEqual(expectedAction); 35 | }); 36 | 37 | it("should create action for - UPDATE_UTXO_FAIL", () => { 38 | const expectedAction = { 39 | type: actionTypes.UPDATE_UTXO_FAIL, 40 | payload: null 41 | }; 42 | expect(actions.updateUtxoFail()).toEqual(expectedAction); 43 | }); 44 | }); 45 | 46 | describe("utxos::actions::async", () => { 47 | afterEach(() => { 48 | fetchMock.restore(); 49 | }); 50 | 51 | it.todo("creates UPDATE_UTXO_SUCCESS when fetching UTXOS completes"); 52 | }); 53 | -------------------------------------------------------------------------------- /data/utxos/actions.ts: -------------------------------------------------------------------------------- 1 | import uuidv5 from "uuid/v5"; 2 | 3 | import { 4 | UPDATE_UTXO_START, 5 | UPDATE_UTXO_SUCCESS, 6 | ADDREMOVE_UTXO_SUCCESS, 7 | UPDATE_UTXO_FAIL 8 | } from "./constants"; 9 | import { UTXOJSON } from "./reducer"; 10 | 11 | import { getAllUtxos } from "../../utils/transaction-utils"; 12 | 13 | // Generated from `uuid` cli command 14 | const BADGER_UUID_NAMESPACE = "9fcd327c-41df-412f-ba45-3cc90970e680"; 15 | 16 | const updateUtxoStart = () => ({ 17 | type: UPDATE_UTXO_START, 18 | payload: null 19 | }); 20 | 21 | const updateUtxoSuccess = (utxos: UTXOJSON[], address: string) => ({ 22 | type: UPDATE_UTXO_SUCCESS, 23 | payload: { 24 | utxos, 25 | address 26 | } 27 | }); 28 | 29 | const addRemoveUtxoSuccess = ( 30 | utxos: UTXOJSON[], 31 | address: string, 32 | spentIds: string[] 33 | ) => ({ 34 | type: ADDREMOVE_UTXO_SUCCESS, 35 | payload: { 36 | utxos, 37 | address, 38 | spentIds 39 | } 40 | }); 41 | 42 | const updateUtxoFail = () => ({ 43 | type: UPDATE_UTXO_FAIL, 44 | payload: null 45 | }); 46 | 47 | // Simple unique ID for each utxo 48 | const computeUtxoId = ( 49 | utxo: UTXOJSON | { txid: string; vout: number | string } 50 | ) => uuidv5(`${utxo.hash}_${utxo.index}`, BADGER_UUID_NAMESPACE); 51 | 52 | const refreshUtxos = async (getState: Function, address: string) => { 53 | // Get all UTXO for account 54 | const utxosAll: UTXOJSON[] = await getAllUtxos(address); 55 | return utxosAll; 56 | }; 57 | 58 | const updateUtxos = (address: string, addressSlp: string) => { 59 | return async (dispatch: Function, getState: Function) => { 60 | if (!address || !addressSlp) { 61 | return; 62 | } 63 | 64 | dispatch(updateUtxoStart()); 65 | 66 | const [utxosUpdatedFull, utxosUpdatedFullSlp] = await Promise.all([ 67 | refreshUtxos(getState, address), 68 | refreshUtxos(getState, addressSlp) 69 | ]); 70 | 71 | dispatch( 72 | updateUtxoSuccess([...utxosUpdatedFull, ...utxosUpdatedFullSlp], address) 73 | ); 74 | }; 75 | }; 76 | 77 | export { 78 | updateUtxos, 79 | updateUtxoStart, 80 | addRemoveUtxoSuccess, 81 | updateUtxoSuccess, 82 | updateUtxoFail 83 | }; 84 | -------------------------------------------------------------------------------- /data/utxos/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UPDATE_UTXO_START, 3 | UPDATE_UTXO_SUCCESS, 4 | UPDATE_UTXO_FAIL 5 | } from "./constants"; 6 | 7 | describe("utxos::constants", () => { 8 | it("UPDATE_UTXO_START defined", () => { 9 | expect(UPDATE_UTXO_START).toBeDefined(); 10 | }); 11 | 12 | it("UPDATE_UTXO_SUCCESS defined", () => { 13 | expect(UPDATE_UTXO_SUCCESS).toBeDefined(); 14 | }); 15 | it("UPDATE_UTXO_FAIL defined", () => { 16 | expect(UPDATE_UTXO_FAIL).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /data/utxos/constants.ts: -------------------------------------------------------------------------------- 1 | export const UPDATE_UTXO_START = "badger::utxos::UPDATE_UTXO_START"; 2 | export const UPDATE_UTXO_SUCCESS = "badger::utxos::UPDATE_UTXO_SUCCESS"; 3 | export const ADDREMOVE_UTXO_SUCCESS = "badger::utxos::ADDREMOVE_UTXO_SUCCESS"; 4 | export const UPDATE_UTXO_FAIL = "badger::utxos::UPDATE_UTXO_FAIL"; 5 | -------------------------------------------------------------------------------- /data/utxos/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import utxosReducer, { initialState, UTXO } from "./reducer"; 2 | import { updateUtxoStart, updateUtxoFail, updateUtxoSuccess } from "./actions"; 3 | import { logoutAccount } from "../accounts/actions"; 4 | 5 | describe("utxos::reducer", () => { 6 | it("should return the initial state", () => { 7 | expect(utxosReducer(undefined, { type: "__init", payload: null })).toEqual( 8 | initialState 9 | ); 10 | }); 11 | 12 | it("handle utxo update start", () => { 13 | const stateBefore = { ...initialState }; 14 | const stateAfter = utxosReducer(stateBefore, updateUtxoStart()); 15 | 16 | const expectedState = { ...initialState, updating: true }; 17 | expect(stateAfter).toEqual(expectedState); 18 | }); 19 | 20 | it("handle utxo update fail basic", () => { 21 | const stateBefore = { ...initialState, updating: true }; 22 | const stateAfter = utxosReducer(stateBefore, updateUtxoFail()); 23 | 24 | const expectedState = { ...initialState, updating: false }; 25 | expect(stateAfter).toEqual(expectedState); 26 | }); 27 | 28 | describe("handle utxo update success", () => { 29 | it("adds new utxo to the store, normalized", () => { 30 | const stateBefore = { ...initialState }; 31 | 32 | const utxo = ({ 33 | _id: "someUtxoHash" 34 | } as unknown) as UTXO; 35 | 36 | const newUTXOs = [utxo]; 37 | const address = "bchAddress"; 38 | const stateAfter = utxosReducer( 39 | stateBefore, 40 | updateUtxoSuccess(newUTXOs, address) 41 | ); 42 | 43 | const expectedState = { 44 | ...initialState, 45 | allIds: ["someUtxoHash"], 46 | byId: { 47 | someUtxoHash: utxo 48 | }, 49 | byAccount: { 50 | bchAddress: ["someUtxoHash"] 51 | }, 52 | updating: false 53 | }; 54 | 55 | expect(stateAfter).toEqual(expectedState); 56 | }); 57 | }); 58 | it("handle logout account by resetting UTXO state", () => { 59 | const utxo = ({ 60 | _id: "someUtxoHash" 61 | } as unknown) as UTXO; 62 | 63 | const stateBefore = { 64 | ...initialState, 65 | allIds: ["someUtxoHash"], 66 | byId: { 67 | someUtxoHash: utxo 68 | }, 69 | byAccount: { 70 | bchAddress: ["someUtxoHash"] 71 | }, 72 | updating: false 73 | }; 74 | 75 | const stateAfter = utxosReducer(stateBefore, logoutAccount()); 76 | 77 | const expectedState = { 78 | ...initialState 79 | }; 80 | 81 | expect(stateAfter).toEqual(expectedState); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /data/utxos/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import BigNumber from "bignumber.js"; 3 | 4 | import { 5 | UPDATE_UTXO_START, 6 | UPDATE_UTXO_SUCCESS, 7 | ADDREMOVE_UTXO_SUCCESS, 8 | UPDATE_UTXO_FAIL 9 | } from "./constants"; 10 | 11 | import { LOGOUT_ACCOUNT } from "../accounts/constants"; 12 | import { ECPair } from "../accounts/reducer"; 13 | 14 | export type UTXO = { 15 | _id: string; 16 | txid: string; 17 | confirmations: number; 18 | amount: number; 19 | height: number; 20 | vout: any; 21 | tx: { vout: { scriptPubKey: { hex: string } }[] }; 22 | satoshis: number; 23 | slp: { 24 | baton: any; 25 | token: string; 26 | quantity: BigNumber; 27 | }; 28 | validSlpTx: boolean; 29 | spendable: boolean; 30 | address: string; 31 | keypair?: ECPair; 32 | }; 33 | 34 | export type CoinJSON = { 35 | vout: number; 36 | tokenId: string; 37 | value: string; 38 | type: "SEND" | "MINT" | "BATON" | "GENESIS"; 39 | }; 40 | 41 | export type UTXOJSON = { 42 | version: number; 43 | height: number; 44 | value: number; 45 | script: string; 46 | address: string; 47 | coinbase: boolean; 48 | hash: string; 49 | index: number; 50 | slp?: CoinJSON; 51 | }; 52 | 53 | export type State = { 54 | byId: { 55 | [utxoId: string]: UTXOJSON; 56 | }; 57 | allIds: string[]; 58 | byAccount: { 59 | [accountId: string]: string[]; 60 | }; 61 | updating: boolean; 62 | timestamp?: number; 63 | spentIds?: string[]; 64 | }; 65 | 66 | export const initialState: State = { 67 | byId: {}, 68 | allIds: [], 69 | byAccount: {}, 70 | updating: false 71 | }; 72 | 73 | const addUtxos = ( 74 | state: State, 75 | payload: { 76 | utxos: UTXOJSON[]; 77 | address: string; 78 | spentIds?: string[]; 79 | } 80 | ) => { 81 | const { address, utxos, spentIds } = payload; 82 | 83 | const timestamp = spentIds 84 | ? Date.now() 85 | : Date.now() - (state.timestamp || 0) < 180000 86 | ? state.timestamp 87 | : undefined; 88 | const fullSpentIds = timestamp 89 | ? spentIds 90 | ? [...(state.spentIds || []), ...spentIds] 91 | : state.spentIds || [] 92 | : []; 93 | 94 | const allIds = utxos.map(utxo => `${utxo.hash}_${utxo.index}`); 95 | const nextById = utxos.reduce((prev, curr, index) => { 96 | return { 97 | ...prev, 98 | [allIds[index]]: curr 99 | }; 100 | }, {}); 101 | 102 | const nextState = { 103 | ...state, 104 | byId: nextById, 105 | allIds, 106 | byAccount: { 107 | ...state.byAccount, 108 | [address]: allIds 109 | }, 110 | updating: false, 111 | timestamp: timestamp, 112 | spentIds: fullSpentIds 113 | }; 114 | 115 | return nextState; 116 | }; 117 | 118 | const utxos = (state: State = initialState, action: AnyAction): State => { 119 | switch (action.type) { 120 | case UPDATE_UTXO_START: 121 | return { 122 | ...state, 123 | updating: true 124 | }; 125 | 126 | case UPDATE_UTXO_SUCCESS: 127 | return addUtxos(state, action.payload); 128 | 129 | case ADDREMOVE_UTXO_SUCCESS: 130 | return addUtxos(state, action.payload); 131 | 132 | case UPDATE_UTXO_FAIL: 133 | return { 134 | ...state, 135 | updating: false 136 | }; 137 | 138 | case LOGOUT_ACCOUNT: 139 | return initialState; 140 | 141 | default: 142 | return state; 143 | } 144 | }; 145 | 146 | export default utxos; 147 | -------------------------------------------------------------------------------- /data/utxos/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | utxosSelector, 3 | utxosByAccountSelector, 4 | doneInitialLoadSelector, 5 | isUpdatingUTXO 6 | } from "./selectors"; 7 | import { FullState } from "../store"; 8 | 9 | describe("utxos::selectors", () => { 10 | it("select full utxo state slice", () => { 11 | const utxosSlice = { arbitrary: "values" }; 12 | 13 | const state = ({ utxos: utxosSlice } as unknown) as FullState; 14 | 15 | const result = utxosSelector(state); 16 | 17 | expect(result).toEqual(utxosSlice); 18 | }); 19 | 20 | it("select all utxo for a single account", () => { 21 | const utxosSlice = { 22 | byId: { 23 | utxoId1: "utxoData1", 24 | utxoId2: "utxoData2", 25 | utxoId3: "utxoData3" 26 | }, 27 | byAccount: { account1: ["utxoId1", "utxoId3"] } 28 | }; 29 | const state = ({ utxos: utxosSlice } as unknown) as FullState; 30 | const result = utxosByAccountSelector(state, "account1"); 31 | 32 | expect(result).toEqual(["utxoData1", "utxoData3"]); 33 | }); 34 | 35 | it("isUpdatingUtxoSelector", () => { 36 | const utxosSlice = { updating: false }; 37 | const state = ({ utxos: utxosSlice } as unknown) as FullState; 38 | const result = isUpdatingUTXO(state); 39 | 40 | expect(result).toEqual(false); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /data/utxos/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import { FullState } from "../store"; 3 | 4 | const utxosSelector = (state: FullState) => state.utxos; 5 | 6 | const utxosByAccountSelector = (state: FullState, address?: string | null) => { 7 | if (!address) return []; 8 | 9 | const { byId, byAccount } = state.utxos; 10 | const accountUtxoIds = byAccount[address]; 11 | 12 | if (!accountUtxoIds) return []; 13 | 14 | return byAccount[address].map(utxoId => byId[utxoId]); 15 | }; 16 | 17 | const doneInitialLoadSelector = createSelector( 18 | utxosByAccountSelector, 19 | utxos => { 20 | return !!utxos; 21 | } 22 | ); 23 | 24 | const isUpdatingUTXO = (state: FullState) => { 25 | return state.utxos.updating; 26 | }; 27 | 28 | export { 29 | utxosSelector, 30 | utxosByAccountSelector, 31 | doneInitialLoadSelector, 32 | isUpdatingUTXO 33 | }; 34 | -------------------------------------------------------------------------------- /hooks/useBlockheight.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { getCurrentBlockheight } from "../api/bcash"; 3 | 4 | const useBlockheight = () => { 5 | const [blockheight, setBlockheight] = useState(0); 6 | 7 | useEffect(() => { 8 | const updateBlockheight = async () => { 9 | try { 10 | const blockheightNow = await getCurrentBlockheight(); 11 | setBlockheight(blockheightNow); 12 | } catch (e) { 13 | console.warn(e); 14 | } 15 | }; 16 | 17 | updateBlockheight(); 18 | const blockheightInterval = setInterval(updateBlockheight, 45 * 1000); 19 | 20 | return () => clearInterval(blockheightInterval); 21 | }, []); 22 | 23 | return blockheight; 24 | }; 25 | 26 | export default useBlockheight; 27 | -------------------------------------------------------------------------------- /hooks/useSimpleledgerFormat.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { toSlpAddress } from "bchaddrjs-slp"; 3 | 4 | const useSimpleledgerFormat = (address: string) => { 5 | const addressSimpleledger = useMemo(() => { 6 | return toSlpAddress(address); 7 | }, [address]); 8 | 9 | return addressSimpleledger; 10 | }; 11 | 12 | export default useSimpleledgerFormat; 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import "./shim"; 2 | 3 | import { AppRegistry } from "react-native"; 4 | import App from "./App"; 5 | import { name as appName } from "./app.json"; 6 | 7 | AppRegistry.registerComponent(appName, () => App); 8 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '10.0' 5 | 6 | target 'badgerMobile' do 7 | # Pods for badgerMobile app 8 | config = use_native_modules! 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | target 'badgerMobileTests' do 16 | inherit! :search_paths 17 | # Pods for testing 18 | end 19 | 20 | post_install do |installer| 21 | react_native_post_install(installer) 22 | end 23 | end 24 | 25 | # target 'badgerMobile-tvOS' do 26 | # # Pods for RnDiffApp-tvOS 27 | 28 | # target 'badgerMobile-tvOSTests' do 29 | # inherit! :complete 30 | # # Pods for testing 31 | # end 32 | 33 | # end -------------------------------------------------------------------------------- /ios/badgerMobile-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/badgerMobile-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/badgerMobile.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/badgerMobile.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/badgerMobile.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | PreviewsEnabled 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/badgerMobile/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/badgerMobile/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 20 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 21 | moduleName:@"badgerMobile" 22 | initialProperties:nil]; 23 | 24 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 25 | 26 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 27 | UIViewController *rootViewController = [UIViewController new]; 28 | rootViewController.view = rootView; 29 | self.window.rootViewController = rootViewController; 30 | [self.window makeKeyAndVisible]; 31 | return YES; 32 | } 33 | 34 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 35 | { 36 | #if DEBUG 37 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 38 | #else 39 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 40 | #endif 41 | } 42 | 43 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url 44 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 45 | { 46 | return [RCTLinkingManager application:application openURL:url 47 | sourceApplication:sourceApplication annotation:annotation]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /ios/badgerMobile/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon_20pt@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon_20pt@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon_29pt@2x.png", 19 | "scale": "2x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon_29pt@3x.png", 25 | "scale": "3x" 26 | }, 27 | { 28 | "size": "40x40", 29 | "idiom": "iphone", 30 | "filename": "icon_40pt@2x.png", 31 | "scale": "2x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon_40pt@3x.png", 37 | "scale": "3x" 38 | }, 39 | { 40 | "size": "60x60", 41 | "idiom": "iphone", 42 | "filename": "icon_60pt@2x.png", 43 | "scale": "2x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "icon_60pt@3x.png", 49 | "scale": "3x" 50 | }, 51 | { 52 | "size": "1024x1024", 53 | "idiom": "ios-marketing", 54 | "filename": "Icon.png", 55 | "scale": "1x" 56 | } 57 | ], 58 | "info": { 59 | "version": 1, 60 | "author": "xcode" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_20pt@2x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_20pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_20pt@3x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_29pt@2x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_29pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_29pt@3x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_40pt@2x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_40pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_40pt@3x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_60pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_60pt@2x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_60pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/AppIcon.appiconset/icon_60pt@3x.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "extent": "full-screen", 5 | "idiom": "iphone", 6 | "subtype": "2688h", 7 | "filename": "LaunchImage-1242@3x~iphoneXsMax-portrait_1242x2688.png", 8 | "minimum-system-version": "12.0", 9 | "orientation": "portrait", 10 | "scale": "3x" 11 | }, 12 | { 13 | "extent": "full-screen", 14 | "idiom": "iphone", 15 | "subtype": "2688h", 16 | "filename": "LaunchImage-2688@3x~iphoneXsMax-landscape_2688x1242.png", 17 | "minimum-system-version": "12.0", 18 | "orientation": "landscape", 19 | "scale": "3x" 20 | }, 21 | { 22 | "extent": "full-screen", 23 | "idiom": "iphone", 24 | "subtype": "1792h", 25 | "filename": "LaunchImage-828@2x~iphoneXr-portrait_828x1792.png", 26 | "minimum-system-version": "12.0", 27 | "orientation": "portrait", 28 | "scale": "2x" 29 | }, 30 | { 31 | "extent": "full-screen", 32 | "idiom": "iphone", 33 | "subtype": "1792h", 34 | "filename": "LaunchImage-1792@2x~iphoneXr-landscape_1792x828.png", 35 | "minimum-system-version": "12.0", 36 | "orientation": "landscape", 37 | "scale": "2x" 38 | }, 39 | { 40 | "extent": "full-screen", 41 | "idiom": "iphone", 42 | "subtype": "2436h", 43 | "filename": "LaunchImage-1125@3x~iphoneX-portrait_1125x2436.png", 44 | "minimum-system-version": "11.0", 45 | "orientation": "portrait", 46 | "scale": "3x" 47 | }, 48 | { 49 | "extent": "full-screen", 50 | "idiom": "iphone", 51 | "subtype": "2436h", 52 | "filename": "LaunchImage-2436@3x~iphoneX-landscape_2436x1125.png", 53 | "minimum-system-version": "11.0", 54 | "orientation": "landscape", 55 | "scale": "3x" 56 | }, 57 | { 58 | "extent": "full-screen", 59 | "idiom": "iphone", 60 | "subtype": "736h", 61 | "filename": "LaunchImage-1242@3x~iphone6s-portrait_1242x2208.png", 62 | "minimum-system-version": "8.0", 63 | "orientation": "portrait", 64 | "scale": "3x" 65 | }, 66 | { 67 | "extent": "full-screen", 68 | "idiom": "iphone", 69 | "subtype": "736h", 70 | "filename": "LaunchImage-1242@3x~iphone6s-landscape_2208x1242.png", 71 | "minimum-system-version": "8.0", 72 | "orientation": "landscape", 73 | "scale": "3x" 74 | }, 75 | { 76 | "extent": "full-screen", 77 | "idiom": "iphone", 78 | "subtype": "667h", 79 | "filename": "LaunchImage-750@2x~iphone6-portrait_750x1334.png", 80 | "minimum-system-version": "8.0", 81 | "orientation": "portrait", 82 | "scale": "2x" 83 | }, 84 | { 85 | "orientation": "portrait", 86 | "idiom": "iphone", 87 | "filename": "LaunchImage~iphone_640x960.png", 88 | "extent": "full-screen", 89 | "minimum-system-version": "7.0", 90 | "scale": "2x" 91 | }, 92 | { 93 | "extent": "full-screen", 94 | "idiom": "iphone", 95 | "subtype": "retina4", 96 | "filename": "LaunchImage-568h@2x~iphone_640x1136.png", 97 | "minimum-system-version": "7.0", 98 | "orientation": "portrait", 99 | "scale": "2x" 100 | }, 101 | { 102 | "orientation": "portrait", 103 | "idiom": "iphone", 104 | "filename": "LaunchImage~iphone-320x480.png", 105 | "extent": "full-screen", 106 | "scale": "1x" 107 | }, 108 | { 109 | "orientation": "portrait", 110 | "idiom": "iphone", 111 | "filename": "LaunchImage~iphone_640x960-1.png", 112 | "extent": "full-screen", 113 | "scale": "2x" 114 | }, 115 | { 116 | "orientation": "portrait", 117 | "idiom": "iphone", 118 | "filename": "LaunchImage-568h@2x~iphone_640x1136-1.png", 119 | "extent": "full-screen", 120 | "subtype": "retina4", 121 | "scale": "2x" 122 | } 123 | ], 124 | "info": { 125 | "version": 1, 126 | "author": "xcode" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1125@3x~iphoneX-portrait_1125x2436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1125@3x~iphoneX-portrait_1125x2436.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphone6s-landscape_2208x1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphone6s-landscape_2208x1242.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphone6s-portrait_1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphone6s-portrait_1242x2208.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphoneXsMax-portrait_1242x2688.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1242@3x~iphoneXsMax-portrait_1242x2688.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1792@2x~iphoneXr-landscape_1792x828.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-1792@2x~iphoneXr-landscape_1792x828.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-2436@3x~iphoneX-landscape_2436x1125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-2436@3x~iphoneX-landscape_2436x1125.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-2688@3x~iphoneXsMax-landscape_2688x1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-2688@3x~iphoneXsMax-landscape_2688x1242.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-568h@2x~iphone_640x1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-568h@2x~iphone_640x1136-1.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-568h@2x~iphone_640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-568h@2x~iphone_640x1136.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-750@2x~iphone6-portrait_750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-750@2x~iphone6-portrait_750x1334.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-828@2x~iphoneXr-portrait_828x1792.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage-828@2x~iphoneXr-portrait_828x1792.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone-320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone-320x480.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone_640x960-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone_640x960-1.png -------------------------------------------------------------------------------- /ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone_640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/ios/badgerMobile/Images.xcassets/LaunchImage-fits.launchimage/LaunchImage~iphone_640x960.png -------------------------------------------------------------------------------- /ios/badgerMobile/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Badger Wallet 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | Badger Wallet 30 | CFBundleURLSchemes 31 | 32 | bitcoincash 33 | 34 | 35 | 36 | CFBundleTypeRole 37 | Editor 38 | CFBundleURLName 39 | Badger Wallet 40 | CFBundleURLSchemes 41 | 42 | simpleledger 43 | 44 | 45 | 46 | CFBundleVersion 47 | $(CURRENT_PROJECT_VERSION) 48 | LSRequiresIPhoneOS 49 | 50 | NSAppTransportSecurity 51 | 52 | NSAllowsArbitraryLoads 53 | 54 | NSExceptionDomains 55 | 56 | localhost 57 | 58 | NSExceptionAllowsInsecureHTTPLoads 59 | 60 | 61 | 62 | 63 | NSAppleMusicUsageDescription 64 | Not used by app 65 | NSBluetoothAlwaysUsageDescription 66 | Not used by app 67 | NSBluetoothPeripheralUsageDescription 68 | Not used by app 69 | NSCalendarsUsageDescription 70 | Not used by app 71 | NSCameraUsageDescription 72 | Allow access to the camera to scan QR codes 73 | NSContactsUsageDescription 74 | Not use by app 75 | NSLocationAlwaysUsageDescription 76 | Not used by app 77 | NSLocationWhenInUseUsageDescription 78 | Not used by app 79 | NSMotionUsageDescription 80 | Not used by app 81 | NSPhotoLibraryAddUsageDescription 82 | Not used by app 83 | NSPhotoLibraryUsageDescription 84 | Not used by app 85 | NSSpeechRecognitionUsageDescription 86 | Not used by app 87 | UIAppFonts 88 | 89 | AntDesign.ttf 90 | Entypo.ttf 91 | EvilIcons.ttf 92 | Feather.ttf 93 | FontAwesome.ttf 94 | FontAwesome5_Brands.ttf 95 | FontAwesome5_Regular.ttf 96 | FontAwesome5_Solid.ttf 97 | Foundation.ttf 98 | Ionicons.ttf 99 | MaterialCommunityIcons.ttf 100 | MaterialIcons.ttf 101 | Octicons.ttf 102 | SimpleLineIcons.ttf 103 | Zocial.ttf 104 | Fontisto.ttf 105 | 106 | UIRequiredDeviceCapabilities 107 | 108 | armv7 109 | 110 | UISupportedInterfaceOrientations 111 | 112 | UIInterfaceOrientationPortrait 113 | 114 | UIViewControllerBasedStatusBarAppearance 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /ios/badgerMobile/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/badgerMobileTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/badgerMobileTests/badgerMobileTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 16 | 17 | @interface badgerMobileTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation badgerMobileTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | #ifdef DEBUG 44 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 45 | if (level >= RCTLogLevelError) { 46 | redboxError = message; 47 | } 48 | }); 49 | #endif 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | #ifdef DEBUG 64 | RCTSetLogFunction(RCTDefaultLogFunction); 65 | #endif 66 | 67 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 68 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 69 | } 70 | 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Copyright 2019 Saint Bitts LLC, Bitcoin.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true 14 | } 15 | }), 16 | minifierConfig: { 17 | mangle: { 18 | keep_fnames: true 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /navigation/AuthLoadingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useCallback } from "react"; 2 | import { connect, ConnectedProps } from "react-redux"; 3 | import styled from "styled-components"; 4 | import { ActivityIndicator, View } from "react-native"; 5 | import { NavigationScreenProps } from "react-navigation"; 6 | 7 | import { T, Spacer } from "../atoms"; 8 | import { getMnemonicSelector } from "../data/accounts/selectors"; 9 | import { getAccount } from "../data/accounts/actions"; 10 | 11 | import { addressToSlp, addressToCash } from "../utils/account-utils"; 12 | import { getType } from "../utils/schemeParser-utils"; 13 | import { FullState } from "../data/store"; 14 | 15 | const Wrapper = styled(View)` 16 | justify-content: center; 17 | align-items: center; 18 | flex: 1; 19 | `; 20 | 21 | const InnerWrapper = styled(View)` 22 | justify-content: center; 23 | align-items: center; 24 | flex: 1; 25 | `; 26 | 27 | type PropsFromParent = NavigationScreenProps & {}; 28 | 29 | const mapStateToProps = (state: FullState) => { 30 | return { 31 | mnemonic: getMnemonicSelector(state) 32 | }; 33 | }; 34 | 35 | const mapDispatchToProps = { 36 | getAccount 37 | }; 38 | 39 | const connector = connect(mapStateToProps, mapDispatchToProps); 40 | 41 | type PropsFromRedux = ConnectedProps; 42 | type Props = PropsFromParent & PropsFromRedux; 43 | 44 | const AuthLoadingScreen = ({ 45 | navigation, 46 | route, 47 | mnemonic, 48 | getAccount 49 | }: Props) => { 50 | const navParams = route.params; 51 | 52 | const handleDeepLink = useCallback( 53 | async params => { 54 | const { uri, r } = params; 55 | const formattedURI = uri && uri.startsWith(":") ? uri.slice(1) : uri; 56 | 57 | if (r) { 58 | navigation.navigate("Bip70Confirm", { 59 | paymentURL: r 60 | }); 61 | return; 62 | } 63 | 64 | let amountFormatted = null; 65 | let addressFormatted = null; 66 | let tokenId = null; 67 | let parseError = null; 68 | 69 | const amounts = Object.entries( 70 | params 71 | ).filter(([key, val]: [string, any]) => key.startsWith("amount")); 72 | 73 | const amountsFormatted = amounts.map(curr => { 74 | const amountRaw = curr[1] as string; 75 | let currTokenId = null; 76 | let currAmount = null; 77 | 78 | if (amountRaw != null && amountRaw.includes("-")) { 79 | [currAmount, currTokenId] = amountRaw.split("-"); 80 | } else { 81 | currAmount = amountRaw; 82 | } 83 | 84 | return { 85 | tokenId: currTokenId, 86 | paramAmount: currAmount 87 | }; 88 | }); 89 | 90 | if (amountsFormatted.length > 1) { 91 | parseError = 92 | "Badger Wallet currently only supports sending one coin at a time. The URI is requesting multiple coins."; 93 | } else if (amountsFormatted.length === 1) { 94 | const target = amountsFormatted[0]; 95 | tokenId = target.tokenId; 96 | 97 | amountFormatted = target.paramAmount; 98 | } 99 | 100 | let type = null; 101 | 102 | try { 103 | type = getType(formattedURI); 104 | 105 | addressFormatted = 106 | type === "cashaddr" 107 | ? await addressToCash(formattedURI) 108 | : await addressToSlp(formattedURI); 109 | } catch (e) { 110 | parseError = `Invalid address detected`; 111 | } 112 | 113 | navigation.navigate("SendSetup", { 114 | tokenId, 115 | uriError: parseError, 116 | uriAddress: 117 | typeof addressFormatted === "string" ? addressFormatted : "", 118 | 119 | uriAmount: amountFormatted 120 | }); 121 | }, 122 | [navigation] 123 | ); 124 | // re-generate accounts keypair then go to Main. 125 | useEffect(() => { 126 | if (mnemonic) { 127 | getAccount(mnemonic); 128 | 129 | if (navParams?.uri && navParams?.r) { 130 | handleDeepLink(navParams); 131 | return; 132 | } 133 | setTimeout(() => navigation.navigate("Main"), 500); 134 | } else { 135 | setTimeout(() => navigation.navigate("AuthStack"), 500); 136 | } 137 | }, [mnemonic, handleDeepLink, navigation, getAccount]); 138 | 139 | return ( 140 | 141 | 142 | 143 | 144 | Herding Badgers 145 | 146 | 147 | ); 148 | }; 149 | 150 | export default connector(AuthLoadingScreen); 151 | -------------------------------------------------------------------------------- /screens/Bip70SuccessScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ScrollView, SafeAreaView, View } from "react-native"; 4 | import { NavigationScreenProps } from "react-navigation"; 5 | import FontAwesome from "react-native-vector-icons/FontAwesome"; 6 | 7 | import { Button, T, H1, Spacer } from "../atoms"; 8 | 9 | const ScreenCover = styled(View)` 10 | flex: 1; 11 | background-color: ${props => props.theme.primary500}; 12 | padding: 0 16px; 13 | `; 14 | 15 | const TopArea = styled(View)``; 16 | 17 | const BottomArea = styled(View)``; 18 | const ReceiptArea = styled(View)` 19 | flex: 1; 20 | justify-content: center; 21 | `; 22 | 23 | type Props = NavigationScreenProps & { 24 | route: { 25 | params: { 26 | txid: string; 27 | }; 28 | }; 29 | }; 30 | 31 | const Bip70SuccessScreen = ({ navigation, route }: Props) => { 32 | const { txid } = route.params; 33 | return ( 34 | 35 | 40 | 45 | 46 | 47 |

48 | Success! 49 |

50 | 51 | 52 | Payment sent to merchant 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | Transaction ID 62 | 63 | 64 | 65 | {txid} 66 | 67 | 68 | 69 | 70 |