├── .watchmanconfig ├── .node-version ├── .ruby-version ├── backend ├── src │ ├── types │ │ ├── yamlenv.d.ts │ │ └── types.ts │ ├── shared │ │ ├── currency.ts │ │ ├── arg.ts │ │ ├── payment.ts │ │ ├── member.ts │ │ ├── activity.ts │ │ └── api.ts │ ├── handlers │ │ ├── withdraw.test.ts │ │ ├── test-curls │ │ │ ├── deposit.sh │ │ │ ├── save-member.sh │ │ │ ├── send.sh │ │ │ └── genCurl.ts │ │ ├── withdraw.ts │ │ ├── account.ts │ │ ├── query-recipients.ts │ │ ├── deposit.ts │ │ ├── deposit.test.ts │ │ ├── send.ts │ │ ├── account.test.ts │ │ ├── query-recipients.test.ts │ │ ├── save-member.ts │ │ ├── payment-methods.test.ts │ │ ├── payment-methods.ts │ │ └── save-member.test.ts │ ├── dev │ │ └── testing │ │ │ ├── utils.ts │ │ │ ├── generate.ts │ │ │ └── MockCircleClient.ts │ ├── circle │ │ ├── client.ts │ │ ├── open-pgp.ts │ │ └── circle-emulator.ts │ ├── program │ │ ├── constants.ts │ │ └── workspace.ts │ ├── index.ts │ ├── helpers │ │ ├── sampleData.ts │ │ └── solana.ts │ └── db │ │ └── client.ts ├── .gitignore ├── jest.config.js ├── tsconfig.json ├── .env.test.yml ├── .gcloudignore ├── .env.example.yml └── package.json ├── app.json ├── src ├── shared │ ├── currency.ts │ ├── arg.ts │ ├── payment.ts │ ├── member.ts │ ├── activity.ts │ └── api.ts ├── images │ ├── activity │ │ ├── send.png │ │ ├── deposit.png │ │ ├── recieve.png │ │ └── withdraw.png │ ├── icons │ │ └── wallet.png │ ├── misc │ │ └── tap-flow.png │ ├── loaders │ │ ├── loader-lg.gif │ │ └── loader-sm.gif │ ├── payments │ │ ├── visa-mini.png │ │ ├── unknown-mini.png │ │ ├── visa-square.png │ │ └── unknown-square.png │ └── images.ts ├── components │ ├── Screen.tsx │ ├── Text.tsx │ ├── View.tsx │ ├── Loading.tsx │ ├── BigDollars.tsx │ ├── AppLogo.tsx │ ├── RecipientProfile.tsx │ ├── TextInput.tsx │ ├── Button.tsx │ ├── ShimmerBars.tsx │ ├── TransactionStatus.tsx │ └── Activity.tsx ├── common │ ├── text.ts │ ├── debounce.ts │ ├── number.ts │ ├── constants.ts │ └── navigation.ts ├── modules │ ├── SplashScreen.tsx │ ├── deposit │ │ ├── DepositStack.tsx │ │ ├── AmountInputScreen.tsx │ │ └── DepositingScreen.tsx │ ├── profile │ │ ├── ProfileStack.tsx │ │ └── PaymentMethodsScreen.tsx │ ├── send │ │ ├── SendStack.tsx │ │ └── ConfirmScreen.tsx │ └── AuthenticateScreen.tsx └── solana │ ├── solana.ts │ └── web3auth.ts ├── .bundle └── config ├── tsconfig.json ├── program └── tap_cash │ ├── programs │ └── tap_cash │ │ ├── src │ │ ├── model │ │ │ ├── mod.rs │ │ │ └── error.rs │ │ ├── constants │ │ │ ├── mod.rs │ │ │ ├── seeds.rs │ │ │ └── constants.rs │ │ ├── id.rs │ │ ├── state │ │ │ ├── mod.rs │ │ │ ├── bank.rs │ │ │ ├── member.rs │ │ │ └── member_account.rs │ │ ├── instructions │ │ │ ├── mod.rs │ │ │ ├── bank.rs │ │ │ ├── member.rs │ │ │ └── member_account.rs │ │ └── lib.rs │ │ ├── Xargo.toml │ │ └── Cargo.toml │ ├── .prettierignore │ ├── .gitignore │ ├── Cargo.toml │ ├── tsconfig.json │ ├── Anchor.toml │ ├── migrations │ └── deploy.ts │ ├── package.json │ └── tests │ └── helpers │ └── airdrop.ts ├── android ├── app │ ├── debug.keystore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── drawable │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ ├── assets │ │ │ │ └── fonts │ │ │ │ │ ├── Jost-Black.ttf │ │ │ │ │ ├── Jost-Bold.ttf │ │ │ │ │ ├── Jost-Italic.ttf │ │ │ │ │ ├── Jost-Light.ttf │ │ │ │ │ ├── Jost-Medium.ttf │ │ │ │ │ ├── Jost-Thin.ttf │ │ │ │ │ ├── Jost-Regular.ttf │ │ │ │ │ ├── Jost-SemiBold.ttf │ │ │ │ │ ├── Jost-BlackItalic.ttf │ │ │ │ │ ├── Jost-BoldItalic.ttf │ │ │ │ │ ├── Jost-ExtraBold.ttf │ │ │ │ │ ├── Jost-ExtraLight.ttf │ │ │ │ │ ├── Jost-LightItalic.ttf │ │ │ │ │ ├── Jost-ThinItalic.ttf │ │ │ │ │ ├── Jost-MediumItalic.ttf │ │ │ │ │ ├── Jost-ExtraBoldItalic.ttf │ │ │ │ │ ├── Jost-ExtraLightItalic.ttf │ │ │ │ │ └── Jost-SemiBoldItalic.ttf │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── tapcash │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── tapcash │ │ │ │ └── ReactNativeFlipper.java │ │ └── release │ │ │ └── java │ │ │ └── com │ │ │ └── tapcash │ │ │ └── ReactNativeFlipper.java │ └── proguard-rules.pro ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle ├── build.gradle ├── gradle.properties ├── link-assets-manifest.json └── gradlew.bat ├── assets └── fonts │ ├── Jost-Bold.ttf │ ├── Jost-Thin.ttf │ ├── Jost-Black.ttf │ ├── Jost-Italic.ttf │ ├── Jost-Light.ttf │ ├── Jost-Medium.ttf │ ├── Jost-Regular.ttf │ ├── Jost-ExtraBold.ttf │ ├── Jost-SemiBold.ttf │ ├── Jost-BlackItalic.ttf │ ├── Jost-BoldItalic.ttf │ ├── Jost-ExtraLight.ttf │ ├── Jost-LightItalic.ttf │ ├── Jost-MediumItalic.ttf │ ├── Jost-ThinItalic.ttf │ ├── Jost-SemiBoldItalic.ttf │ ├── Jost-ExtraBoldItalic.ttf │ └── Jost-ExtraLightItalic.ttf ├── ios ├── TapCash │ ├── Images.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── main.m │ ├── AppDelegate.mm │ └── Info.plist ├── .xcode.env ├── TapCashTests │ ├── Info.plist │ └── TapCashTests.m ├── Podfile ├── link-assets-manifest.json └── TapCash.xcodeproj │ └── xcshareddata │ └── xcschemes │ └── TapCash.xcscheme ├── babel.config.js ├── react-native.config.js ├── index.js ├── Gemfile ├── .vscode └── settings.json ├── __tests__ └── App-test.tsx ├── metro.config.js ├── .env.example ├── .github └── workflows │ └── test.yml ├── .eslintrc.js ├── .gitignore ├── package.json └── App.tsx /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.6 2 | -------------------------------------------------------------------------------- /backend/src/types/yamlenv.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'yamlenv'; 2 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TapCash", 3 | "displayName": "Tap" 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/currency.ts: -------------------------------------------------------------------------------- 1 | export enum Currency { 2 | USD = "USD" 3 | } 4 | -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /backend/src/shared/currency.ts: -------------------------------------------------------------------------------- 1 | export enum Currency { 2 | USD = "USD" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/react-native/tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub use error::*; -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/debug.keystore -------------------------------------------------------------------------------- /assets/fonts/Jost-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-Regular.ttf -------------------------------------------------------------------------------- /src/images/activity/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/activity/send.png -------------------------------------------------------------------------------- /src/images/icons/wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/icons/wallet.png -------------------------------------------------------------------------------- /src/images/misc/tap-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/misc/tap-flow.png -------------------------------------------------------------------------------- /assets/fonts/Jost-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-SemiBold.ttf -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /src/images/activity/deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/activity/deposit.png -------------------------------------------------------------------------------- /src/images/activity/recieve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/activity/recieve.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Tap 3 | 4 | -------------------------------------------------------------------------------- /assets/fonts/Jost-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-ThinItalic.ttf -------------------------------------------------------------------------------- /src/images/activity/withdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/activity/withdraw.png -------------------------------------------------------------------------------- /src/images/loaders/loader-lg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/loaders/loader-lg.gif -------------------------------------------------------------------------------- /src/images/loaders/loader-sm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/loaders/loader-sm.gif -------------------------------------------------------------------------------- /src/images/payments/visa-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/payments/visa-mini.png -------------------------------------------------------------------------------- /assets/fonts/Jost-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /ios/TapCash/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /program/tap_cash/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /src/images/payments/unknown-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/payments/unknown-mini.png -------------------------------------------------------------------------------- /src/images/payments/visa-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/payments/visa-square.png -------------------------------------------------------------------------------- /assets/fonts/Jost-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Jost-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/assets/fonts/Jost-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .env.* 4 | !.env.example.yml 5 | keys 6 | **/test-ledger/** 7 | !.env.test.yml 8 | -------------------------------------------------------------------------------- /src/images/payments/unknown-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/src/images/payments/unknown-square.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/TapCash/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/constants/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub use constants::*; 3 | 4 | pub mod seeds; 5 | pub use seeds::*; -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Black.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Light.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Thin.ttf -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/id.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_id; 2 | 3 | declare_id!("TAPAPp2YoguQQDkicGyzTzkA3t4AgECvR1eL1hbx9qz"); -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-SemiBold.ttf -------------------------------------------------------------------------------- /program/tap_cash/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | wallet.json 8 | tests/helpers/usdc.json 9 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-BlackItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-BoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-ExtraBold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-ExtraLight.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-LightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-ThinItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-MediumItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Jost-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/assets/fonts/Jost-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austinmilt/tap-cash/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /backend/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | rootDir: "src" 6 | }; 7 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bank; 2 | pub use bank::*; 3 | pub mod member; 4 | pub use member::*; 5 | pub mod member_account; 6 | pub use member_account::*; -------------------------------------------------------------------------------- /backend/src/handlers/withdraw.test.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('withdraw handler', () => { 4 | it.skip('send - all good - user account is debited', async () => { 5 | //TODO 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | 'react-native-reanimated/plugin', 5 | "module:react-native-dotenv" 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {}, // grouped into "project" 5 | }, 6 | assets: ["./assets/fonts/"], // stays the same 7 | }; 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bank; 2 | pub use bank::*; 3 | pub mod member; 4 | pub use member::*; 5 | pub mod member_account; 6 | pub use member_account::*; 7 | pub mod send_spl; 8 | pub use send_spl::*; -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby File.read(File.join(__dir__, '.ruby-version')).strip 5 | 6 | gem 'cocoapods', '~> 1.11', '>= 1.11.3' 7 | -------------------------------------------------------------------------------- /ios/TapCash/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /backend/src/handlers/test-curls/deposit.sh: -------------------------------------------------------------------------------- 1 | curl -X POST -H "Content-Type: application/json" -d '{ 2 | "emailAddress": "gjimbn@random.com", 3 | "amount": "500" 4 | }' http://localhost:8080/deposit 5 | 6 | 7 | emailAddress: string; 8 | amount: number; 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "eslint.validate": [ 6 | "javascript", 7 | "react" 8 | ], 9 | "css.lint.validProperties": [ 10 | "composes" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'TapCash' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/react-native-gradle-plugin') 5 | -------------------------------------------------------------------------------- /program/tap_cash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /src/shared/arg.ts: -------------------------------------------------------------------------------- 1 | export class Arg { 2 | private constructor() { 3 | // do not instantiate 4 | } 5 | 6 | public static notNullish(arg: T, name: string): asserts arg is NonNullable & void { 7 | if (arg == null) { 8 | throw new Error(`${name} cannot be nullish`); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import "react-native"; 6 | import React from "react"; 7 | import App from "../App"; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from "react-test-renderer"; 11 | 12 | it("renders correctly", () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /backend/src/shared/arg.ts: -------------------------------------------------------------------------------- 1 | export class Arg { 2 | private constructor() { 3 | // do not instantiate 4 | } 5 | 6 | public static notNullish(arg: T, name: string): asserts arg is NonNullable & void { 7 | if (arg == null) { 8 | throw new Error(`${name} cannot be nullish`); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "strict": true, 7 | "outDir": "dist", 8 | }, 9 | "include": [ 10 | "src/**/*" 11 | ], 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /program/tap_cash/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/handlers/test-curls/save-member.sh: -------------------------------------------------------------------------------- 1 | curl -X POST -H "Content-Type: application/json" -d '{ 2 | "emailAddress": "amilz@123.com", 3 | "profilePictureUrl": "https://test.net/1.png", 4 | "name": "tino", 5 | "signerAddressBase58": "Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE" 6 | }' http://localhost:8080/save-member 7 | 8 | 9 | emailAddress: string; 10 | destinationAccountId: string; 11 | amount: number; 12 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/constants/seeds.rs: -------------------------------------------------------------------------------- 1 | /// Anchor seeds for a Bank PDA. 2 | pub const BANK_SEED: [u8; 8] = *b"tap-bank"; 3 | 4 | /// Anchor seeds for a Member account. 5 | pub const MEMBER_SEED: [u8; 6] = *b"member"; 6 | 7 | /// Anchor seeds for a Member checking account. 8 | pub const CHECKING_SEED:[u8; 8] = *b"checking"; 9 | 10 | /// Anchor seeds for a Member savings account. 11 | pub const SAVINGS_SEED: [u8; 7] = *b"savings"; 12 | -------------------------------------------------------------------------------- /program/tap_cash/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | [programs.localnet] 5 | tap_cash = "TAPAPp2YoguQQDkicGyzTzkA3t4AgECvR1eL1hbx9qz" 6 | 7 | [programs.devnet] 8 | tap_cash = "TAPAPp2YoguQQDkicGyzTzkA3t4AgECvR1eL1hbx9qz" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "devnet" 15 | wallet = "wallet.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tap_cash" 3 | version = "0.1.1" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "tap_cash" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "0.26.0" 20 | solana-program = "=1.14.14" 21 | anchor-spl = "0.26.0" -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/model/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum BankError { 5 | #[msg("Authority must be owned by System Program")] 6 | InvalidAuthority, 7 | #[msg("Bank already initialized")] 8 | AlreadyInitialized 9 | } 10 | 11 | #[error_code] 12 | pub enum MemberError { 13 | #[msg("Invalid authority")] 14 | InvalidAuthority, 15 | #[msg("Member already initialized")] 16 | AlreadyInitialized 17 | } -------------------------------------------------------------------------------- /program/tap_cash/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@project-serum/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/constants/constants.rs: -------------------------------------------------------------------------------- 1 | /// The public key of the native mint account on the Solana blockchain. 2 | pub const NATIVE_MINT: &str = "So11111111111111111111111111111111111111112"; 3 | 4 | /// The current version of the Bank account. 5 | pub const BANK_VERSION: u8 = 1; 6 | 7 | /// The current version of the Member account. 8 | pub const MEMBER_VERSION: u8 = 1; 9 | 10 | /// The current version of the Member account. 11 | pub const MEMBER_ACCOUNT_VERSION: u8 = 1; -------------------------------------------------------------------------------- /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: 11 | -------------------------------------------------------------------------------- /src/components/Screen.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { ViewProps } from "react-native-ui-lib"; 3 | import { ViewStyleProps } from "../common/styles"; 4 | import { View } from "./View"; 5 | 6 | export function Screen(props: ViewProps & ViewStyleProps): JSX.Element { 7 | 8 | return ( 9 | 15 | {props.children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /src/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import { TextProps as RNUITextProps } from "react-native"; 2 | import { TextStyleProps, useTextStyle } from "../common/styles"; 3 | import { Text as RNUIText } from "react-native-ui-lib"; 4 | 5 | export type TextProps = RNUITextProps & TextStyleProps; 6 | 7 | export function Text(props: TextProps): JSX.Element { 8 | const style = useTextStyle(props); 9 | 10 | return ( 11 | 12 | {props.children} 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/View.tsx: -------------------------------------------------------------------------------- 1 | import { View as RNUIView, ViewProps } from "react-native-ui-lib"; 2 | import { ViewStyleProps, useViewStyle } from "../common/styles"; 3 | 4 | export function View(props: ViewProps & ViewStyleProps): JSX.Element { 5 | const style = useViewStyle(props); 6 | 7 | if (!props.flex && !props.flexG && !props.flexS) { 8 | props.flexG = true; 9 | } 10 | 11 | return ( 12 | 13 | {props.children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/payment.ts: -------------------------------------------------------------------------------- 1 | export interface PaymentMethodSummary { 2 | type: PaymentMethodType; 3 | creditCard?: CreditCardSummary; 4 | } 5 | 6 | 7 | export enum PaymentMethodType { 8 | CREDIT_CARD 9 | } 10 | 11 | 12 | export interface CreditCardSummary { 13 | carrier: CreditCardCarrier; 14 | lastFourDigits: number; 15 | holderName: string; 16 | expiration: string; 17 | } 18 | 19 | 20 | export enum CreditCardCarrier { 21 | AMERICAN_EXPRESS, 22 | MASTERCARD, 23 | DISCOVER, 24 | VISA, 25 | UNKNOWN 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/shared/payment.ts: -------------------------------------------------------------------------------- 1 | export interface PaymentMethodSummary { 2 | type: PaymentMethodType; 3 | creditCard?: CreditCardSummary; 4 | } 5 | 6 | 7 | export enum PaymentMethodType { 8 | CREDIT_CARD 9 | } 10 | 11 | 12 | export interface CreditCardSummary { 13 | carrier: CreditCardCarrier; 14 | lastFourDigits: number; 15 | holderName: string; 16 | expiration: string; 17 | } 18 | 19 | 20 | export enum CreditCardCarrier { 21 | AMERICAN_EXPRESS, 22 | MASTERCARD, 23 | DISCOVER, 24 | VISA, 25 | UNKNOWN 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/member.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "../helpers/solana"; 2 | 3 | export type EmailAddress = string; 4 | export type MemberId = string; 5 | export type AccountId = string; 6 | export type ProfilePicture = string; 7 | 8 | export interface MemberPublicProfile { 9 | email: EmailAddress; 10 | profile: ProfilePicture; 11 | name: string; 12 | } 13 | 14 | 15 | export interface MemberPrivateProfile { 16 | email: EmailAddress; 17 | profile: ProfilePicture; 18 | name: string; 19 | signerAddress: PublicKey; 20 | usdcAddress: PublicKey; 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/shared/member.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "../helpers/solana"; 2 | 3 | export type EmailAddress = string; 4 | export type MemberId = string; 5 | export type AccountId = string; 6 | export type ProfilePicture = string; 7 | 8 | export interface MemberPublicProfile { 9 | email: EmailAddress; 10 | profile: ProfilePicture; 11 | name: string; 12 | } 13 | 14 | 15 | export interface MemberPrivateProfile { 16 | email: EmailAddress; 17 | profile: ProfilePicture; 18 | name: string; 19 | signerAddress: PublicKey; 20 | usdcAddress: PublicKey; 21 | } 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SAVE_MEMBER_URI=http://10.0.2.2:8080/save-member 2 | DEPOSIT_URI=http://10.0.2.2:8080/deposit 3 | SEND_URI=http://10.0.2.2:8080/send 4 | WITHDRAW_URI=http://10.0.2.2:8080/withdraw 5 | QUERY_RECIPIENTS_URI=http://10.0.2.2:8080/query-recipients 6 | MEMBER_ACCOUNT_URI=http://10.0.2.2:8080/account 7 | RECENT_ACTIVITY_URI=http://10.0.2.2:8080/recent-activity 8 | SAVED_PAYMENT_METHODS_URI=http://10.0.2.2:8080/payment-methods 9 | # mainnet | testnet 10 | WEB3_AUTH_NETWORK=testnet 11 | WEB3_AUTH_CLIENT_ID= 12 | SOLANA_RPC_URL=http://localhost:8899 13 | USDC_MINT_ADDRESS=REPLACE -------------------------------------------------------------------------------- /src/common/text.ts: -------------------------------------------------------------------------------- 1 | export function truncateName(name: string, maxLength = 12): string { 2 | if (name.length > maxLength) { 3 | if (name.includes(' ')) { 4 | const [first, last] = name.split(' ', 2); 5 | if (first.length + 4 >= maxLength) { 6 | return `${first.slice(0, maxLength - 4)}...`; 7 | } else { 8 | return `${first} ${last.slice(0, 1)}.`; 9 | } 10 | } else { 11 | return `${name.slice(0, maxLength - 3)}...`; 12 | } 13 | } else { 14 | return name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Image } from 'react-native-ui-lib'; 2 | import { IMAGES } from "../images/images"; 3 | import { View } from "./View"; 4 | 5 | export function Loading(): JSX.Element { 6 | return ( 7 | 12 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/BigDollars.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { ViewStyleProps } from "../common/styles" 3 | import { Text } from "./Text"; 4 | import { formatUsd } from "../common/number"; 5 | 6 | export function BigDollars(props: { children: number } & ViewStyleProps): JSX.Element { 7 | return ( 8 | {formatUsd(props.children, { leadingSymbol: true, short: true })} 9 | ) 10 | } 11 | 12 | 13 | const STYLES = StyleSheet.create({ 14 | text: { 15 | fontSize: 53, 16 | textAlignVertical: "center" 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI Backend Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | 11 | runs-on: ubuntu-latest 12 | defaults: 13 | run: 14 | working-directory: ./backend 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js 18 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18.x 22 | 23 | - name: Install Dependencies 24 | run: npm ci 25 | 26 | - name: Run Tests 27 | run: | 28 | npm run build --if-present 29 | npm test 30 | -------------------------------------------------------------------------------- /backend/.env.test.yml: -------------------------------------------------------------------------------- 1 | SERVER_ENV: test 2 | CIRCLE_API_KEY: gibberish 3 | GOOGLE_APPLICATION_CREDENTIALS: gibberish 4 | GCLOUD_PROJECT: gibberish 5 | FIRESTORE_MEMBERS_COLLECTION: gibberish 6 | FAKE_USDC: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 7 | BANK_KEY: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 8 | BANK_USDC_WALLET: rand4XuRxdtPS9gDYy6KDeGkEpi69xmkCy5oEmDYfoC 9 | USDC_MINT_ADDRESS: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU 10 | CIRCLE_MASTER_WALLET: 0000000000 11 | -------------------------------------------------------------------------------- /backend/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | node_modules 17 | #!include:.gitignore 18 | scripts/ 19 | README.md 20 | .env.* 21 | -------------------------------------------------------------------------------- /program/tap_cash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@project-serum/anchor": "^0.26.0", 8 | "@solana/spl-token": "^0.3.7" 9 | }, 10 | "devDependencies": { 11 | "@types/bn.js": "^5.1.0", 12 | "@types/chai": "^4.3.0", 13 | "@types/mocha": "^9.0.0", 14 | "chai": "^4.3.4", 15 | "mocha": "^9.0.3", 16 | "prettier": "^2.6.2", 17 | "ts-mocha": "^10.0.0", 18 | "typescript": "^4.3.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/types/types.ts: -------------------------------------------------------------------------------- 1 | import { web3 } from "@project-serum/anchor"; 2 | 3 | export interface MemberAccounts { 4 | 5 | /** 6 | * PublicKey address of the member's wallet used to sign transactions. 7 | */ 8 | signerAddress: web3.PublicKey; 9 | 10 | /** 11 | * PublicKey address of the user's USDC associated token account. 12 | */ 13 | usdcAddress: web3.PublicKey; 14 | } 15 | 16 | 17 | export type CircleCardId = string; 18 | 19 | 20 | export enum ServerEnv { 21 | LOCAL, 22 | DEV, 23 | PROD, 24 | TEST 25 | } 26 | 27 | 28 | export enum CircleClientType { 29 | MOCK, 30 | EMULATOR, 31 | MAIN 32 | } 33 | -------------------------------------------------------------------------------- /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 = "33.0.0" 6 | minSdkVersion = 21 7 | compileSdkVersion = 33 8 | targetSdkVersion = 33 9 | 10 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 11 | ndkVersion = "23.1.7779620" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath("com.android.tools.build:gradle:7.3.1") 19 | classpath("com.facebook.react:react-native-gradle-plugin") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/.env.example.yml: -------------------------------------------------------------------------------- 1 | # copy this to .env and change the values to what fits your env, e.g. .env.prod.yml 2 | CIRCLE_API_KEY: 3 | CIRCLE_ENVIRONMENT: 4 | GOOGLE_APPLICATION_CREDENTIALS: 5 | GCLOUD_PROJECT: 6 | FIRESTORE_MEMBERS_COLLECTION: 7 | BANK_KEY: 8 | FAKE_USDC: 9 | BANK_USDC_WALLET: rand4XuRxdtPS9gDYy6KDeGkEpi69xmkCy5oEmDYfoC 10 | SOLANA_CLUSTER: local #mainnet, devnet, local 11 | RPC_URL: http://localhost:8899/ 12 | USE_DUMMY_CARD: true 13 | USDC_MINT_ADDRESS: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU 14 | CIRCLE_CLIENT_TYPE: mock 15 | CIRCLE_MASTER_WALLET: 1111111111 16 | -------------------------------------------------------------------------------- /android/app/src/release/java/com/tapcash/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.tapcash; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/images/images.ts: -------------------------------------------------------------------------------- 1 | export const IMAGES = { 2 | activity: { 3 | deposit: require("./activity/deposit.png"), 4 | receive: require("./activity/recieve.png"), 5 | send: require("./activity/send.png"), 6 | withdraw: require("./activity/withdraw.png") 7 | }, 8 | payments: { 9 | visaMini: require("./payments/visa-mini.png"), 10 | visaSquare: require("./payments/visa-square.png"), 11 | unknownMini: require("./payments/unknown-square.png"), 12 | unknownSquare: require("./payments/unknown-square.png") 13 | }, 14 | loaders: { 15 | loaderSmall: require("./loaders/loader-sm.gif"), 16 | loaderLarge: require("./loaders/loader-lg.gif") 17 | }, 18 | icons: { 19 | wallet: require("./icons/wallet.png"), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/dev/testing/utils.ts: -------------------------------------------------------------------------------- 1 | import * as ff from "@google-cloud/functions-framework"; 2 | 3 | export function buildPostRequest(body: T): ff.Request { 4 | // Our handlers use very little of the request object, so 5 | // we can get away with just spoofing it. If we tried to use 6 | // this on a live local server, it would break. 7 | return { 8 | body: body, 9 | } as unknown as ff.Request; 10 | } 11 | 12 | 13 | export function buildGetRequest(params: T): ff.Request { 14 | // Our handlers use very little of the request object, so 15 | // we can get away with just spoofing it. If we tried to use 16 | // this on a live local server, it would break. 17 | return { 18 | query: params, 19 | } as unknown as ff.Request; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/AppLogo.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { TextStyleProps } from "../common/styles"; 3 | import { Text } from "./Text"; 4 | 5 | interface Props { 6 | primary?: boolean; 7 | secondary?: boolean; 8 | fontSize?: number; 9 | } 10 | 11 | const defaultProps: Props = { 12 | fontSize: 84 13 | }; 14 | 15 | export function AppLogo(props: Props): JSX.Element { 16 | const finalProps: Props = { ...defaultProps, ...props }; 17 | const extraProps = useMemo(() => { 18 | const textProps: TextStyleProps = {}; 19 | if (props.primary) textProps["primary-medium"] = true; 20 | if (props.secondary) textProps["whiteish"] = true; 21 | return textProps; 22 | }, [props]); 23 | 24 | return tap 25 | } 26 | -------------------------------------------------------------------------------- /ios/TapCashTests/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 | -------------------------------------------------------------------------------- /src/modules/SplashScreen.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { TopNavScreen, TopRouteParams } from "../common/navigation"; 3 | import { Screen } from "../components/Screen"; 4 | import { View } from "../components/View"; 5 | import { AppLogo } from "../components/AppLogo"; 6 | import { NativeStackScreenProps } from "@react-navigation/native-stack"; 7 | 8 | type Props = NativeStackScreenProps; 9 | 10 | export function SplashScreen(props: Props): JSX.Element { 11 | useEffect(() => { 12 | setTimeout(() => props.navigation.navigate(TopNavScreen.AUTHENTICATE), 1500); 13 | }, []); 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/state/bank.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct Bank { 5 | /// Version of the Bank Account 6 | pub version: u8, 7 | /// The authority key for the bank 8 | pub authority: Pubkey, 9 | /// The fee payer key for the bank 10 | pub fee_payer: Pubkey, 11 | /// Bump seed for the bank 12 | pub bump: u8 13 | } 14 | 15 | impl Bank { 16 | /// Returns the expected size of the account in bytes. 17 | pub fn get_space() -> usize { 18 | 8 + // account discriminator 19 | 1 + // version 20 | 32 + // authority 21 | 32 + // fee_payer 22 | 1 // bump 23 | } 24 | /// Logs a message indicating that a new bank has been initialized. 25 | pub fn log_init(&self){ 26 | msg!("Init new bank v.{}", self.version); 27 | } 28 | } -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod state; 2 | pub mod constants; 3 | pub mod instructions; 4 | pub mod model; 5 | pub mod id; 6 | 7 | use anchor_lang::prelude::*; 8 | use instructions::*; 9 | 10 | pub use id::ID; 11 | 12 | #[program] 13 | pub mod tap_cash { 14 | use super::*; 15 | 16 | pub fn initialize_bank(ctx: Context) -> Result<()> { 17 | instructions::initialize_bank(ctx) 18 | } 19 | 20 | pub fn initialize_member(ctx: Context) -> Result<()> { 21 | instructions::initialize_member(ctx) 22 | } 23 | 24 | pub fn initialize_account(ctx: Context) -> Result<()>{ 25 | instructions::init_account(ctx) 26 | } 27 | 28 | pub fn send_spl(ctx: Context, withdraw_amount: u64) -> Result<()>{ 29 | instructions::send_spl(ctx, withdraw_amount) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/state/member.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct Member { 5 | /// Version of the Member Account 6 | pub version: u8, 7 | /// The bank pda 8 | pub bank: Pubkey, 9 | /// The member PubKey received on enrollment (currently via Web3 auth) 10 | pub user_id: Pubkey, 11 | /// Bump seed for the member 12 | pub bump: u8, 13 | /// Number of accounts 14 | pub num_accounts: u8 15 | } 16 | 17 | impl Member { 18 | /// Returns the expected size of the account in bytes. 19 | pub fn get_space() -> usize { 20 | 8 + // account discriminator 21 | 1 + // version 22 | 32 + // bank pda 23 | 32 + // user id 24 | 1 + // num_accounts 25 | 1 // bump 26 | } 27 | /// Logs a message indicating that a new member has been initialized. 28 | pub fn log_init(&self){ 29 | msg!("Init new member:{}", self.user_id); 30 | } 31 | } -------------------------------------------------------------------------------- /backend/src/circle/client.ts: -------------------------------------------------------------------------------- 1 | import { Card } from "@circle-fin/circle-sdk"; 2 | import { EmailAddress } from "../shared/member"; 3 | import { CircleCardId } from "../types/types"; 4 | 5 | /** 6 | * Tap-specific Circle client for interacting with the Circle API. 7 | */ 8 | export interface CircleClient { 9 | depositUsdc(args: CircleDepositArgs): Promise; 10 | fetchCard(id: string): Promise; 11 | } 12 | 13 | /** 14 | * This is the set of arguments that are needed to deposit funds into a user's account 15 | */ 16 | export interface CircleDepositArgs { 17 | /* The destination USDC associated token account to deposit the funds into */ 18 | destinationAtaString: string; 19 | /* The amount of USDC to deposit */ 20 | amount: number; 21 | /* The email address of the user */ 22 | member: EmailAddress; 23 | /* The id of the card to use */ 24 | cardId: CircleCardId; 25 | /* The cvv of the card to use */ 26 | cardCvv: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/common/debounce.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | 3 | 4 | export function useStateWithDebounce( 5 | callback: (value: T) => void, 6 | intervalMs: number 7 | ): [T | undefined, (value: T) => void] { 8 | const [currentValue, setCurrentValue] = useState(); 9 | 10 | const setValue: (value: T) => void = useCallback(value => { 11 | if (value !== currentValue) { 12 | setCurrentValue(value); 13 | } 14 | }, [currentValue]); 15 | 16 | 17 | // call the callback `intervalMs` after the value stops changing 18 | useEffect(() => { 19 | const timeoutId: NodeJS.Timeout = setTimeout(() => { 20 | if (currentValue != null) { 21 | callback(currentValue); 22 | } 23 | }, intervalMs); 24 | 25 | return () => { 26 | clearTimeout(timeoutId); 27 | } 28 | }, [currentValue]); 29 | 30 | 31 | return [currentValue, setValue]; 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/dev/testing/generate.ts: -------------------------------------------------------------------------------- 1 | import { Card, CvvResults } from "@circle-fin/circle-sdk"; 2 | import { v4 as uuid } from "uuid"; 3 | 4 | /** 5 | * 6 | * @returns a Circle Card with randomly generated attributes 7 | */ 8 | export function generateCircleCard(): Card { 9 | return { 10 | id: uuid(), 11 | status: "pending", 12 | billingDetails: { 13 | name: "Baron Bilano", 14 | city: "Baronville", 15 | country: "Baronia", 16 | line1: "123 Baron Street", 17 | postalCode: "12345" 18 | }, 19 | expMonth: 1, 20 | expYear: 2025, 21 | network: "VISA", 22 | last4: "4321", 23 | fingerprint: "alskjdflajksdflj", 24 | verification: { 25 | avs: "laksjlkasjdf", 26 | cvv: CvvResults.Pass 27 | }, 28 | metadata: { 29 | email: "baron.bilano@gmail.com" 30 | }, 31 | createDate: "21:30:38Z", 32 | updateDate: "21:30:38Z" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/program/constants.ts: -------------------------------------------------------------------------------- 1 | import { parseKeypair, parsePublicKey } from "../constants"; 2 | import { Keypair, PublicKey } from "../helpers/solana"; 3 | 4 | // Program Seed's Ref: program/tap_cash/programs/tap_cash/src/constants/ 5 | 6 | /* Seed used for BANK PDA */ 7 | export const BANK_SEED = "tap-bank"; 8 | /* Seed used for MEMBER PDA */ 9 | export const MEMBER_SEED = "member"; 10 | /* Seed used for Account PDA (checking) */ 11 | export const CHECKING_SEED = "checking"; 12 | 13 | /* Authority of Program's BANK (will be used to sign transactions and pay fees) */ 14 | export const BANK_AUTH: Keypair = parseKeypair("BANK_AUTH", process.env.BANK_KEY); 15 | /* ATA of USDC Wallet of the BANK */ 16 | export const BANK_USDC_WALLET: PublicKey = parsePublicKey("BANK_USDC_WALLET", process.env.BANK_USDC_WALLET); 17 | /* Environment of the Program */ 18 | export const PROGRAM_ENV = process.env.SOLANA_ENVIRONMENT; 19 | /* Program ID of the Program (ref: program/tap_cash/programs/tap_cash/src/id.rs) */ 20 | export const TAPCASH_PROGRAM_ID: PublicKey = new PublicKey("TAPAPp2YoguQQDkicGyzTzkA3t4AgECvR1eL1hbx9qz"); 21 | -------------------------------------------------------------------------------- /ios/TapCash/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"TapCash"; 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 14 | { 15 | #if DEBUG 16 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 17 | #else 18 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 19 | #endif 20 | } 21 | 22 | /// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. 23 | /// 24 | /// @see: https://reactjs.org/blog/2022/03/29/react-v18.html 25 | /// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). 26 | /// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`. 27 | - (BOOL)concurrentRootEnabled 28 | { 29 | return true; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /src/components/RecipientProfile.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, ViewProps } from "react-native-ui-lib"; 2 | import { View } from "./View"; 3 | import { MemberPublicProfile } from "../shared/member"; 4 | import { StyleSheet } from "react-native"; 5 | import { Text } from "./Text"; 6 | import { ViewStyleProps } from "../common/styles"; 7 | 8 | 9 | export function RecipientProfile(props: MemberPublicProfile & ViewProps & ViewStyleProps): JSX.Element { 10 | return ( 11 | 12 | 16 | 17 | {props.name} 18 | {props.email} 19 | 20 | 21 | ); 22 | } 23 | 24 | 25 | const STYLES = StyleSheet.create({ 26 | container: { 27 | paddingVertical: 12, 28 | gap: 16, 29 | }, 30 | 31 | email: { 32 | fontSize: 14 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /ios/TapCash/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /program/tap_cash/programs/tap_cash/src/state/member_account.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct MemberAccount { 5 | /// Version of the Member Account 6 | pub version: u8, 7 | /// The member pda 8 | pub member: Pubkey, 9 | /// The token mint 10 | pub token_mint: Pubkey, 11 | /// The associated token account 12 | pub ata: Pubkey, 13 | /// Bump seed for the member account 14 | pub bump: u8, 15 | /// Account number 16 | pub acct_no: u8, 17 | /// Account type (0=checking, 1=savings) 18 | pub acct_type: u8 19 | } 20 | 21 | impl MemberAccount { 22 | /// Returns the size of the `MemberAccount` account in bytes 23 | pub fn get_space() -> usize { 24 | 8 + // account discriminator 25 | 1 + // version 26 | 32 + // member pda 27 | 32 + // token_mint 28 | 32 + // ata 29 | 1 + // bump 30 | 1 + // account no 31 | 1 // account type 32 | } 33 | /// Prints a log message with the member's PDA address 34 | pub fn log_init(&self){ 35 | msg!("Init new acct for:{}", self.member); 36 | } 37 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | // By extending from a plugin config, we can get recommended rules without having to add them manually. 5 | 'eslint:recommended', 6 | "plugin:react/recommended", 7 | 'plugin:import/recommended', 8 | 'plugin:jsx-a11y/recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:react/jsx-runtime', 11 | ], 12 | settings: { 13 | react: { 14 | // Tells eslint-plugin-react to automatically detect the version of React to use. 15 | version: "detect", 16 | }, 17 | // Tells eslint how to resolve imports 18 | 'import/resolver': { 19 | node: { 20 | paths: ["src"], 21 | extensions: [".js", ".jsx", ".ts", ".tsx"], 22 | }, 23 | }, 24 | }, 25 | rules: { 26 | camelcase: "warn", 27 | '@typescript-eslint/no-inferrable-types': "off", 28 | quotes: ["error", "double"], 29 | semi: ["error", "always"], 30 | indent: ["error", 4], 31 | 'no-multi-spaces': ["error"], 32 | 'max-len': ["warn", 120], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/TextInput.tsx: -------------------------------------------------------------------------------- 1 | import { Incubator, TextFieldProps } from "react-native-ui-lib"; 2 | import { TextStyleProps, ViewStyleProps, useTextStyle, useViewStyle } from "../common/styles"; 3 | import { TextProps } from "./Text"; 4 | import { StyleSheet } from "react-native"; 5 | 6 | type Props = TextFieldProps & TextProps & TextStyleProps & { 7 | inputFieldStyle?: ViewStyleProps 8 | } 9 | 10 | export function TextInput(props: Props): JSX.Element { 11 | const textStyle = useTextStyle({ 12 | "gray-dark": true, 13 | "text-md": true, 14 | ...props 15 | }); 16 | const fieldStyle = useViewStyle({ 17 | ...props.inputFieldStyle 18 | }); 19 | 20 | return ( 21 | 26 | {props.children} 27 | 28 | ) 29 | } 30 | 31 | const STYLE = StyleSheet.create({ 32 | fieldBase: { 33 | paddingHorizontal: 16, 34 | paddingVertical: 10, 35 | }, 36 | textBase: { 37 | textDecorationLine: "none" 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /src/shared/activity.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from "./currency"; 2 | import { AccountId, MemberPublicProfile } from "./member"; 3 | 4 | export interface MemberActivity { 5 | type: MemberActivityType; 6 | deposit?: DepositActivity; 7 | send?: SendActivity; 8 | receive?: ReceiveActivity; 9 | withdraw?: WithdrawActivity; 10 | 11 | /** 12 | * Timestamp in seconds since Unix epoch. Expect to receive value from Solana, 13 | * but optional in case none is returned. 14 | */ 15 | unixTimestamp?: number; 16 | } 17 | 18 | export enum MemberActivityType { 19 | DEPOSIT, 20 | SEND, 21 | RECEIVE, 22 | WITHDRAW, 23 | } 24 | 25 | export interface DepositActivity { 26 | account: AccountId; 27 | currency: Currency; 28 | amount: number; 29 | } 30 | 31 | 32 | export interface SendActivity { 33 | recipient: MemberPublicProfile; 34 | currency: Currency; 35 | amount: number; 36 | } 37 | 38 | export interface ReceiveActivity { 39 | sender: MemberPublicProfile; 40 | currency: Currency; 41 | amount: number; 42 | } 43 | 44 | 45 | export interface WithdrawActivity { 46 | source: AccountId; 47 | currency: Currency; 48 | amount: number; 49 | } 50 | -------------------------------------------------------------------------------- /backend/src/shared/activity.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from "./currency"; 2 | import { AccountId, MemberPublicProfile } from "./member"; 3 | 4 | export interface MemberActivity { 5 | type: MemberActivityType; 6 | deposit?: DepositActivity; 7 | send?: SendActivity; 8 | receive?: ReceiveActivity; 9 | withdraw?: WithdrawActivity; 10 | 11 | /** 12 | * Timestamp in seconds since Unix epoch. Expect to receive value from Solana, 13 | * but optional in case none is returned. 14 | */ 15 | unixTimestamp?: number; 16 | } 17 | 18 | export enum MemberActivityType { 19 | DEPOSIT, 20 | SEND, 21 | RECEIVE, 22 | WITHDRAW, 23 | } 24 | 25 | export interface DepositActivity { 26 | account: AccountId; 27 | currency: Currency; 28 | amount: number; 29 | } 30 | 31 | 32 | export interface SendActivity { 33 | recipient: MemberPublicProfile; 34 | currency: Currency; 35 | amount: number; 36 | } 37 | 38 | export interface ReceiveActivity { 39 | sender: MemberPublicProfile; 40 | currency: Currency; 41 | amount: number; 42 | } 43 | 44 | 45 | export interface WithdrawActivity { 46 | source: AccountId; 47 | currency: Currency; 48 | amount: number; 49 | } 50 | -------------------------------------------------------------------------------- /backend/src/handlers/test-curls/send.sh: -------------------------------------------------------------------------------- 1 | curl -X POST -H "Content-Type: application/json" -d '{ 2 | "emailAddress": "amilz@123.com", 3 | "profilePictureUrl": "https://test.net/1.png", 4 | "name": "tino", 5 | "signerAddressBase58": "Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE" 6 | }' http://localhost:8080/save-member 7 | 8 | 9 | [113,154,196,249,251,248,198,116,19,104,102,173,218,135,205,227,227,179,179,184,25,7,242,128,8,209,160,39,38,99,218,152,178,89,12,233,233,89,37,201,228,156,171,238,113,143,72,205,158,98,122,60,11,92,186,127,63,190,14,17,104,107,128,238] 10 | 11 | curl -X POST -H "Content-Type: application/json" -d '{ 12 | "senderEmailAddress": "5eiqhs@example.com", 13 | "recipientEmailAddress": "51xgcg@test.com", 14 | "amount": "69", 15 | "privateKey": [113,154,196,249,251,248,198,116,19,104,102,173,218,135,205,227,227,179,179,184,25,7,242,128,8,209,160,39,38,99,218,152,178,89,12,233,233,89,37,201,228,156,171,238,113,143,72,205,158,98,122,60,11,92,186,127,63,190,14,17,104,107,128,238] 16 | }' http://localhost:8080/send 17 | 18 | export interface ApiSendRequest { 19 | senderEmailAddress: string; 20 | recipientEmailAddress: string; 21 | amount: number; 22 | privateKey: number[]; 23 | } 24 | 25 | 51xgcg@test.com 26 | -------------------------------------------------------------------------------- /src/modules/deposit/DepositStack.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps, createNativeStackNavigator } from "@react-navigation/native-stack"; 2 | import { ProfileStackRouteParams, ProfileNavScreen, DepositStackRouteParams, DepositNavScreen, STACK_DEFAULTS } from "../../common/navigation"; 3 | import { AmountInputScreen } from "./AmountInputScreen"; 4 | import { DepositingScreen } from "./DepositingScreen"; 5 | 6 | const Stack = createNativeStackNavigator(); 7 | 8 | type Props = NativeStackScreenProps; 9 | 10 | export function DepositStack(props: Props): JSX.Element { 11 | return ( 12 | 16 | 21 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /.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 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | /ios/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | .env* 66 | !.env.example 67 | 68 | secrets/ 69 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonProps, Button as RNUIButton } from "react-native-ui-lib" 2 | import { COLORS, useTextStyle } from "../common/styles"; 3 | import { useMemo } from "react"; 4 | 5 | interface Props { 6 | label: string; 7 | onPress: () => void; 8 | primary?: boolean; 9 | secondary?: boolean; 10 | tertiary?: boolean; 11 | disabled?: boolean; 12 | } 13 | 14 | export function Button(props: Props & ButtonProps): JSX.Element { 15 | const [fontColor, bgColor, borderColor]: [string, string, string | undefined] = useMemo(() => { 16 | if (props.secondary) return [COLORS.whiteish, COLORS.secondaryMedium, undefined]; 17 | if (props.tertiary) return [COLORS.grayDark, COLORS.whiteish, COLORS.grayLight]; 18 | else return [COLORS.whiteish, COLORS.primaryMedium, undefined]; 19 | }, [props.secondary, props.tertiary, props.primary]); 20 | 21 | const labelStyle = { 22 | ...useTextStyle({ 23 | "text-md": true, 24 | }), 25 | color: fontColor 26 | }; 27 | 28 | return ( 29 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ShimmerBars.tsx: -------------------------------------------------------------------------------- 1 | import LinearGradient from 'react-native-linear-gradient'; 2 | import ShimmerPlaceHolder from 'react-native-shimmer-placeholder'; 3 | import { View } from './View'; 4 | import { useWindowDimensions } from 'react-native'; 5 | 6 | interface Props { 7 | loading: boolean; 8 | numBars: number; 9 | } 10 | 11 | export function ShimmerBars({ loading, numBars }: Props): JSX.Element { 12 | const windowWidth = useWindowDimensions().width; 13 | const width = Math.round(windowWidth * 0.8); 14 | 15 | const ShimmerBar = ( 16 | 27 | ); 28 | 29 | const bars = Array.from({ length: numBars }, () => ShimmerBar); 30 | 31 | return ( 32 | 37 | {bars.map((bar, index) => ( 38 | 39 | {bar} 40 | 41 | ))} 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/handlers/withdraw.ts: -------------------------------------------------------------------------------- 1 | 2 | //TODO tests 3 | 4 | import { ApiWithdrawRequest, ApiWithdrawResult } from "../shared/api"; 5 | import { EmailAddress, AccountId } from "../shared/member"; 6 | import { getRequiredParam, makePostHandler } from "./model"; 7 | 8 | interface WithdrawArgs { 9 | emailAddress: EmailAddress; 10 | sourceAccount: AccountId; 11 | amount: number; 12 | // TODO something about the destination bank account 13 | //TODO probably the user's private key 14 | } 15 | 16 | 17 | interface WithdrawResult { 18 | //TODO something about the result of the withdraw attempt 19 | } 20 | 21 | 22 | export const handleWithdraw = makePostHandler(withdraw, transformRequest, transformResult); 23 | 24 | 25 | async function withdraw(request: WithdrawArgs): Promise { 26 | return { 27 | //TODO 28 | } 29 | } 30 | 31 | 32 | function transformRequest(body: ApiWithdrawRequest): WithdrawArgs { 33 | return { 34 | emailAddress: getRequiredParam(body, "emailAddress"), 35 | sourceAccount: getRequiredParam(body, "sourceAccount"), 36 | amount: getRequiredParam(body, "amount", Number.parseFloat) 37 | }; 38 | } 39 | 40 | 41 | function transformResult(result: WithdrawResult): ApiWithdrawResult { 42 | // nothing to return 43 | return {}; 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /backend/src/circle/open-pgp.ts: -------------------------------------------------------------------------------- 1 | // Ref Implementation: https://github.com/circlefin/payments-sample-app/blob/78e3d1b5b3b548775e755f1b619720bcbe5a8789/lib/openpgp.ts 2 | 3 | import { createMessage, encrypt, readKey } from 'openpgp'; 4 | 5 | interface PublicKey { 6 | keyId: string 7 | /* uuid Public Key (Not a web3 PublicKey) */ 8 | publicKey: string 9 | } 10 | 11 | interface Result { 12 | /* Encrypted message */ 13 | encryptedMessage: string; 14 | keyId: string; 15 | } 16 | 17 | /** 18 | * Encrypt dataToEncrypt 19 | * 20 | * @param {Object} dataToEncrypt 21 | * @param {PublicKey} Object containing keyId and publicKey properties 22 | * 23 | * @return {Object} Object containing encryptedMessage and keyId 24 | */ 25 | export async function pgpEncrypt(dataToEncrypt: object, { keyId, publicKey }: PublicKey): Promise { 26 | if (!publicKey || !keyId) { 27 | throw new Error('Unable to encrypt data'); 28 | } 29 | 30 | const decodedPublicKey = await readKey({ armoredKey: atob(publicKey) }); 31 | const message = await createMessage({ text: JSON.stringify(dataToEncrypt) }); 32 | const ciphertext = await encrypt({ 33 | message, 34 | encryptionKeys: decodedPublicKey, 35 | }); 36 | 37 | return { 38 | // TODO replace btoa 39 | // @ts-ignore copied from online example... 40 | encryptedMessage: btoa(ciphertext), 41 | keyId, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/src/handlers/account.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ApiAccountRequest, ApiAccountResult } from "../shared/api"; 3 | import { getRequiredParam, makeGetHandler } from "./model"; 4 | import { getDatabaseClient } from "../helpers/singletons"; 5 | import { EmailAddress, MemberPrivateProfile } from "../shared/member"; 6 | 7 | interface AccountArgs { 8 | member: EmailAddress; 9 | } 10 | 11 | 12 | //TODO this needs to have authentication even though we're not (yet) sharing 13 | // anything too sensitive 14 | export const handleAccount = makeGetHandler(getAccount, transformRequest, transformResult); 15 | 16 | /** 17 | * 18 | * Fetch a member's private profile from the Database Client 19 | * 20 | * @param request AccountArgs - the request arguments 21 | * @returns a Member's private profile 22 | */ 23 | async function getAccount(request: AccountArgs): Promise { 24 | return await getDatabaseClient().getMemberPrivateProfile(request.member); 25 | } 26 | 27 | function transformRequest(params: ApiAccountRequest): AccountArgs { 28 | return { 29 | member: getRequiredParam(params, "memberEmail") 30 | }; 31 | } 32 | 33 | function transformResult(result: MemberPrivateProfile): ApiAccountResult { 34 | return { 35 | email: result.email, 36 | name: result.name, 37 | profile: result.profile, 38 | signerAddress: result.signerAddress.toBase58(), 39 | usdcAddress: result.usdcAddress.toBase58() 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /backend/src/handlers/test-curls/genCurl.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | 3 | interface ApiInitializeMemberRequest { 4 | emailAddress: string; 5 | profilePictureUrl: string; 6 | name: string; 7 | signerAddressBase58: string; 8 | } 9 | 10 | function generateRandomRequest(): ApiInitializeMemberRequest { 11 | const emailSuffixes = ['@example.com', '@test.com', '@random.com']; 12 | const profilePictureUrls = [ 13 | 'https://picsum.photos/200/300', 14 | 'https://picsum.photos/300/200', 15 | 'https://picsum.photos/250/250', 16 | ]; 17 | const names = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve']; 18 | 19 | const email = `${Math.random() 20 | .toString(36) 21 | .substring(7)}${emailSuffixes[Math.floor(Math.random() * emailSuffixes.length)]}`; 22 | const profilePictureUrl = 23 | profilePictureUrls[Math.floor(Math.random() * profilePictureUrls.length)]; 24 | const name = names[Math.floor(Math.random() * names.length)]; 25 | const signerAddressBase58 = 26 | Keypair.generate().publicKey.toBase58(); // Need to write this to do send function later 27 | 28 | return { emailAddress: email, profilePictureUrl, name, signerAddressBase58 }; 29 | } 30 | 31 | const requestData = generateRandomRequest(); 32 | 33 | const curlCommand = `curl -X POST -H "Content-Type: application/json" -d '${JSON.stringify( 34 | requestData, 35 | )}' http://localhost:8080/save-member`; 36 | 37 | console.log(curlCommand); 38 | -------------------------------------------------------------------------------- /backend/src/handlers/query-recipients.ts: -------------------------------------------------------------------------------- 1 | 2 | //TODO tests 3 | 4 | import { MemberPublicProfile } from "../shared/member"; 5 | import { DatabaseClient } from "../db/client"; 6 | import { FirestoreClient } from "../db/firestore"; 7 | import { ApiQueryRecipientsRequest, ApiQueryRecipientsResult } from "../shared/api"; 8 | import { getRequiredParam, getRequiredIntegerParam, makeGetHandler } from "./model"; 9 | import { getDatabaseClient } from "../helpers/singletons"; 10 | 11 | interface QueryRecipientsArgs { 12 | emailQuery: string; 13 | limit: number; 14 | } 15 | 16 | 17 | type QueryRecipientsResult = MemberPublicProfile[]; 18 | 19 | 20 | export const handleQueryRecipients = makeGetHandler(queryRecipients, transformRequest, transformResult); 21 | 22 | async function queryRecipients(request: QueryRecipientsArgs): Promise { 23 | return await getDatabaseClient().queryMembersByEmail(request.emailQuery, request.limit); 24 | } 25 | 26 | 27 | function transformRequest(params: ApiQueryRecipientsRequest): QueryRecipientsArgs { 28 | return { 29 | emailQuery: getRequiredParam(params, "emailQuery"), 30 | limit: getRequiredIntegerParam(params, "limit"), 31 | }; 32 | } 33 | 34 | 35 | function transformResult(result: QueryRecipientsResult): ApiQueryRecipientsResult { 36 | return result.map(r => ({ 37 | emailAddress: r.email, 38 | name: r.name, 39 | profilePicture: r.profile 40 | })); 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/profile/ProfileStack.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps, createNativeStackNavigator } from "@react-navigation/native-stack"; 2 | import { ProfileStackRouteParams, ProfileNavScreen, TopRouteParams, TopNavScreen, STACK_DEFAULTS } from "../../common/navigation"; 3 | import { ProfileOverviewScreen } from "./ProfileSummaryScreen"; 4 | import { PaymentMethodsScreen } from "./PaymentMethodsScreen"; 5 | import { DepositStack } from "../deposit/DepositStack"; 6 | 7 | const Stack = createNativeStackNavigator(); 8 | 9 | type Props = NativeStackScreenProps; 10 | 11 | export function ProfileStack(props: Props): JSX.Element { 12 | return ( 13 | 17 | 22 | 27 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/tapcash/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tapcash; 2 | 3 | import android.os.Bundle; 4 | import com.facebook.react.ReactActivity; 5 | import com.facebook.react.ReactActivityDelegate; 6 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 7 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 8 | 9 | public class MainActivity extends ReactActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(null); 14 | } 15 | 16 | /** 17 | * Returns the name of the main component registered from JavaScript. This is used to schedule 18 | * rendering of the component. 19 | */ 20 | @Override 21 | protected String getMainComponentName() { 22 | return "TapCash"; 23 | } 24 | 25 | /** 26 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 27 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 28 | * (aka React 18) with two boolean flags. 29 | */ 30 | @Override 31 | protected ReactActivityDelegate createReactActivityDelegate() { 32 | return new DefaultReactActivityDelegate( 33 | this, 34 | getMainComponentName(), 35 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 36 | DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled 37 | // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18). 38 | DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/common/number.ts: -------------------------------------------------------------------------------- 1 | const USDC_FORMATTER = Intl.NumberFormat(undefined, { 2 | minimumFractionDigits: 2, 3 | minimumIntegerDigits: 1, 4 | maximumFractionDigits: 2, 5 | }); 6 | 7 | const USDC_FORMATTER_SHORT = Intl.NumberFormat(undefined, { 8 | notation: "compact", 9 | compactDisplay: "short", 10 | minimumFractionDigits: 1, 11 | maximumFractionDigits: 1 12 | }); 13 | 14 | export function formatUsd(amount: number, options?: { 15 | leadingSymbol?: boolean 16 | short?: boolean; 17 | stripZeroCents?: boolean; 18 | }): string { 19 | const leadingSymbol: boolean = options?.leadingSymbol ?? true; 20 | const short: boolean = options?.short ?? false; 21 | const stripZeroCents: boolean = options?.stripZeroCents ?? true; 22 | 23 | const useShortFormatter: boolean = short && (amount >= 10000); 24 | 25 | let result: string; 26 | if (useShortFormatter) { 27 | result = USDC_FORMATTER_SHORT.format(amount); 28 | 29 | } else { 30 | result = USDC_FORMATTER.format(amount); 31 | } 32 | 33 | if (leadingSymbol) { 34 | result = "$" + result; 35 | } 36 | 37 | if (stripZeroCents && !useShortFormatter && Number.isInteger(amount)) { 38 | result = result.slice(0, -3); 39 | } 40 | 41 | return result; 42 | } 43 | 44 | const DATE_FORMATTER = new Intl.DateTimeFormat('en-US', { 45 | year: 'numeric', 46 | month: 'short', 47 | day: 'numeric' 48 | }); 49 | 50 | export function formatDate(unixTimestamp: number): string { 51 | const date = new Date(unixTimestamp * 1000); // Convert to milliseconds 52 | return DATE_FORMATTER.format(date); 53 | } 54 | -------------------------------------------------------------------------------- /program/tap_cash/tests/helpers/airdrop.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@project-serum/anchor"; 2 | import { web3 } from '@project-serum/anchor'; 3 | 4 | const { LAMPORTS_PER_SOL } = web3; 5 | 6 | /** 7 | * 8 | * Airdrops 100 SOL to each address in the array (for use in local environments) 9 | * 10 | * @param connection Solana connection 11 | * @param addresses array of addresses to request airdrops for 12 | * @returns array of results 13 | * @throw error if airdrop fails 14 | */ 15 | export async function requestAirdrops(connection, addresses: anchor.web3.PublicKey[]) { 16 | const promises = addresses.map(async (address) => { 17 | try { 18 | const airdropBlock = await connection.getLatestBlockhash('finalized'); 19 | const airdrop = await connection.requestAirdrop(address, 100 * LAMPORTS_PER_SOL); 20 | await connection.confirmTransaction({ 21 | signature: airdrop, 22 | blockhash: airdropBlock.blockhash, 23 | lastValidBlockHeight: airdropBlock.lastValidBlockHeight 24 | }); 25 | return { publicKey: address.toString(), status: 'success', txSignature: airdrop }; 26 | } catch (error) { 27 | console.error(`Error while requesting airdrop for ${address.toString()}: ${error}`); 28 | return { publicKey: address.toString(), status: 'error', error: error }; 29 | } 30 | }); 31 | const results = await Promise.allSettled(promises); 32 | return results.map((result, index) => { 33 | const address = addresses[index]; 34 | if (result.status === 'fulfilled') { 35 | return result.value; 36 | } else { 37 | return { publicKey: address.toString(), status: 'error', error: result.reason }; 38 | } 39 | }); 40 | } -------------------------------------------------------------------------------- /src/modules/deposit/AmountInputScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from "@react-navigation/native-stack"; 2 | import { DepositNavScreen, DepositStackRouteParams } from "../../common/navigation"; 3 | import { Screen } from "../../components/Screen"; 4 | import { Button } from "../../components/Button"; 5 | import { useCallback, useState } from "react"; 6 | import { StyleSheet } from "react-native"; 7 | import { COLORS } from "../../common/styles"; 8 | import { View } from "../../components/View"; 9 | import { Text } from "../../components/Text"; 10 | import { MAX_TX_AMOUNT } from "../../common/constants"; 11 | import { DollarInput } from "../../components/DollarInput"; 12 | 13 | type Props = NativeStackScreenProps; 14 | 15 | export function AmountInputScreen(props: Props): JSX.Element { 16 | const [amount, setAmount] = useState(); 17 | 18 | const onSubmit = useCallback(() => { 19 | props.navigation.navigate(DepositNavScreen.DEPOSITING, { amount: (amount ?? 0) }); 20 | }, [props.navigation.navigate, amount]); 21 | 22 | return ( 23 | 24 | 25 | 30 | 31 | deposit to your Tap account 32 | 33 | {/* TODO Replace dummy CC */} 34 | {`from: •••• •••• •••• 4567`} 35 | 36 |