Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/with-solidstart/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generate using `openssl rand -hex 32`
SESSION_SECRET=your-32char-secret-key-goes-here
5 changes: 5 additions & 0 deletions examples/with-solidstart/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.data
pnpm-lock.yaml
package-lock.json
.env
63 changes: 63 additions & 0 deletions examples/with-solidstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[![Banner](https://github.com/blocknative/web3-onboard/blob/develop/assets/core.svg)](https://web3onboard.thirdweb.com)

# SolidStart Example

Everything you need to integrate Web3Onboard in [SolidStart](https://start.solidjs.com)

## Features

- **SSR Compliant**: Web3 code loads only on the client
- **Auth Context**: A reactive context to monitor wallet changes, handle signatures, and more
- **Database**: Includes `unstorage`, a lightweight file-based DB
- **Client-Only**: Easily isolate client-side logic for Web3 interactions

## Getting Started

1. Rename `.env.example` to `.env`. For production, generate a secure `SESSION_SECRET` with

```bash
openssl rand -hex 32
```

2. Install dependencies

```bash
# use preferred package manager
npm install
```

3. Run the development server

```bash
# use preferred package manager
npm run dev
```

For more details, refer to SolidStart's [README.md](https://github.com/solidjs/solid-start/blob/main/packages/start/README.md)

## Usage

To ensure Web3-related logic runs only on the client, use the `clientOnly` utility from SolidStart.
Here are two ways to implement client-only code:

1. **In Component** (e.g. for a component showing eth balance)

```jsx
import { clientOnly } from "@solidjs/start/client";

const ClientComponent = clientOnly(() => import("./ClientOnlyComponent"));
```

2. **In Routes** (e.g. for a `/swap` page)

```jsx
import { clientOnly } from "@solidjs/start/client";

export default clientOnly(async () => ({ default: MyPage }));
```

For more details, refer to the `clientOnly` [documentation](https://docs.solidjs.com/solid-start/reference/client/client-only#clientonly).

<div align="center">
<img src="public/logo.png" width="350px">
</div>
7 changes: 7 additions & 0 deletions examples/with-solidstart/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
server: { preset: "" }, // your deployment
vite: { plugins: [tailwindcss()] }
});
26 changes: 26 additions & 0 deletions examples/with-solidstart/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"type": "module",
"scripts": {
"dev": "vinxi dev --port 3001",
"build": "vinxi build",
"start": "vinxi start"
},
"dependencies": {
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.3",
"@solidjs/start": "^1.2.0",
"@web3-onboard/core": "^2.24.1",
"@web3-onboard/injected-wallets": "^2.11.3",
"ethers": "^6.15.0",
"solid-js": "^1.9.10",
"unstorage": "1.17.1",
"vinxi": "^0.5.8"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.16",
"tailwindcss": "^4.1.16"
},
"engines": {
"node": ">=22"
}
}
92 changes: 92 additions & 0 deletions examples/with-solidstart/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/with-solidstart/public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/with-solidstart/public/onboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions examples/with-solidstart/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@import "tailwindcss";

#app {
@apply flex flex-col items-center justify-center min-h-screen bg-gray-50 gap-16 px-4 select-none noscript:hidden;
}

h1 {
@apply uppercase text-6xl text-sky-700 font-thin;
}

button {
@apply cursor-pointer;
}

.loader {
@apply animate-spin border-current border-t-transparent text-current rounded-full;
}
33 changes: 33 additions & 0 deletions examples/with-solidstart/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Suspense } from "solid-js";
import { Router, type RouteDefinition } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { MetaProvider } from "@solidjs/meta";
import { querySession } from "./auth";
import AuthProvider from "./auth/Provider";
import Nav from "./components/Nav";
import ErrorNotification from "./components/Error";
import "./app.css";

export const route: RouteDefinition = {
preload: ({ location }) => querySession(location.pathname)
};

export default function App() {
return (
<Router
root={(props) => (
<MetaProvider>
<AuthProvider>
<Suspense>
<Nav />
{props.children}
<ErrorNotification />
</Suspense>
</AuthProvider>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}
90 changes: 90 additions & 0 deletions examples/with-solidstart/src/auth/Provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { createEffect, createResource, on, type ParentProps } from "solid-js";
import {
useLocation,
createAsync,
useAction,
useSearchParams
} from "@solidjs/router";
import injectedWallets from "@web3-onboard/injected-wallets";
import { signOutAction, querySession } from "~/auth";
import { sign, authWalletAction, addWalletAction } from "~/auth/web3";
import useWeb3Onboard, { load } from "~/web3";
import Context from "./context";

export default function AuthProvider(props: ParentProps) {
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const session = createAsync(() => querySession(location.pathname), {
deferStream: true
});

const authWallet = useAction(authWalletAction);
const addWallet = useAction(addWalletAction);
const signOut = useAction(signOutAction);
const signedIn = () => Boolean(session()?.id);

const onboard = useWeb3Onboard({
wallets: [injectedWallets()],
connect: { autoConnectLastWallet: true },
chains: [{ id: "0x1" }, { id: "0x2105" }],
appMetadata: {
name: "SolidStart",
description: "Web3-onboard template",
icon: "favicon.svg"
}
});

createResource(
() => searchParams.login === "true" && !signedIn() && onboard,
async (onboard) => {
try {
const [wallet] = await onboard.connectWallet();
if (!wallet?.provider) throw new Error("Wallet connection failed");
const address = await sign(wallet.provider);
const r = searchParams.redirect;
await authWallet(address, Array.isArray(r) ? r[0] : r);
} catch (err) {
setSearchParams({
error: err instanceof Error ? err.message : "",
login: ""
});
}
}
);

// with SSR enabled make sure to only access the web3 resource on the client
const [web3] = createResource(onboard?.connectedWallet, load);

createEffect(
on(
() => onboard?.walletAddress(),
async (curr, prev) => {
const saved = session()?.wallets;
if (!saved?.length || !prev) return;
if (!curr) await signOut();
if (curr && curr !== prev) {
try {
const { provider } = onboard!.connectedWallet();
const verified = saved.includes(curr) ? curr : await sign(provider);
await addWallet(verified);
} catch (err) {
setSearchParams({ error: err instanceof Error ? err.message : "" });
}
}
},
{ defer: true }
)
);

const logout = async () => {
const wallet = onboard?.connectedWallet();
if (wallet) await onboard!.disconnectWallet({ label: wallet.label });
return signOut();
};

return (
<Context.Provider value={{ session, signedIn, logout, web3 }}>
{props.children}
</Context.Provider>
);
}
13 changes: 13 additions & 0 deletions examples/with-solidstart/src/auth/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext, type Resource } from "solid-js";
import type { AccessorWithLatest } from "@solidjs/router";
import type { Session } from "~/auth/server";
import type { Web3 } from "~/web3";

interface Context {
session: AccessorWithLatest<Session | undefined>;
signedIn: () => boolean;
logout: () => Promise<never>;
web3: Resource<Web3>;
}

export default createContext<Context>();
35 changes: 35 additions & 0 deletions examples/with-solidstart/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useContext } from "solid-js";
import { query, redirect, action } from "@solidjs/router";
import { getSession } from "./server";
import context from "./context";

const PROTECTED_ROUTES = ["/"];
const FALLBACK_PAGE = "/about";

export const protectedRoute = (path: string) =>
PROTECTED_ROUTES.some((route) =>
route.endsWith("/*")
? path.startsWith(route.slice(0, -2))
: path === route || path.startsWith(route + "/")
);

export const querySession = query(async (path: string) => {
"use server";
const { data: session } = await getSession();
if (session.wallets?.length) return session;
if (protectedRoute(path))
throw redirect(`${FALLBACK_PAGE}?login=true&redirect=${path}`);
}, "querySession");

export const signOutAction = action(async () => {
"use server";
const session = await getSession();
await session.update({ wallets: undefined });
throw redirect(FALLBACK_PAGE, { revalidate: querySession.key });
});

export default function useAuth() {
const ctx = useContext(context);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}
Loading