create-lightning-scaffold 1.0.0 → 1.0.1

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.
Files changed (65) hide show
  1. package/README.md +107 -25
  2. package/dist/index.js +169 -64
  3. package/package.json +1 -1
  4. package/templates/backend/firebase/index.ts +86 -9
  5. package/templates/backend/supabase/index.ts +107 -6
  6. package/templates/base/.env.example.ejs +17 -10
  7. package/templates/lib/backend.ts.ejs +59 -0
  8. package/templates/mobile/app/_layout.tsx +22 -52
  9. package/templates/mobile/app/index.tsx.ejs +44 -0
  10. package/templates/mobile/components/History.tsx.ejs +114 -0
  11. package/templates/mobile/components/Onboarding.tsx.ejs +79 -0
  12. package/templates/mobile/components/Recovery.tsx.ejs +119 -0
  13. package/templates/mobile/components/Swap.tsx.ejs +304 -0
  14. package/templates/mobile/package.json.ejs +4 -0
  15. package/templates/vite/README.md +73 -0
  16. package/templates/vite/eslint.config.js +23 -0
  17. package/templates/vite/index.html +13 -0
  18. package/templates/vite/package.json.ejs +37 -0
  19. package/templates/vite/postcss.config.js.ejs +8 -0
  20. package/templates/vite/public/vite.svg +1 -0
  21. package/templates/vite/src/App.tsx.ejs +65 -0
  22. package/templates/vite/src/assets/react.svg +1 -0
  23. package/templates/vite/src/components/History.tsx.ejs +112 -0
  24. package/templates/vite/src/components/Onboarding.tsx.ejs +64 -0
  25. package/templates/vite/src/components/Recovery.tsx.ejs +107 -0
  26. package/templates/vite/src/components/Swap.tsx.ejs +241 -0
  27. package/templates/vite/src/index.css.ejs +55 -0
  28. package/templates/vite/src/main.tsx +25 -0
  29. package/templates/vite/tailwind.config.js.ejs +13 -0
  30. package/templates/vite/tsconfig.app.json +28 -0
  31. package/templates/vite/tsconfig.json +7 -0
  32. package/templates/vite/tsconfig.node.json +26 -0
  33. package/templates/vite/vite.config.ts +15 -0
  34. package/templates/web/app/globals.css.ejs +53 -0
  35. package/templates/web/app/layout.tsx.ejs +8 -15
  36. package/templates/web/app/page.tsx.ejs +45 -0
  37. package/templates/web/app/providers.tsx +27 -0
  38. package/templates/web/components/History.tsx.ejs +114 -0
  39. package/templates/web/components/Onboarding.tsx.ejs +66 -0
  40. package/templates/web/components/Recovery.tsx.ejs +108 -0
  41. package/templates/web/components/Swap.tsx.ejs +289 -0
  42. package/templates/web/package.json.ejs +3 -1
  43. package/templates/examples/mobile/biometric-onboard.tsx +0 -104
  44. package/templates/examples/mobile/gasless-transfer.tsx +0 -72
  45. package/templates/examples/mobile/index.tsx +0 -30
  46. package/templates/examples/mobile/passkey-login.tsx +0 -55
  47. package/templates/examples/web/biometric-onboard/page.tsx +0 -70
  48. package/templates/examples/web/gasless-transfer/page.tsx +0 -85
  49. package/templates/examples/web/page.tsx +0 -27
  50. package/templates/examples/web/passkey-login/page.tsx +0 -50
  51. package/templates/lib/lazorkit/mobile/index.ts +0 -53
  52. package/templates/lib/lazorkit/web/index.ts +0 -67
  53. package/templates/mobile/app/(tabs)/_layout.tsx +0 -59
  54. package/templates/mobile/app/(tabs)/index.tsx +0 -31
  55. package/templates/mobile/app/(tabs)/two.tsx +0 -31
  56. package/templates/mobile/app/+html.tsx +0 -38
  57. package/templates/mobile/app/+not-found.tsx +0 -40
  58. package/templates/mobile/app/modal.tsx +0 -35
  59. package/templates/mobile/components/EditScreenInfo.tsx +0 -77
  60. package/templates/mobile/components/StyledText.tsx +0 -5
  61. package/templates/mobile/components/__tests__/StyledText-test.js +0 -10
  62. package/templates/mobile/lib/lazorkit/index.ts +0 -53
  63. package/templates/web/app/globals.css +0 -26
  64. package/templates/web/app/page.tsx +0 -65
  65. package/templates/web/lib/lazorkit/index.ts +0 -67
@@ -1,85 +0,0 @@
1
- "use client";
2
- import { useState } from "react";
3
- import Link from "next/link";
4
- import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js";
5
- import { signAndSendTransaction, sponsorTransaction } from "@/lib/lazorkit";
6
- import { useStore } from "@/lib/store";
7
-
8
- export default function GaslessTransfer() {
9
- const { wallet } = useStore();
10
- const [recipient, setRecipient] = useState("");
11
- const [amount, setAmount] = useState("");
12
- const [loading, setLoading] = useState(false);
13
- const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);
14
-
15
- const handleTransfer = async () => {
16
- if (!wallet || !recipient || !amount) return;
17
- setLoading(true);
18
- setResult(null);
19
-
20
- try {
21
- const tx = new Transaction().add(
22
- SystemProgram.transfer({
23
- fromPubkey: wallet.publicKey,
24
- toPubkey: new PublicKey(recipient),
25
- lamports: parseFloat(amount) * LAMPORTS_PER_SOL,
26
- })
27
- );
28
-
29
- const sponsoredTx = await sponsorTransaction(tx);
30
- const signature = await signAndSendTransaction(sponsoredTx, wallet);
31
-
32
- setResult(signature ? { success: true, message: `Sent! ${signature.slice(0, 20)}...` } : { success: false, message: "Cancelled" });
33
- } catch (e) {
34
- setResult({ success: false, message: e instanceof Error ? e.message : "Failed" });
35
- } finally {
36
- setLoading(false);
37
- }
38
- };
39
-
40
- if (!wallet) {
41
- return (
42
- <div className="min-h-screen flex flex-col items-center justify-center p-6">
43
- <h1 className="text-xl font-bold mb-4">Connect wallet first</h1>
44
- <Link href="/examples/passkey-login" className="bg-blue-600 text-white px-6 py-3 rounded-xl">Go to Login</Link>
45
- </div>
46
- );
47
- }
48
-
49
- return (
50
- <div className="min-h-screen flex flex-col items-center justify-center p-6">
51
- <h1 className="text-3xl font-bold">Gasless Transfer</h1>
52
- <p className="text-gray-500 mt-2 mb-8">Send SOL without paying gas</p>
53
-
54
- <div className="w-full max-w-sm space-y-4">
55
- <input
56
- type="text"
57
- placeholder="Recipient address"
58
- value={recipient}
59
- onChange={(e) => setRecipient(e.target.value)}
60
- className="w-full border border-gray-300 rounded-lg p-3"
61
- />
62
- <input
63
- type="text"
64
- placeholder="Amount (SOL)"
65
- value={amount}
66
- onChange={(e) => setAmount(e.target.value)}
67
- className="w-full border border-gray-300 rounded-lg p-3"
68
- />
69
- <button
70
- onClick={handleTransfer}
71
- disabled={loading || !recipient || !amount}
72
- className="w-full bg-blue-600 text-white py-4 rounded-xl font-semibold hover:bg-blue-700 disabled:opacity-50"
73
- >
74
- {loading ? "Sending..." : "Send (Gasless)"}
75
- </button>
76
- </div>
77
-
78
- {result && (
79
- <p className={`mt-4 ${result.success ? "text-green-600" : "text-red-500"}`}>{result.message}</p>
80
- )}
81
-
82
- <Link href="/examples" className="text-blue-600 mt-8">← Back to Examples</Link>
83
- </div>
84
- );
85
- }
@@ -1,27 +0,0 @@
1
- import Link from "next/link";
2
-
3
- export default function Examples() {
4
- const examples = [
5
- { href: "/examples/passkey-login", title: "Passkey Login", desc: "WebAuthn-based authentication" },
6
- { href: "/examples/gasless-transfer", title: "Gasless Transfer", desc: "Send SOL without gas fees" },
7
- { href: "/examples/biometric-onboard", title: "Biometric Onboarding", desc: "Secure wallet setup flow" },
8
- ];
9
-
10
- return (
11
- <div className="min-h-screen flex flex-col items-center justify-center p-6">
12
- <h1 className="text-3xl font-bold mb-2">LazorKit Examples</h1>
13
- <p className="text-gray-500 mb-8">Explore the SDK integration demos</p>
14
-
15
- <div className="grid gap-4 w-full max-w-md">
16
- {examples.map((ex) => (
17
- <Link key={ex.href} href={ex.href} className="block p-4 border border-gray-200 rounded-xl hover:border-blue-500 hover:bg-blue-50 transition">
18
- <h2 className="font-semibold">{ex.title}</h2>
19
- <p className="text-sm text-gray-500">{ex.desc}</p>
20
- </Link>
21
- ))}
22
- </div>
23
-
24
- <Link href="/" className="text-blue-600 mt-8">← Back to Home</Link>
25
- </div>
26
- );
27
- }
@@ -1,50 +0,0 @@
1
- "use client";
2
- import { useState } from "react";
3
- import Link from "next/link";
4
- import { connectPasskey } from "@/lib/lazorkit";
5
- import { useStore } from "@/lib/store";
6
-
7
- export default function PasskeyLogin() {
8
- const { wallet, setWallet } = useStore();
9
- const [loading, setLoading] = useState(false);
10
- const [error, setError] = useState<string | null>(null);
11
-
12
- const handleConnect = async () => {
13
- setLoading(true);
14
- setError(null);
15
- try {
16
- const result = await connectPasskey();
17
- if (result) setWallet(result);
18
- else setError("Connection cancelled");
19
- } catch (e) {
20
- setError(e instanceof Error ? e.message : "Connection failed");
21
- } finally {
22
- setLoading(false);
23
- }
24
- };
25
-
26
- return (
27
- <div className="min-h-screen flex flex-col items-center justify-center p-6">
28
- <h1 className="text-3xl font-bold">Passkey Login</h1>
29
- <p className="text-gray-500 mt-2 mb-8">Connect with biometric authentication</p>
30
-
31
- {wallet ? (
32
- <div className="bg-gray-100 p-6 rounded-xl w-full max-w-sm">
33
- <p className="text-sm text-gray-500">Connected Wallet</p>
34
- <p className="font-mono font-semibold">{wallet.address.slice(0, 8)}...{wallet.address.slice(-8)}</p>
35
- <button onClick={() => setWallet(null)} className="w-full mt-4 border border-gray-300 p-3 rounded-lg text-gray-600 hover:bg-gray-50">
36
- Disconnect
37
- </button>
38
- </div>
39
- ) : (
40
- <button onClick={handleConnect} disabled={loading} className="bg-blue-600 text-white px-8 py-4 rounded-xl font-semibold hover:bg-blue-700 disabled:opacity-50">
41
- {loading ? "Connecting..." : "Connect with Passkey"}
42
- </button>
43
- )}
44
-
45
- {error && <p className="text-red-500 mt-4">{error}</p>}
46
-
47
- <Link href="/examples" className="text-blue-600 mt-8">← Back to Examples</Link>
48
- </div>
49
- );
50
- }
@@ -1,53 +0,0 @@
1
- import * as WebBrowser from "expo-web-browser";
2
- import * as Linking from "expo-linking";
3
- import { Connection, PublicKey, Transaction } from "@solana/web3.js";
4
-
5
- const PORTAL_URL = process.env.EXPO_PUBLIC_LAZORKIT_PORTAL_URL || "https://portal.lazor.sh";
6
- const PAYMASTER_URL = process.env.EXPO_PUBLIC_LAZORKIT_PAYMASTER_URL || "https://kora.devnet.lazorkit.com";
7
- const RPC_URL = process.env.EXPO_PUBLIC_SOLANA_RPC || "https://api.devnet.solana.com";
8
-
9
- export interface LazorWallet {
10
- address: string;
11
- publicKey: PublicKey;
12
- }
13
-
14
- export async function connectPasskey(redirectUrl: string): Promise<LazorWallet | null> {
15
- const authUrl = `${PORTAL_URL}/auth?redirect=${encodeURIComponent(redirectUrl)}`;
16
- const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl);
17
-
18
- if (result.type === "success" && result.url) {
19
- const params = Linking.parse(result.url);
20
- const address = params.queryParams?.address as string;
21
- if (address) return { address, publicKey: new PublicKey(address) };
22
- }
23
- return null;
24
- }
25
-
26
- export async function signAndSendTransaction(tx: Transaction, wallet: LazorWallet, redirectUrl: string): Promise<string | null> {
27
- const connection = new Connection(RPC_URL);
28
- const { blockhash } = await connection.getLatestBlockhash();
29
- tx.recentBlockhash = blockhash;
30
- tx.feePayer = wallet.publicKey;
31
-
32
- const serialized = tx.serialize({ requireAllSignatures: false }).toString("base64");
33
- const signUrl = `${PORTAL_URL}/sign?tx=${encodeURIComponent(serialized)}&redirect=${encodeURIComponent(redirectUrl)}`;
34
- const result = await WebBrowser.openAuthSessionAsync(signUrl, redirectUrl);
35
-
36
- if (result.type === "success" && result.url) {
37
- const params = Linking.parse(result.url);
38
- return (params.queryParams?.signature as string) || null;
39
- }
40
- return null;
41
- }
42
-
43
- export async function sponsorTransaction(tx: Transaction): Promise<Transaction> {
44
- const res = await fetch(`${PAYMASTER_URL}/sponsor`, {
45
- method: "POST",
46
- headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify({ transaction: tx.serialize({ requireAllSignatures: false }).toString("base64") }),
48
- });
49
- const { sponsoredTx } = await res.json();
50
- return Transaction.from(Buffer.from(sponsoredTx, "base64"));
51
- }
52
-
53
- export const connection = new Connection(RPC_URL);
@@ -1,67 +0,0 @@
1
- "use client";
2
- import { Connection, PublicKey, Transaction } from "@solana/web3.js";
3
-
4
- const PORTAL_URL = process.env.NEXT_PUBLIC_LAZORKIT_PORTAL_URL || "https://portal.lazor.sh";
5
- const PAYMASTER_URL = process.env.NEXT_PUBLIC_LAZORKIT_PAYMASTER_URL || "https://kora.devnet.lazorkit.com";
6
- const RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC || "https://api.devnet.solana.com";
7
-
8
- export interface LazorWallet {
9
- address: string;
10
- publicKey: PublicKey;
11
- }
12
-
13
- export async function connectPasskey(): Promise<LazorWallet | null> {
14
- const redirectUrl = `${window.location.origin}/auth/callback`;
15
- const popup = window.open(`${PORTAL_URL}/auth?redirect=${encodeURIComponent(redirectUrl)}`, "lazorkit", "width=500,height=600");
16
-
17
- return new Promise((resolve) => {
18
- const handler = (e: MessageEvent) => {
19
- if (e.origin === window.location.origin && e.data?.address) {
20
- window.removeEventListener("message", handler);
21
- popup?.close();
22
- resolve({ address: e.data.address, publicKey: new PublicKey(e.data.address) });
23
- }
24
- };
25
- window.addEventListener("message", handler);
26
- const check = setInterval(() => {
27
- if (popup?.closed) { clearInterval(check); window.removeEventListener("message", handler); resolve(null); }
28
- }, 500);
29
- });
30
- }
31
-
32
- export async function signAndSendTransaction(tx: Transaction, wallet: LazorWallet): Promise<string | null> {
33
- const connection = new Connection(RPC_URL);
34
- const { blockhash } = await connection.getLatestBlockhash();
35
- tx.recentBlockhash = blockhash;
36
- tx.feePayer = wallet.publicKey;
37
-
38
- const serialized = tx.serialize({ requireAllSignatures: false }).toString("base64");
39
- const redirectUrl = `${window.location.origin}/auth/callback`;
40
- const popup = window.open(`${PORTAL_URL}/sign?tx=${encodeURIComponent(serialized)}&redirect=${encodeURIComponent(redirectUrl)}`, "lazorkit", "width=500,height=600");
41
-
42
- return new Promise((resolve) => {
43
- const handler = (e: MessageEvent) => {
44
- if (e.origin === window.location.origin && e.data?.signature) {
45
- window.removeEventListener("message", handler);
46
- popup?.close();
47
- resolve(e.data.signature);
48
- }
49
- };
50
- window.addEventListener("message", handler);
51
- const check = setInterval(() => {
52
- if (popup?.closed) { clearInterval(check); window.removeEventListener("message", handler); resolve(null); }
53
- }, 500);
54
- });
55
- }
56
-
57
- export async function sponsorTransaction(tx: Transaction): Promise<Transaction> {
58
- const res = await fetch(`${PAYMASTER_URL}/sponsor`, {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body: JSON.stringify({ transaction: tx.serialize({ requireAllSignatures: false }).toString("base64") }),
62
- });
63
- const { sponsoredTx } = await res.json();
64
- return Transaction.from(Buffer.from(sponsoredTx, "base64"));
65
- }
66
-
67
- export const connection = new Connection(RPC_URL);
@@ -1,59 +0,0 @@
1
- import React from 'react';
2
- import FontAwesome from '@expo/vector-icons/FontAwesome';
3
- import { Link, Tabs } from 'expo-router';
4
- import { Pressable } from 'react-native';
5
-
6
- import Colors from '@/constants/Colors';
7
- import { useColorScheme } from '@/components/useColorScheme';
8
- import { useClientOnlyValue } from '@/components/useClientOnlyValue';
9
-
10
- // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
11
- function TabBarIcon(props: {
12
- name: React.ComponentProps<typeof FontAwesome>['name'];
13
- color: string;
14
- }) {
15
- return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />;
16
- }
17
-
18
- export default function TabLayout() {
19
- const colorScheme = useColorScheme();
20
-
21
- return (
22
- <Tabs
23
- screenOptions={{
24
- tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
25
- // Disable the static render of the header on web
26
- // to prevent a hydration error in React Navigation v6.
27
- headerShown: useClientOnlyValue(false, true),
28
- }}>
29
- <Tabs.Screen
30
- name="index"
31
- options={{
32
- title: 'Tab One',
33
- tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
34
- headerRight: () => (
35
- <Link href="/modal" asChild>
36
- <Pressable>
37
- {({ pressed }) => (
38
- <FontAwesome
39
- name="info-circle"
40
- size={25}
41
- color={Colors[colorScheme ?? 'light'].text}
42
- style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
43
- />
44
- )}
45
- </Pressable>
46
- </Link>
47
- ),
48
- }}
49
- />
50
- <Tabs.Screen
51
- name="two"
52
- options={{
53
- title: 'Tab Two',
54
- tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
55
- }}
56
- />
57
- </Tabs>
58
- );
59
- }
@@ -1,31 +0,0 @@
1
- import { StyleSheet } from 'react-native';
2
-
3
- import EditScreenInfo from '@/components/EditScreenInfo';
4
- import { Text, View } from '@/components/Themed';
5
-
6
- export default function TabOneScreen() {
7
- return (
8
- <View style={styles.container}>
9
- <Text style={styles.title}>Tab One</Text>
10
- <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
11
- <EditScreenInfo path="app/(tabs)/index.tsx" />
12
- </View>
13
- );
14
- }
15
-
16
- const styles = StyleSheet.create({
17
- container: {
18
- flex: 1,
19
- alignItems: 'center',
20
- justifyContent: 'center',
21
- },
22
- title: {
23
- fontSize: 20,
24
- fontWeight: 'bold',
25
- },
26
- separator: {
27
- marginVertical: 30,
28
- height: 1,
29
- width: '80%',
30
- },
31
- });
@@ -1,31 +0,0 @@
1
- import { StyleSheet } from 'react-native';
2
-
3
- import EditScreenInfo from '@/components/EditScreenInfo';
4
- import { Text, View } from '@/components/Themed';
5
-
6
- export default function TabTwoScreen() {
7
- return (
8
- <View style={styles.container}>
9
- <Text style={styles.title}>Tab Two</Text>
10
- <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
11
- <EditScreenInfo path="app/(tabs)/two.tsx" />
12
- </View>
13
- );
14
- }
15
-
16
- const styles = StyleSheet.create({
17
- container: {
18
- flex: 1,
19
- alignItems: 'center',
20
- justifyContent: 'center',
21
- },
22
- title: {
23
- fontSize: 20,
24
- fontWeight: 'bold',
25
- },
26
- separator: {
27
- marginVertical: 30,
28
- height: 1,
29
- width: '80%',
30
- },
31
- });
@@ -1,38 +0,0 @@
1
- import { ScrollViewStyleReset } from 'expo-router/html';
2
-
3
- // This file is web-only and used to configure the root HTML for every
4
- // web page during static rendering.
5
- // The contents of this function only run in Node.js environments and
6
- // do not have access to the DOM or browser APIs.
7
- export default function Root({ children }: { children: React.ReactNode }) {
8
- return (
9
- <html lang="en">
10
- <head>
11
- <meta charSet="utf-8" />
12
- <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
13
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
14
-
15
- {/*
16
- Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
17
- However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
18
- */}
19
- <ScrollViewStyleReset />
20
-
21
- {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
22
- <style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
23
- {/* Add any additional <head> elements that you want globally available on web... */}
24
- </head>
25
- <body>{children}</body>
26
- </html>
27
- );
28
- }
29
-
30
- const responsiveBackground = `
31
- body {
32
- background-color: #fff;
33
- }
34
- @media (prefers-color-scheme: dark) {
35
- body {
36
- background-color: #000;
37
- }
38
- }`;
@@ -1,40 +0,0 @@
1
- import { Link, Stack } from 'expo-router';
2
- import { StyleSheet } from 'react-native';
3
-
4
- import { Text, View } from '@/components/Themed';
5
-
6
- export default function NotFoundScreen() {
7
- return (
8
- <>
9
- <Stack.Screen options={{ title: 'Oops!' }} />
10
- <View style={styles.container}>
11
- <Text style={styles.title}>This screen doesn't exist.</Text>
12
-
13
- <Link href="/" style={styles.link}>
14
- <Text style={styles.linkText}>Go to home screen!</Text>
15
- </Link>
16
- </View>
17
- </>
18
- );
19
- }
20
-
21
- const styles = StyleSheet.create({
22
- container: {
23
- flex: 1,
24
- alignItems: 'center',
25
- justifyContent: 'center',
26
- padding: 20,
27
- },
28
- title: {
29
- fontSize: 20,
30
- fontWeight: 'bold',
31
- },
32
- link: {
33
- marginTop: 15,
34
- paddingVertical: 15,
35
- },
36
- linkText: {
37
- fontSize: 14,
38
- color: '#2e78b7',
39
- },
40
- });
@@ -1,35 +0,0 @@
1
- import { StatusBar } from 'expo-status-bar';
2
- import { Platform, StyleSheet } from 'react-native';
3
-
4
- import EditScreenInfo from '@/components/EditScreenInfo';
5
- import { Text, View } from '@/components/Themed';
6
-
7
- export default function ModalScreen() {
8
- return (
9
- <View style={styles.container}>
10
- <Text style={styles.title}>Modal</Text>
11
- <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
12
- <EditScreenInfo path="app/modal.tsx" />
13
-
14
- {/* Use a light status bar on iOS to account for the black space above the modal */}
15
- <StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />
16
- </View>
17
- );
18
- }
19
-
20
- const styles = StyleSheet.create({
21
- container: {
22
- flex: 1,
23
- alignItems: 'center',
24
- justifyContent: 'center',
25
- },
26
- title: {
27
- fontSize: 20,
28
- fontWeight: 'bold',
29
- },
30
- separator: {
31
- marginVertical: 30,
32
- height: 1,
33
- width: '80%',
34
- },
35
- });
@@ -1,77 +0,0 @@
1
- import React from 'react';
2
- import { StyleSheet } from 'react-native';
3
-
4
- import { ExternalLink } from './ExternalLink';
5
- import { MonoText } from './StyledText';
6
- import { Text, View } from './Themed';
7
-
8
- import Colors from '@/constants/Colors';
9
-
10
- export default function EditScreenInfo({ path }: { path: string }) {
11
- return (
12
- <View>
13
- <View style={styles.getStartedContainer}>
14
- <Text
15
- style={styles.getStartedText}
16
- lightColor="rgba(0,0,0,0.8)"
17
- darkColor="rgba(255,255,255,0.8)">
18
- Open up the code for this screen:
19
- </Text>
20
-
21
- <View
22
- style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
23
- darkColor="rgba(255,255,255,0.05)"
24
- lightColor="rgba(0,0,0,0.05)">
25
- <MonoText>{path}</MonoText>
26
- </View>
27
-
28
- <Text
29
- style={styles.getStartedText}
30
- lightColor="rgba(0,0,0,0.8)"
31
- darkColor="rgba(255,255,255,0.8)">
32
- Change any of the text, save the file, and your app will automatically update.
33
- </Text>
34
- </View>
35
-
36
- <View style={styles.helpContainer}>
37
- <ExternalLink
38
- style={styles.helpLink}
39
- href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet">
40
- <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
41
- Tap here if your app doesn't automatically update after making changes
42
- </Text>
43
- </ExternalLink>
44
- </View>
45
- </View>
46
- );
47
- }
48
-
49
- const styles = StyleSheet.create({
50
- getStartedContainer: {
51
- alignItems: 'center',
52
- marginHorizontal: 50,
53
- },
54
- homeScreenFilename: {
55
- marginVertical: 7,
56
- },
57
- codeHighlightContainer: {
58
- borderRadius: 3,
59
- paddingHorizontal: 4,
60
- },
61
- getStartedText: {
62
- fontSize: 17,
63
- lineHeight: 24,
64
- textAlign: 'center',
65
- },
66
- helpContainer: {
67
- marginTop: 15,
68
- marginHorizontal: 20,
69
- alignItems: 'center',
70
- },
71
- helpLink: {
72
- paddingVertical: 15,
73
- },
74
- helpLinkText: {
75
- textAlign: 'center',
76
- },
77
- });
@@ -1,5 +0,0 @@
1
- import { Text, TextProps } from './Themed';
2
-
3
- export function MonoText(props: TextProps) {
4
- return <Text {...props} style={[props.style, { fontFamily: 'SpaceMono' }]} />;
5
- }
@@ -1,10 +0,0 @@
1
- import * as React from 'react';
2
- import renderer from 'react-test-renderer';
3
-
4
- import { MonoText } from '../StyledText';
5
-
6
- it(`renders correctly`, () => {
7
- const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
8
-
9
- expect(tree).toMatchSnapshot();
10
- });
@@ -1,53 +0,0 @@
1
- import * as WebBrowser from "expo-web-browser";
2
- import * as Linking from "expo-linking";
3
- import { Connection, PublicKey, Transaction } from "@solana/web3.js";
4
-
5
- const PORTAL_URL = process.env.EXPO_PUBLIC_LAZORKIT_PORTAL_URL || "https://portal.lazor.sh";
6
- const PAYMASTER_URL = process.env.EXPO_PUBLIC_LAZORKIT_PAYMASTER_URL || "https://kora.devnet.lazorkit.com";
7
- const RPC_URL = process.env.EXPO_PUBLIC_SOLANA_RPC || "https://api.devnet.solana.com";
8
-
9
- export interface LazorWallet {
10
- address: string;
11
- publicKey: PublicKey;
12
- }
13
-
14
- export async function connectPasskey(redirectUrl: string): Promise<LazorWallet | null> {
15
- const authUrl = `${PORTAL_URL}/auth?redirect=${encodeURIComponent(redirectUrl)}`;
16
- const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl);
17
-
18
- if (result.type === "success" && result.url) {
19
- const params = Linking.parse(result.url);
20
- const address = params.queryParams?.address as string;
21
- if (address) return { address, publicKey: new PublicKey(address) };
22
- }
23
- return null;
24
- }
25
-
26
- export async function signAndSendTransaction(tx: Transaction, wallet: LazorWallet, redirectUrl: string): Promise<string | null> {
27
- const connection = new Connection(RPC_URL);
28
- const { blockhash } = await connection.getLatestBlockhash();
29
- tx.recentBlockhash = blockhash;
30
- tx.feePayer = wallet.publicKey;
31
-
32
- const serialized = tx.serialize({ requireAllSignatures: false }).toString("base64");
33
- const signUrl = `${PORTAL_URL}/sign?tx=${encodeURIComponent(serialized)}&redirect=${encodeURIComponent(redirectUrl)}`;
34
- const result = await WebBrowser.openAuthSessionAsync(signUrl, redirectUrl);
35
-
36
- if (result.type === "success" && result.url) {
37
- const params = Linking.parse(result.url);
38
- return (params.queryParams?.signature as string) || null;
39
- }
40
- return null;
41
- }
42
-
43
- export async function sponsorTransaction(tx: Transaction): Promise<Transaction> {
44
- const res = await fetch(`${PAYMASTER_URL}/sponsor`, {
45
- method: "POST",
46
- headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify({ transaction: tx.serialize({ requireAllSignatures: false }).toString("base64") }),
48
- });
49
- const { sponsoredTx } = await res.json();
50
- return Transaction.from(Buffer.from(sponsoredTx, "base64"));
51
- }
52
-
53
- export const connection = new Connection(RPC_URL);