create-lightning-scaffold 1.0.2 β 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -1
- package/package.json +1 -1
- package/templates/base/.env.example.ejs +5 -0
- package/templates/mobile/components/History.tsx.ejs +57 -52
- package/templates/mobile/components/Recovery.tsx.ejs +78 -67
- package/templates/mobile/components/Swap.tsx.ejs +58 -152
- package/templates/state/redux/index.ts +2 -7
- package/templates/state/zustand/index.ts +0 -5
- package/templates/vite/src/components/History.tsx.ejs +16 -16
- package/templates/vite/src/components/Recovery.tsx.ejs +24 -18
- package/templates/vite/src/components/Swap.tsx.ejs +52 -91
- package/templates/web/components/History.tsx.ejs +17 -17
- package/templates/web/components/Recovery.tsx.ejs +27 -19
- package/templates/web/components/Swap.tsx.ejs +58 -134
package/README.md
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# create-lightning-scaffold
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> β‘ **5 minutes from `npx` to first gasless transaction**
|
|
3
6
|
|
|
4
7
|
CLI to scaffold Solana apps with LazorKit SDK. Generate React Native (Expo) or Next.js projects with passkey authentication, gasless transactions, and a ready-to-use swap interface.
|
|
5
8
|
|
|
9
|
+
## π Live Demo
|
|
10
|
+
|
|
11
|
+
**[Try it live on Devnet β](https://lighting-demo.vercel.app)**
|
|
12
|
+
|
|
13
|
+
Experience passkey authentication and gasless transactions without installing anything.
|
|
14
|
+
|
|
6
15
|
## Quick Start
|
|
7
16
|
|
|
8
17
|
```bash
|
|
@@ -142,6 +151,35 @@ const { swapTransaction } = await fetch('https://api.jup.ag/swap/v1/swap', {
|
|
|
142
151
|
}).then(r => r.json());
|
|
143
152
|
```
|
|
144
153
|
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
βββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
158
|
+
β User ββββββΆβ LazorKit Portal ββββββΆβ Smart Wallet β
|
|
159
|
+
β (Passkey) β β (Auth + Keys) β β (PDA on-chain) β
|
|
160
|
+
βββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
|
|
161
|
+
β
|
|
162
|
+
βΌ
|
|
163
|
+
ββββββββββββββββββββ
|
|
164
|
+
β Paymaster β
|
|
165
|
+
β (Pays gas fees) β
|
|
166
|
+
ββββββββββββββββββββ
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Troubleshooting
|
|
170
|
+
|
|
171
|
+
| Issue | Solution |
|
|
172
|
+
|-------|----------|
|
|
173
|
+
| Passkey not working | Ensure HTTPS (localhost OK for dev), check browser supports WebAuthn |
|
|
174
|
+
| Transaction failing | Verify wallet has balance, check RPC endpoint, confirm paymaster config |
|
|
175
|
+
| Mobile redirect issues | Ensure deep link scheme matches `app.json` config |
|
|
176
|
+
|
|
177
|
+
## More Resources
|
|
178
|
+
|
|
179
|
+
- **[SNIPPETS.md](./SNIPPETS.md)** - Copy-paste code examples
|
|
180
|
+
- **[TUTORIALS.md](./TUTORIALS.md)** - Step-by-step integration guides
|
|
181
|
+
- **[Twitter Thread](https://x.com/Tobi_Builder/status/2011043242251293087)** - Passkey wallet tutorial
|
|
182
|
+
|
|
145
183
|
## License
|
|
146
184
|
|
|
147
185
|
MIT
|
package/package.json
CHANGED
|
@@ -8,6 +8,11 @@ EXPO_PUBLIC_LAZORKIT_PAYMASTER_URL=https://kora.devnet.lazorkit.com
|
|
|
8
8
|
NEXT_PUBLIC_SOLANA_RPC=https://api.devnet.solana.com
|
|
9
9
|
EXPO_PUBLIC_SOLANA_RPC=https://api.devnet.solana.com
|
|
10
10
|
VITE_SOLANA_RPC=https://api.devnet.solana.com
|
|
11
|
+
|
|
12
|
+
# Jupiter API (get free key at https://portal.jup.ag)
|
|
13
|
+
NEXT_PUBLIC_JUPITER_API_KEY=
|
|
14
|
+
EXPO_PUBLIC_JUPITER_API_KEY=
|
|
15
|
+
VITE_JUPITER_API_KEY=
|
|
11
16
|
<% if (backend === 'supabase') { %>
|
|
12
17
|
# Supabase
|
|
13
18
|
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
@@ -6,18 +6,19 @@ import * as Linking from 'expo-linking';
|
|
|
6
6
|
<% if (styling !== 'nativewind') { %>
|
|
7
7
|
const styles = StyleSheet.create({
|
|
8
8
|
container: { flex: 1, backgroundColor: '#fff', padding: 24, paddingTop: 60 },
|
|
9
|
+
card: { backgroundColor: '#0a0a0a', borderRadius: 16, padding: 20 },
|
|
9
10
|
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 },
|
|
10
|
-
title: { fontSize: 18, fontWeight: '600', color: '#
|
|
11
|
+
title: { fontSize: 18, fontWeight: '600', color: '#fafafa' },
|
|
11
12
|
back: { fontSize: 13, color: '#737373' },
|
|
12
13
|
list: { gap: 8 },
|
|
13
|
-
item: { borderWidth: 1, borderColor: '#
|
|
14
|
+
item: { borderWidth: 1, borderColor: '#262626', borderRadius: 12, padding: 12, backgroundColor: '#171717' },
|
|
14
15
|
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
|
15
|
-
sig: { fontFamily: 'monospace', fontSize: 13, color: '#
|
|
16
|
-
time: { fontSize: 12, color: '#
|
|
16
|
+
sig: { fontFamily: 'monospace', fontSize: 13, color: '#fafafa' },
|
|
17
|
+
time: { fontSize: 12, color: '#737373', marginTop: 4 },
|
|
17
18
|
status: { fontSize: 11, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, overflow: 'hidden' },
|
|
18
|
-
success: { backgroundColor: '#
|
|
19
|
-
failed: { backgroundColor: '#
|
|
20
|
-
empty: { textAlign: 'center', paddingVertical: 32, color: '#
|
|
19
|
+
success: { backgroundColor: '#14532d', color: '#4ade80' },
|
|
20
|
+
failed: { backgroundColor: '#7f1d1d', color: '#f87171' },
|
|
21
|
+
empty: { textAlign: 'center', paddingVertical: 32, color: '#737373', fontSize: 14 },
|
|
21
22
|
});
|
|
22
23
|
<% } %>
|
|
23
24
|
interface Props {
|
|
@@ -27,7 +28,7 @@ interface Props {
|
|
|
27
28
|
interface TxInfo {
|
|
28
29
|
signature: string;
|
|
29
30
|
slot: number;
|
|
30
|
-
blockTime: number | null;
|
|
31
|
+
blockTime: number | null | undefined;
|
|
31
32
|
err: any;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -59,55 +60,59 @@ export function History({ onBack }: Props) {
|
|
|
59
60
|
return (
|
|
60
61
|
<% if (styling === 'nativewind') { %>
|
|
61
62
|
<View className="flex-1 bg-white p-6 pt-16">
|
|
62
|
-
<View className="
|
|
63
|
-
<
|
|
64
|
-
|
|
63
|
+
<View className="bg-neutral-900 rounded-2xl p-5">
|
|
64
|
+
<View className="flex-row items-center justify-between mb-4">
|
|
65
|
+
<Text className="text-lg font-semibold text-white">History</Text>
|
|
66
|
+
<TouchableOpacity onPress={onBack}><Text className="text-sm text-neutral-500">β Back</Text></TouchableOpacity>
|
|
67
|
+
</View>
|
|
68
|
+
{loading ? (
|
|
69
|
+
<View className="py-8 items-center"><ActivityIndicator color="#737373" /></View>
|
|
70
|
+
) : txs.length === 0 ? (
|
|
71
|
+
<Text className="text-center py-8 text-neutral-500">No transactions yet</Text>
|
|
72
|
+
) : (
|
|
73
|
+
<ScrollView className="gap-2" showsVerticalScrollIndicator={false}>
|
|
74
|
+
{txs.map((tx) => (
|
|
75
|
+
<TouchableOpacity key={tx.signature} onPress={() => openTx(tx.signature)} className="border border-neutral-700 rounded-xl p-3 mb-2 bg-neutral-800">
|
|
76
|
+
<View className="flex-row justify-between items-center">
|
|
77
|
+
<Text className="font-mono text-sm text-white">{tx.signature.slice(0, 8)}...{tx.signature.slice(-8)}</Text>
|
|
78
|
+
<Text className={`text-xs px-2 py-0.5 rounded ${tx.err ? 'bg-red-900 text-red-400' : 'bg-green-900 text-green-400'}`}>
|
|
79
|
+
{tx.err ? 'Failed' : 'Success'}
|
|
80
|
+
</Text>
|
|
81
|
+
</View>
|
|
82
|
+
<Text className="text-xs text-neutral-500 mt-1">{formatTime(tx.blockTime)}</Text>
|
|
83
|
+
</TouchableOpacity>
|
|
84
|
+
))}
|
|
85
|
+
</ScrollView>
|
|
86
|
+
)}
|
|
65
87
|
</View>
|
|
66
|
-
{loading ? (
|
|
67
|
-
<View className="py-8 items-center"><ActivityIndicator color="#a3a3a3" /></View>
|
|
68
|
-
) : txs.length === 0 ? (
|
|
69
|
-
<Text className="text-center py-8 text-neutral-400">No transactions yet</Text>
|
|
70
|
-
) : (
|
|
71
|
-
<ScrollView className="gap-2" showsVerticalScrollIndicator={false}>
|
|
72
|
-
{txs.map((tx) => (
|
|
73
|
-
<TouchableOpacity key={tx.signature} onPress={() => openTx(tx.signature)} className="border border-neutral-200 rounded-xl p-3 mb-2">
|
|
74
|
-
<View className="flex-row justify-between items-center">
|
|
75
|
-
<Text className="font-mono text-sm text-black">{tx.signature.slice(0, 8)}...{tx.signature.slice(-8)}</Text>
|
|
76
|
-
<Text className={`text-xs px-2 py-0.5 rounded ${tx.err ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`}>
|
|
77
|
-
{tx.err ? 'Failed' : 'Success'}
|
|
78
|
-
</Text>
|
|
79
|
-
</View>
|
|
80
|
-
<Text className="text-xs text-neutral-400 mt-1">{formatTime(tx.blockTime)}</Text>
|
|
81
|
-
</TouchableOpacity>
|
|
82
|
-
))}
|
|
83
|
-
</ScrollView>
|
|
84
|
-
)}
|
|
85
88
|
</View>
|
|
86
89
|
<% } else { %>
|
|
87
90
|
<View style={styles.container}>
|
|
88
|
-
<View style={styles.
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
+
<View style={styles.card}>
|
|
92
|
+
<View style={styles.header}>
|
|
93
|
+
<Text style={styles.title}>History</Text>
|
|
94
|
+
<TouchableOpacity onPress={onBack}><Text style={styles.back}>β Back</Text></TouchableOpacity>
|
|
95
|
+
</View>
|
|
96
|
+
{loading ? (
|
|
97
|
+
<View style={{ paddingVertical: 32, alignItems: 'center' }}><ActivityIndicator color="#737373" /></View>
|
|
98
|
+
) : txs.length === 0 ? (
|
|
99
|
+
<Text style={styles.empty}>No transactions yet</Text>
|
|
100
|
+
) : (
|
|
101
|
+
<ScrollView style={styles.list} showsVerticalScrollIndicator={false}>
|
|
102
|
+
{txs.map((tx) => (
|
|
103
|
+
<TouchableOpacity key={tx.signature} onPress={() => openTx(tx.signature)} style={[styles.item, { marginBottom: 8 }]}>
|
|
104
|
+
<View style={styles.row}>
|
|
105
|
+
<Text style={styles.sig}>{tx.signature.slice(0, 8)}...{tx.signature.slice(-8)}</Text>
|
|
106
|
+
<Text style={[styles.status, tx.err ? styles.failed : styles.success]}>
|
|
107
|
+
{tx.err ? 'Failed' : 'Success'}
|
|
108
|
+
</Text>
|
|
109
|
+
</View>
|
|
110
|
+
<Text style={styles.time}>{formatTime(tx.blockTime)}</Text>
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
))}
|
|
113
|
+
</ScrollView>
|
|
114
|
+
)}
|
|
91
115
|
</View>
|
|
92
|
-
{loading ? (
|
|
93
|
-
<View style={{ paddingVertical: 32, alignItems: 'center' }}><ActivityIndicator color="#a3a3a3" /></View>
|
|
94
|
-
) : txs.length === 0 ? (
|
|
95
|
-
<Text style={styles.empty}>No transactions yet</Text>
|
|
96
|
-
) : (
|
|
97
|
-
<ScrollView style={styles.list} showsVerticalScrollIndicator={false}>
|
|
98
|
-
{txs.map((tx) => (
|
|
99
|
-
<TouchableOpacity key={tx.signature} onPress={() => openTx(tx.signature)} style={[styles.item, { marginBottom: 8 }]}>
|
|
100
|
-
<View style={styles.row}>
|
|
101
|
-
<Text style={styles.sig}>{tx.signature.slice(0, 8)}...{tx.signature.slice(-8)}</Text>
|
|
102
|
-
<Text style={[styles.status, tx.err ? styles.failed : styles.success]}>
|
|
103
|
-
{tx.err ? 'Failed' : 'Success'}
|
|
104
|
-
</Text>
|
|
105
|
-
</View>
|
|
106
|
-
<Text style={styles.time}>{formatTime(tx.blockTime)}</Text>
|
|
107
|
-
</TouchableOpacity>
|
|
108
|
-
))}
|
|
109
|
-
</ScrollView>
|
|
110
|
-
)}
|
|
111
116
|
</View>
|
|
112
117
|
<% } %>
|
|
113
118
|
);
|
|
@@ -6,20 +6,21 @@ import * as Clipboard from 'expo-clipboard';
|
|
|
6
6
|
<% if (styling !== 'nativewind') { %>
|
|
7
7
|
const styles = StyleSheet.create({
|
|
8
8
|
container: { flex: 1, backgroundColor: '#fff', padding: 24, paddingTop: 60 },
|
|
9
|
-
|
|
9
|
+
card: { backgroundColor: '#0a0a0a', borderRadius: 16, padding: 20 },
|
|
10
|
+
title: { fontSize: 18, fontWeight: '600', color: '#fafafa' },
|
|
10
11
|
subtitle: { marginTop: 8, fontSize: 14, color: '#737373', lineHeight: 20 },
|
|
11
|
-
|
|
12
|
-
cardTitle: { fontSize: 14, fontWeight: '500', color: '#
|
|
12
|
+
inputCard: { marginTop: 16, borderWidth: 1, borderColor: '#262626', borderRadius: 12, padding: 16, backgroundColor: '#171717' },
|
|
13
|
+
cardTitle: { fontSize: 14, fontWeight: '500', color: '#fafafa' },
|
|
13
14
|
cardDesc: { marginTop: 4, fontSize: 13, color: '#737373' },
|
|
14
|
-
button: { marginTop: 12, backgroundColor: '#
|
|
15
|
-
buttonText: { color: '#
|
|
16
|
-
buttonSecondary: { marginTop: 12, backgroundColor: 'transparent', borderWidth: 1, borderColor: '#
|
|
17
|
-
buttonTextSecondary: { color: '#
|
|
18
|
-
info: { marginTop: 16, padding: 12, backgroundColor: '#
|
|
19
|
-
infoText: { fontSize: 12, color: '#
|
|
15
|
+
button: { marginTop: 12, backgroundColor: '#fafafa', paddingVertical: 12, borderRadius: 10, alignItems: 'center' },
|
|
16
|
+
buttonText: { color: '#0a0a0a', fontSize: 14, fontWeight: '500' },
|
|
17
|
+
buttonSecondary: { marginTop: 12, backgroundColor: 'transparent', borderWidth: 1, borderColor: '#262626', paddingVertical: 12, borderRadius: 10, alignItems: 'center' },
|
|
18
|
+
buttonTextSecondary: { color: '#fafafa', fontSize: 14, fontWeight: '500' },
|
|
19
|
+
info: { marginTop: 16, padding: 12, backgroundColor: '#171717', borderRadius: 8 },
|
|
20
|
+
infoText: { fontSize: 12, color: '#a3a3a3' },
|
|
20
21
|
back: { marginTop: 16 },
|
|
21
22
|
backText: { fontSize: 13, color: '#737373' },
|
|
22
|
-
copied: { marginLeft: 8, color: '#
|
|
23
|
+
copied: { marginLeft: 8, color: '#4ade80', fontSize: 12 },
|
|
23
24
|
});
|
|
24
25
|
<% } %>
|
|
25
26
|
interface Props {
|
|
@@ -32,11 +33,17 @@ export function Recovery({ onBack }: Props) {
|
|
|
32
33
|
const portalUrl = process.env.EXPO_PUBLIC_LAZORKIT_PORTAL_URL || 'https://portal.lazor.sh';
|
|
33
34
|
|
|
34
35
|
const handleAddDevice = () => {
|
|
35
|
-
|
|
36
|
+
const url = wallet?.smartWallet
|
|
37
|
+
? `${portalUrl}?wallet=${wallet.smartWallet}&action=add-device`
|
|
38
|
+
: portalUrl;
|
|
39
|
+
Linking.openURL(url);
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
const handleManageDevices = () => {
|
|
39
|
-
|
|
43
|
+
const url = wallet?.smartWallet
|
|
44
|
+
? `${portalUrl}?wallet=${wallet.smartWallet}&action=manage-devices`
|
|
45
|
+
: portalUrl;
|
|
46
|
+
Linking.openURL(url);
|
|
40
47
|
};
|
|
41
48
|
|
|
42
49
|
const copyAddress = async () => {
|
|
@@ -50,69 +57,73 @@ export function Recovery({ onBack }: Props) {
|
|
|
50
57
|
return (
|
|
51
58
|
<% if (styling === 'nativewind') { %>
|
|
52
59
|
<View className="flex-1 bg-white p-6 pt-16">
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
60
|
+
<View className="bg-neutral-900 rounded-2xl p-5">
|
|
61
|
+
<Text className="text-lg font-semibold text-white">Recovery & Backup</Text>
|
|
62
|
+
<Text className="mt-2 text-sm text-neutral-500 leading-5">
|
|
63
|
+
Add backup passkeys from other devices to ensure you never lose access.
|
|
64
|
+
</Text>
|
|
65
|
+
<View className="mt-4 border border-neutral-700 rounded-xl p-4 bg-neutral-800">
|
|
66
|
+
<Text className="text-sm font-medium text-white">Add Backup Device</Text>
|
|
67
|
+
<Text className="mt-1 text-xs text-neutral-500">Register a passkey from another phone, tablet, or computer.</Text>
|
|
68
|
+
<TouchableOpacity onPress={handleAddDevice} className="mt-3 bg-white py-3 rounded-lg items-center">
|
|
69
|
+
<Text className="text-black text-sm font-medium">Add Device</Text>
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
</View>
|
|
72
|
+
<View className="mt-3 border border-neutral-700 rounded-xl p-4 bg-neutral-800">
|
|
73
|
+
<Text className="text-sm font-medium text-white">Manage Devices</Text>
|
|
74
|
+
<Text className="mt-1 text-xs text-neutral-500">View and remove registered passkeys.</Text>
|
|
75
|
+
<TouchableOpacity onPress={handleManageDevices} className="mt-3 border border-neutral-700 py-3 rounded-lg items-center">
|
|
76
|
+
<Text className="text-white text-sm font-medium">View Devices</Text>
|
|
77
|
+
</TouchableOpacity>
|
|
78
|
+
</View>
|
|
79
|
+
<View className="mt-4 p-3 bg-neutral-800 rounded-lg">
|
|
80
|
+
<TouchableOpacity onPress={copyAddress} className="flex-row items-center">
|
|
81
|
+
<Text className="text-xs text-neutral-400">
|
|
82
|
+
<Text className="font-semibold">Wallet: </Text>
|
|
83
|
+
<Text className="font-mono">{wallet?.smartWallet?.slice(0, 8)}...{wallet?.smartWallet?.slice(-8)}</Text>
|
|
84
|
+
</Text>
|
|
85
|
+
{copied && <Text className="ml-2 text-xs text-green-400">Copied!</Text>}
|
|
86
|
+
</TouchableOpacity>
|
|
87
|
+
</View>
|
|
88
|
+
<TouchableOpacity onPress={onBack} className="mt-4">
|
|
89
|
+
<Text className="text-sm text-neutral-500">β Back</Text>
|
|
62
90
|
</TouchableOpacity>
|
|
63
91
|
</View>
|
|
64
|
-
<View className="mt-3 border border-neutral-200 rounded-xl p-4">
|
|
65
|
-
<Text className="text-sm font-medium text-black">Manage Devices</Text>
|
|
66
|
-
<Text className="mt-1 text-xs text-neutral-500">View and remove registered passkeys.</Text>
|
|
67
|
-
<TouchableOpacity onPress={handleManageDevices} className="mt-3 border border-neutral-200 py-3 rounded-lg items-center">
|
|
68
|
-
<Text className="text-black text-sm font-medium">View Devices</Text>
|
|
69
|
-
</TouchableOpacity>
|
|
70
|
-
</View>
|
|
71
|
-
<View className="mt-4 p-3 bg-neutral-100 rounded-lg">
|
|
72
|
-
<TouchableOpacity onPress={copyAddress} className="flex-row items-center">
|
|
73
|
-
<Text className="text-xs text-neutral-600">
|
|
74
|
-
<Text className="font-semibold">Wallet: </Text>
|
|
75
|
-
<Text className="font-mono">{wallet?.smartWallet?.slice(0, 8)}...{wallet?.smartWallet?.slice(-8)}</Text>
|
|
76
|
-
</Text>
|
|
77
|
-
{copied && <Text className="ml-2 text-xs text-green-600">Copied!</Text>}
|
|
78
|
-
</TouchableOpacity>
|
|
79
|
-
</View>
|
|
80
|
-
<TouchableOpacity onPress={onBack} className="mt-4">
|
|
81
|
-
<Text className="text-sm text-neutral-500">β Back</Text>
|
|
82
|
-
</TouchableOpacity>
|
|
83
92
|
</View>
|
|
84
93
|
<% } else { %>
|
|
85
94
|
<View style={styles.container}>
|
|
86
|
-
<Text style={styles.title}>Recovery & Backup</Text>
|
|
87
|
-
<Text style={styles.subtitle}>
|
|
88
|
-
Add backup passkeys from other devices to ensure you never lose access.
|
|
89
|
-
</Text>
|
|
90
95
|
<View style={styles.card}>
|
|
91
|
-
<Text style={styles.
|
|
92
|
-
<Text style={styles.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
<Text style={styles.title}>Recovery & Backup</Text>
|
|
97
|
+
<Text style={styles.subtitle}>
|
|
98
|
+
Add backup passkeys from other devices to ensure you never lose access.
|
|
99
|
+
</Text>
|
|
100
|
+
<View style={styles.inputCard}>
|
|
101
|
+
<Text style={styles.cardTitle}>Add Backup Device</Text>
|
|
102
|
+
<Text style={styles.cardDesc}>Register a passkey from another phone, tablet, or computer.</Text>
|
|
103
|
+
<TouchableOpacity onPress={handleAddDevice} style={styles.button}>
|
|
104
|
+
<Text style={styles.buttonText}>Add Device</Text>
|
|
105
|
+
</TouchableOpacity>
|
|
106
|
+
</View>
|
|
107
|
+
<View style={styles.inputCard}>
|
|
108
|
+
<Text style={styles.cardTitle}>Manage Devices</Text>
|
|
109
|
+
<Text style={styles.cardDesc}>View and remove registered passkeys.</Text>
|
|
110
|
+
<TouchableOpacity onPress={handleManageDevices} style={styles.buttonSecondary}>
|
|
111
|
+
<Text style={styles.buttonTextSecondary}>View Devices</Text>
|
|
112
|
+
</TouchableOpacity>
|
|
113
|
+
</View>
|
|
114
|
+
<View style={styles.info}>
|
|
115
|
+
<TouchableOpacity onPress={copyAddress}>
|
|
116
|
+
<Text style={styles.infoText}>
|
|
117
|
+
<Text style={{ fontWeight: '600' }}>Wallet: </Text>
|
|
118
|
+
<Text style={{ fontFamily: 'monospace' }}>{wallet?.smartWallet?.slice(0, 8)}...{wallet?.smartWallet?.slice(-8)}</Text>
|
|
119
|
+
{copied && <Text style={styles.copied}> Copied!</Text>}
|
|
120
|
+
</Text>
|
|
121
|
+
</TouchableOpacity>
|
|
122
|
+
</View>
|
|
123
|
+
<TouchableOpacity onPress={onBack} style={styles.back}>
|
|
124
|
+
<Text style={styles.backText}>β Back</Text>
|
|
111
125
|
</TouchableOpacity>
|
|
112
126
|
</View>
|
|
113
|
-
<TouchableOpacity onPress={onBack} style={styles.back}>
|
|
114
|
-
<Text style={styles.backText}>β Back</Text>
|
|
115
|
-
</TouchableOpacity>
|
|
116
127
|
</View>
|
|
117
128
|
<% } %>
|
|
118
129
|
);
|