# JAW Quickstart & Guides > Getting started with JAW smart accounts - setup, tutorials, and common use cases. **This file is self-contained.** You have everything needed to help with this topic. Do NOT fetch other llms-*.txt files unless the user explicitly asks about a different topic. ## Key Info - **Package:** `@jaw.id/wagmi (React) or @jaw.id/core (vanilla JS)` - **Install:** `npm install @jaw.id/wagmi wagmi @tanstack/react-query` - **Dashboard:** https://dashboard.jaw.id - **Docs:** https://docs.jaw.id ## Quick Example ```typescript // 1. Get API key at https://dashboard.jaw.id // 2. Install packages // 3. Configure connector (see full docs below) ``` --- ## Embed Stablecoin Payments Source: https://docs.jaw.id/guides/embed-stablecoin-payments ## Embed Stablecoin Payments Embed onchain stablecoin payments directly into your telecom, neobank, or fintech application. No wallet UI, no seed phrases - just passkey authentication and instant USDC transfers. :::info\[Using Provider or Wagmi?] This guide is for **headless mode** using the `Account` class directly. If you're using the JAW Provider or Wagmi connector, stablecoin gas payments are handled automatically — the built-in UI modals let users select fee tokens across all supported chains with no configuration needed. ::: ### Why Stablecoin Payments? Traditional cross-border payments are slow and expensive. When you send $1,000 to a contractor in Brazil using traditional rails, the costs add up quickly: payment processor fees ($10–$40), intermediary bank cuts (0.1%–4.0% FX spread), and 3-5 days of settlement time. By the time the payment clears, the recipient has lost $80 to fees. **Stablecoins change this entirely:** \| Traditional Rails | Stablecoin Rails | \|-------------------|------------------| \| 3-5 business days | Seconds | \| $40-80 in fees per $1,000 | \~$0.01-0.10 | \| Bank hours only | 24/7/365 | \| Currency volatility risk | Dollar-denominated stability | \| Opaque reconciliation | On-chain transparency | With JAW, you can embed these benefits directly into your application. Users authenticate with passkeys (no seed phrases), pay gas fees in USDC (no ETH needed), and send payments that settle in seconds. ### Prerequisites Install the core SDK: :::code-group ```bash [npm] npm install @jaw.id/core ``` ```bash [pnpm] pnpm add @jaw.id/core ``` ```bash [yarn] yarn add @jaw.id/core ``` ```bash [bun] bun add @jaw.id/core ``` ::: Get your API key at **[dashboard.jaw.id](https://dashboard.jaw.id/)**. ### Configuration Set up your account configuration with the ERC-20 paymaster for USDC gas payments: ```typescript import { Account, JAW_PAYMASTER_URL } from '@jaw.id/core'; const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base const config = { chainId: 8453, // Base apiKey: 'YOUR_API_KEY', }; const paymasterUrl = `${JAW_PAYMASTER_URL}?chainId=8453&api-key=YOUR_API_KEY`; ``` ### Account Management Since you're using the `Account` class directly (headless mode), no JAW UI is rendered. The only prompt users see is the native browser/OS passkey dialog. :::code-group ```typescript [Create Account] // New user: create account with passkey const account = await Account.create(config, { username: 'user@yourapp.com' // Your app's user identifier }); // Store the credentialId for future sign-ins const { credentialId } = account.getMetadata(); // Save credentialId in your own backend/database (not an SDK function) await storeCredentialId('user@yourapp.com', credentialId); ``` ```typescript [Sign In] // Returning user: look up the stored credentialId from your backend/database const credentialId = await getCredentialId('user@yourapp.com'); const account = await Account.get(config, credentialId); ``` ::: :::info Your application manages the `username → credentialId` mapping. When a user creates an account, store their `credentialId` in your backend. On subsequent sign-ins, look up the `credentialId` by username and pass it to `Account.get()`. ::: ### Send a USDC Payment Send USDC with gas fees paid in USDC - users don't need ETH: ```typescript import { encodeFunctionData, erc20Abi, parseUnits } from 'viem'; // Send 10 USDC to a recipient const { id, chainId } = await account.sendCalls( [{ to: USDC_ADDRESS, data: encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [ '0xRecipientAddress...', // Recipient address parseUnits('10', 6) // 10 USDC (6 decimals) ] }) }], undefined, // options paymasterUrl, // ERC-20 paymaster { token: USDC_ADDRESS } // Pay gas in USDC ); console.log('Transaction submitted:', id); ``` The SDK automatically handles the USDC approval for gas payment - no extra step required. ### Check Transaction Status Poll for transaction confirmation: ```typescript const status = account.getCallStatus(id); if (status) { switch (status.status) { case 100: console.log('Pending...'); break; case 200: console.log('Completed!', status.receipts?.[0]?.transactionHash); break; case 400: console.log('Failed (off-chain)'); break; case 500: console.log('Reverted (on-chain)'); break; } } ``` ### Batch Payments Send multiple payments in a single transaction for payroll or mass payouts: ```typescript const recipients = [ { address: '0xAlice...', amount: '500' }, { address: '0xBob...', amount: '750' }, { address: '0xCharlie...', amount: '300' }, ]; const calls = recipients.map(({ address, amount }) => ({ to: USDC_ADDRESS, data: encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [address, parseUnits(amount, 6)] }) })); const { id } = await account.sendCalls( calls, undefined, paymasterUrl, { token: USDC_ADDRESS } ); ``` All transfers execute atomically - either all succeed or all fail together. ### Supported Networks See [Supported Networks](/supported-networks) for the full list. ### Related * [Account.create()](/account/create) - Create accounts with passkeys * [Account.get()](/account/get) - Sign in to existing accounts * [account.sendCalls()](/account/sendCalls) - Send batched transactions * [Gas Sponsoring](/guides/gas-sponsoring) - Fully sponsor gas for users * [Subscription Payments](/guides/subscription) - Recurring payments with permissions ## Paymaster & Gas Sponsoring Source: https://docs.jaw.id/guides/gas-sponsoring ## Paymaster & Gas Sponsoring Let users interact with your app without holding ETH or native tokens first. Paymasters allow you to sponsor gas fees for your users entirely. **What you can do:** * **Sponsor Gas** - Cover transaction costs for your users * **Multi-Chain** - Same experience across all supported networks * **Set Policies** - Control which transactions you sponsor :::info\[How Gas Works Without This Config] Without a sponsoring paymaster, the built-in UI modals let users choose to pay gas in stablecoins (like USDC,USDT...) across all supported chains — no configuration needed. Adding a sponsoring paymaster for a chain **overrides** this and covers gas costs entirely, so users pay nothing. ::: :::warning **Requirement:** This guide uses EIP-7677 compatible paymasters with EntryPoint v0.8. ::: ### 1. Choose a Provider Pick a paymaster provider and create an account. Generate your API key in their dashboard and deposit funds to cover gas costs. **Compatible Providers:** \| Provider | Dashboard | \|----------|-----------| \| [Pimlico](https://pimlico.io) | [dashboard.pimlico.io](https://dashboard.pimlico.io) | \| [Etherspot](https://etherspot.io) | [developer.etherspot.io](https://developer.etherspot.io/dashboard) | :::info Ensure your provider supports **EntryPoint v0.8** and **EIP-7677**. Not all providers support this yet. ::: ### 2. Integration Path Pick your integration method: \| Approach | Best For | \|----------|----------| \| **Wagmi** | React apps already using wagmi connectors. | \| **Core SDK** | Custom implementations or non-React apps. | ### 3. Enable Paymasters Add the `paymasters` configuration to your existing JAW setup. Use the URL from your provider along with your API key. :::code-group ```typescript [Wagmi] import { createConfig, http } from 'wagmi'; import { base } from 'wagmi/chains'; import { jaw } from '@jaw.id/wagmi'; export const config = createConfig({ chains: [base], connectors: [ jaw({ apiKey: 'YOUR_JAW_API_KEY', appName: 'My App', // Add paymasters configuration paymasters: { 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_PIMLICO_KEY' }, }, }), ], transports: { [base.id]: http(), }, }); ``` ```typescript [Core SDK] import { JAW } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'YOUR_JAW_API_KEY', appName: 'My App', defaultChainId: 8453, // Add paymasters configuration paymasters: { 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_PIMLICO_KEY' }, }, }); ``` ::: :::info If you haven't set up JAW yet, start with the [Quickstart](/guides/quickstart) guide first. ::: ### 4. Configuration Format Each network is configured by chain ID with its own paymaster URL. The optional `context` object passes sponsorship policies or provider-specific settings. ```typescript paymasters: { [chainId: number]: { url: string; // Paymaster service URL (required) context?: Record; // Additional context sent with requests (optional) } } ``` ### 5. Add Sponsorship Policies Control which transactions you sponsor with policies. Create these in your paymaster provider's dashboard. **Common policy options:** * Sponsor only new users * Set daily limits per user * Sponsor during active time periods * Whitelist specific contracts * Blacklist certain operations **To use a policy:** 1. Log into your paymaster provider dashboard 2. Create a sponsorship policy with your rules 3. Copy the policy ID from the policy page 4. Add it to your paymaster configuration's `context` :::code-group ```typescript [Wagmi] import { jaw } from '@jaw.id/wagmi'; const connector = jaw({ apiKey: 'YOUR_JAW_API_KEY', paymasters: { 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_PIMLICO_KEY', // Add sponsorship policy (key name varies by provider) context: { sponsorshipPolicyId: 'sp_your_policy_id', // Pimlico }, }, }, }); ``` ```typescript [Core SDK] import { JAW } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'YOUR_JAW_API_KEY', paymasters: { 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_PIMLICO_KEY', // Add sponsorship policy (key name varies by provider) context: { sponsorshipPolicyId: 'sp_your_policy_id', // Pimlico }, }, }, }); ``` ::: :::warning The `context` keys are provider-specific. Check your paymaster provider's documentation for the correct format. ::: ### 6. Multi-Chain Configuration Support multiple blockchains with gas sponsorship. Set up a paymaster for each network so users get the same smooth experience everywhere. :::code-group ```typescript [Wagmi] import { jaw } from '@jaw.id/wagmi'; const connector = jaw({ apiKey: 'YOUR_JAW_API_KEY', paymasters: { 1: { url: 'https://api.pimlico.io/v2/1/rpc?apikey=...' }, // Ethereum Mainnet 10: { url: 'https://api.pimlico.io/v2/10/rpc?apikey=...' }, // Optimism 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=...' }, // Base 42161: { url: 'https://api.pimlico.io/v2/42161/rpc?apikey=...' }, // Arbitrum }, }); ``` ```typescript [Core SDK] import { JAW } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'YOUR_JAW_API_KEY', paymasters: { 1: { url: 'https://api.pimlico.io/v2/1/rpc?apikey=...' }, // Ethereum Mainnet 10: { url: 'https://api.pimlico.io/v2/10/rpc?apikey=...' }, // Optimism 8453: { url: 'https://api.pimlico.io/v2/8453/rpc?apikey=...' }, // Base 42161: { url: 'https://api.pimlico.io/v2/42161/rpc?apikey=...' }, // Arbitrum }, }); ``` ::: :::info Sponsorship policies are configured per-provider. Check your provider's documentation for chain-specific settings. ::: ### Configuration Reference \| Property | Type | Required | Description | \|----------|------|----------|-------------| \| `url` | `string` | Yes | The paymaster service endpoint URL | \| `context` | `Record` | No | Additional context passed to the paymaster (e.g., `sponsorshipPolicyId`) | ### Related * [paymasters Configuration](/configuration/paymasters) - Full configuration reference * [Quickstart](/guides/quickstart) - Initial JAW setup * [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Batched transactions with sponsorship * [Supported Networks](/supported-networks) - Available chains ## Onchain Identity & Profiles Source: https://docs.jaw.id/guides/onchain-identity ## Onchain Identity & Profiles Replace wallet addresses with readable usernames powered by ENS. Instead of `0x7b2C...`, users get `alice.yourapp.eth` - making your app more human-friendly. JAW handles subname registration during account creation. For profile resolution and advanced ENS features, use the `@justaname.id/sdk` package. **What you can do:** * **Buy & Configure** - Get your ENS domain from JAW dashboard * **Issue Subnames** - Users claim usernames under your domain during onboarding * **Resolve Profiles** - Look up avatars, bios, social links, and multichain addresses * **Update Profiles** - Let users manage their profile records :::info For advanced ENS features, check the [JustaName documentation](https://docs.justaname.id). ::: ### 1. Configure ENS Domain Before using ENS features, you need to configure your domain in the JAW Dashboard. **[Set up your ENS domain at dashboard.jaw.id](https://dashboard.jaw.id)** ### 2. Integration Path \| Approach | Best For | \|----------|----------| \| **Wagmi** | React apps using wagmi connectors | \| **Core SDK** | Custom implementations or non-React apps | ### 3. Add Domain to Configuration Add the `ens` option to your existing JAW configuration. Users will automatically be prompted to claim a username (e.g., `alice.yourdomain.eth`) during account creation. :::code-group ```typescript [Wagmi] import { createConfig, http } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import { jaw } from '@jaw.id/wagmi'; export const config = createConfig({ chains: [mainnet], connectors: [ jaw({ apiKey: 'YOUR_API_KEY', appName: 'My App', appLogoUrl: 'https://myapp.com/logo.png', // Add ENS domain for subname issuance ens: 'yourdomain.eth', }), ], transports: { [mainnet.id]: http(), }, }); ``` ```typescript [Core SDK] import { JAW } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'YOUR_API_KEY', appName: 'My App', appLogoUrl: 'https://myapp.com/logo.png', defaultChainId: 1, // Add ENS domain for subname issuance ens: 'yourdomain.eth', }); ``` ::: :::warning You must configure your ENS domain in the [JAW Dashboard](https://dashboard.jaw.id) before subname issuance will work. ::: ### 4. Attach Profile Data on Connect When a user creates an account with a subname, their address is automatically attached to all supported networks - enabling multichain address resolution out of the box. You can also attach text records (avatar, bio, social links) to the user's subname during account creation using the `subnameTextRecords` capability in `wallet_connect`. :::code-group ```tsx [Wagmi] import { useConnect } from '@jaw.id/wagmi'; function ConnectWithProfile() { const { mutate: connect } = useConnect(); const handleConnect = () => { connect({ connector: config.connectors[0], capabilities: { subnameTextRecords: [ { key: 'avatar', value: 'https://myapp.com/avatars/default.png' }, { key: 'description', value: 'New user on MyApp' }, { key: 'url', value: 'https://myapp.com' }, ], }, }); }; return ; } ``` ```typescript [Core SDK] const result = await jaw.provider.request({ method: 'wallet_connect', params: [{ capabilities: { subnameTextRecords: [ { key: 'avatar', value: 'https://myapp.com/avatars/default.png' }, { key: 'description', value: 'New user on MyApp' }, { key: 'url', value: 'https://myapp.com' }, ], }, }], }); ``` ::: :::info Text records are only attached if the `ens` option is configured and the user creates a new account with a subname. ::: ### 5. Resolve Profiles To retrieve profile data from ENS names, use the `@justaname.id/sdk` package. This returns avatars, bios, social links, and multichain addresses. #### Installation :::code-group ```bash [npm] npm install @justaname.id/sdk ``` ```bash [pnpm] pnpm add @justaname.id/sdk ``` ```bash [yarn] yarn add @justaname.id/sdk ``` ```bash [bun] bun add @justaname.id/sdk ``` ::: #### Resolve by Name ```typescript import { JustaName } from '@justaname.id/sdk'; // Initialize the SDK const justaName = JustaName.init({ networks: [{ chainId: 1, providerUrl: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY', }], }); // Get profile by ENS name const profile = await justaName.subnames.getRecords({ ens: 'alice.yourdomain.eth', }); ``` #### Resolve by Address (Reverse Lookup) ```typescript // Get ENS name for an address const name = await justaName.subnames.reverseResolve({ address: '0x1234567890123456789012345678901234567890', chainId: 1, }); ``` #### Profile Response Structure ```typescript interface SubnameResponse { ens: string; // e.g., "alice.yourdomain.eth" records: { texts: Array<{ key: string; // e.g., "avatar", "description", "url", "twitter" value: string; }>; coins: Array<{ id: number; // Coin type (60 = ETH, 0 = BTC, etc.) name: string; // e.g., "ETH", "BTC" value: string; // Address on that chain }>; contentHash: { protocolType: string; // e.g., "ipfs" decoded: string; // Content hash value } | null; }; } ``` ### 6. Advanced Identity Features For more control over subname management, custom profile fields, or direct ENS integration, check the JustaName documentation. **[JustaName Documentation](https://docs.justaname.id)** ### Related * [ens Configuration](/configuration/ens) - ENS configuration reference * [wallet\_connect](/api-reference/wallet_connect) - Attach text records during connect * [Quickstart](/guides/quickstart) - Initial JAW setup ## Quickstart Source: https://docs.jaw.id/guides/quickstart ## Quickstart Integrate smart accounts with passkey authentication in minutes. Your users get seedless wallets that support batched transactions, stablecoin gas payments, and optional gas sponsorship - all through a familiar Web3 interface. **What you'll build:** * **Issue Accounts** - Users create accounts with passkeys, no seed phrases * **Passkey Auth** - Secure, passwordless WebAuthn authentication * **Send & Receive** - Standard transaction support with gas sponsorship * **Batch Transactions** - Multiple operations in a single approval ### 1. Integration Path Pick your integration method: \| Approach | Best For | \|----------|----------| \| **Wagmi** | React apps already using wagmi connectors. Instant integration with familiar hooks. | \| **Core SDK** | Custom implementations, non-React apps, or when you need direct control over account creation and signing. | :::info This guide shows both approaches. Choose the tabs that match your setup. ::: ### 2. Install the SDK **Wagmi:** :::code-group ```bash [npm] npm install @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [pnpm] pnpm add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [yarn] yarn add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [bun] bun add @jaw.id/wagmi wagmi @tanstack/react-query ``` ::: **Core SDK:** :::code-group ```bash [npm] npm install @jaw.id/core ``` ```bash [pnpm] pnpm add @jaw.id/core ``` ```bash [yarn] yarn add @jaw.id/core ``` ```bash [bun] bun add @jaw.id/core ``` ::: ### 3. Create API Key Create an API key to authenticate your application's SDK requests for account deployment, identity operations, and transaction routing. **[Get your API key at dashboard.jaw.id](https://dashboard.jaw.id/)** ### 4. Configure the SDK Initialize JAW with your API key, app details, and default chain. The `appName` and `appLogoUrl` appear in user-facing interfaces during authentication. The `defaultChainId` specifies the primary chain for account operations. You can override this per operation by passing a `chainId` parameter, or by using [`wallet_switchEthereumChain`](/api-reference/wallet_switchEthereumChain). :::code-group ```typescript [Wagmi] // config.ts import { createConfig, http } from 'wagmi'; import { mainnet, base } from 'wagmi/chains'; import { jaw } from '@jaw.id/wagmi'; export const config = createConfig({ chains: [mainnet, base], connectors: [ jaw({ apiKey: 'YOUR_API_KEY', appName: 'My App', appLogoUrl: 'https://myapp.com/logo.png', defaultChainId: 1, // Ethereum mainnet }), ], transports: { [mainnet.id]: http(), [base.id]: http(), }, }); ``` ```typescript [Core SDK] import { JAW, Mode } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'YOUR_API_KEY', appName: 'My App', appLogoUrl: 'https://myapp.com/logo.png', defaultChainId: 1, // Ethereum mainnet preference: { mode: Mode.CrossPlatform, }, }); ``` ::: :::info Add paymasters, ENS subnames, and other features later using the [Configuration](/configuration) options. ::: ### 5. Connect and Display Account Add wallet connection to your app. Users authenticate with passkeys and see their account details. :::code-group ```tsx [Wagmi] import { useAccount } from 'wagmi'; import { useConnect, useDisconnect } from '@jaw.id/wagmi'; import { config } from './config'; function ConnectButton() { const { address, isConnected } = useAccount(); const { mutate: connect, isPending } = useConnect(); const { mutate: disconnect } = useDisconnect(); if (isConnected) { return (

Connected: {address}

); } return ( ); } ``` ```typescript [Core SDK] // Connect - opens authentication popup const result = await jaw.provider.request({ method: 'wallet_connect', params: [{}], }); console.log('Connected:', result.accounts[0].address); // Get current connected account const accounts = await jaw.provider.request({ method: 'eth_accounts', }); console.log('Address:', accounts[0]); ``` ::: ### 6. Send Transactions Send single and batched transactions by providing recipient addresses and amounts. The SDK manages gas estimation, routing, and confirmation. Batched calls bundle multiple actions into one approval for better UX. :::code-group ```tsx [Wagmi] import { useSendCalls } from 'wagmi'; import { parseEther, encodeFunctionData } from 'viem'; function SendTransaction() { const { sendCalls, isPending } = useSendCalls(); // Single transaction const handleSend = () => { sendCalls({ calls: [{ to: '0xRecipientAddress...', value: parseEther('0.01'), }], }); }; // Batch multiple transactions const handleBatchSend = () => { sendCalls({ calls: [ { to: '0xAlice...', value: parseEther('0.01') }, { to: '0xBob...', value: parseEther('0.02') }, ], }); }; return (
); } ``` ```typescript [Core SDK] // Batch multiple transactions const result = await jaw.provider.request({ method: 'wallet_sendCalls', params: [{ calls: [ { to: '0xAlice...', value: '0x2386F26FC10000', // 0.01 ETH in hex }, { to: '0xBob...', value: '0x470DE4DF820000', // 0.02 ETH in hex }, ], }], }); console.log('Batch ID:', result); ``` ::: :::info Users can pay gas fees in stablecoins natively - no extra configuration required. For fully sponsored (gasless) transactions, see [paymasters](/configuration/paymasters). ::: ### 7. Disconnect Account Disconnect the wallet from JAW to end the session. :::code-group ```tsx [Wagmi] import { useDisconnect } from '@jaw.id/wagmi'; function DisconnectButton() { const { mutate: disconnect } = useDisconnect(); return ( ); } ``` ```typescript [Core SDK] await jaw.provider.request({ method: 'wallet_disconnect', }); console.log('Disconnected'); ``` ::: ### Next Steps Now that you have the basics working, explore these features: * **[Gas Sponsorship](/configuration/paymasters)** - Sponsor gas fees for your users * **[ENS Subnames](/configuration/ens)** - Issue human-readable names like `alice.yourapp.eth` * **[Subscription Payments](/guides/subscription)** - Set up recurring payments with permissions * **[Batch Transactions](/api-reference/wallet_sendCalls)** - Advanced batching patterns ### Related * [Configuration](/configuration) - All configuration options * [Wagmi Integration](/wagmi) - Full wagmi hooks reference * [Provider API Reference](/api-reference) - All RPC methods * [Supported Networks](/supported-networks) - Available chains ## Sign-In With Ethereum (SIWE) Source: https://docs.jaw.id/guides/siwe ## Sign-In With Ethereum (SIWE) Authenticate users with their signature. When users connect, your frontend knows their account, but your backend doesn't - SIWE bridges that gap by providing cryptographic proof of account ownership. **What you can do:** * **Wallet Login** - Users sign in with their JAW account * **Backend Auth** - Verify signatures server-side * **Secure Sessions** - Issue JWTs or session cookies * **Link Accounts** - Associate wallets with existing user accounts ### 1. Integration Path \| Approach | Best For | \|----------|----------| \| **Wagmi** | React apps using wagmi connectors | \| **Core SDK** | Custom implementations or non-React apps | ### 2. Backend Endpoints Set up three endpoints on your server to handle the SIWE flow: \| Endpoint | Purpose | \|----------|---------| \| `GET /api/siwe/nonce` | Generate a fresh nonce to prevent replay attacks | \| `POST /api/siwe/verify` | Validate signature and issue session token | \| `POST /api/siwe/logout` | Clear user session | #### Nonce Endpoint ```typescript // Server: GET /api/siwe/nonce import { generateSiweNonce } from 'viem/siwe'; export async function GET() { const nonce = generateSiweNonce(); // Store nonce in session/cache for verification // (e.g., Redis, memory cache, or database) return Response.json({ nonce }); } ``` #### Verify Endpoint ```typescript // Server: POST /api/siwe/verify import { parseSiweMessage, verifySiweMessage } from 'viem/siwe'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; const client = createPublicClient({ chain: mainnet, transport: http(), }); export async function POST(request: Request) { const { message, signature } = await request.json(); // Parse the SIWE message const siweMessage = parseSiweMessage(message); // Verify the signature const isValid = await verifySiweMessage(client, { message, signature, }); if (!isValid) { return Response.json({ error: 'Invalid signature' }, { status: 401 }); } // Create session/JWT for the verified address const token = await createSessionToken(siweMessage.address); // Set HTTP-only cookie for security return new Response(JSON.stringify({ success: true }), { headers: { 'Set-Cookie': `auth_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/`, }, }); } ``` #### Logout Endpoint ```typescript // Server: POST /api/siwe/logout export async function POST() { return new Response(JSON.stringify({ success: true }), { headers: { 'Set-Cookie': 'auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0', }, }); } ``` ### 3. Client-Side Sign-In Request a SIWE signature during connection using the `signInWithEthereum` capability in `wallet_connect`. :::code-group ```tsx [Wagmi] import { useConnect } from '@jaw.id/wagmi'; function SignInButton() { const { mutate: connect } = useConnect(); const handleSignIn = async () => { // 1. Fetch nonce from your backend const nonceRes = await fetch('/api/siwe/nonce'); const { nonce } = await nonceRes.json(); // 2. Connect with SIWE capability connect({ connector: config.connectors[0], capabilities: { signInWithEthereum: { nonce, chainId: '0x1', domain: window.location.host, uri: window.location.origin, statement: 'Sign in to My App', expirationTime: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour }, }, }, { onSuccess: async (data) => { const siweResponse = data.accounts[0].capabilities?.signInWithEthereum; if (siweResponse && 'message' in siweResponse) { // 3. Send to backend for verification await fetch('/api/siwe/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: siweResponse.message, signature: siweResponse.signature, }), }); } }, }); }; return ; } ``` ```typescript [Core SDK] // 1. Fetch nonce from your backend const nonceRes = await fetch('/api/siwe/nonce'); const { nonce } = await nonceRes.json(); // 2. Connect with SIWE capability const result = await jaw.provider.request({ method: 'wallet_connect', params: [{ capabilities: { signInWithEthereum: { nonce, chainId: '0x1', domain: window.location.host, uri: window.location.origin, statement: 'Sign in to My App', expirationTime: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour }, }, }], }); // 3. Extract SIWE response const siweResponse = result.accounts[0].capabilities?.signInWithEthereum; if (siweResponse && 'message' in siweResponse) { // 4. Send to backend for verification await fetch('/api/siwe/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: siweResponse.message, signature: siweResponse.signature, }), }); } ``` ::: ### 4. SIWE Parameters \| Parameter | Required | Description | \|-----------|----------|-------------| \| `nonce` | Yes | Random string from your backend to prevent replay attacks | \| `chainId` | Yes | Chain ID in hex format (e.g., `'0x1'` for mainnet) | \| `domain` | No | Your app's domain (e.g., `'myapp.com'`) | \| `uri` | No | Your app's full URI (e.g., `'https://myapp.com'`) | \| `statement` | No | Human-readable message the user is signing | \| `expirationTime` | No | ISO 8601 timestamp when signature expires | \| `issuedAt` | No | ISO 8601 timestamp when signature was issued | \| `resources` | No | Array of resource URIs the user is authorizing | ### 5. Security Best Practices * **Always verify signatures on your backend** - Never trust client-side verification alone * **Use nonces** - Generate a fresh nonce for each sign-in attempt to prevent replay attacks * **Set expiration times** - Limit signature validity to a reasonable window (e.g., 1 hour) * **Store sessions securely** - Use HTTP-only cookies or encrypted tokens * **Validate the domain** - Ensure the signed domain matches your application ### Related * [wallet\_connect](/api-reference/wallet_connect) - Full API reference for SIWE capability * [Quickstart](/guides/quickstart) - Initial JAW setup ## Subscription Payments with Permissions Source: https://docs.jaw.id/guides/subscription ## Subscription Payments with Permissions Learn how to implement recurring subscription payments using JAW's permission system. This enables services like Netflix, Spotify, or any SaaS to charge users automatically without requiring approval for each payment. ### The Problem with Traditional Crypto Payments In traditional crypto payments, users must approve **every single transaction**. This creates a poor user experience for recurring payments: * User subscribes to Service * Every month, Service sends a payment request * User must open their wallet and approve * If user misses the approval, subscription lapses This friction makes crypto unsuitable for subscription businesses. ### How Permissions Solve This JAW's permission system (based on ERC-7715) allows users to grant **time-limited, amount-capped permissions** to service providers. The key insight is that permissions are **scoped** - they can only be used for specific actions you define. **Flow:** 1. **User Subscribes** - User grants permission: $10 USDC/month to Service 2. **Service Stores Permission ID** - Service stores permissionId in their database 3. **Monthly Charge** - Service transfers USDC using permissionId (no user interaction needed!) 4. **User Can Cancel Anytime** - User revokes permission, subscription ends immediately ### Implementation #### Installation **Wagmi:** :::code-group ```bash [npm] npm install @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [pnpm] pnpm add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [yarn] yarn add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [bun] bun add @jaw.id/wagmi wagmi @tanstack/react-query ``` ::: **Core SDK:** :::code-group ```bash [npm] npm install @jaw.id/core ``` ```bash [pnpm] pnpm add @jaw.id/core ``` ```bash [yarn] yarn add @jaw.id/core ``` ```bash [bun] bun add @jaw.id/core ``` ::: #### Setup :::code-group ```typescript [Wagmi] import { createConfig, http } from 'wagmi'; import { base } from 'wagmi/chains'; import { jaw } from '@jaw.id/wagmi'; export const config = createConfig({ chains: [base], connectors: [ jaw({ apiKey: 'YOUR_API_KEY', appName: 'My Streaming Service', appLogoUrl: 'https://myservice.com/logo.png', }), ], transports: { [base.id]: http(), }, }); ``` ```typescript [Core SDK] import { JAW, Mode } from '@jaw.id/core'; const jaw = JAW.create({ appName: 'My Streaming Service', appLogoUrl: 'https://myservice.com/logo.png', defaultChainId: 8453, apiKey: 'YOUR_API_KEY', preference: { mode: Mode.AppSpecific }, }); ``` ::: #### 1. Grant Subscription Permission When a user clicks "Subscribe", request a permission that allows your service to charge them periodically. :::code-group ```tsx [Wagmi] import { useGrantPermissions } from '@jaw.id/wagmi'; import { parseUnits, type Address } from 'viem'; const SERVICE_SPENDER: Address = '0x...'; // Your backend wallet const USDC_ADDRESS: Address = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base function SubscribeButton({ planPrice }: { planPrice: string }) { const { mutate: grantPermission, isPending } = useGrantPermissions(); const handleSubscribe = () => { grantPermission({ expiry: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, // 1 year spender: SERVICE_SPENDER, permissions: { spends: [{ token: USDC_ADDRESS, allowance: parseUnits(planPrice, 6).toString(), unit: 'month', multiplier: 1, }], calls: [{ target: USDC_ADDRESS, functionSignature: 'transfer(address,uint256)', }], }, }, { onSuccess: (data) => { // Store data.permissionId in your backend! console.log('Permission ID:', data.permissionId); }, }); }; return ( ); } ``` ```typescript [Core SDK] import { parseUnits, type Address } from 'viem'; const SERVICE_SPENDER: Address = '0x...'; // Your backend wallet const USDC_ADDRESS: Address = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base async function subscribe(userAddress: Address, planPrice: string) { const result = await jaw.provider.request({ method: 'wallet_grantPermissions', params: [{ address: userAddress, expiry: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, spender: SERVICE_SPENDER, permissions: { spends: [{ token: USDC_ADDRESS, allowance: parseUnits(planPrice, 6).toString(), unit: 'month', multiplier: 1, }], calls: [{ target: USDC_ADDRESS, functionSignature: 'transfer(address,uint256)', }], }, }], }); const permissionId = (result as { permissionId: string }).permissionId; // Store permissionId in your backend! return permissionId; } ``` ::: :::info **Store the Permission ID**: The `permissionId` returned is required to execute charges. Save it to your backend database. ::: #### 2. Execute Charge (Server Side) Your backend service charges users using their stored permission IDs. This typically runs as a cron job or scheduled task - no user approval needed since the permission authorizes the charge. ```typescript import { Account } from '@jaw.id/core'; import { privateKeyToAccount } from 'viem/accounts'; import { encodeFunctionData, parseUnits, type Address } from 'viem'; const USDC_ADDRESS: Address = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base const SERVICE_TREASURY: Address = '0x...'; // Your treasury address // Load the spender's private key from environment const spenderAccount = privateKeyToAccount(process.env.SPENDER_PRIVATE_KEY as `0x${string}`); async function chargeSubscription(permissionId: string, amount: string) { // Create account instance from the spender's private key const account = await Account.fromLocalAccount( { chainId: 8453, apiKey: process.env.JAW_API_KEY! }, spenderAccount ); // Transfer USDC from user to your treasury const { id } = await account.sendCalls( [{ to: USDC_ADDRESS, data: encodeFunctionData({ abi: [{ name: 'transfer', type: 'function', inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' } ], outputs: [{ type: 'bool' }] }], functionName: 'transfer', args: [SERVICE_TREASURY, parseUnits(amount, 6)], }), }], { permissionId: permissionId as `0x${string}` } ); return id; // user operation ID } ``` #### 3. Cancel Subscription Users can cancel anytime by revoking the permission. Once revoked, the spender can no longer execute charges. :::code-group ```tsx [Wagmi] import { useRevokePermissions } from '@jaw.id/wagmi'; function CancelButton({ permissionId }: { permissionId: string }) { const { mutate: revokePermission, isPending } = useRevokePermissions(); return ( ); } ``` ```typescript [Core SDK] async function cancelSubscription(userAddress: Address, permissionId: string) { await jaw.provider.request({ method: 'wallet_revokePermissions', params: [{ address: userAddress, id: permissionId as `0x${string}`, }], }); } ``` ::: :::info **Listing Permissions**: Use `usePermissions()` (Wagmi) or `wallet_getPermissions` (Core SDK) to retrieve all active permissions for a user. ::: ### Permission Parameters Reference \| Parameter | Purpose | Example | \|-----------|---------|---------| \| `spender` | Address authorized to use this permission | Your backend wallet | \| `expiry` | Unix timestamp when permission expires | 1 year from now | \| `permissions.spends.token` | Token address to spend | USDC address | \| `permissions.spends.allowance` | Max amount per period | $10 (10000000 for USDC) | \| `permissions.spends.unit` | Time period | `month` | \| `permissions.calls.target` | Contract that can be called | USDC contract address | \| `permissions.calls.functionSignature` | Function that can be called | `transfer(address,uint256)` | #### Time Units \| Unit | Description | \|------|-------------| \| `minute` | Resets every minute | \| `hour` | Resets every hour | \| `day` | Resets every 24 hours | \| `week` | Resets every 7 days | \| `month` | Resets every \~30 days | \| `year` | Resets every 365 days | ### Related * [useGrantPermissions](/wagmi/useGrantPermissions) - Wagmi hook reference * [useRevokePermissions](/wagmi/useRevokePermissions) - Cancel subscriptions * [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) - RPC method reference ## Getting Started Source: https://docs.jaw.id/index ## Getting Started ### What is JAW? The JAW SDK is a TypeScript library for building identity-first smart accounts on EVM chains: passkey-authenticated, programmable, and extensible through delegated permissions. **Smart wallets** are blockchain accounts controlled by smart contracts instead of private keys. This enables powerful features like: * **Gasless transactions** - Users don't need ETH to pay for gas * **Batch operations** - Execute multiple actions in a single transaction * **Programmable permissions** - Grant limited access to third parties **Passkeys** replace seed phrases and passwords with biometric authentication (Face ID, fingerprint, or device PIN). Your users get: * No seed phrases to lose or steal * Phishing-resistant authentication * Cross-device sync via iCloud/Google ### Installation :::code-group ```bash [npm] npm install @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [pnpm] pnpm add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [yarn] yarn add @jaw.id/wagmi wagmi @tanstack/react-query ``` ```bash [bun] bun add @jaw.id/wagmi wagmi @tanstack/react-query ``` ::: :::warning You need an API key to use the SDK. Get one at [JAW Dashboard](https://dashboard.jaw.id/). ::: ### Quick Start #### 1. Configure Wagmi Create your wagmi config with the JAW connector: ```typescript // config.ts import { createConfig, http } from 'wagmi'; import { mainnet, base } from 'wagmi/chains'; import { jaw } from '@jaw.id/wagmi'; export const config = createConfig({ chains: [mainnet, base], connectors: [ jaw({ apiKey: 'your-api-key', appName: 'My DApp', appLogoUrl: 'https://my-dapp.com/logo.png', }), ], transports: { [mainnet.id]: http(), [base.id]: http(), }, }); ``` #### 2. Set Up Providers Wrap your app with the required providers: ```tsx // App.tsx import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { config } from './config'; const queryClient = new QueryClient(); function App({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` #### 3. Connect & Interact ```tsx import { useAccount, useSendTransaction } from 'wagmi'; import { useConnect, useDisconnect } from '@jaw.id/wagmi'; import { parseEther } from 'viem'; import { config } from './config'; function WalletButton() { const { address, isConnected } = useAccount(); const { mutate: connect, isPending } = useConnect(); const { mutate: disconnect } = useDisconnect(); const { sendTransaction } = useSendTransaction(); if (isConnected) { return (

Connected: {address}

); } return ( ); } ``` When users click "Connect Wallet", a popup appears for passkey authentication. ### Using the Provider Directly For non-React applications or advanced use cases, you can use the provider API directly: ```typescript import { JAW } from '@jaw.id/core'; const jaw = JAW.create({ apiKey: 'your-api-key', appName: 'My DApp', }); // EIP-1193 compatible provider const accounts = await jaw.provider.request({ method: 'wallet_connect', }); ``` See the [Provider API Reference](/api-reference) for all available methods. ### Next Steps * **[Configuration](/configuration)** - All configuration options * **[Wagmi Integration](/wagmi)** - Full wagmi hooks reference * **[Guides](/guides)** - Tutorials for common use cases * **[Supported Networks](/supported-networks)** - Available chains ## Supported Networks Source: https://docs.jaw.id/supported-networks ## Supported Networks import { CapabilitiesTable } from '../components/CapabilitiesTable'; JAW smart accounts are supported across multiple EVM networks. The table below shows real-time capabilities fetched from the JAW API. ### Mainnets ### Testnets Testnets are available when `preference.showTestnets` is enabled: ```typescript const jaw = JAW.create({ apiKey: 'your-api-key', preference: { showTestnets: true, }, }); ``` ### Capabilities Explained \| Capability | Description | \|------------|-------------| \| **Batch** | Atomic batch transactions via `wallet_sendCalls` (EIP-5792) - all calls succeed or fail together | \| **Gasless** | Sponsored transactions via paymaster service (ERC-7677) - users don't pay gas | \| **Permissions** | Permission system for delegating spend and call rights to third parties | \| **Fee Tokens** | Tokens that can be used to pay for gas fees on each network | ### Related * [defaultChainId](/configuration/defaultChainId) - Set the default network * [wallet\_switchEthereumChain](/api-reference/wallet_switchEthereumChain) - Switch networks programmatically * [wallet\_getCapabilities](/api-reference/wallet_getCapabilities) - Query capabilities programmatically