starkzap-starter 0.1.0
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/.env.example +23 -0
- package/README.md +164 -0
- package/cli-package.json +31 -0
- package/next.config.mjs +8 -0
- package/package.json +33 -0
- package/postcss.config.js +6 -0
- package/scripts/create-starkzap-app.mjs +1749 -0
- package/src/app/globals.css +46 -0
- package/src/app/layout.tsx +21 -0
- package/src/app/page.tsx +159 -0
- package/src/components/payment/PaymentForm.tsx +158 -0
- package/src/components/wallet/TokenBalanceCard.tsx +79 -0
- package/src/components/wallet/WalletButton.tsx +59 -0
- package/src/hooks/useGaslessTransfer.ts +88 -0
- package/src/hooks/useTokenBalance.ts +66 -0
- package/src/hooks/useWallet.ts +88 -0
- package/src/lib/starkzap.ts +22 -0
- package/src/lib/utils.ts +54 -0
- package/tailwind.config.js +43 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* hooks/useWallet.ts
|
|
5
|
+
*
|
|
6
|
+
* Manages Starkzap wallet connection lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const { wallet, address, isConnecting, connect, disconnect } = useWallet();
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useState, useCallback, useRef } from "react";
|
|
13
|
+
import { StarkSigner } from "starkzap";
|
|
14
|
+
import { sdk } from "@/lib/starkzap";
|
|
15
|
+
|
|
16
|
+
export type WalletState = {
|
|
17
|
+
wallet: Awaited<ReturnType<typeof sdk.connectWallet>> | null;
|
|
18
|
+
address: string | null;
|
|
19
|
+
isConnecting: boolean;
|
|
20
|
+
isReady: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
connect: () => Promise<void>;
|
|
23
|
+
disconnect: () => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Connect via a browser-injected wallet (Argent X, Braavos).
|
|
28
|
+
* For social/email login, swap StarkSigner for PrivySigner — see README.
|
|
29
|
+
*/
|
|
30
|
+
export function useWallet(): WalletState {
|
|
31
|
+
const [wallet, setWallet] = useState<WalletState["wallet"]>(null);
|
|
32
|
+
const [address, setAddress] = useState<string | null>(null);
|
|
33
|
+
const [isConnecting, setIsConnecting] = useState(false);
|
|
34
|
+
const [isReady, setIsReady] = useState(false);
|
|
35
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
|
+
const walletRef = useRef<WalletState["wallet"]>(null);
|
|
37
|
+
|
|
38
|
+
const connect = useCallback(async () => {
|
|
39
|
+
setIsConnecting(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Detect injected Starknet wallet (Argent X / Braavos)
|
|
44
|
+
const starknetWindow = (window as unknown as { starknet?: { enable: () => Promise<string[]>; account: { address: string; privateKey?: string } } }).starknet;
|
|
45
|
+
|
|
46
|
+
if (!starknetWindow) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"No Starknet wallet detected. Please install Argent X or Braavos."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await starknetWindow.enable();
|
|
53
|
+
const userAddress = starknetWindow.account.address;
|
|
54
|
+
|
|
55
|
+
// Build a signer from the injected provider
|
|
56
|
+
// In production with Privy, replace with: new PrivySigner(privyClient)
|
|
57
|
+
const signer = new StarkSigner(
|
|
58
|
+
starknetWindow.account.privateKey ?? "0x0" // placeholder — injected wallets handle signing internally
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const connectedWallet = await sdk.connectWallet({
|
|
62
|
+
account: { signer },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await connectedWallet.ensureReady({ deploy: "if_needed" });
|
|
66
|
+
|
|
67
|
+
walletRef.current = connectedWallet;
|
|
68
|
+
setWallet(connectedWallet);
|
|
69
|
+
setAddress(userAddress);
|
|
70
|
+
setIsReady(true);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const message = err instanceof Error ? err.message : "Connection failed";
|
|
73
|
+
setError(message);
|
|
74
|
+
} finally {
|
|
75
|
+
setIsConnecting(false);
|
|
76
|
+
}
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const disconnect = useCallback(() => {
|
|
80
|
+
walletRef.current = null;
|
|
81
|
+
setWallet(null);
|
|
82
|
+
setAddress(null);
|
|
83
|
+
setIsReady(false);
|
|
84
|
+
setError(null);
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
return { wallet, address, isConnecting, isReady, error, connect, disconnect };
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/starkzap.ts
|
|
3
|
+
*
|
|
4
|
+
* Central Starkzap SDK configuration.
|
|
5
|
+
* Import `sdk` anywhere you need to interact with Starknet.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { StarkZap } from "starkzap";
|
|
9
|
+
|
|
10
|
+
const network =
|
|
11
|
+
(process.env.NEXT_PUBLIC_STARKNET_NETWORK as "mainnet" | "sepolia") ??
|
|
12
|
+
"sepolia";
|
|
13
|
+
|
|
14
|
+
const rpcUrl = process.env.NEXT_PUBLIC_STARKNET_RPC_URL;
|
|
15
|
+
|
|
16
|
+
export const sdk = new StarkZap({
|
|
17
|
+
network,
|
|
18
|
+
...(rpcUrl ? { rpcUrl } : {}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type Network = typeof network;
|
|
22
|
+
export { network };
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/utils.ts — shared utility functions
|
|
3
|
+
*/
|
|
4
|
+
import { clsx, type ClassValue } from "clsx";
|
|
5
|
+
import { twMerge } from "tailwind-merge";
|
|
6
|
+
|
|
7
|
+
/** Merge Tailwind class names safely */
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Truncate a Starknet address for display.
|
|
14
|
+
* e.g. 0x049d...a3f2
|
|
15
|
+
*/
|
|
16
|
+
export function formatAddress(address: string, chars = 4): string {
|
|
17
|
+
if (!address) return "";
|
|
18
|
+
const clean = address.startsWith("0x") ? address : `0x${address}`;
|
|
19
|
+
return `${clean.slice(0, chars + 2)}...${clean.slice(-chars)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format a raw token amount (bigint) to a human-readable string.
|
|
24
|
+
* @param raw - Raw base units (e.g. 1_000_000_000_000_000_000n for 1 ETH)
|
|
25
|
+
* @param decimals - Token decimals (default 18)
|
|
26
|
+
* @param precision - Display decimal places (default 4)
|
|
27
|
+
*/
|
|
28
|
+
export function formatAmount(
|
|
29
|
+
raw: bigint,
|
|
30
|
+
decimals = 18,
|
|
31
|
+
precision = 4
|
|
32
|
+
): string {
|
|
33
|
+
const divisor = 10n ** BigInt(decimals);
|
|
34
|
+
const whole = raw / divisor;
|
|
35
|
+
const fraction = raw % divisor;
|
|
36
|
+
const fractionStr = fraction
|
|
37
|
+
.toString()
|
|
38
|
+
.padStart(decimals, "0")
|
|
39
|
+
.slice(0, precision);
|
|
40
|
+
return `${whole}.${fractionStr}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Return the Starknet explorer URL for an address or tx hash */
|
|
44
|
+
export function explorerUrl(
|
|
45
|
+
value: string,
|
|
46
|
+
type: "address" | "tx" = "address",
|
|
47
|
+
network: "mainnet" | "sepolia" = "sepolia"
|
|
48
|
+
): string {
|
|
49
|
+
const base =
|
|
50
|
+
network === "mainnet"
|
|
51
|
+
? "https://starkscan.co"
|
|
52
|
+
: "https://sepolia.starkscan.co";
|
|
53
|
+
return `${base}/${type}/${value}`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: [
|
|
4
|
+
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
5
|
+
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
6
|
+
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
7
|
+
],
|
|
8
|
+
theme: {
|
|
9
|
+
extend: {
|
|
10
|
+
colors: {
|
|
11
|
+
stark: {
|
|
12
|
+
50: "#f0f4ff",
|
|
13
|
+
100: "#dce6ff",
|
|
14
|
+
200: "#b9cdff",
|
|
15
|
+
300: "#86a8ff",
|
|
16
|
+
400: "#4d78ff",
|
|
17
|
+
500: "#1a47fb",
|
|
18
|
+
600: "#0c2ef0",
|
|
19
|
+
700: "#0920d4",
|
|
20
|
+
800: "#0d1dac",
|
|
21
|
+
900: "#111d87",
|
|
22
|
+
950: "#0a1152",
|
|
23
|
+
},
|
|
24
|
+
accent: "#ff6b35",
|
|
25
|
+
},
|
|
26
|
+
fontFamily: {
|
|
27
|
+
mono: ["'IBM Plex Mono'", "monospace"],
|
|
28
|
+
sans: ["'DM Sans'", "sans-serif"],
|
|
29
|
+
},
|
|
30
|
+
animation: {
|
|
31
|
+
"fade-in": "fadeIn 0.4s ease forwards",
|
|
32
|
+
pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
|
33
|
+
},
|
|
34
|
+
keyframes: {
|
|
35
|
+
fadeIn: {
|
|
36
|
+
from: { opacity: "0", transform: "translateY(6px)" },
|
|
37
|
+
to: { opacity: "1", transform: "translateY(0)" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
plugins: [],
|
|
43
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"module": "esnext",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"plugins": [{ "name": "next" }],
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["./src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
21
|
+
"exclude": ["node_modules"]
|
|
22
|
+
}
|