(
35 | persist(
36 | {
37 | wallet: generic({}),
38 | accounts: [],
39 | hasWallet: computed(
40 | (state) =>
41 | Object.keys(state.wallet).length !== 0 && state.accounts.length !== 0
42 | ),
43 | addWallet: action((state, payload) => {
44 | state.wallet = {
45 | mnemonic: payload.mnemonic,
46 | seed: payload.seed,
47 | };
48 | }),
49 | addDefaultAccount: action((state, payload) => {
50 | state.accounts.push({
51 | index: 0,
52 | title: "default",
53 | derivationPath: "bip44Change",
54 | });
55 | }),
56 | addAccount: action((state, payload) => {
57 | state.accounts.push({
58 | index: payload.index,
59 | title: payload.title,
60 | derivationPath: "bip44Change",
61 | });
62 | }),
63 | },
64 | {
65 | storage: storage,
66 | }
67 | )
68 | );
69 |
70 | export default store;
71 |
--------------------------------------------------------------------------------
/components/Block.tsx:
--------------------------------------------------------------------------------
1 | import {StyleSheet, View} from 'react-native';
2 |
3 |
4 | import tw from '../lib/tailwind';
5 | import {useDeviceContext} from 'twrnc';
6 |
7 | type Props = {
8 | bg?: string;
9 | children: React.ReactNode;
10 | justify?: true;
11 | noPaddingBottom?: true;
12 | noPaddingLeft?: true;
13 | noPaddingRight?: true;
14 | noPaddingTop?: true;
15 | roundedTop?: boolean;
16 | row?: true;
17 | };
18 |
19 | const Block = ({
20 | bg,
21 | children,
22 | justify,
23 | noPaddingBottom,
24 | noPaddingLeft,
25 | noPaddingRight,
26 | noPaddingTop,
27 | roundedTop,
28 | row,
29 | }: Props) => {
30 | useDeviceContext(tw);
31 |
32 | return (
33 |
46 | {children}
47 |
48 | );
49 | };
50 |
51 | const styles = StyleSheet.create({
52 | container: {
53 | ...tw`p-4`,
54 | },
55 | row: {
56 | ...tw`flex-row`,
57 | },
58 | justify: {
59 | ...tw`justify-between items-center`,
60 | },
61 | noPt: {
62 | ...tw`pt-0`,
63 | },
64 | noPr: {
65 | ...tw`pr-0`,
66 | },
67 | noPl: {
68 | ...tw`pl-0`,
69 | },
70 | noPb: {
71 | ...tw`pb-0`,
72 | },
73 | roundedTop: {
74 | backgroundColor: 'white',
75 | marginTop: 20,
76 | borderTopLeftRadius: 30,
77 | borderTopRightRadius: 30,
78 | minHeight: 800,
79 | }
80 | });
81 |
82 | export default Block;
83 |
--------------------------------------------------------------------------------
/utils/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-case-declarations */
2 | import * as solanaWeb3 from "@solana/web3.js";
3 | import * as Random from "expo-random";
4 | import { ethers } from "ethers";
5 | import { Buffer } from "buffer";
6 | import * as ed25519 from "ed25519-hd-key";
7 | import nacl from "tweetnacl";
8 |
9 | export const DERIVATION_PATH = {
10 | bip44Change: "bip44Change",
11 | };
12 |
13 | const generateMnemonic = async () => {
14 | const randomBytes = await Random.getRandomBytesAsync(32);
15 | const mnemonic = ethers.utils.entropyToMnemonic(randomBytes);
16 | return mnemonic;
17 | };
18 |
19 | const mnemonicToSeed = async (mnemonic: string) => {
20 | const bip39 = await import("bip39");
21 | const seed = await bip39.mnemonicToSeed(mnemonic);
22 | return Buffer.from(seed).toString("hex");
23 | };
24 |
25 | const accountFromSeed = (
26 | seed: string,
27 | walletIndex: number,
28 | derivationPath: string,
29 | accountIndex: 0
30 | ) => {
31 | const derivedSeed = deriveSeed(
32 | seed,
33 | walletIndex,
34 | derivationPath,
35 | accountIndex
36 | );
37 | const keyPair = nacl.sign.keyPair.fromSeed(derivedSeed);
38 |
39 | const acc = new solanaWeb3.Keypair(keyPair);
40 | return acc;
41 | };
42 |
43 | const maskedAddress = (address: string) => {
44 | if (!address) return;
45 | return `${address.slice(0, 4)}...${address.slice(address.length - 4)}`;
46 | };
47 |
48 | const deriveSeed = (
49 | seed: string,
50 | walletIndex: number,
51 | derivationPath: string,
52 | accountIndex: number
53 | ): Buffer | undefined => {
54 | const path44Change = `m/44'/501'/${walletIndex}'/0'`;
55 | return ed25519.derivePath(path44Change, Buffer.from(seed, "hex")).key;
56 | };
57 |
58 | export {
59 | generateMnemonic,
60 | mnemonicToSeed,
61 | accountFromSeed,
62 | maskedAddress,
63 | deriveSeed,
64 | };
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wallet",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web",
10 | "eject": "expo eject",
11 | "test": "jest --watchAll"
12 | },
13 | "jest": {
14 | "preset": "jest-expo"
15 | },
16 | "dependencies": {
17 | "@expo/vector-icons": "^12.0.0",
18 | "@react-native-async-storage/async-storage": "^1.16.1",
19 | "@react-navigation/bottom-tabs": "^6.0.5",
20 | "@react-navigation/native": "^6.0.2",
21 | "@react-navigation/native-stack": "^6.1.0",
22 | "@solana/web3.js": "1.21.0",
23 | "buffer": "^6.0.3",
24 | "easy-peasy": "^5.0.4",
25 | "ed25519-hd-key": "^1.2.0",
26 | "ethers": "^5.5.4",
27 | "events": "^3.3.0",
28 | "expo": "~44.0.0",
29 | "expo-asset": "~8.4.4",
30 | "expo-barcode-scanner": "~11.2.0",
31 | "expo-constants": "~13.0.0",
32 | "expo-font": "~10.0.4",
33 | "expo-linear-gradient": "~11.0.3",
34 | "expo-linking": "~3.0.0",
35 | "expo-random": "~12.1.1",
36 | "expo-splash-screen": "~0.14.0",
37 | "expo-status-bar": "~1.2.0",
38 | "expo-updates": "~0.11.6",
39 | "expo-web-browser": "~10.1.0",
40 | "process": "^0.11.10",
41 | "react": "17.0.1",
42 | "react-dom": "17.0.1",
43 | "react-native": "0.64.3",
44 | "react-native-get-random-values": "~1.7.0",
45 | "react-native-qrcode-svg": "^6.1.2",
46 | "react-native-safe-area-context": "3.3.2",
47 | "react-native-screens": "~3.10.1",
48 | "react-native-svg": "^12.3.0",
49 | "react-native-url-polyfill": "^1.3.0",
50 | "react-native-web": "0.17.1",
51 | "readable-stream": "^3.6.0",
52 | "tweetnacl": "^1.0.3",
53 | "twrnc": "^3.0.2",
54 | "uuid": "3.4.0"
55 | },
56 | "devDependencies": {
57 | "@babel/core": "^7.12.9",
58 | "@types/react": "~17.0.21",
59 | "@types/react-native": "~0.64.12",
60 | "jest": "^26.6.3",
61 | "jest-expo": "~44.0.1",
62 | "react-test-renderer": "17.0.1",
63 | "typescript": "~4.3.5"
64 | },
65 | "private": true
66 | }
67 |
--------------------------------------------------------------------------------
/screens/ActivityScreen.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Block,
3 | Container,
4 | Header,
5 | List,
6 | ListItem,
7 | Gradient,
8 | Text,
9 | Wrapper
10 | } from '../components';
11 |
12 | import {RootTabScreenProps} from '../types';
13 |
14 | import {C} from "../common"
15 |
16 | export default function ActivityScreen({navigation}: RootTabScreenProps<'Activity'>) {
17 | const {THEME} = C;
18 |
19 | const activity = [
20 | {
21 | id: 1,
22 | icon: 'arrow-collapse-down',
23 | title: 'Received 100 PIP',
24 | subtitle: '',
25 | when: '29d'
26 | },
27 | {
28 | id: 2,
29 | icon: 'arrow-collapse-down',
30 | title: 'Received 2.10 SOL',
31 | subtitle: '',
32 | when: '1mo'
33 | },
34 | {
35 | id: 3,
36 | icon: 'send',
37 | title: 'Sent 500 USDC',
38 | subtitle: '',
39 | when: '1mo'
40 | },
41 | {
42 | id: 4,
43 | icon: 'check-bold',
44 | title: 'vBWp...afyn',
45 | subtitle: 'Success',
46 | when: '3mo'
47 | },
48 | ];
49 |
50 | return (
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
63 | Recent Activity
64 |
65 |
66 |
67 |
68 |
69 | {activity.map(item => (
70 | console.log('Pressed!')}
76 | amount={item.when}
77 | />
78 | ))}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ◎ solacademy.xyz
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | A Expo, React Native & Solana Web3 Boilerplate
11 |
12 | This is a boilerplate ready to start building a Cross-Platform Wallet App with Expo, React Native and `solana/web3`.
13 |
14 | ## What does it include?
15 |
16 | - [Expo](https://expo.dev/)
17 | - [React Native](https://reactnative.dev/)
18 | - [@solana/web3.js](https://solana-labs.github.io/solana-web3.js/)
19 | - [React Navigation](https://reactnavigation.org/)
20 | - [Easy Peasy](https://easy-peasy.vercel.app/)
21 | - [Tailwind CSS](https://tailwindcss.com/)
22 |
23 | ## Requirements
24 |
25 | In order to run this project, install:
26 |
27 | - [NodeJS](https://nodejs.org/)
28 | - [Expo](https://expo.dev/)
29 |
30 |
31 | ## How to run the project?
32 |
33 | Clone this repository:
34 |
35 | ```
36 | git clone https://github.com/jferrer/solacademy-wallet
37 | ```
38 |
39 | Enter the project:
40 |
41 | ```
42 | cd solacademy-wallet
43 | ```
44 |
45 | Install dependencies:
46 |
47 | ```
48 | yarn install
49 | ```
50 |
51 | Run on web:
52 |
53 | ```
54 | expo web
55 | ```
56 |
57 | Run on mobile (through Expo):
58 |
59 | ```
60 | expo start
61 | ```
62 |
63 | ## How to run the project on Android:
64 |
65 | Download [Expo Go](https://play.google.com/store/apps/details?id=host.exp.exponent&referrer=www) and can the QR code [here](https://expo.dev/@moviendome/solacademy-wallet).
66 |
67 | ## How to run the project on iOS:
68 |
69 | Download [Expo Go](https://apps.apple.com/app/apple-store/id982107779) and visit this [link](exp://exp.host/@moviendome/solacademy-wallet?release-channel=default) on your device.
70 |
71 | ## Learn how to build a Cross-Platform Solana Wallet App
72 |
73 | Visit [Solacademy](https://www.solacademy.xyz)
74 |
75 | ## Donate
76 |
77 | Consider donating some `SOL` or `SPL Tokens` if you find useful and want support this project.
78 |
79 |
80 |
81 |
82 |
83 |
84 | Eg2SAgUB5VsNdcSMggLpyrvqcBqJBwmBUNC6e4XC7LPv
85 |
86 |
--------------------------------------------------------------------------------
/screens/OnboardingScreen.tsx:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native';
2 |
3 | import {
4 | Block,
5 | Button,
6 | Column,
7 | Container,
8 | Header,
9 | Gradient,
10 | Text,
11 | Wrapper
12 | } from '../components';
13 |
14 | import { C } from "../common"
15 |
16 | import { useStoreActions } from "../hooks/storeHooks";
17 |
18 | import { RootTabScreenProps } from '../types';
19 |
20 | import { generateMnemonic, mnemonicToSeed, accountFromSeed } from "../utils";
21 |
22 | export default function DashboardScreen({ navigation }: RootTabScreenProps<'Onboarding'>) {
23 | const { THEME } = C;
24 |
25 | const addWallet = useStoreActions((actions) => actions.addWallet);
26 |
27 | const addDefaultAccount = useStoreActions(
28 | (actions) => actions.addDefaultAccount
29 | );
30 |
31 | const createWallet = async () => {
32 | const mnemonic = await generateMnemonic();
33 | const seed = await mnemonicToSeed(mnemonic);
34 |
35 | addWallet({
36 | mnemonic: mnemonic,
37 | seed: seed,
38 | });
39 |
40 | addDefaultAccount();
41 | }
42 |
43 | return (
44 | <>
45 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Create Wallet
61 |
62 |
63 |
64 |
65 |
66 |
67 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius sapien mauris, a eleifend dolor dictum in. Etiam fermentum vel mi ac posuere.
68 |
69 |
70 |
71 |
72 |
73 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius sapien mauris, a eleifend dolor dictum in. Etiam fermentum vel mi ac posuere.
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | >
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/components/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import {StyleSheet, TouchableOpacity, View} from 'react-native';
2 |
3 | import {Text} from '../components';
4 |
5 | import {MaterialCommunityIcons} from '@expo/vector-icons';
6 |
7 | import tw from '../lib/tailwind';
8 | import {useDeviceContext} from 'twrnc';
9 |
10 | const ListItem = ({
11 | last,
12 | onPress,
13 | left,
14 | right,
15 | icon,
16 | iconColor,
17 | title,
18 | subtitle,
19 | amount,
20 | }: {
21 | last?: true;
22 | onPress(): void;
23 | left?: object;
24 | right?: object;
25 | title: string;
26 | subtitle: string;
27 | icon?: string;
28 | iconColor?: string;
29 | amount: string;
30 | }) => {
31 | useDeviceContext(tw);
32 |
33 | return (
34 |
35 |
36 |
37 | {icon && (
38 |
39 |
40 |
41 |
42 |
43 | )}
44 | {left && (
45 |
46 | {left}
47 |
48 | )}
49 |
50 | {title}
51 | {subtitle}
52 |
53 | {amount && (
54 |
55 | {amount}
56 |
57 | )}
58 | {right && (
59 |
60 | {right}
61 |
62 | )}
63 |
64 |
65 |
66 | )
67 | };
68 |
69 | const styles = StyleSheet.create({
70 | container: {
71 | ...tw`w-full flex-row items-center border-b border-gray-200 pb-2 mb-2 justify-between`,
72 | height: 60,
73 | },
74 | last: {
75 | ...tw`border-white pb-0 mb-0`,
76 | },
77 | leftContainer: {
78 | ...tw`w-1/5 justify-center`,
79 | },
80 | iconCircle: {
81 | ...tw`rounded-full h-8 w-8 items-center justify-center bg-gray-100`,
82 | },
83 | content3: {
84 | ...tw`w-2.5/5`,
85 | },
86 | content4: {
87 | ...tw`w-4/5`,
88 | },
89 | content5: {
90 | ...tw`w-5/5`,
91 | },
92 | right: {
93 | ...tw`w-1.5/5 items-end`,
94 | },
95 | amount: {
96 | ...tw`text-sm font-bold`,
97 | },
98 |
99 | row: {
100 | ...tw`flex-1 flex-row items-center`
101 | },
102 | rowLeft: {
103 | width: 40,
104 | },
105 | rowContent: {
106 | ...tw`flex-1`,
107 | },
108 | rowRight: {
109 | ...tw`items-end`,
110 | width: 80,
111 | }
112 | });
113 |
114 | export default ListItem;
115 |
--------------------------------------------------------------------------------
/screens/SwapScreen.tsx:
--------------------------------------------------------------------------------
1 | import {Image} from 'react-native';
2 |
3 | import {
4 | Block,
5 | Button,
6 | Column,
7 | Container,
8 | Header,
9 | Gradient,
10 | Separator,
11 | Text,
12 | TextInput,
13 | Wrapper
14 | } from '../components';
15 |
16 | import {C} from '../common';
17 |
18 | import {RootTabScreenProps} from '../types';
19 |
20 | export default function SwapScreen({navigation}: RootTabScreenProps<'Swap'>) {
21 | const {THEME} = C;
22 |
23 | const assets = [
24 | {
25 | id: 1,
26 | name: 'Solana',
27 | symbol: 'SOL',
28 | decimals: 9,
29 | logoURI: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png',
30 | },
31 | {
32 | id: 2,
33 | name: 'USDC Coin',
34 | symbol: 'USDC',
35 | decimals: 6,
36 | logoURI: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png',
37 | },
38 | ];
39 |
40 | return (
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Swap From
55 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
69 | {assets[0].symbol}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Swap to
78 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
92 | {assets[1].symbol}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ActivityIndicator,
3 | StyleSheet,
4 | Text,
5 | TouchableHighlight,
6 | View
7 | } from 'react-native';
8 |
9 | import {MaterialCommunityIcons} from '@expo/vector-icons';
10 |
11 | import tw from '../lib/tailwind';
12 | import {useDeviceContext} from 'twrnc';
13 |
14 | const Button = ({
15 | mode = 'contained',
16 | color,
17 | children,
18 | onPress,
19 | iconLeft,
20 | iconRight,
21 | loading,
22 | }: {
23 | mode?: 'text' | 'outlined' | 'contained';
24 | color: string;
25 | children: React.ReactNode;
26 | onPress(): void;
27 | iconLeft?: string;
28 | iconRight?: string;
29 | loading?: boolean;
30 | }) => {
31 | useDeviceContext(tw);
32 |
33 | const _mode = () => {
34 | switch (mode) {
35 | case 'text': {
36 | return [styles.textContainer];
37 | }
38 | case 'outlined': {
39 | if (color) return [styles.outlinedContainer, {borderColor: color}];
40 | return styles.outlinedContainer;
41 | }
42 | default: {
43 | if (color)
44 | return [styles.containedContainer, {backgroundColor: color}];
45 | return styles.containedContainer;
46 | }
47 | }
48 | };
49 |
50 | const _text = () => {
51 | switch (mode) {
52 | case 'text': {
53 | return styles.text;
54 | }
55 | case 'outlined': {
56 | return styles.text;
57 | }
58 | default: {
59 | return styles.textContained;
60 | }
61 | }
62 | };
63 |
64 | const _icon = () => {
65 | switch (mode) {
66 | case 'text': {
67 | return 'black';
68 | }
69 | case 'outlined': {
70 | return 'black';
71 | }
72 | default: {
73 | return 'white';
74 | }
75 | }
76 | };
77 |
78 | return (
79 |
80 |
81 | {loading && (
82 |
83 |
84 |
85 | )}
86 | {iconLeft && (
87 |
88 |
89 |
90 | )}
91 | {!loading && (
92 | {children}
93 | )}
94 | {iconRight && (
95 |
96 |
97 |
98 | )}
99 |
100 |
101 | );
102 | };
103 |
104 | const styles = StyleSheet.create({
105 | row: {
106 | ...tw`flex-row items-center`,
107 | },
108 | iconLeft: {
109 | ...tw`pr-2`,
110 | },
111 | iconRight: {
112 | ...tw`pl-2`,
113 | },
114 | textContainer: {
115 | ...tw`px-4 py-3 items-center justify-center`,
116 | minWidth: 50,
117 | height: 50,
118 | },
119 | containedContainer: {
120 | minWidth: 50,
121 | height: 50,
122 | ...tw`rounded-lg px-4 py-3 items-center justify-center`,
123 | },
124 | outlinedContainer: {
125 | minWidth: 50,
126 | height: 50,
127 | ...tw`rounded-lg border px-4 py-3 items-center justify-center`,
128 | },
129 | text: {
130 | ...tw`text-body font-bold text-center uppercase text-primary`,
131 | },
132 | textContained: {
133 | ...tw`text-white text-body font-bold text-center uppercase`,
134 | },
135 | });
136 |
137 | export default Button;
138 |
--------------------------------------------------------------------------------
/navigation/index.tsx:
--------------------------------------------------------------------------------
1 | import {FontAwesome} from '@expo/vector-icons';
2 | import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
3 | import {NavigationContainer} from '@react-navigation/native';
4 | import {createNativeStackNavigator} from '@react-navigation/native-stack';
5 |
6 | import {C} from '../common';
7 |
8 | import {
9 | OnboardingScreen,
10 | DashboardScreen,
11 | CollectiblesScreen,
12 | SwapScreen,
13 | ActivityScreen,
14 | SettingsScreen,
15 | ModalScreen,
16 | NotFoundScreen,
17 | } from '../screens'
18 |
19 | import {RootStackParamList, RootTabParamList, RootTabScreenProps} from '../types';
20 | import LinkingConfiguration from './LinkingConfiguration';
21 |
22 | import { useStoreState } from "../hooks/storeHooks";
23 |
24 | const {THEME} = C;
25 |
26 | export default function Navigation() {
27 | return (
28 |
31 |
32 |
33 | );
34 | }
35 |
36 | const Stack = createNativeStackNavigator();
37 |
38 | function RootNavigator() {
39 | const hasWallet = useStoreState((state) => state.hasWallet);
40 |
41 | if (hasWallet) {
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | } else {
52 | return (
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | const BottomTab = createBottomTabNavigator();
61 |
62 | function BottomTabNavigator() {
63 | return (
64 |
70 | ,
76 | }}
77 | />
78 | ,
84 | }}
85 | />
86 | ,
92 | }}
93 | />
94 | ,
100 | }}
101 | />
102 | ,
108 | }}
109 | />
110 |
111 | );
112 | }
113 |
114 | function TabBarIcon(props: {
115 | name: React.ComponentProps['name'];
116 | color: string;
117 | }) {
118 | return ;
119 | }
120 |
--------------------------------------------------------------------------------
/components/Text.tsx:
--------------------------------------------------------------------------------
1 | import {Text as T, StyleSheet} from 'react-native';
2 |
3 | import tw from '../lib/tailwind';
4 | import {useDeviceContext} from 'twrnc';
5 |
6 | type Props = React.ComponentProps & {
7 | children: React.ReactNode;
8 | size?:
9 | | 'xs'
10 | | 'sm'
11 | | 'tiny'
12 | | 'lg'
13 | | 'xl'
14 | | '2xl'
15 | | '3xl'
16 | | '4xl'
17 | | '5xl'
18 | | '6xl'
19 | | '7xl';
20 | weight?:
21 | | 'thin'
22 | | 'extralight'
23 | | 'light'
24 | | 'medium'
25 | | 'semibold'
26 | | 'bold'
27 | | 'extrabold'
28 | | 'black';
29 | uppercase?: true;
30 | color?: string;
31 | };
32 |
33 | const Text = ({children, size, weight, uppercase, color, ...rest}: Props) => {
34 | useDeviceContext(tw);
35 |
36 | const _size = () => {
37 | switch (size) {
38 | case 'xs': {
39 | return styles.xs;
40 | }
41 | case 'sm': {
42 | return styles.sm;
43 | }
44 | case 'tiny': {
45 | return styles.tiny;
46 | }
47 | case 'lg': {
48 | return styles.lg;
49 | }
50 | case 'xl': {
51 | return styles.xl;
52 | }
53 | case '2xl': {
54 | return styles.s2xl;
55 | }
56 | case '3xl': {
57 | return styles.s3xl;
58 | }
59 | case '4xl': {
60 | return styles.s4xl;
61 | }
62 | case '5xl': {
63 | return styles.s5xl;
64 | }
65 | case '6xl': {
66 | return styles.s6xl;
67 | }
68 | case '7xl': {
69 | return styles.s7xl;
70 | }
71 | }
72 | };
73 |
74 | const _weight = () => {
75 | switch (weight) {
76 | case 'thin': {
77 | return styles.thin;
78 | }
79 | case 'extralight': {
80 | return styles.extralight;
81 | }
82 | case 'light': {
83 | return styles.light;
84 | }
85 | case 'medium': {
86 | return styles.medium;
87 | }
88 | case 'semibold': {
89 | return styles.semibold;
90 | }
91 | case 'bold': {
92 | return styles.bold;
93 | }
94 | case 'extrabold': {
95 | return styles.extrabold;
96 | }
97 | case 'black': {
98 | return styles.black;
99 | }
100 | }
101 | };
102 |
103 | const _uppercase = () => {
104 | return styles.uppercase;
105 | }
106 |
107 | return (
108 |
117 | {children}
118 |
119 | );
120 | };
121 |
122 | const styles = StyleSheet.create({
123 | xs: {
124 | ...tw`text-xs`,
125 | },
126 | sm: {
127 | ...tw`text-sm`,
128 | },
129 | tiny: {
130 | ...tw`text-tiny`,
131 | },
132 | lg: {
133 | ...tw`text-lg`,
134 | },
135 | xl: {
136 | ...tw`text-xl`,
137 | },
138 | s2xl: {
139 | ...tw`text-2xl`,
140 | },
141 | s3xl: {
142 | ...tw`text-3xl`,
143 | },
144 | s4xl: {
145 | ...tw`text-4xl`,
146 | },
147 | s5xl: {
148 | ...tw`text-5xl`,
149 | },
150 | s6xl: {
151 | ...tw`text-6xl`,
152 | },
153 | s7xl: {
154 | ...tw`text-7xl`,
155 | },
156 | body: {
157 | ...tw`text-base`,
158 | },
159 | thin: {
160 | ...tw`font-thin`,
161 | },
162 | extralight: {
163 | ...tw`font-extralight`,
164 | },
165 | light: {
166 | ...tw`font-light`,
167 | },
168 | medium: {
169 | ...tw`font-medium`,
170 | },
171 | normal: {
172 | ...tw`font-normal`,
173 | },
174 | semibold: {
175 | ...tw`font-semibold`,
176 | },
177 | bold: {
178 | ...tw`font-bold`,
179 | },
180 | extrabold: {
181 | ...tw`font-extrabold`,
182 | },
183 | black: {
184 | ...tw`font-black`,
185 | },
186 | uppercase: {
187 | ...tw`uppercase`,
188 | },
189 | });
190 |
191 | export default Text;
192 |
--------------------------------------------------------------------------------
/screens/CollectiblesScreen.tsx:
--------------------------------------------------------------------------------
1 | import {ScrollView, StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
2 | import {MaterialCommunityIcons} from '@expo/vector-icons';
3 |
4 | import tw from '../lib/tailwind';
5 |
6 | import {
7 | Block,
8 | Container,
9 | Header,
10 | Gradient,
11 | Text,
12 | Wrapper
13 | } from '../components';
14 |
15 | import {RootTabScreenProps} from '../types';
16 |
17 | import {C} from '../common';
18 |
19 | const data = [
20 | {
21 | id: '1',
22 | name: 'One',
23 | // image: require('../assets/images/')
24 | },
25 | {
26 | id: '2',
27 | name: 'Two',
28 | // image: require('../assets/images/')
29 | },
30 | {
31 | id: '3',
32 | name: 'Three',
33 | // image: require('../assets/images/')
34 | },
35 | {
36 | id: '4',
37 | name: 'Four',
38 | // image: require('../assets/images/')
39 | },
40 | {
41 | id: '5',
42 | name: 'Five',
43 | // image: require('../assets/images/')
44 | },
45 | {
46 | id: '6',
47 | name: 'Six',
48 | // image: require('../assets/images/')
49 | },
50 | {
51 | id: '7',
52 | name: 'Seven',
53 | // image: require('../assets/images/')
54 | },
55 | {
56 | id: '8',
57 | name: 'Eight',
58 | // image: require('../assets/images/')
59 | },
60 | {
61 | id: '9',
62 | name: 'Nine',
63 | // image: require('../assets/images/')
64 | },
65 | {
66 | id: '10',
67 | name: 'Ten',
68 | // image: require('../assets/images/')
69 | },
70 | {
71 | id: '11',
72 | name: 'Eleven',
73 | // image: require('../assets/images/')
74 | },
75 | {
76 | id: '12',
77 | name: 'Twelve',
78 | // image: require('../assets/images/')
79 | },
80 | ]
81 |
82 | export default function CollectiblesScreen({navigation}: RootTabScreenProps<'Collectibles'>) {
83 | const {THEME} = C;
84 |
85 | const AddIcon = () => {
86 | return (
87 | console.log('Pressed!')}>
88 |
89 |
90 | );
91 | };
92 |
93 | const RenderContent = ({children}: any) => {
94 | if (C.IS_MOBILE) {
95 | return (
96 | {children}
97 | )
98 | }
99 |
100 | return children
101 | }
102 |
103 | const renderRow = () => {
104 | const elementsInRow = C.IS_MOBILE ? 2 : 4
105 |
106 | const chunk = (arr: object[], size: number) =>
107 | Array.from({length: Math.ceil(arr.length / size)}, (v, i) =>
108 | arr.slice(i * size, i * size + size)
109 | );
110 |
111 | const rows = chunk(data, elementsInRow)
112 |
113 | return rows.map((row, index) => {
114 | return
115 | {
116 | row.map(item => (
117 |
118 | { /*
119 |
123 | */ }
124 |
125 | ))
126 | }
127 |
128 | })
129 | }
130 |
131 | return (
132 |
133 |
134 |
135 |
136 | }
139 | />
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {data ? renderRow() : (Loading....)}
148 |
149 |
150 |
151 |
152 |
153 |
154 | );
155 | }
156 |
157 | const styles = StyleSheet.create({
158 | content: {
159 | flex: 1,
160 | alignItems: 'center',
161 | margin: 20,
162 | },
163 | row: {
164 | width: '100%',
165 | maxWidth: C.IS_MOBILE ? 320 : 860,
166 | ...tw`flex-row`
167 | },
168 | card: {
169 | width: C.IS_MOBILE ? 150 : 174,
170 | height: C.IS_MOBILE ? 150 : 174,
171 | marginRight: 20,
172 | marginBottom: 20,
173 | backgroundColor: '#CCCCCC',
174 | justifyContent: 'center',
175 | alignItems: 'center'
176 | },
177 | })
178 |
--------------------------------------------------------------------------------
/screens/DashboardScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useCallback, useState } from 'react';
2 | import { Image } from 'react-native';
3 |
4 | import {
5 | Block,
6 | Button,
7 | Column,
8 | Container,
9 | Header,
10 | List,
11 | ListItem,
12 | Gradient,
13 | Text,
14 | Wrapper
15 | } from '../components';
16 |
17 | import { C } from '../common';
18 |
19 | import { RootTabScreenProps } from '../types';
20 |
21 | import { useStoreState } from '../hooks/storeHooks';
22 | import { accountFromSeed, maskedAddress } from '../utils';
23 |
24 | import {
25 | getBalance,
26 | getSolanaPrice,
27 | requestAirdrop,
28 | } from '../api';
29 | import { useFocusEffect } from '@react-navigation/native';
30 |
31 | export default function DashboardScreen({ navigation }: RootTabScreenProps<'Dashboard'>) {
32 | const { THEME } = C;
33 |
34 | const wallet = useStoreState((state) => state.wallet);
35 | const accounts = useStoreState((state) => state.accounts);
36 |
37 | const [account, setAccount] = useState({});
38 |
39 | useEffect(() => {
40 | async function generate() {
41 | const currentAccount = accounts[0];
42 | setAccount({
43 | index: currentAccount.index,
44 | title: currentAccount.title,
45 | keyPair: accountFromSeed(
46 | wallet.seed,
47 | currentAccount.index,
48 | currentAccount.derivationPath,
49 | 0
50 | ),
51 | });
52 | }
53 |
54 | generate();
55 | }, []);
56 |
57 | const [balance, setBalance] = useState({
58 | usd: 0.0,
59 | sol: 0
60 | });
61 |
62 | useFocusEffect(
63 | useCallback(() => {
64 | async function getAsyncBalance() {
65 | if (account?.keyPair?.publicKey?.toString()) {
66 | const sol = await getBalance(account.keyPair.publicKey.toString());
67 | const usdPrice = await getSolanaPrice();
68 |
69 | setBalance({
70 | sol,
71 | usd: (sol * usdPrice).toFixed(2),
72 | });
73 | }
74 | }
75 | getAsyncBalance();
76 | }, [account])
77 | );
78 |
79 | const assets = [
80 | {
81 | id: 1,
82 | name: "Solana",
83 | symbol: "SOL",
84 | decimals: 9,
85 | logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png",
86 | },
87 | {
88 | id: 2,
89 | name: "USDC Coin",
90 | symbol: "USDC",
91 | decimals: 6,
92 | logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png",
93 | },
94 | {
95 | id: 3,
96 | name: "Samoyed Coin",
97 | symbol: "SAMO",
98 | decimals: 9,
99 | logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/logo.png",
100 | },
101 | ];
102 |
103 | const getAirdrop = async () => {
104 | const address = account.keyPair.publicKey.toString();
105 |
106 | console.log("Requesting Airdrop...")
107 |
108 | await requestAirdrop(address);
109 | const sol = await getBalance(address);
110 | const usdPrice = await getSolanaPrice();
111 |
112 | console.log("Setting updated balance...")
113 |
114 | setBalance({
115 | sol,
116 | usd: (sol * usdPrice).toFixed(2),
117 | });
118 | }
119 |
120 | return (
121 |
122 |
123 |
124 |
125 |
129 |
130 |
131 |
132 |
133 |
134 |
140 |
141 |
142 |
148 |
149 |
150 |
151 |
152 |
153 | Assets
154 |
155 |
156 |
157 |
158 |
159 |
168 | }
169 | title={assets[0].name}
170 | subtitle={`${balance?.sol} ${assets[0].symbol}`}
171 | onPress={() => console.log('Pressed!')}
172 | amount={balance?.usd ? `$${balance.usd}` : '-'}
173 | />
174 |
175 |
176 |
177 |
178 |
179 |
180 | );
181 | }
182 |
--------------------------------------------------------------------------------