Skip to content

Commit 62a5325

Browse files
authored
Add basic documentation, clean up code (#9)
* wip: docs * remove 05-patterns as it is not correct * move docs * clean up wallet adpater compat provider * docs(changeset): Remove debug logs from wallet-adapter-compat * more docs updates, clean up code
1 parent 6f0eabd commit 62a5325

File tree

20 files changed

+1716
-81
lines changed

20 files changed

+1716
-81
lines changed

.changeset/warm-mirrors-throw.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@macalinao/wallet-adapter-compat": patch
3+
---
4+
5+
Remove debug logs from wallet-adapter-compat

README.md

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![npm version](https://img.shields.io/npm/v/@macalinao/grill.svg)](https://www.npmjs.com/package/@macalinao/grill)
44

5-
A comprehensive toolkit for building Solana applications with React, featuring automatic account batching and caching.
5+
A comprehensive toolkit for building Solana applications with React, featuring automatic account batching, type-safe account decoding, and seamless transaction management. Built on top of [gill](https://github.com/DecalLabs/gill) and [@solana/kit](https://github.com/anza-xyz/kit).
66

77
## Packages
88

@@ -38,25 +38,35 @@ bun add @macalinao/dataloader-es
3838

3939
```tsx
4040
import { GrillProvider } from "@macalinao/grill";
41+
import { WalletAdapterCompatProvider } from "@macalinao/wallet-adapter-compat";
4142
import { createSolanaClient } from "gill";
4243
import { SolanaProvider } from "gill-react";
4344
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
4445
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
4546
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
47+
import { Toaster } from "sonner";
4648

4749
const queryClient = new QueryClient();
4850
const solanaClient = createSolanaClient({ urlOrMoniker: "mainnet-beta" });
4951

5052
function App() {
53+
const wallets = useMemo(
54+
() => [new PhantomWalletAdapter(), new SolflareWalletAdapter()],
55+
[]
56+
);
57+
5158
return (
5259
<QueryClientProvider client={queryClient}>
5360
<SolanaProvider client={solanaClient}>
5461
<ConnectionProvider endpoint="https://api.mainnet-beta.solana.com">
55-
<WalletProvider wallets={[]} autoConnect>
62+
<WalletProvider wallets={wallets} autoConnect>
5663
<WalletModalProvider>
57-
<GrillProvider>
58-
{/* Your app components */}
59-
</GrillProvider>
64+
<WalletAdapterCompatProvider>
65+
<GrillProvider>
66+
{/* Your app components */}
67+
<Toaster position="bottom-right" />
68+
</GrillProvider>
69+
</WalletAdapterCompatProvider>
6070
</WalletModalProvider>
6171
</WalletProvider>
6272
</ConnectionProvider>
@@ -66,45 +76,95 @@ function App() {
6676
}
6777
```
6878

69-
## Using Grill
79+
## Core Features
80+
81+
### 🎯 Automatic Account Batching
82+
83+
Multiple account requests are automatically batched into single RPC calls:
7084

7185
```tsx
72-
import { useAccount } from "@macalinao/grill";
86+
import { useAccount, useAssociatedTokenAccount } from "@macalinao/grill";
87+
88+
function Dashboard() {
89+
// All these requests are batched into 1 RPC call!
90+
const { data: userAccount } = useAccount({
91+
address: userAddress
92+
});
93+
94+
const { data: usdcAccount } = useAssociatedTokenAccount({
95+
mint: USDC_MINT,
96+
owner: userAddress
97+
});
98+
99+
const { data: solAccount } = useAccount({
100+
address: poolAddress
101+
});
102+
}
103+
```
73104

74-
function MyComponent() {
75-
const { data: account, isLoading, error } = useAccount('So11111111111111111111111111111111111111112');
105+
### 🔄 Simple Transaction Management
76106

77-
if (isLoading) return <div>Loading...</div>;
78-
if (error) return <div>Error: {error.message}</div>;
79-
if (!account) return <div>Account not found</div>;
107+
Send transactions with automatic status notifications:
80108

81-
return (
82-
<div>
83-
<p>Owner: {account.owner}</p>
84-
<p>Lamports: {account.lamports}</p>
85-
</div>
86-
);
109+
```tsx
110+
import { useSendTX, useKitWallet } from "@macalinao/grill";
111+
112+
function SwapButton() {
113+
const { signer } = useKitWallet();
114+
const sendTX = useSendTX();
115+
116+
const handleSwap = async () => {
117+
const instructions = buildSwapInstructions();
118+
await sendTX("Swap USDC for SOL", instructions);
119+
// Automatic toast notifications for each stage!
120+
};
121+
122+
return <button onClick={handleSwap}>Swap</button>;
87123
}
88124
```
89125

90-
## Features
126+
### 📖 Type-Safe Account Decoding
127+
128+
```tsx
129+
import { useAccount } from "@macalinao/grill";
130+
import { getTokenAccountDecoder } from "@solana-program/token";
131+
132+
function TokenBalance({ tokenAccountAddress }) {
133+
const { data: account } = useAccount({
134+
address: tokenAccountAddress,
135+
decoder: getTokenAccountDecoder()
136+
});
137+
138+
// account.data is fully typed as TokenAccount!
139+
return <div>Balance: {account?.data.amount.toString()}</div>;
140+
}
141+
```
142+
143+
## Why Grill?
144+
145+
Traditional Solana development suffers from the N+1 query problem. Every component that needs account data makes its own RPC request. Grill solves this with automatic batching using the DataLoader pattern from GraphQL.
146+
147+
**Without Grill:** 10 components = 10 RPC calls = Rate limiting & slow UX
148+
**With Grill:** 10 components = 1 batched RPC call = Fast & efficient
149+
150+
## Key Benefits
91151

92-
- 🚀 Built on gill and gill-react for modern Solana development
93-
- ⚡ Automatic account batching - multiple concurrent requests are batched into single RPC calls
94-
- 📊 React Query integration for caching and state management
95-
- 🔐 Seamless wallet adapter integration
96-
- 🎯 Type-safe with full TypeScript support
97-
- 📦 Modern ESM package structure
152+
- **10x fewer RPC calls** through automatic batching
153+
- 🎯 **Type-safe everything** - accounts, transactions, PDAs
154+
- 🔄 **Automatic cache management** with React Query
155+
- 🎨 **Beautiful transaction UX** with toast notifications
156+
- 🏗️ **Incremental migration** - works alongside existing code
157+
- 📦 **Modern stack** - Built on @solana/kit and gill
98158

99-
## Architecture
159+
## Documentation
100160

101-
Grill provides a `GrillProvider` that creates a DataLoader for batching account requests. When multiple components request account data simultaneously, Grill automatically batches these requests into a single RPC call, significantly improving performance.
161+
Check out the comprehensive guides in [`docs/grill/`](./docs/grill/):
102162

103-
The `useAccount` hook integrates with React Query to provide:
104-
- Automatic caching with configurable stale times
105-
- Background refetching
106-
- Loading and error states
107-
- Manual refetch capabilities
163+
1. [**Introduction**](./docs/grill/01-intro.md) - Why Grill and the core concepts
164+
2. [**Setup & Migration**](./docs/grill/02-setup.md) - Installation and incremental migration
165+
3. [**Reading Accounts**](./docs/grill/03-accounts.md) - Efficient account fetching with batching
166+
4. [**Making Transactions**](./docs/grill/04-transactions.md) - Clean transaction management
167+
5. [**Advanced Patterns**](./docs/grill/05-patterns.md) - Production patterns and best practices
108168

109169
## Development
110170

apps/example-dapp/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1313
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
1414
import { createRouter, RouterProvider } from "@tanstack/react-router";
15+
import { createSolanaClient } from "gill";
1516
import { SolanaProvider } from "gill-react";
17+
import type * as React from "react";
1618
import { useMemo } from "react";
1719
import { Toaster } from "sonner";
1820

@@ -30,7 +32,6 @@ declare module "@tanstack/react-router" {
3032
}
3133

3234
import "@solana/wallet-adapter-react-ui/styles.css";
33-
import { createSolanaClient } from "gill";
3435

3536
console.log(import.meta.env);
3637
console.log(import.meta.env.VITE_SOLANA_RPC_URL ?? "mainnet-beta");

apps/example-dapp/src/components/SimpleDashboard.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useAccount, useKitWallet } from "@macalinao/grill";
22
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
33
import { useSolanaClient } from "gill-react";
4+
import type * as React from "react";
45
import { useState } from "react";
56
import { toast } from "sonner";
67
import { Button } from "@/components/ui/button";
@@ -12,15 +13,15 @@ import {
1213
CardTitle,
1314
} from "@/components/ui/card";
1415

15-
export function SimpleDashboard() {
16+
export const SimpleDashboard: React.FC = () => {
1617
const { signer } = useKitWallet();
1718
const { rpc } = useSolanaClient();
1819
// Only fetch account if signer is available
1920
const accountQuery = useAccount({ address: signer ? signer.address : null });
2021
const [slot, setSlot] = useState<number | null>(null);
2122
const [loading, setLoading] = useState(false);
2223

23-
const handleRefreshBalance = () => {
24+
const handleRefreshBalance = (): void => {
2425
if (!signer) {
2526
toast.error("Please connect your wallet first");
2627
return;
@@ -30,7 +31,7 @@ export function SimpleDashboard() {
3031
toast.success("Balance refreshed");
3132
};
3233

33-
const handleGetSlot = async () => {
34+
const handleGetSlot = async (): Promise<void> => {
3435
setLoading(true);
3536
try {
3637
const currentSlot = await rpc.getSlot().send();
@@ -122,4 +123,4 @@ export function SimpleDashboard() {
122123
</div>
123124
</div>
124125
);
125-
}
126+
};

apps/example-dapp/src/components/layout/examples/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Link, Outlet, useLocation } from "@tanstack/react-router";
22
import { Coins, LayoutDashboard } from "lucide-react";
3-
import type { FC } from "react";
3+
import type * as React from "react";
44
import {
55
Sidebar,
66
SidebarContent,
@@ -40,7 +40,9 @@ const exampleNavItems: ExampleNavItem[] = [
4040
},
4141
];
4242

43-
export const ExamplesLayout: FC<ExamplesLayoutProps> = ({ className }) => {
43+
export const ExamplesLayout: React.FC<ExamplesLayoutProps> = ({
44+
className,
45+
}) => {
4446
const location = useLocation();
4547

4648
return (

apps/example-dapp/src/components/layout/main/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
22
import { Link, Outlet } from "@tanstack/react-router";
3-
import type { FC } from "react";
3+
import type * as React from "react";
44
import { ThemeToggle } from "@/components/theme-toggle";
55
import {
66
NavigationMenu,
@@ -14,7 +14,7 @@ export interface MainLayoutProps {
1414
className?: string;
1515
}
1616

17-
export const MainLayout: FC<MainLayoutProps> = ({ className }) => {
17+
export const MainLayout: React.FC<MainLayoutProps> = ({ className }) => {
1818
return (
1919
<div className={cn("flex min-h-screen flex-col bg-background", className)}>
2020
<header className="fixed top-0 z-50 flex h-16 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">

apps/example-dapp/src/components/theme-provider.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type * as React from "react";
12
import { createContext, useContext, useEffect, useState } from "react";
23

34
type Theme = "dark" | "light" | "system";
@@ -20,12 +21,12 @@ const initialState: ThemeProviderState = {
2021

2122
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
2223

23-
export function ThemeProvider({
24+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
2425
children,
2526
defaultTheme = "system",
2627
storageKey = "vite-ui-theme",
2728
...props
28-
}: ThemeProviderProps) {
29+
}) => {
2930
const [theme, setTheme] = useState<Theme>(
3031
() => (localStorage.getItem(storageKey) ?? defaultTheme) as Theme,
3132
);
@@ -61,10 +62,10 @@ export function ThemeProvider({
6162
{children}
6263
</ThemeProviderContext.Provider>
6364
);
64-
}
65+
};
6566

6667
// eslint-disable-next-line react-refresh/only-export-components
67-
export const useTheme = () => {
68+
export const useTheme = (): ThemeProviderState => {
6869
const context = useContext(ThemeProviderContext);
6970
return context;
7071
};

apps/example-dapp/src/components/theme-toggle.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Moon, Sun } from "lucide-react";
2+
import type * as React from "react";
23
import { useTheme } from "@/components/theme-provider";
34
import { Button } from "@/components/ui/button";
45

5-
export function ThemeToggle() {
6+
export const ThemeToggle: React.FC = () => {
67
const { theme, setTheme } = useTheme();
78

89
return (
@@ -18,4 +19,4 @@ export function ThemeToggle() {
1819
<span className="sr-only">Toggle theme</span>
1920
</Button>
2021
);
21-
}
22+
};

apps/example-dapp/src/components/ui/input-token-amount.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FC } from "react";
1+
import type * as React from "react";
22
import { Button } from "@/components/ui/button";
33
import { Input } from "@/components/ui/input";
44
import { cn } from "@/lib/utils";
@@ -14,7 +14,7 @@ export interface InputTokenAmountProps {
1414
disabled?: boolean;
1515
}
1616

17-
export const InputTokenAmount: FC<InputTokenAmountProps> = ({
17+
export const InputTokenAmount: React.FC<InputTokenAmountProps> = ({
1818
token,
1919
value,
2020
onChange,
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { createRootRoute } from "@tanstack/react-router";
22
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
3+
import type * as React from "react";
34
import { MainLayout } from "@/components/layout/main";
45

6+
const RootComponent: React.FC = () => (
7+
<>
8+
<MainLayout />
9+
<TanStackRouterDevtools position="bottom-left" />
10+
</>
11+
);
12+
513
export const Route = createRootRoute({
6-
component: () => (
7-
<>
8-
<MainLayout />
9-
<TanStackRouterDevtools position="bottom-left" />
10-
</>
11-
),
14+
component: RootComponent,
1215
});

0 commit comments

Comments
 (0)