├── .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 |
81 |
82 |
83 |
84 | );
85 | };
86 |
87 | export default Bip70SuccessScreen;
88 |
--------------------------------------------------------------------------------
/screens/ContactUsScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | SafeAreaView,
4 | ScrollView,
5 | Linking,
6 | TouchableOpacity
7 | } from "react-native";
8 | import { NavigationScreenProps } from "react-navigation";
9 | import styled from "styled-components";
10 | import Ionicons from "react-native-vector-icons/Ionicons";
11 | import FontAwesome from "react-native-vector-icons/FontAwesome";
12 |
13 | import { T, Spacer } from "../atoms";
14 |
15 | const ScreenWrapper = styled(ScrollView)`
16 | padding: 7px 16px;
17 | `;
18 |
19 | type Props = NavigationScreenProps & {};
20 |
21 | const ContactUsScreen = (props: Props) => {
22 | return (
23 |
28 |
33 |
34 | We hope you are enjoying Badger Wallet
35 |
36 |
37 | If you wish to give feedback, ask a question, or contact us for
38 | another reason, get in touch with the team through electronic mail or
39 | Telegram
40 |
41 |
42 |
43 | Email
44 |
45 |
46 |
48 | Linking.openURL("mailto:info@badger.cash?subject=Badger Wallet")
49 | }
50 | >
51 |
52 | info@badger.cash
53 |
54 |
55 |
56 |
57 | Telegram
58 |
59 |
60 |
62 | Linking.openURL("https://t.me/joinchat/188N82Umbe5lNGVh")
63 | }
64 | >
65 |
66 | Badger Wallet Group
67 |
68 |
69 |
70 |
71 |
72 | );
73 | };
74 |
75 | export default ContactUsScreen;
76 |
--------------------------------------------------------------------------------
/screens/CreateWalletScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import styled from "styled-components";
3 | import { SafeAreaView, ActivityIndicator } from "react-native";
4 | import { NavigationScreenProps } from "react-navigation";
5 | import { connect, ConnectedProps } from "react-redux";
6 |
7 | import { Spacer, T } from "../atoms";
8 |
9 | import { hasMnemonicSelector } from "../data/accounts/selectors";
10 | import { getAccount } from "../data/accounts/actions";
11 | import { FullState } from "../data/store";
12 |
13 | const ScreenWrapper = styled(SafeAreaView)`
14 | align-items: center;
15 | justify-content: center;
16 | flex: 1;
17 | `;
18 |
19 | type PropsFromParent = NavigationScreenProps & {};
20 |
21 | // Redux connection
22 | const mapStateToProps = (state: FullState) => ({
23 | isCreated: hasMnemonicSelector(state)
24 | });
25 |
26 | const mapDispatchToProps = {
27 | getAccount
28 | };
29 |
30 | const connector = connect(mapStateToProps, mapDispatchToProps);
31 |
32 | type PropsFromRedux = ConnectedProps;
33 | type Props = PropsFromRedux & PropsFromParent;
34 |
35 | const CreateWalletScreen = ({ navigation, isCreated, getAccount }: Props) => {
36 | useEffect(() => {
37 | if (isCreated) {
38 | navigation.navigate("Main");
39 | } else {
40 | getAccount();
41 | }
42 | }, [isCreated]);
43 |
44 | return (
45 |
46 |
47 |
48 | Loading Wallet...
49 |
50 | );
51 | };
52 |
53 | export default connector(CreateWalletScreen);
54 |
--------------------------------------------------------------------------------
/screens/LogoutScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, ScrollView, SafeAreaView } from "react-native";
3 | import { NavigationScreenProps } from "react-navigation";
4 | import { connect, ConnectedProps } from "react-redux";
5 | import styled from "styled-components";
6 | import _ from "lodash";
7 |
8 | import { Button, T, Spacer, SwipeButton } from "../atoms";
9 | import { logoutAccount } from "../data/accounts/actions";
10 | import { FullState } from "../data/store";
11 |
12 | const ButtonContainer = styled(View)``;
13 | const Screen = styled(SafeAreaView)`
14 | flex: 1;
15 | margin: 0 16px;
16 | `;
17 |
18 | type PropsFromParent = NavigationScreenProps & {};
19 |
20 | const mapStateToProps = (state: FullState) => {
21 | return {};
22 | };
23 |
24 | const mapDispatchToProps = {
25 | logoutAccount
26 | };
27 |
28 | const connector = connect(mapStateToProps, mapDispatchToProps);
29 |
30 | type PropsFromRedux = ConnectedProps;
31 | type Props = PropsFromParent & PropsFromRedux;
32 |
33 | const LogoutScreen = ({ navigation, logoutAccount }: Props) => {
34 | return (
35 |
36 |
41 |
42 |
43 | You are about to logout of your wallet. You will need to use this
44 | wallets seed phrase to access the held funds again.
45 |
46 |
47 |
48 | Make sure you have the seed phrase backed up in a secure location
49 | before logging out.
50 |
51 |
52 |
53 | If the seed phrase is lost, we are unable to recover the wallet for
54 | you.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default connector(LogoutScreen);
85 |
--------------------------------------------------------------------------------
/screens/PrivacyNoticeScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { SafeAreaView, Linking } from "react-native";
4 | import { NavigationScreenProps } from "react-navigation";
5 |
6 | import { H1, T, Spacer, Button } from "../atoms";
7 |
8 | type Props = NavigationScreenProps & {};
9 |
10 | const ScreenView = styled(SafeAreaView)`
11 | margin: 0 16px;
12 | `;
13 |
14 | // This version no longer active, useful during onboarding. Consider re-activating or removing
15 | const PrivacyNoticeScreen = ({ navigation }: Props) => {
16 | return (
17 |
22 |
23 | Privacy Overview
24 |
25 | Privacy is important. We will respect yours.
26 |
27 | To view our complete privacy policy visit
28 |
29 |
32 | Linking.openURL("https://www.bitcoin.com/privacy-policy")
33 | }
34 | >
35 | https://www.bitcoin.com/privacy-policy
36 |
37 |
38 |
39 |
45 | );
46 | };
47 |
48 | // Accessed from Menu List
49 | const ViewPrivacyNoticeScreen = ({ navigation }: Props) => {
50 | return (
51 |
56 |
57 | Privacy Overview
58 |
59 | Privacy is important. We will respect yours.
60 |
61 | To view our complete privacy policy visit
62 |
63 |
66 | Linking.openURL("https://www.bitcoin.com/privacy-policy")
67 | }
68 | >
69 | https://www.bitcoin.com/privacy-policy
70 |
71 |
72 |
73 |
76 | );
77 | };
78 |
79 | export { ViewPrivacyNoticeScreen };
80 | export default PrivacyNoticeScreen;
81 |
--------------------------------------------------------------------------------
/screens/SelectCurrencyScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { connect, ConnectedProps } from "react-redux";
4 | import Ionicons from "react-native-vector-icons/Ionicons";
5 | import { NavigationScreenProps } from "react-navigation";
6 |
7 | import {
8 | SafeAreaView,
9 | View,
10 | ScrollView,
11 | StyleSheet,
12 | TouchableOpacity
13 | } from "react-native";
14 |
15 | import { FullState } from "../data/store";
16 | import { setFiatCurrency } from "../data/prices/actions";
17 | import { currencySelector } from "../data/prices/selectors";
18 | import {
19 | CurrencyCode,
20 | currencyOptions,
21 | currencySymbolMap,
22 | currencyNameMap
23 | } from "../utils/currency-utils";
24 |
25 | import { T, Spacer } from "../atoms";
26 |
27 | const ScreenWrapper = styled(View)`
28 | height: 100%;
29 | `;
30 |
31 | const ActiveSection = styled(View)`
32 | padding: 0 16px;
33 | border-bottom-width: ${StyleSheet.hairlineWidth};
34 | border-color: ${props => props.theme.fg500};
35 | `;
36 |
37 | const RowContainer = styled(TouchableOpacity)`
38 | padding: 0 16px;
39 | border-bottom-width: ${StyleSheet.hairlineWidth};
40 | border-color: ${props => props.theme.fg500};
41 | `;
42 | const RowTextContainer = styled(View)`
43 | flex-direction: row;
44 | justify-content: space-between;
45 | align-items: center;
46 | `;
47 |
48 | type PropsCurrencyRow = {
49 | text: string;
50 | onPress(): void;
51 | isActive: boolean;
52 | };
53 |
54 | const CurrencyRow = ({ text, onPress, isActive }: PropsCurrencyRow) => (
55 |
56 |
57 |
58 | {text}
59 | {isActive && (
60 |
61 |
62 |
63 | )}
64 |
65 |
66 |
67 | );
68 |
69 | type PropsFromParent = NavigationScreenProps & {};
70 |
71 | const mapStateToProps = (state: FullState) => {
72 | return {
73 | currencyActive: currencySelector(state)
74 | };
75 | };
76 |
77 | const mapDispatchToProps = {
78 | setFiatCurrency
79 | };
80 |
81 | const connector = connect(mapStateToProps, mapDispatchToProps);
82 |
83 | type PropsFromRedux = ConnectedProps;
84 | type Props = PropsFromParent & PropsFromRedux;
85 |
86 | const SelectCurrencyScreen = ({
87 | navigation,
88 | currencyActive,
89 | setFiatCurrency
90 | }: Props) => {
91 | const updateFiatCurrency = (code: CurrencyCode) => {
92 | setFiatCurrency(code);
93 | };
94 |
95 | return (
96 |
97 |
98 |
99 |
100 | Active Currency:
101 |
102 |
103 | {`${currencySymbolMap[currencyActive]} ${currencyActive} - ${currencyNameMap[currencyActive]} `}
104 |
105 |
106 |
107 |
108 | {currencyOptions.map((currencyCode: CurrencyCode) => {
109 | return (
110 | updateFiatCurrency(currencyCode)}
114 | isActive={currencyActive === currencyCode}
115 | />
116 | );
117 | })}
118 |
119 |
120 |
121 |
122 | );
123 | };
124 |
125 | export default connector(SelectCurrencyScreen);
126 |
--------------------------------------------------------------------------------
/screens/ViewSeedScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { connect, ConnectedProps } from "react-redux";
3 | import styled from "styled-components";
4 |
5 | import { View, ScrollView, SafeAreaView } from "react-native";
6 | import { NavigationEvents, NavigationScreenProps } from "react-navigation";
7 |
8 | import { viewSeed } from "../data/accounts/actions";
9 | import {
10 | getMnemonicSelector,
11 | getAddressSelector
12 | } from "../data/accounts/selectors";
13 | import { T, Spacer, Button } from "../atoms";
14 | import { FullState } from "../data/store";
15 |
16 | const Screen = styled(ScrollView)`
17 | padding: 0 16px;
18 | `;
19 |
20 | const WordHolder = styled(View)`
21 | position: relative;
22 | `;
23 |
24 | const WordRow = styled(View)``;
25 |
26 | const Cover = styled(View)`
27 | position: absolute;
28 | background-color: ${props => props.theme.coverBg};
29 | align-items: center;
30 | justify-content: center;
31 | top: 0;
32 | left: 0;
33 | right: 0;
34 | bottom: 0;
35 | height: 100%;
36 | width: 100%;
37 | z-index: 1;
38 | `;
39 |
40 | type PropsFromParent = NavigationScreenProps & {};
41 |
42 | const mapStateToProps = (state: FullState) => ({
43 | address: getAddressSelector(state),
44 | mnemonic: getMnemonicSelector(state)
45 | });
46 |
47 | const mapDispatchToProps = {
48 | viewSeed
49 | };
50 |
51 | const connector = connect(mapStateToProps, mapDispatchToProps);
52 |
53 | type PropsFromRedux = ConnectedProps;
54 | type Props = PropsFromParent & PropsFromRedux;
55 |
56 | const ViewSeedScreen = ({ mnemonic, viewSeed, address }: Props) => {
57 | const [showing, setShowing] = useState(false);
58 |
59 | const words = showing ? mnemonic : "---------- ".repeat(12).trim();
60 | const separated = words.split(" ");
61 |
62 | return (
63 |
64 |
65 |
66 | This seed phrase is the key to the funds in this wallet.
67 |
68 |
69 | Losing this phrase is losing access to this wallet. If lost we will be
70 | unable to help you recover your Seed Phrase or wallet.
71 |
72 |
73 |
74 | Write the 12-word seed phrase down in order, keep it safe, and do not
75 | share it with anyone you do not trust with access to your wallet.
76 |
77 |
78 |
79 | {!showing && (
80 |
81 |
82 | I am in a private area and wish to see my seed phrase
83 |
84 |
85 |
93 | )}
94 |
95 | {separated.map((word, idx) => (
96 |
97 |
98 |
99 | {`${idx + 1}.`.padStart(3, " ")}
100 |
101 | {word}
102 |
103 |
104 |
105 | ))}
106 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default connect(mapStateToProps, mapDispatchToProps)(ViewSeedScreen);
115 |
--------------------------------------------------------------------------------
/screens/WelcomeScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SafeAreaView, View, Image } from "react-native";
3 | import { NavigationScreenProps } from "react-navigation";
4 | import styled from "styled-components";
5 |
6 | import { T, H1, H2, Spacer, Button } from "../atoms";
7 |
8 | import BadgerIcon from "../assets/images/icon-full.png";
9 |
10 | const StyledWrapper = styled(SafeAreaView)`
11 | display: flex;
12 | flex: 1;
13 | align-items: center;
14 | margin: 0 16px;
15 | `;
16 |
17 | type Props = NavigationScreenProps & {};
18 |
19 | const WelcomeScreen = ({ navigation }: Props) => {
20 | return (
21 |
22 |
23 | Badger Wallet
24 |
25 |
32 |
33 |
38 |
43 | Your gateway to the world of Bitcoin Cash (BCH)
44 |
45 |
46 | Bitcoin Cash (BCH) and Simple Token (SLP) wallet
47 |
48 |
49 |
54 |
64 |
65 | );
66 | };
67 |
68 | export default WelcomeScreen;
69 |
--------------------------------------------------------------------------------
/shim.js:
--------------------------------------------------------------------------------
1 | if (typeof __dirname === "undefined") global.__dirname = "/";
2 | if (typeof __filename === "undefined") global.__filename = "";
3 | if (typeof process === "undefined") {
4 | global.process = require("process");
5 | } else {
6 | const bProcess = require("process");
7 | for (var p in bProcess) {
8 | if (!(p in process)) {
9 | process[p] = bProcess[p];
10 | }
11 | }
12 | }
13 |
14 | process.browser = false;
15 | if (typeof Buffer === "undefined") global.Buffer = require("buffer").Buffer;
16 |
17 | // global.location = global.location || { port: 80 }
18 | const isDev = typeof __DEV__ === "boolean" && __DEV__;
19 | process.env["NODE_ENV"] = isDev ? "development" : "production";
20 | if (typeof localStorage !== "undefined") {
21 | localStorage.debug = isDev ? "*" : "";
22 | }
23 |
24 | // If using the crypto shim, uncomment the following line to ensure
25 | // crypto is loaded first, so it can populate global.crypto
26 | require("crypto");
27 |
--------------------------------------------------------------------------------
/themes/README.md:
--------------------------------------------------------------------------------
1 | # Themes
2 |
3 | Themes must contain all of the color values outlined in `spaceBadger.ts`.
4 | Colors go from dark `100` to light `900`, and the reverse for dark themes.
5 |
--------------------------------------------------------------------------------
/themes/spaceBadger.ts:
--------------------------------------------------------------------------------
1 | type Theme = {
2 | accent500: string;
3 | accent900: string;
4 | bg900: string;
5 | coverBg: string;
6 | danger300: string;
7 | danger500: string;
8 | danger700: string;
9 | fg100: string;
10 | fg200: string;
11 | fg300: string;
12 | fg500: string;
13 | fg700: string;
14 | fg800: string;
15 | payout900: string;
16 | pending500: string;
17 | primary300: string;
18 | primary500: string;
19 | primary900: string;
20 | success500: string;
21 | success700: string;
22 | };
23 |
24 | const spaceBadger: Theme = {
25 | accent500: "#F5871A",
26 | accent900: "#faf6ed",
27 | bg900: "#ffffff",
28 | coverBg: "rgba(255, 255, 255, 0.95)",
29 | danger300: "#ff5454",
30 | danger500: "#ff8484",
31 | danger700: "#fff4f4",
32 | fg100: "#131720",
33 | fg200: "#454c53",
34 | fg300: "#7d7d7d",
35 | fg500: "#c8cdd8",
36 | fg700: "#ededed",
37 | fg800: "#fbfbfb",
38 | payout900: "#f6edfa",
39 | pending500: "rgb(0, 193, 225)",
40 | primary300: "#11a87e",
41 | primary500: "#0AC18E",
42 | primary900: "#edfaf6",
43 | success500: "rgb(62, 193, 10)",
44 | success700: "#f6fff4"
45 | };
46 |
47 | export { spaceBadger };
48 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | "isolatedModules": false,
7 | "jsx": "react",
8 | "lib": ["es6"],
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "strict": true,
12 | "target": "esnext",
13 | "resolveJsonModule": true // Required for JSON files
14 | },
15 | "exclude": [
16 | "node_modules",
17 | "e2e",
18 | "**/*.json", // Don't try and check JSON files
19 | "**/*.spec.ts"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/utils/account-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("account-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/account-utils.ts:
--------------------------------------------------------------------------------
1 | import { toCashAddress, toSlpAddress } from "bchaddrjs-slp";
2 | import bcoin from "bcash";
3 |
4 | const generateMnemonic = () => {
5 | const mnemonic = new bcoin.Mnemonic({
6 | bits: 128
7 | });
8 | return mnemonic.getPhrase();
9 | };
10 |
11 | const deriveAccount = (
12 | mnemonic: string,
13 | accountIndex: number = 0,
14 | childIndex: number = 0,
15 | hdPathString: string
16 | ) => {
17 | if (!mnemonic) {
18 | throw new Error("Mnemonic required to derive account"); // mnemonic = SLP.Mnemonic.generate(128);
19 | }
20 |
21 | const mnemonicObj = new bcoin.Mnemonic(mnemonic);
22 | const master = bcoin.hd.fromMnemonic(mnemonicObj);
23 | const hdkey = master.derivePath(`${hdPathString}/${accountIndex}'/0`);
24 | const child = hdkey.derive(childIndex);
25 | const keyring = bcoin.KeyRing.fromPrivate(child.privateKey);
26 |
27 | const address = keyring.getKeyAddress().toString();
28 | return {
29 | mnemonic,
30 | keypair: keyring,
31 | address,
32 | accountIndex
33 | };
34 | };
35 |
36 | const addressToSlp = async (address: string) => {
37 | return toSlpAddress(address);
38 | };
39 |
40 | const addressToCash = async (address: string) => {
41 | return toCashAddress(address);
42 | };
43 |
44 | export { deriveAccount, addressToSlp, addressToCash, generateMnemonic };
45 |
--------------------------------------------------------------------------------
/utils/balance-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("balance-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/balance-utils.ts:
--------------------------------------------------------------------------------
1 | import BigNumber from "bignumber.js";
2 |
3 | import {
4 | currencyDecimalMap,
5 | currencySymbolMap,
6 | CurrencyCode
7 | } from "./currency-utils";
8 |
9 | // Minimal interface for what the app needs.
10 |
11 | const removeTrailingChars = (word: string, target: string): string => {
12 | if (word.slice(-1) === target) {
13 | return removeTrailingChars(word.slice(0, -1), target);
14 | }
15 |
16 | return word;
17 | };
18 |
19 | const formatAmount = (
20 | amount: BigNumber | null | undefined,
21 |
22 | decimals: number | null | undefined,
23 | trimEnd?: boolean
24 | ): string => {
25 | if (decimals == null) {
26 | return "-.--------";
27 | }
28 |
29 | if (!amount) {
30 | return `-.`.padEnd(decimals + 2, "-");
31 | }
32 |
33 | let adjustDecimals = amount.shiftedBy(-1 * decimals).toFormat(decimals);
34 |
35 | if (trimEnd) {
36 | adjustDecimals = removeTrailingChars(adjustDecimals, "0");
37 |
38 | if (adjustDecimals.slice(-1) === ".") {
39 | adjustDecimals = adjustDecimals.slice(0, -1);
40 | }
41 | }
42 |
43 | return adjustDecimals;
44 | };
45 |
46 | const computeFiatAmount = (
47 | coinAmount: BigNumber,
48 | spotPrices: any,
49 | fiatCurrency: CurrencyCode,
50 | coin: string
51 | ): BigNumber | null => {
52 | const coinSpotPrice = spotPrices[coin];
53 |
54 | if (!coinSpotPrice) {
55 | return null;
56 | }
57 | const spotPrice = coinSpotPrice[fiatCurrency];
58 | if (!spotPrice) {
59 | return null;
60 | }
61 | const rate = spotPrice.rate;
62 | let amount = new BigNumber(0);
63 |
64 | if (coin === "bch") {
65 | const balance = coinAmount.shiftedBy(-1 * 8);
66 | amount = balance.times(rate);
67 | }
68 |
69 | return amount;
70 | };
71 |
72 | const formatFiatAmount = (
73 | amount: BigNumber | null | undefined,
74 | fiatCurrency: CurrencyCode,
75 | coin: string
76 | ) => {
77 | return amount && !amount.isNaN()
78 | ? `${currencySymbolMap[fiatCurrency]} ${amount.toFormat(
79 | currencyDecimalMap[fiatCurrency]
80 | )} ${fiatCurrency}`
81 | : `${currencySymbolMap[fiatCurrency]} -.-- ${fiatCurrency}`;
82 | };
83 |
84 | const formatAmountInput = (
85 | amount: string,
86 | maxDecimals: number | null
87 | ): string => {
88 | if (maxDecimals == null) {
89 | return "";
90 | }
91 | const amountEnglish = amount.replace(",", ".");
92 |
93 | // Filter non-valid characters
94 | const validCharacters = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
95 |
96 | // Max one decimal
97 | let decimalCount = 0;
98 | const valid = amountEnglish.split("").reduce((prev, curr, idx, array) => {
99 | // Add a 0 if first digit is a '.'
100 | if (idx === 1 && curr === "0" && array[0] === "0") return prev;
101 | if (validCharacters.includes(curr)) return [...prev, curr];
102 |
103 | if (curr === "." && decimalCount === 0) {
104 | decimalCount++;
105 | return [...prev, curr];
106 | }
107 |
108 | return prev;
109 | }, [] as string[]);
110 |
111 | // Restrict decimals
112 | const maybeZero = valid[0] && valid[0] === "." ? ["0", ...valid] : valid;
113 | const decimalIndex = maybeZero.indexOf(".");
114 | const decimalAdjusted =
115 | decimalIndex >= 0
116 | ? maybeZero.slice(0, decimalIndex + maxDecimals + 1)
117 | : maybeZero;
118 |
119 | return decimalAdjusted.join("");
120 | };
121 |
122 | export { computeFiatAmount, formatAmount, formatAmountInput, formatFiatAmount };
123 |
--------------------------------------------------------------------------------
/utils/bip-70-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("bip-70-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/currency-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("currency-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/schemeParser-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("schemeParser-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/token-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("token-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/utils/token-utils.ts:
--------------------------------------------------------------------------------
1 | import makeBlockie from "ethereum-blockies-base64";
2 |
3 | import ACDCoinImage from "../assets/images/token-icons/959a6818cba5af8aba391d3f7649f5f6a5ceb6cdcd2c2a3dcb5d2fbfc4b08e98.png";
4 | import Bitcoin2Image from "../assets/images/token-icons/1074bafb678b85f90bca79fa201a26011e09bfc6f723b95c770c0850f8d44fe8.png";
5 | import HonestCoinImage from "../assets/images/token-icons/c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479.png";
6 | import HonkTokenImage from "../assets/images/token-icons/f35007140e40c4b6ce4ecc9ad166101ad94562b3e4f650a30de10b8a80c0b987.png";
7 | import LiberlandMeritImage from "../assets/images/token-icons/527a337f34e04b1974cb8a1edc7ca30b2e444bea111afc122259552243c1dbe3.png";
8 | import SLPTorchTokenImage from "../assets/images/token-icons/49be89bbbe018bcfaebcb41cac8340bc555f022b47b922599e510b143603f4b6.png";
9 | import SpiceTokenImage from "../assets/images/token-icons/4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf.png";
10 | import DropTokenImage from "../assets/images/token-icons/0f3f223902c44dc2bee6d3f77d565904d8501affba5ee0c56f7b32e8080ce14b.png";
11 | import SAITokenImage from "../assets/images/token-icons/7853218e23fdabb103b4bccbe6e987da8974c7bc775b7e7e64722292ac53627f.png";
12 | import GOCTokenImage from "../assets/images/token-icons/3f83fa9f168f01d68933ef5fdb77143b2376ba7bf3a78175258861982d90d500.png";
13 | import LEADTokenImage from "../assets/images/token-icons/29d353a3d19cdd7324f1c14b3fe289293976842869fed1bea3f9510558f6f006.png";
14 | import ZBCHTokenImage from "../assets/images/token-icons/f66c6d0ac6b8c5c4ed469234ec9734f6d3499b0351b22349f40e617d22254fec.png";
15 | import HonkHonkTokenImage from "../assets/images/token-icons/7f8889682d57369ed0e32336f8b7e0ffec625a35cca183f4e81fde4e71a538a1.png";
16 | import MiamiTokenImage from "../assets/images/token-icons/eebaa04d0e715b7bd21901cb60e10d7f71d219626daf24c57ce6ea9584333149.png";
17 | import BitgoudTokenImage from "../assets/images/token-icons/10db44eb221797d8015d55855edbec656b73559f2afb28da3f7d5d19529ae624.png";
18 | import TetherTokenImage from "../assets/images/token-icons/9fc89d6b7d5be2eac0b3787c5b8236bca5de641b5bafafc8f450727b63615c11.png";
19 |
20 | import BitcoinCashImage from "../assets/images/icon.png";
21 |
22 | const tokenIdImageMap: { [tokenId: string]: any } = {
23 | "1074bafb678b85f90bca79fa201a26011e09bfc6f723b95c770c0850f8d44fe8": Bitcoin2Image,
24 | "49be89bbbe018bcfaebcb41cac8340bc555f022b47b922599e510b143603f4b6": SLPTorchTokenImage,
25 | "4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf": SpiceTokenImage,
26 | "527a337f34e04b1974cb8a1edc7ca30b2e444bea111afc122259552243c1dbe3": LiberlandMeritImage,
27 | "959a6818cba5af8aba391d3f7649f5f6a5ceb6cdcd2c2a3dcb5d2fbfc4b08e98": ACDCoinImage,
28 | c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479: HonestCoinImage,
29 | f35007140e40c4b6ce4ecc9ad166101ad94562b3e4f650a30de10b8a80c0b987: HonkTokenImage,
30 | "0f3f223902c44dc2bee6d3f77d565904d8501affba5ee0c56f7b32e8080ce14b": DropTokenImage,
31 | "7853218e23fdabb103b4bccbe6e987da8974c7bc775b7e7e64722292ac53627f": SAITokenImage,
32 | "3f83fa9f168f01d68933ef5fdb77143b2376ba7bf3a78175258861982d90d500": GOCTokenImage,
33 | "29d353a3d19cdd7324f1c14b3fe289293976842869fed1bea3f9510558f6f006": LEADTokenImage,
34 | f66c6d0ac6b8c5c4ed469234ec9734f6d3499b0351b22349f40e617d22254fec: ZBCHTokenImage,
35 | "7f8889682d57369ed0e32336f8b7e0ffec625a35cca183f4e81fde4e71a538a1": HonkHonkTokenImage,
36 | eebaa04d0e715b7bd21901cb60e10d7f71d219626daf24c57ce6ea9584333149: MiamiTokenImage,
37 | "10db44eb221797d8015d55855edbec656b73559f2afb28da3f7d5d19529ae624": BitgoudTokenImage,
38 | "9fc89d6b7d5be2eac0b3787c5b8236bca5de641b5bafafc8f450727b63615c11": TetherTokenImage
39 | };
40 |
41 | let blockieCache: { [tokenId: string]: any } = {};
42 |
43 | const getTokenImage = (tokenId?: string | null) => {
44 | if (!tokenId) {
45 | return BitcoinCashImage;
46 | }
47 |
48 | if (tokenIdImageMap[tokenId]) {
49 | return tokenIdImageMap[tokenId];
50 | }
51 |
52 | let blockie = blockieCache[tokenId];
53 |
54 | if (!blockie) {
55 | const newBlockie = makeBlockie(tokenId);
56 | blockieCache = {
57 | ...blockieCache,
58 | [tokenId]: newBlockie
59 | };
60 | blockie = newBlockie;
61 | }
62 |
63 | const imageSource = {
64 | uri: blockie
65 | };
66 |
67 | return imageSource;
68 | };
69 |
70 | export { tokenIdImageMap, getTokenImage };
71 |
--------------------------------------------------------------------------------
/utils/transaction-utils.test.ts:
--------------------------------------------------------------------------------
1 | describe("transaction-utils", () => {
2 | it.todo("to complete");
3 | });
4 |
--------------------------------------------------------------------------------
/yarn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badger-cash/badger-mobile/c30ef30efc631fdce10df6ac6bca12d1ce9195a3/yarn
--------------------------------------------------------------------------------