-
Notifications
You must be signed in to change notification settings - Fork 0
WIP: minimal single-feature transport examples (Vite + React) #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| VITE_JAW_API_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Example: Core SDK, no wagmi (popup) | ||
|
|
||
| Integration **without wagmi** — uses the core SDK's EIP-1193 provider directly | ||
| (`JAW.create().provider.request(...)`), with the **popup** transport. Post-connect | ||
| action: read the smart account's **wallet capabilities** (EIP-5792 | ||
| `wallet_getCapabilities`) — gasless/paymaster, atomic batching, permissions, per chain. | ||
|
|
||
| ## Run | ||
|
|
||
| From the repo root, after `bun install`: | ||
|
|
||
| ```bash | ||
| npx nx dev core-popup-capabilities | ||
| ``` | ||
|
|
||
| Set your API key in `examples/core-popup-capabilities/.env.local`: | ||
|
|
||
| ```bash | ||
| VITE_JAW_API_KEY=<your-key> | ||
| # VITE_KEYS_URL=http://localhost:3001 # optional (local keys app) | ||
| ``` | ||
|
|
||
| ## What it shows | ||
|
|
||
| - No wagmi / React Query — just `JAW.create()` and `provider.request(...)`. | ||
| - `eth_requestAccounts` connects (popup); `wallet_getCapabilities` reads what the account supports. | ||
| - The same provider also serves `personal_sign`, `wallet_sendCalls`, `wallet_grantPermissions`, etc. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>JAW Example — Core SDK (popup)</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "name": "core-popup-capabilities", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 5182", | ||
| "build": "vite build", | ||
| "typecheck": "tsc --noEmit" | ||
| }, | ||
| "dependencies": { | ||
| "@jaw.id/core": "latest", | ||
| "react": "19.2.6", | ||
| "react-dom": "19.2.6" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^19.2.0", | ||
| "@types/react-dom": "^19.2.0", | ||
| "@vitejs/plugin-react": "^6.0.0", | ||
| "typescript": "~5.9.2", | ||
| "vite": "^8.0.0" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { useState } from 'react'; | ||
| import { JAW } from '@jaw.id/core'; | ||
|
|
||
| /** | ||
| * Integration WITHOUT wagmi: the core SDK's EIP-1193 provider directly. Uses the | ||
| * POPUP transport. Post-connect action: read the smart account's wallet | ||
| * capabilities (EIP-5792 `wallet_getCapabilities`) — gasless/paymaster, atomic | ||
| * batching, permissions, etc., per chain. | ||
| */ | ||
| const sdk = JAW.create({ | ||
| apiKey: import.meta.env.VITE_JAW_API_KEY ?? '', | ||
| appName: 'JAW Example — Core (popup)', | ||
| defaultChainId: 84532, // Base Sepolia | ||
| preference: { | ||
| ...(import.meta.env.VITE_KEYS_URL ? { keysUrl: import.meta.env.VITE_KEYS_URL } : {}), | ||
| showTestnets: true, | ||
| transportMode: 'popup', | ||
| }, | ||
| }); | ||
|
|
||
| export function App() { | ||
| const [address, setAddress] = useState<string>(); | ||
| const [capabilities, setCapabilities] = useState<unknown>(); | ||
| const [error, setError] = useState<string>(); | ||
|
|
||
| const connect = async () => { | ||
| setError(undefined); | ||
| try { | ||
| const accounts = (await sdk.provider.request({ method: 'eth_requestAccounts' })) as readonly `0x${string}`[]; | ||
| setAddress(accounts[0]); | ||
| } catch (e) { | ||
| setError(e instanceof Error ? e.message : String(e)); | ||
| } | ||
| }; | ||
|
|
||
| const getCapabilities = async () => { | ||
| if (!address) return; | ||
| setError(undefined); | ||
| setCapabilities(undefined); | ||
| try { | ||
| const result = await sdk.provider.request({ | ||
| method: 'wallet_getCapabilities', | ||
| params: [address as `0x${string}`], | ||
| }); | ||
| setCapabilities(result); | ||
| } catch (e) { | ||
| setError(e instanceof Error ? e.message : String(e)); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <main style={{ fontFamily: 'system-ui', maxWidth: 560, margin: '3rem auto', padding: '0 1rem' }}> | ||
| <h1>JAW — Core SDK (no wagmi)</h1> | ||
| <p style={{ color: '#666' }}>EIP-1193 provider directly · popup transport</p> | ||
|
|
||
| {!address ? ( | ||
| <button onClick={connect}>Connect with JAW</button> | ||
| ) : ( | ||
| <> | ||
| <p> | ||
| Connected: <code>{address}</code> | ||
| </p> | ||
| <button onClick={getCapabilities}>Get wallet capabilities</button> | ||
| </> | ||
| )} | ||
|
|
||
| {capabilities !== undefined && ( | ||
| <pre | ||
| style={{ | ||
| marginTop: 16, | ||
| padding: 12, | ||
| background: '#f4f4f5', | ||
| borderRadius: 8, | ||
| whiteSpace: 'pre-wrap', | ||
| overflowWrap: 'anywhere', | ||
| }} | ||
| > | ||
| {JSON.stringify(capabilities, null, 2)} | ||
| </pre> | ||
| )} | ||
| {error && <p style={{ color: 'crimson' }}>⚠️ {error}</p>} | ||
| </main> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { StrictMode } from 'react'; | ||
| import { createRoot } from 'react-dom/client'; | ||
| import { App } from './App'; | ||
|
|
||
| // No wagmi / react-query providers — this example talks to the EIP-1193 provider directly. | ||
| createRoot(document.getElementById('root')!).render( | ||
| <StrictMode> | ||
| <App /> | ||
| </StrictMode> | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "lib": ["ES2022", "DOM", "DOM.Iterable"], | ||
| "module": "ESNext", | ||
| "moduleResolution": "bundler", | ||
| "jsx": "react-jsx", | ||
| "strict": true, | ||
| "skipLibCheck": true, | ||
| "noEmit": true, | ||
| "types": ["vite/client"], | ||
| "verbatimModuleSyntax": true | ||
| }, | ||
| "include": ["src"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { defineConfig } from 'vite'; | ||
| import react from '@vitejs/plugin-react'; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [react()], | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| VITE_JAW_API_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Example: ENS identity (wagmi · iframe) | ||
|
|
||
| wagmi `jaw()` connector with the default embedded iframe. Post-connect action: | ||
| resolve and display the connected account's **ENS subname** — the JustaName | ||
| identity layer that travels with the user across dApps. | ||
|
|
||
| ## Run | ||
|
|
||
| From the repo root, after `bun install`: | ||
|
|
||
| ```bash | ||
| npx nx dev wagmi-ens-identity | ||
| ``` | ||
|
|
||
| Set your API key in `examples/wagmi-ens-identity/.env.local`: | ||
|
|
||
| ```bash | ||
| VITE_JAW_API_KEY=<your-key> | ||
| # VITE_RPC_URL=https://sepolia.base.org # optional (defaults to Base Sepolia) | ||
| # VITE_KEYS_URL=http://localhost:3001 # optional (local keys app) | ||
| ``` | ||
|
|
||
| ## What it shows | ||
|
|
||
| - Connect, then reverse-resolve the address → ENS subname via the JustaName endpoint. | ||
| - Subnames are off-chain, so resolution uses `api.justaname.id` (not a plain on-chain lookup). | ||
| - Shows the user's portable identity right after connect. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>JAW Example — ENS identity (wagmi, iframe)</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "wagmi-ens-identity", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 5185", | ||
| "build": "vite build", | ||
| "typecheck": "tsc --noEmit" | ||
| }, | ||
| "dependencies": { | ||
| "@jaw.id/core": "latest", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [WARNING] dependency-risk Dependency "@jaw.id/core" uses an unpinned version spec "latest". Pin to a specific semver range.
|
||
| "@jaw.id/wagmi": "latest", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [WARNING] dependency-risk Dependency "@jaw.id/wagmi" uses an unpinned version spec "latest". Pin to a specific semver range.
|
||
| "@tanstack/react-query": "^5.62.0", | ||
| "react": "19.2.6", | ||
| "react-dom": "19.2.6", | ||
| "viem": "^2.38.2", | ||
| "wagmi": "3.0.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^19.2.0", | ||
| "@types/react-dom": "^19.2.0", | ||
| "@vitejs/plugin-react": "^6.0.0", | ||
| "typescript": "~5.9.2", | ||
| "vite": "^8.0.0" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { useState } from 'react'; | ||
| import { useAccount, useChainId, useConnect, useDisconnect } from 'wagmi'; | ||
|
|
||
| // JustaName subnames are off-chain, so resolve them via the JustaName reverse | ||
| // endpoint (not a plain on-chain ENS lookup). Needs an RPC URL for the chain. | ||
| const RPC_URL = import.meta.env.VITE_RPC_URL ?? 'https://sepolia.base.org'; | ||
|
|
||
| async function reverseResolveEnsName(address: string, chainId: number, rpcUrl: string): Promise<string | null> { | ||
| const coinType = chainId === 1 ? 60 : 2147483648 + chainId; // ENSIP-11 | ||
| const url = `https://api.justaname.id/ens/v2/reverse?rpcUrl=${encodeURIComponent(rpcUrl)}&address=${encodeURIComponent( | ||
| address | ||
| )}&coinType=${coinType}`; | ||
| try { | ||
| const res = await fetch(url); | ||
| if (!res.ok) return null; | ||
| const body = (await res.json()) as { result?: { data?: { name?: string | null } | null } }; | ||
| return body.result?.data?.name ?? null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Post-connect action: resolve and show the connected account's ENS subname (the | ||
| * JustaName identity layer). Integration: wagmi connector, default iframe transport. | ||
| */ | ||
| export function App() { | ||
| const { address, isConnected } = useAccount(); | ||
| const chainId = useChainId(); | ||
| const { connect, connectors, isPending } = useConnect(); | ||
| const { disconnect } = useDisconnect(); | ||
|
|
||
| const [name, setName] = useState<string | null>(); | ||
| const [loading, setLoading] = useState(false); | ||
| const [error, setError] = useState<string>(); | ||
|
|
||
| const jawConnector = connectors.find((c) => c.id === 'jaw'); | ||
|
|
||
| const lookup = async () => { | ||
| if (!address) return; | ||
| setError(undefined); | ||
| setName(undefined); | ||
| setLoading(true); | ||
| try { | ||
| setName(await reverseResolveEnsName(address, chainId, RPC_URL)); | ||
| } catch (e) { | ||
| setError(e instanceof Error ? e.message : String(e)); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <main style={{ fontFamily: 'system-ui', maxWidth: 560, margin: '3rem auto', padding: '0 1rem' }}> | ||
| <h1>JAW — ENS identity</h1> | ||
| <p style={{ color: '#666' }}>wagmi connector · embedded iframe · JustaName subname</p> | ||
|
|
||
| {!isConnected ? ( | ||
| <button | ||
| disabled={isPending || !jawConnector} | ||
| onClick={() => jawConnector && connect({ connector: jawConnector })} | ||
| > | ||
| {isPending ? 'Connecting…' : 'Connect with JAW'} | ||
| </button> | ||
| ) : ( | ||
| <> | ||
| <p> | ||
| Connected: <code>{address}</code> | ||
| </p> | ||
| <button disabled={loading} onClick={lookup}> | ||
| {loading ? 'Resolving…' : 'Resolve my ENS name'} | ||
| </button>{' '} | ||
| <button onClick={() => disconnect()}>Disconnect</button> | ||
| </> | ||
| )} | ||
|
|
||
| {name !== undefined && ( | ||
| <p style={{ marginTop: 16, fontSize: 18 }}> | ||
| {name ? ( | ||
| <> | ||
| 🪪 <strong>{name}</strong> | ||
| </> | ||
| ) : ( | ||
| <span style={{ color: '#666' }}>No ENS subname found for this address.</span> | ||
| )} | ||
| </p> | ||
| )} | ||
| {error && <p style={{ color: 'crimson' }}>⚠️ {error}</p>} | ||
| </main> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[WARNING] dependency-risk
dr-unpinnedDependency "@jaw.id/core" uses an unpinned version spec "latest". Pin to a specific semver range.
latest