);
}
```
### Related
* [usePermissions](/wagmi/usePermissions) - Get current permissions
* [useGrantPermissions](/wagmi/useGrantPermissions) - Grant new permissions
* [wallet\_revokePermissions](/api-reference/wallet_revokePermissions) - RPC method reference
## 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 viem @tanstack/react-query
```
```bash [pnpm]
pnpm add @jaw.id/wagmi wagmi viem @tanstack/react-query
```
```bash [yarn]
yarn add @jaw.id/wagmi wagmi viem @tanstack/react-query
```
```bash [bun]
bun add @jaw.id/wagmi wagmi viem @tanstack/react-query
```
:::
**Core SDK:**
:::code-group
```bash [npm]
npm install @jaw.id/core viem
```
```bash [pnpm]
pnpm add @jaw.id/core viem
```
```bash [yarn]
yarn add @jaw.id/core viem
```
```bash [bun]
bun add @jaw.id/core viem
```
:::
#### 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 allowance = parseUnits(planPrice, 6);
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: `0x${allowance.toString(16)}`,
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
## apiKey
Your JAW API key for authentication with JAW services.
**Type:** `string`
**Required:** Yes
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key-here',
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key-here',
});
```
### How to Get an API Key
1. Visit the [JAW Dashboard](https://dashboard.jaw.id/)
2. Create a new project or select an existing one
3. Navigate to the API Keys section
4. Generate a new API key
### Setting Up Allowed Domains
For security, you must configure which domains are allowed to use your API key:
1. In the [JAW Dashboard](https://dashboard.jaw.id/), go to your api-key settings
2. Find the **Allowed Domains** section
3. Add the domains where your application will run:
* `localhost` for local development
* `yourdomain.com` for production
* `staging.yourdomain.com` for staging environments
Requests from domains not in your allowed list will be rejected.
### Related Configuration
* [appName](/configuration/appName) - Application name
* [preference](/configuration/preference) - Advanced options
## appLogoUrl
URL to your application's logo image. Displayed alongside your app name in the authentication interface.
**Type:** `string | null`
**Required:** No
**Default:** `null` (uses default icon)
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
appName: 'My DApp',
appLogoUrl: 'https://my-dapp.com/logo.png',
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
appName: 'My DApp',
appLogoUrl: 'https://my-dapp.com/logo.png',
});
```
### Where It Appears
Your app logo is shown to users in:
* Authentication popup header
* Transaction approval screens
* Permission grant dialogs
### Image Requirements
* **Format:** PNG, JPG, or SVG
* **Size:** 200x200px minimum (square recommended)
* **Protocol:** Must use HTTPS
### Related Configuration
* [appName](/configuration/appName) - Application name
* [apiKey](/configuration/apiKey) - API key for authentication
## appName
The name of your application, displayed to users during authentication and transaction signing.
**Type:** `string`
**Required:** No
**Default:** `'DApp'`
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
appName: 'My Awesome DApp',
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
appName: 'My Awesome DApp',
});
```
### Where It Appears
Your app name is shown to users in:
* Authentication popup header
* Transaction approval screens
* Permission grant dialogs
### Related Configuration
* [appLogoUrl](/configuration/appLogoUrl) - Application logo
* [apiKey](/configuration/apiKey) - API key for authentication
## defaultChainId
The default blockchain network to connect to when the SDK initializes.
**Type:** `number`
**Required:** No
**Default:** `1` (Ethereum Mainnet)
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
defaultChainId: 1, // Mainnet
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
defaultChainId: 1, // Mainnet
});
```
### Using Testnets
To use a testnet as the default chain, enable `showTestnets` in preferences:
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
defaultChainId: 84532, // Base Sepolia
preference: {
showTestnets: true,
},
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
defaultChainId: 84532, // Base Sepolia
preference: {
showTestnets: true,
},
});
```
### Related
* [Supported Networks](/supported-networks) - Full list of supported chains
* [preference](/configuration/preference) - Advanced options including testnet visibility
* [wallet\_switchEthereumChain](/api-reference/wallet_switchEthereumChain) - Switch networks programmatically
## ens
ENS domain used to issue subnames to users during authentication.
**Type:** `string`
**Required:** No
:::warning
**Important:** Configuring your ENS at the [JAW Dashboard](https://dashboard.jaw.id/) is a pre-requirement for subname issuance and management.
:::
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
ens: 'myapp.eth',
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
ens: 'myapp.eth',
});
```
### Description
When configured, users can receive a subname under your ENS domain during the authentication flow. For example, if your `ens` is set to `myapp.eth`, users can get subnames like `alice.myapp.eth`.
By default, subname will be active on all supported networks.
To attach text records on creation, please check the [wallet\_connect](/api-reference/wallet_connect) `subnameTextRecords` capability.
### Related Configuration
* [apiKey](/configuration/apiKey) - API key for authentication
* [wallet\_connect](/api-reference/wallet_connect) - Add subname text records using wallet\_connect capability
## Configuration
Configuration options for the JAW SDK. These options apply to both the **Wagmi connector** and the **direct provider**.
### Usage
#### With Wagmi (Recommended)
```typescript
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
appName: 'My DApp',
appLogoUrl: 'https://my-dapp.com/logo.png',
defaultChainId: 1,
ens: 'myapp.eth',
preference: {
showTestnets: true,
},
paymasters: {
1: { url: 'https://paymaster.example.com/mainnet' },
},
});
```
#### With Provider Directly
```typescript
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
appName: 'My DApp',
appLogoUrl: 'https://my-dapp.com/logo.png',
defaultChainId: 1,
ens: 'myapp.eth',
preference: {
showTestnets: true,
},
paymasters: {
1: { url: 'https://paymaster.example.com/mainnet' },
},
});
```
### Configuration Options
#### Core Parameters
| Parameter | Type | Required | Description |
| ----------------------------------------------- | -------------------------------------------------------------------- | -------- | ---------------------------------------- |
| [apiKey](/configuration/apiKey) | `string` | Yes | API key for JAW services authentication |
| [appName](/configuration/appName) | `string` | No | Application name displayed to users |
| [appLogoUrl](/configuration/appLogoUrl) | `string \| null` | No | URL to application logo image |
| [ens](/configuration/ens) | `string` | No | ENS domain for issuing subnames |
| [defaultChainId](/configuration/defaultChainId) | `number` | No | Default blockchain network |
| [preference](/configuration/preference) | `object` | No | Advanced SDK behavior options |
| [paymasters](/configuration/paymasters) | `Record }>` | No | Custom paymaster configuration per chain |
### Preference Options
The `preference` object contains advanced configuration:
| Option | Type | Default | Description |
| ------------ | ------------------------------------------ | -------------------- | --------------------------------------- |
| mode | `Mode.CrossPlatform` \| `Mode.AppSpecific` | `Mode.CrossPlatform` | Authentication mode |
| serverUrl | `string` | JAW Server | Custom passkey server URL |
| showTestnets | `boolean` | `false` | Include testnet networks |
| uiHandler | `UIHandler` | `undefined` | Custom UI handler for app-specific mode |
[See all preference options →](/configuration/preference)
### Return Value
#### Wagmi Connector
The `jaw()` function returns a Wagmi `Connector` that can be used in your wagmi config.
```typescript
import { createConfig } from 'wagmi';
import { jaw } from '@jaw.id/wagmi';
const config = createConfig({
connectors: [jaw({ apiKey: 'your-api-key' })],
// ...
});
```
#### Provider
The `JAW.create()` function returns an object with:
##### provider
**Type:** `ProviderInterface`
An EIP-1193 compatible Ethereum provider for making RPC requests.
```typescript
const jaw = JAW.create({ apiKey: 'your-api-key' });
const accounts = await jaw.provider.request({
method: 'wallet_connect',
});
```
##### disconnect()
**Type:** `() => Promise`
Method to disconnect the current session and clean up resources.
```typescript
await jaw.disconnect();
```
### Minimal Configuration
The only required option is `apiKey`:
```typescript
// Wagmi
const connector = jaw({ apiKey: 'your-api-key' });
// Provider
const jaw = JAW.create({ apiKey: 'your-api-key' });
```
### Related
* [Wagmi Integration](/wagmi) - Using with Wagmi
* [Provider API](/api-reference) - Direct provider methods
* [Supported Networks](/supported-networks) - Available chains
## paymasters
Custom paymaster configuration for sponsoring gas fees on different blockchain networks.
**Type:** `Record }>`
**Required:** No
**Default:** `undefined`
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
paymasters: {
1: { url: 'https://paymaster.example.com/mainnet' },
8453: {
url: 'https://paymaster.example.com/base',
context: { sponsorshipPolicyId: 'sp_my_policy' }
},
},
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
paymasters: {
1: { url: 'https://paymaster.example.com/mainnet' },
8453: {
url: 'https://paymaster.example.com/base',
context: { sponsorshipPolicyId: 'sp_my_policy' }
},
},
});
```
### Description
Paymasters are services that sponsor transaction gas fees, enabling gasless transactions for your users. The `paymasters` configuration allows you to specify custom paymaster endpoints and context for each supported network.
**Benefits:**
* **Gasless Transactions**: Users don't need native tokens to pay gas
* **Better UX**: Remove friction from onboarding
* **Flexible Sponsorship**: Control which transactions to sponsor via context/policies
* **Cost Management**: Use your own paymaster infrastructure
### Important Requirements
#### EntryPoint Version
JAW SDK **only supports EntryPoint v0.8** (ERC-4337 v0.8). Your paymaster service must be compatible with:
* [EntryPoint v0.8](https://github.com/eth-infinitism/account-abstraction/releases/tag/v0.8.0)
* [EIP-7677](https://eips.ethereum.org/EIPS/eip-7677) Paymaster Web Service Capability
#### Compatible Paymaster Providers
Works with any EIP-7677 compliant paymaster that supports EntryPoint v0.8:
* **[Pimlico](https://dashboard.pimlico.io/)**
* **[Etherspot](https://developer.etherspot.io/dashboard)**
### Configuration Format
```typescript
paymasters: {
[chainId: number]: {
url: string; // Paymaster service URL (required)
context?: Record; // Additional context sent with requests (optional)
}
}
```
#### Properties
| Property | Type | Required | Description |
| --------- | ------------------------- | -------- | ------------------------------------------------------------------------ |
| `url` | `string` | Yes | The paymaster service endpoint URL |
| `context` | `Record` | No | Additional context passed to the paymaster (e.g., sponsorship policy ID) |
### Examples
#### Basic Configuration
```typescript
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
paymasters: {
8453: { url: 'https://paymaster.etherspot.io/base' },
},
});
```
#### With Sponsorship Policy
```typescript
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
paymasters: {
8453: {
url: 'https://paymaster.etherspot.io/base',
context: {
sponsorshipPolicyId: 'sp_my_policy_id'
}
},
},
});
```
#### Multiple Networks
```typescript
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
paymasters: {
1: { url: 'https://pm.etherspot.io/eth' }, // Ethereum Mainnet
10: { url: 'https://pm.etherspot.io/op' }, // Optimism
8453: { url: 'https://pm.etherspot.io/base' }, // Base
42161: { url: 'https://pm.etherspot.io/arb' }, // Arbitrum
84532: {
url: 'https://api.pimlico.io/v2/84532/rpc?apikey=YOUR_API_KEY', // Base Sepolia
context: { sponsorshipPolicyId: 'sp_test_policy' }
},
},
});
```
### Paymaster Services
#### Recommended Provider
* **[Etherspot](https://developer.etherspot.io/dashboard)**
* **[Pimlico](https://dashboard.pimlico.io/)**
#### Other Compatible Providers
Ensure any provider you use supports:
* EntryPoint v0.8
* EIP-7677 Paymaster Web Service Capability
### Related
* [eth\_sendTransaction](/api-reference/eth_sendTransaction) - Sponsor Transactions
* [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Sponsor Calls Bundle
## preference
Advanced configuration options to customize SDK behavior and authentication mode.
**Type:** `object`
**Required:** No
**Default:** See individual options below
### Usage
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
import { Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const connector = jaw({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
showTestnets: true,
},
});
```
```typescript
// EIP-1193 Provider
import { JAW, Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
showTestnets: true,
},
});
```
### Options
#### mode
Authentication mode that determines where passkey operations occur.
**Type:** `Mode.CrossPlatform` | `Mode.AppSpecific`
**Default:** `Mode.CrossPlatform`
##### Mode Comparison
| Feature | Mode.CrossPlatform | Mode.AppSpecific |
| ---------------------- | ------------------------ | ------------------- |
| **Passkey operations** | On `keys.jaw.id` (popup) | Within your dApp |
| **User experience** | Redirects to popup | Stays in your app |
| **Wallet reuse** | ✅ Universal | ❌ App-specific only |
| **Origin binding** | `keys.jaw.id` | Your domain |
| **Branding** | JAW interface | Can use custom UI |
:::warning
**Important:** Passkeys are always stored by the browser/device (not on any server). The mode determines where passkey operations happen and whether wallets can be reused across applications.
:::
##### CrossPlatform Mode
Passkey operations redirect to `keys.jaw.id` in a popup, enabling wallet reuse across multiple applications.
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
import { Mode } from '@jaw.id/core';
const connector = jaw({
apiKey: 'your-api-key',
preference: {
mode: Mode.CrossPlatform,
},
});
```
```typescript
// EIP-1193 Provider
import { JAW, Mode } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.CrossPlatform,
},
});
```
**How it works:**
* User clicks "Connect" → Popup opens to `keys.jaw.id`
* Passkey operations happen on `keys.jaw.id` origin
* Wallet can be reused across any app using CrossPlatform mode
* Users see consistent experience across applications
**Benefits:**
* Single wallet across multiple dApps
* Seamless cross-application experience
* Users don't recreate wallets for each app
* Consistent authentication flow
##### AppSpecific Mode
Passkey operations stay within your dApp (no redirect). Wallets are specific to your application's origin.
**How it works:**
* User clicks "Connect" → Modal opens within your dApp
* Passkey operations happen on your dApp's origin
* Passkeys are tied to your domain
* Users never leave your application
**Benefits:**
* Users never leave your dApp
* Full control over UI/UX
* White-label experience
* No external redirects
##### Using ReactUIHandler (Recommended for React)
For React applications, install the `@jaw.id/ui` package which provides pre-built UI dialogs:
:::code-group
```bash [npm]
npm install @jaw.id/ui @jaw.id/core
```
```bash [pnpm]
pnpm add @jaw.id/ui @jaw.id/core
```
```bash [yarn]
yarn add @jaw.id/ui @jaw.id/core
```
```bash [bun]
bun add @jaw.id/ui @jaw.id/core
```
:::
Then configure the SDK with `ReactUIHandler`:
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
import { Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const connector = jaw({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
},
});
```
```typescript
// EIP-1193 Provider
import { JAW, Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
},
});
```
##### Custom UI Implementation
For non-React apps or fully custom UI, see the [Custom UI Handler](/advanced/custom-ui-handler) documentation.
#### serverUrl
URL to a passkey metadata server for managing passkey credentials (credential IDs, public keys, display names).
**Type:** `string`
**Default:** `'https://api.justaname.id/wallet/v2/passkeys'`
**Only works with:** `Mode.AppSpecific`
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
import { Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const connector = jaw({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
serverUrl: 'https://custom-backend.example.com/passkeys',
},
});
```
```typescript
// EIP-1193 Provider
import { JAW, Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
serverUrl: 'https://custom-backend.example.com/passkeys',
},
});
```
:::warning
**Important:** This is NOT for storing passkeys themselves (passkeys are always stored by the browser/device). The server stores metadata about passkeys to enable passkey lookups and management.
:::
For details on running your own passkey server, see the [Custom Passkey Server](/advanced/passkey-server) documentation.
#### showTestnets
Whether to include testnet networks in the available chains list.
**Type:** `boolean`
**Default:** `false`
```typescript
// Wagmi
import { jaw } from '@jaw.id/wagmi';
const connector = jaw({
apiKey: 'your-api-key',
preference: {
showTestnets: true,
},
});
```
```typescript
// EIP-1193 Provider
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
showTestnets: true,
},
});
```
#### uiHandler
Custom UI handler for rendering approval dialogs in app-specific mode. When provided, the SDK will use this handler instead of redirecting to external popups.
**Type:** `UIHandler`
**Default:** `undefined`
**Only works with:** `Mode.AppSpecific`
:::info
For React applications, use `ReactUIHandler` from `@jaw.id/ui`. See the [AppSpecific Mode](#appspecific-mode) section above for setup instructions.
:::
For custom UI implementations (non-React or advanced use cases), see the [Custom UI Handler](/advanced/custom-ui-handler) documentation.
### Related Configuration
* [apiKey](/configuration/apiKey) - API key for authentication
* [defaultChainId](/configuration/defaultChainId) - Default network selection
## eth\_accounts
Get currently connected accounts without triggering authentication. Returns an empty array if no accounts are connected.
**Authentication Required:** No
### Request
```typescript
await jaw.provider.request({
method: 'eth_accounts',
});
```
#### Parameters
None
### Response
Returns an array of connected account addresses. Returns an empty array if not authenticated.
**Type:** `0x${string}[]`
#### Example (Connected)
```typescript
["0x1234567890123456789012345678901234567890"]
```
#### Example (Not Connected)
```typescript
[]
```
### Behavior
* Returns empty array if not authenticated
* Returns cached accounts if already connected
* Does not trigger authentication popup
### Example
```typescript
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
});
const accounts = await jaw.provider.request({
method: 'eth_accounts',
});
```
### Related Methods
* [eth\_requestAccounts](/api-reference/eth_requestAccounts) - Request authentication
## eth\_chainId
Get the current chain ID in hexadecimal format.
**Authentication Required:** No (returns default chain if not authenticated)
### Request
```typescript
await jaw.provider.request({
method: 'eth_chainId',
});
```
#### Parameters
None
### Response
Returns the chain ID as a hexadecimal string.
**Type:** `0x${string}`
#### Example
```json
"0x1"
```
### Behavior
* Returns default chain ID if not authenticated
* Returns current active chain ID if authenticated
* Updates when chain is switched via `wallet_switchEthereumChain`
### Example Usage
```typescript
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
});
const chainId = await jaw.provider.request({
method: 'eth_chainId',
});
```
### Related Methods
* [wallet\_switchEthereumChain](/api-reference/wallet_switchEthereumChain) - Switch chains
## eth\_requestAccounts
Request user authentication and account access.
**Authentication Required:** No (triggers authentication)
### Request
```typescript
await jaw.provider.request({
method: 'eth_requestAccounts',
});
```
#### Parameters
None
### Response
Returns an array of account addresses.
**Type:** `0x${string}[]`
#### Example
```typescript
["0x1234567890123456789012345678901234567890"]
```
### Behavior
* Opens authentication popup (in CrossPlatform mode)
* User creates a new account or signs in with an existing one
* Returns array with single account address
* Emits `accountsChanged` event with the connected accounts
* Subsequent calls return cached accounts without opening popup
### Errors
| Code | Description |
| ---- | ------------------------------------ |
| 4001 | User rejected the request |
| 4100 | Unauthorized (authentication failed) |
### Example
```typescript
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'your-api-key',
});
const accounts = await jaw.provider.request({
method: 'eth_requestAccounts',
});
```
### Related Methods
* [eth\_accounts](/api-reference/eth_accounts) - Get accounts without triggering authentication
* [wallet\_connect](/api-reference/wallet_connect) - Connect with advanced capabilities (SIWE, subnames)
## eth\_sendTransaction
Broadcast a transaction to the network.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: '0x9184e72a000',
data: '0x',
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | ------------- | -------- | --------------------------------------------- |
| `to` | `0x${string}` | Yes | Recipient address |
| `value` | `0x${string}` | No | Amount in wei (hexadecimal) |
| `data` | `0x${string}` | No | Transaction data (hexadecimal) |
| `chainId` | `0x${string}` | No | Target chain ID (defaults to connected chain) |
#### Example
```typescript
[{
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"value": "0x9184e72a000",
"data": "0x"
}]
```
### Response
Returns the transaction hash after the transaction is submitted.
**Type:** `0x${string}`
#### Example
```typescript
"0xabc123def456..."
```
### Behavior
* Opens popup for user approval
* Uses ERC-4337 UserOperation under the hood
* Transaction is sponsored by paymaster if configured
* Returns transaction hash after submission
### Errors
| Code | Description |
| ------ | -------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params |
### Example Usage
#### Send Native Token (ETH)
```typescript
const txHash = await jaw.provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: '0xde0b6b3a7640000', // 1 ETH in wei (hex)
}],
});
console.log('Transaction hash:', txHash);
```
#### Call Contract Function
```typescript
import { encodeFunctionData } from 'viem';
// Encode ERC-20 transfer function
const data = encodeFunctionData({
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
}],
functionName: 'transfer',
args: ['0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 1000000n],
});
const txHash = await jaw.provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
data,
}],
});
```
### Related Methods
* [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Send multiple transactions atomically
## eth\_signTypedData\_v4
Sign structured typed data via EIP-712.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'eth_signTypedData_v4',
params: [
'0x1234...',
JSON.stringify(typedData),
],
});
```
#### Parameters
| Name | Type | Required | Description |
| ----------- | ------------- | -------- | ----------------------------------- |
| `address` | `0x${string}` | Yes | Address to sign with |
| `typedData` | `string` | Yes | JSON stringified EIP-712 typed data |
#### TypedData Structure
The typed data must follow EIP-712 format:
```typescript
{
types: {
EIP712Domain: [...],
[primaryType]: [...],
},
primaryType: string,
domain: {
name: string,
version: string,
chainId: number,
verifyingContract?: string,
},
message: object,
}
```
#### Example
```json
[
"0x1234567890123456789012345678901234567890",
"..."
]
```
### Response
Returns the signature as a hexadecimal string.
**Type:** `0x${string}`
#### Example
```typescript
"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
```
### Behavior
* Opens popup showing structured data in human-readable format
* Validates EIP-712 format before signing
* Shows domain, message types, and values to user
### Errors
| Code | Description |
| ------ | --------------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params (malformed EIP-712 data) |
### Example
```typescript
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
primaryType: 'Mail',
domain: {
name: 'My DApp',
version: '1',
chainId: 1,
},
message: {
from: {
name: 'Alice',
wallet: '0x1234...',
},
to: {
name: 'Bob',
wallet: '0x5678...',
},
contents: 'Hello Bob!',
},
};
const signature = await jaw.provider.request({
method: 'eth_signTypedData_v4',
params: [account, JSON.stringify(typedData)],
});
```
### Related Methods
* [personal\_sign](/api-reference/personal_sign) - Sign plain messages
* [wallet\_sign](/api-reference/wallet_sign) - Generic signing method
## Provider - RPC Reference
Complete reference for all RPC methods supported by the JAW SDK provider.
### Installation
:::code-group
```bash [npm]
npm install @jaw.id/core viem
```
```bash [pnpm]
pnpm add @jaw.id/core viem
```
```bash [yarn]
yarn add @jaw.id/core viem
```
```bash [bun]
bun add @jaw.id/core viem
```
:::
### Setup
```typescript
import { JAW } from '@jaw.id/core';
const jaw = JAW.create({
apiKey: 'YOUR_API_KEY',
appName: 'My App',
});
```
### Making Requests
All requests are made through the provider's `request` method:
```typescript
const result = await jaw.provider.request({
method: 'method_name',
params: [...],
});
```
### Provider Events
The provider emits events following the EIP-1193 specification:
```typescript
// Account changes
jaw.provider.on('accountsChanged', (accounts: string[]) => {
console.log('Accounts changed:', accounts);
});
// Chain changes
jaw.provider.on('chainChanged', (chainId: string) => {
console.log('Chain changed:', chainId);
});
// Connection
jaw.provider.on('connect', (info: { chainId: string }) => {
console.log('Connected to chain:', info.chainId);
});
// Disconnection
jaw.provider.on('disconnect', (error: ProviderRpcError) => {
console.log('Disconnected:', error);
});
```
### Account Methods
Methods for managing user accounts and authentication.
| Method | Description | Auth Required |
| ---------------------------------------------------------- | ---------------------------------------------- | ------------- |
| [eth\_requestAccounts](/api-reference/eth_requestAccounts) | Request user authentication and account access | No |
| [eth\_accounts](/api-reference/eth_accounts) | Get currently connected accounts | No |
### Chain Methods
Methods for querying and switching blockchain networks.
| Method | Description | Auth Required |
| ------------------------------------------------------------------------ | --------------------------- | ------------- |
| [eth\_chainId](/api-reference/eth_chainId) | Get current chain ID (hex) | No |
| [wallet\_switchEthereumChain](/api-reference/wallet_switchEthereumChain) | Switch to a different chain | Yes |
### Transaction Methods
Methods for sending transactions and batch operations.
| Method | Description | Auth Required |
| ---------------------------------------------------------------- | ---------------------------------------- | ------------- |
| [eth\_sendTransaction](/api-reference/eth_sendTransaction) | Broadcast a transaction to the network | Yes |
| [wallet\_sendCalls](/api-reference/wallet_sendCalls) | Broadcast bundle of calls to the network | Yes |
| [wallet\_showCallsStatus](/api-reference/wallet_showCallsStatus) | Show batch transaction status UI | Yes |
| [wallet\_getCallsStatus](/api-reference/wallet_getCallsStatus) | Get batch transaction status | No |
### Signing Methods
Methods for signing messages and typed data.
| Method | Description | Auth Required |
| ------------------------------------------------------------- | ---------------------------------------------------------- | ------------- |
| [personal\_sign](/api-reference/personal_sign) | Sign a message with EIP-191 | Yes |
| [eth\_signTypedData\_v4](/api-reference/eth_signTypedData_v4) | Sign structured typed data (EIP-712) | Yes |
| [wallet\_sign](/api-reference/wallet_sign) | Unified signing method supporting multiple message formats | No\* |
\*Can use ephemeral signer if not authenticated
### Wallet Methods
Methods for wallet connection and lifecycle management.
| Method | Description | Auth Required |
| ------------------------------------------------------ | --------------------------------------------------- | ------------- |
| [wallet\_connect](/api-reference/wallet_connect) | Connect with advanced capabilities (SIWE, subnames) | No |
| [wallet\_disconnect](/api-reference/wallet_disconnect) | Disconnect current session | No |
### Capability Methods
Methods for querying wallet capabilities.
| Method | Description | Auth Required |
| ---------------------------------------------------------------- | -------------------------------------------- | ------------- |
| [wallet\_getCapabilities](/api-reference/wallet_getCapabilities) | Get wallet capabilities per chain (EIP-5792) | No |
### Permission Methods
Methods for managing granular permissions for delegated transactions.
| Method | Description | Auth Required |
| -------------------------------------------------------------------- | --------------------------------------------- | ------------- |
| [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) | Grant call and spend permissions to a spender | Yes |
| [wallet\_revokePermissions](/api-reference/wallet_revokePermissions) | Revoke previously granted permissions | Yes |
| [wallet\_getPermissions](/api-reference/wallet_getPermissions) | Get all permissions for an account | No\* |
\*Requires address parameter if not authenticated
### Asset Methods
Methods for querying token balances.
| Method | Description | Auth Required |
| ---------------------------------------------------- | ------------------------------------------- | ------------- |
| [wallet\_getAssets](/api-reference/wallet_getAssets) | Get token balances across chains (EIP-7811) | No\* |
\*Requires address parameter
### Error Codes
JAW SDK follows EIP-1193 error codes:
| Code | Message | Description |
| ------ | --------------------- | ----------------------- |
| 4001 | User Rejected Request | User declined in popup |
| 4100 | Unauthorized | Not authenticated |
| 4200 | Unsupported Method | Method not supported |
| 4900 | Disconnected | Provider disconnected |
| 4901 | Chain Disconnected | Chain not available |
| -32700 | Parse Error | Invalid JSON |
| -32600 | Invalid Request | Invalid request object |
| -32601 | Method Not Found | Method doesn't exist |
| -32602 | Invalid Params | Invalid parameters |
| -32603 | Internal Error | Internal JSON-RPC error |
## personal\_sign
Signs an EIP-191 personal message.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'personal_sign',
params: ['0x48656c6c6f20576f726c64', '0x1234...'],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | ------------- | -------- | ----------------------------- |
| `message` | `0x${string}` | Yes | Message to sign (hex encoded) |
| `address` | `0x${string}` | Yes | Address to sign with |
#### Example
```typescript
["0x48656c6c6f20576f726c64", "0x1234567890123456789012345678901234567890"]
```
### Response
Returns the signature as a hexadecimal string.
**Type:** `0x${string}`
#### Example
```typescript
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c"
```
### Behavior
* Opens popup for user approval
* Displays human-readable message to user
* Returns EIP-191 signature with "\x19Ethereum Signed Message:\n" prefix
### Errors
| Code | Description |
| ------ | ---------------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params (message not hex encoded) |
### Example
```typescript
import { toHex } from 'viem';
const message = 'Hello World';
const messageHex = toHex(message);
const account = '0x1234...';
const signature = await jaw.provider.request({
method: 'personal_sign',
params: [messageHex, account],
});
console.log('Signature:', signature);
```
### Related Methods
* [eth\_signTypedData\_v4](/api-reference/eth_signTypedData_v4) - Sign structured data (EIP-712)
* [wallet\_sign](/api-reference/wallet_sign) - Generic signing method
## wallet\_connect
Requests to connect account(s) with optional capabilities.
**Authentication Required:** No (triggers authentication)
### Request
```typescript
await jaw.provider.request({
method: 'wallet_connect',
params: [{
capabilities: {
signInWithEthereum: {
nonce: 'abc123xyz789',
chainId: '0x1',
domain: 'my-dapp.com',
uri: 'https://my-dapp.com',
statement: 'Sign in to My DApp with your JAW account'
},
subnameTextRecords: [
{ key: 'avatar', value: 'https://my-dapp.com/avatars/1.png' },
{ key: 'description', value: 'Premium member' }
]
}
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------- |
| `capabilities` | `object` | No | Requested capabilities |
#### Capabilities
##### signInWithEthereum
| Name | Type | Required | Description |
| ---------------- | -------- | -------- | --------------------------- |
| `nonce` | `string` | Yes | Random nonce for SIWE |
| `chainId` | `string` | Yes | Chain ID (hex format) |
| `domain` | `string` | No | Domain requesting signature |
| `uri` | `string` | No | URI of the application |
| `statement` | `string` | No | Human-readable statement |
| `version` | `string` | No | SIWE version (default: "1") |
| `issuedAt` | `string` | No | ISO 8601 timestamp |
| `expirationTime` | `string` | No | ISO 8601 expiration |
##### subnameTextRecords
Array of key-value pairs to attach as ENS text records to the user's subname on account creation.
**Note:** This capability is only used if an ENS domain is configured in the SDK preferences (via the `ens` option). If no ENS domain is configured, these records are disregarded.
| Name | Type | Description |
| ------- | -------- | --------------------------------------- |
| `key` | `string` | Text record key (e.g., "avatar", "url") |
| `value` | `string` | Text record value |
### Response
Returns connection result with accounts and capabilities.
**Type:** `object`
#### Example
```json
{
"accounts": [{
"address": "0x1234567890123456789012345678901234567890",
"capabilities": {
"signInWithEthereum": {
"message": "my-dapp.com wants you to sign in with your Ethereum account:\n0x1234...",
"signature": "0xabcdef..."
}
}
}]
}
```
### Behavior
* Opens authentication popup
* User creates account or signs in with existing account
* If `signInWithEthereum` capability is requested:
* Generates and signs a SIWE message with the provided parameters
* Returns the signed message in the response capabilities
* If `subnameTextRecords` capability is provided and `ens` is configured in SDK preferences:
* Attaches the provided text records to the subname
* Returns accounts with capability responses
### Errors
| Code | Description |
| ------ | ------------------------------------ |
| 4001 | User rejected the request |
| 4100 | Unauthorized (authentication failed) |
| -32602 | Invalid params |
### Example
```typescript
const result = await jaw.provider.request({
method: 'wallet_connect',
params: [{}],
});
```
### Related Methods
* [eth\_requestAccounts](/api-reference/eth_requestAccounts) - Simple authentication without capabilities
## wallet\_disconnect
Disconnect the current session and clean up all authentication state.
**Authentication Required:** No
### Request
```typescript
await jaw.provider.request({
method: 'wallet_disconnect',
});
```
#### Parameters
None
### Response
Returns `null` on success.
**Type:** `null`
### Behavior
* Clears the active session and authentication state
* Emits `disconnect` event
* Same effect as calling `jaw.disconnect()`
### Example Usage
#### Disconnect Button
```typescript
await jaw.provider.request({
method: 'wallet_disconnect',
});
```
#### Listen to Disconnect Event
```typescript
// Listen for disconnect events
jaw.provider.on('disconnect', (error) => {
console.log('Wallet disconnected:', error.message);
});
await jaw.provider.request({
method: 'wallet_disconnect',
});
```
### Related Methods
* [eth\_requestAccounts](/api-reference/eth_requestAccounts) - Reconnect wallet
* [wallet\_connect](/api-reference/wallet_connect) - Connect with capabilities
## wallet\_getAssets
Get token balances across multiple chains for an account. Implements EIP-7811.
**Authentication Required:** No (requires account parameter)
### Request
```typescript
await jaw.provider.request({
method: 'wallet_getAssets',
params: [{
account: '0x1234...',
assetFilter: {
'0x1': [{ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', type: 'erc20' }],
},
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| ----------------- | ------------- | -------- | --------------------------------------------- |
| `account` | `string` | Yes | Account address to query |
| `chainFilter` | `string[]` | No | Limit to specific chains (hex chain IDs) |
| `assetTypeFilter` | `AssetType[]` | No | Filter by asset type: `'native'` or `'erc20'` |
| `assetFilter` | `AssetFilter` | No | Filter by specific assets per chain |
#### Types
```typescript
type AssetType = 'native' | 'erc20';
type AssetFilter = Record<`0x${string}`, AssetFilterEntry[]>;
type AssetFilterEntry = {
address: `0x${string}`;
type: AssetType;
};
```
#### Example
```json
[{
"account": "0x1234567890123456789012345678901234567890",
"chainFilter": ["0x1", "0x89"]
}]
```
With asset type filter (ERC-20 tokens only):
```json
[{
"account": "0x1234567890123456789012345678901234567890",
"assetTypeFilter": ["erc20"]
}]
```
With specific asset filter:
```json
[{
"account": "0x1234567890123456789012345678901234567890",
"assetFilter": {
"0x1": [{ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "type": "erc20" }],
"0x2105": [{ "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "type": "erc20" }]
}
}]
```
### Response
Returns an object mapping chain IDs to asset arrays.
**Type:** `WalletGetAssetsResponse`
```typescript
type WalletGetAssetsResponse = {
[chainId: string]: Asset[];
};
type Asset = {
/** Token contract address, null for native tokens */
address: string | null;
/** Balance in hex format */
balance: string;
/** Asset metadata */
metadata: {
decimals: number;
name: string;
symbol: string;
} | null;
/** Asset type */
type: 'native' | 'erc20';
};
```
#### Example
```json
{
"0x1": [
{
"address": null,
"balance": "0x1bc16d674ec80000",
"metadata": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"type": "native"
},
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"balance": "0x5f5e100",
"metadata": {
"decimals": 6,
"name": "USD Coin",
"symbol": "USDC"
},
"type": "erc20"
}
],
"0x89": [
{
"address": null,
"balance": "0x2b5e3af16b1880000",
"metadata": {
"decimals": 18,
"name": "POL",
"symbol": "POL"
},
"type": "native"
}
]
}
```
#### Asset Object Fields
| Field | Type | Description |
| ---------- | --------------------- | ------------------------------------------------ |
| `address` | `string \| null` | Token contract address, `null` for native tokens |
| `balance` | `string` | Balance in smallest unit (wei/raw), hex format |
| `metadata` | `object \| null` | Token metadata (decimals, name, symbol) |
| `type` | `'native' \| 'erc20'` | Asset type |
### Behavior
* Includes native tokens (ETH, MATIC, etc.)
* Includes ERC-20 tokens with non-zero balances
* Auto-filters by `showTestnets` SDK preference
* Can be called without authentication
* Returns only tokens with non-zero balances
### Example
#### Get All Assets
```typescript
const assets = await jaw.provider.request({
method: 'wallet_getAssets',
params: [{
account: '0x1234...',
}],
});
```
#### Filter by Asset Type
```typescript
// Only get ERC-20 tokens
const tokens = await jaw.provider.request({
method: 'wallet_getAssets',
params: [{
account: '0x1234...',
assetTypeFilter: ['erc20'],
}],
});
```
#### Filter by Specific Assets
```typescript
// Get only USDC on mainnet and Base
const usdc = await jaw.provider.request({
method: 'wallet_getAssets',
params: [{
account: '0x1234...',
assetFilter: {
'0x1': [{ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', type: 'erc20' }],
'0x2105': [{ address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', type: 'erc20' }],
},
}],
});
```
### Related Methods
* [eth\_getBalance](/api-reference) (Proxied) - Get native balance for single chain
* [wallet\_getCapabilities](/api-reference/wallet_getCapabilities) - Check supported chains
## wallet\_getCallsStatus
Get the status of a batch of calls.
**Authentication Required:** No
### Request
```typescript
await jaw.provider.request({
method: 'wallet_getCallsStatus',
params: ['0x123abc...'],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | -------- | -------- | ---------------------------------------- |
| `batchId` | `string` | Yes | Batch ID returned from wallet\_sendCalls |
#### Example
```json
["0x123abc456def..."]
```
### Response
Returns detailed status information about the batch execution.
#### Example
```json
{
"id": "0x123abc...",
"chainId": "0x1",
"status": 200,
"atomic": true,
"receipts": [{
"logs": [{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x...",
"topics": ["0x..."]
}],
"status": "0x1",
"blockHash": "0x...",
"blockNumber": "0x...",
"gasUsed": "0x...",
"transactionHash": "0x..."
}]
}
```
#### Response Fields
| Field | Type | Description |
| ---------- | --------- | ------------------------------------- |
| `id` | `string` | Batch identifier |
| `chainId` | `string` | Chain ID (hex) |
| `status` | `number` | Status code (see below) |
| `atomic` | `boolean` | Whether calls are atomic |
| `receipts` | `array` | Transaction receipts (when completed) |
#### Status Codes
| Code | Status | Description |
| ---- | ---------------- | --------------------------------------------------- |
| 100 | Pending | Not yet on-chain |
| 200 | Completed | Successfully executed on-chain |
| 400 | Offchain Failure | Failed before submission (wallet won't retry) |
| 500 | Onchain Revert | Reverted on-chain (has receipt with status 0x0) |
| 600 | Partial Revert | Some calls reverted (not applicable for atomic ops) |
### Behavior
* Can be called without authentication
* Returns current status of the batch operation
* Receipts included only when status is 200 or 500
* Background monitoring updates status automatically
### Example
#### Poll for Completion
```typescript
const result = await jaw.provider.request({
method: 'wallet_sendCalls',
params: [{
version: '1.0',
chainId: '0x1',
from: account,
calls: [...],
}],
});
// Poll until completed
async function waitForCompletion(batchId: string) {
while (true) {
const status = await jaw.provider.request({
method: 'wallet_getCallsStatus',
params: [batchId],
});
if (status.status === 200) {
console.log('Batch completed successfully!');
return status;
} else if (status.status >= 400) {
console.error('Batch failed:', status);
throw new Error('Batch execution failed');
}
// Still pending, wait and retry
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
await waitForCompletion(result.id);
```
### Related Methods
* [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Send atomic batch of calls
* [wallet\_showCallsStatus](/api-reference/wallet_showCallsStatus) - Show status UI
## wallet\_getCapabilities
Get wallet capabilities for supported chains. Implements EIP-5792 for capability discovery.
**Authentication Required:** No
### Request
```typescript
await jaw.provider.request({
method: 'wallet_getCapabilities',
params: ['0x1234...', ['0x1', '0x2105']],
});
```
#### Parameters
| Name | Type | Required | Description |
| ---------- | ---------- | -------- | ---------------------------------- |
| `address` | `string` | No | Account address |
| `chainIds` | `string[]` | No | Filter by specific chain IDs (hex) |
#### Example
```json
["0x1234567890123456789012345678901234567890", ["0x1", "0x2105"]]
```
Or without filtering:
```json
[]
```
### Response
Returns capabilities per chain as an object keyed by chain ID (hex).
**Type:** `Record`
#### Example
```json
{
"0x1": {
"atomicBatch": { "supported": true },
"atomic": { "status": "supported" },
"paymasterService": { "supported": true },
"permissions": { "supported": true },
"feeToken": {
"supported": true,
"tokens": [
{
"uid": "ethereum",
"symbol": "ETH",
"address": "0x0000000000000000000000000000000000000000",
"interop": false,
"decimals": 18,
"feeToken": true
}
]
}
},
"0x2105": {
"atomicBatch": { "supported": true },
"atomic": { "status": "supported" },
"paymasterService": { "supported": true },
"permissions": { "supported": true },
"feeToken": {
"supported": true,
"tokens": [
{
"uid": "ethereum",
"symbol": "ETH",
"address": "0x0000000000000000000000000000000000000000",
"interop": true,
"decimals": 18,
"feeToken": true
}
]
}
}
}
```
### Capabilities Explained
#### atomicBatch
**Value:** `{ supported: true }`
Indicates support for `wallet_sendCalls` - the ability to send multiple transactions atomically (all succeed or all fail).
#### atomic
**Value:** `{ status: "supported" }`
Indicates atomic transaction execution where all operations in a batch are guaranteed to succeed or fail together.
#### paymasterService
**Value:** `{ supported: true }`
Indicates support for ERC-7677 paymaster service - gasless transactions sponsored by paymasters.
#### permissions
**Value:** `{ supported: true }`
Indicates support for the permission system (`wallet_grantPermissions`, `wallet_revokePermissions`).
#### feeToken
**Value:** `{ supported: true, tokens: [...] }`
Indicates the supported fee tokens for gas payments on each chain.
| Field | Type | Description |
| ------------------- | --------- | ------------------------------------------------------- |
| `supported` | `boolean` | Whether fee token selection is supported |
| `tokens` | `array` | List of available fee tokens |
| `tokens[].uid` | `string` | Unique identifier for the token |
| `tokens[].symbol` | `string` | Token symbol (e.g., "ETH", "USDC") |
| `tokens[].address` | `string` | Token contract address (zero address for native token) |
| `tokens[].interop` | `boolean` | Whether the token supports cross-chain interoperability |
| `tokens[].decimals` | `number` | Token decimal places |
| `tokens[].feeToken` | `boolean` | Whether this token can be used for gas fees |
### Behavior
* Can be called without authentication
* Returns capabilities for all supported chains by default
* Filters by chain IDs if `chainIds` parameter provided
* When `showTestnets` is disabled (default), only mainnet chains are returned
### Example
```typescript
const capabilities = await jaw.provider.request({
method: 'wallet_getCapabilities',
});
```
### Related Methods
* [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Use atomic batch capability
* [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) - Use permission capability
## wallet\_getPermissions
Get all permissions for an account.
**Authentication Required:** No (if address provided)
### Request
```typescript
await jaw.provider.request({
method: 'wallet_getPermissions',
params: [{ address: '0x1234...' }],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | -------- | -------- | ------------------------ |
| `address` | `string` | No\* | Account address to query |
\*Required if not authenticated. Auto-injected if authenticated and not provided.
#### Example
```json
[{ "address": "0x1234567890123456789012345678901234567890" }]
```
Or when authenticated (auto-injects address):
```json
[]
```
### Response
Returns an array of permission objects.
**Type:** `array`
#### Example
```json
[
{
"id": "0xabc123...",
"account": "0x1234567890123456789012345678901234567890",
"spender": "0x5678901234567890123456789012345678901234",
"chainId": "0x1",
"expiry": 1735689600,
"calls": [{
"target": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"selector": "0xa9059cbb"
}],
"spends": [{
"token": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"limit": "0x16345785d8a0000",
"period": "day"
}]
}
]
```
#### Permission Object Fields
| Field | Type | Description |
| --------- | -------- | -------------------------------------- |
| `id` | `string` | Unique permission identifier (hash) |
| `account` | `string` | Account that granted the permission |
| `spender` | `string` | Address that received permissions |
| `chainId` | `string` | Chain ID (hex) |
| `expiry` | `number` | Unix timestamp when permission expires |
| `calls` | `array` | Granted call permissions |
| `spends` | `array` | Granted spend limits |
### Behavior
* Can be called without authentication if address provided
* Auto-injects connected address if authenticated
* Returns empty array if no permissions found
### Errors
| Code | Description |
| ------ | -------------------------------------------------------- |
| -32602 | Invalid params (address required when not authenticated) |
### Example
```typescript
const permissions = await jaw.provider.request({
method: 'wallet_getPermissions',
});
```
### Related Methods
* [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) - Grant new permissions
* [wallet\_revokePermissions](/api-reference/wallet_revokePermissions) - Revoke permissions
## wallet\_grantPermissions
Grant granular permissions to a spender address for delegated transaction execution with spending limits and call restrictions.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_grantPermissions',
params: [{
expiry: 1735689600, // Unix timestamp
spender: '0x5678...', // Who gets permissions
permissions: {
calls: [{
target: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
functionSignature: 'transfer(address,uint256)',
}],
spends: [{
token: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
allowance: '0x16345785d8a0000', // 0.1 ETH
unit: 'day',
}],
},
// chainId: '0x1', // Optional - defaults to connected chain
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| ------------- | ------------- | -------- | --------------------------------------------- |
| `expiry` | `number` | Yes | Expiration timestamp (seconds) |
| `spender` | `0x${string}` | Yes | Address receiving permissions |
| `permissions` | `object` | Yes | Permission details |
| `chainId` | `0x${string}` | No | Target chain ID (defaults to connected chain) |
#### Permission Details
##### calls
Array of call permissions. Each permission:
| Name | Type | Required | Description |
| ------------------- | ------------- | -------- | ------------------------------------------------------ |
| `target` | `0x${string}` | Yes | Contract address |
| `functionSignature` | `string` | No\* | Function signature (e.g., "transfer(address,uint256)") |
| `selector` | `0x${string}` | No\* | 4-byte function selector (0x...) |
\*Either `functionSignature` or `selector` is required.
##### spends (Optional\*)
Array of spend limits. Each limit:
| Name | Type | Required | Description |
| ------------ | ------------- | -------- | -------------------------------------------- |
| `token` | `0x${string}` | Yes | Token address (`0xEeee...` for native token) |
| `allowance` | `0x${string}` | Yes | Spending allowance in wei (hex) |
| `unit` | `string` | Yes | Time unit for allowance reset |
| `multiplier` | `number` | No | Multiplier for the time unit (default: 1) |
**Valid Units:**
* `minute` - Resets every minute
* `hour` - Resets every hour
* `day` - Resets every day
* `week` - Resets every week
* `month` - Resets every month
* `year` - Resets every year
* `forever` - Never resets (one-time allowance)
\*Spend Limits are required if moving funds.
### Response
Returns permission grant result with generated permission ID.
```json
{
"account": "0x1234567890123456789012345678901234567890",
"spender": "0x5678901234567890123456789012345678901234",
"start": 1704067200,
"end": 1735689600,
"salt": "0x1a2b3c...",
"calls": [{
"target": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"selector": "0xa9059cbb"
}],
"spends": [{
"token": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"allowance": "0x16345785d8a0000",
"unit": "day",
"multiplier": 1
}],
"permissionId": "0xabc123...",
"chainId": "0x1"
}
```
### Behavior
* Opens popup for user approval
* Approves permission
* Returns permission ID for future revocation
### Errors
| Code | Description |
| ------ | -------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params |
### Example
```typescript
// Allow DeFi protocol to swap USDC with daily limit
const permission = await jaw.provider.request({
method: 'wallet_grantPermissions',
params: [{
expiry: Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60), // 1 year
spender: dexRouterAddress,
permissions: {
calls: [{
target: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
functionSignature: 'approve(address,uint256)',
}, {
target: dexRouterAddress,
functionSignature: 'swap(address,address,uint256,uint256)',
}],
spends: [{
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
allowance: '0x' + (1000n * 10n**6n).toString(16),
unit: 'day',
}],
},
// chainId: '0x1', // Optional - defaults to connected chain
}],
});
```
### Related Methods
* [wallet\_revokePermissions](/api-reference/wallet_revokePermissions) - Revoke permissions
* [wallet\_getPermissions](/api-reference/wallet_getPermissions) - Query permissions
## wallet\_revokePermissions
Revoke previously granted permissions.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_revokePermissions',
params: [{
id: '0xabc123...', // Permission ID
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| ---- | -------- | -------- | ----------------------- |
| `id` | `string` | Yes | Permission ID to revoke |
#### Example
```json
[{
"id": "0xabc123456def789..."
}]
```
### Response
Returns revocation result.
#### Example
```json
{
"success": true
}
```
### Behavior
* Opens popup for user approval
* Revokes permission
### Errors
| Code | Description |
| ------ | ---------------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params (permission ID not found) |
### Example
```typescript
const permissionId = '0xabc123...';
const result = await jaw.provider.request({
method: 'wallet_revokePermissions',
params: [{
id: permissionId,
}],
});
```
### Related Methods
* [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) - Grant permissions
* [wallet\_getPermissions](/api-reference/wallet_getPermissions) - List all permissions
## wallet\_sendCalls
Broadcast bundle of calls to the network.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_sendCalls',
params: [{
calls: [
{
to: '0xContractA...',
value: '0x0',
data: '0x...',
},
{
to: '0xContractB...',
value: '0x0',
data: '0x...',
},
],
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------------- | ------------- | -------- | --------------------------------------------- |
| `calls` | `array` | Yes | Array of call objects |
| `calls[].to` | `0x${string}` | Yes | Recipient address |
| `calls[].value` | `0x${string}` | No | Value in wei (hex), default "0x0" |
| `calls[].data` | `0x${string}` | No | Call data (hex), default "0x" |
| `chainId` | `0x${string}` | No | Target chain ID (defaults to connected chain) |
| `capabilities` | `object` | No | Additional capabilities (see below) |
#### Capabilities
| Name | Type | Description |
| ----------------------------- | ------------- | --------------------------------------------- |
| `capabilities.permissions` | `object` | Permission capability for delegated execution |
| `capabilities.permissions.id` | `0x${string}` | ID of the permission to use for execution |
### Response
Returns a batch call identifier.
#### Example
```json
{
"id": "0x123abc..."
}
```
### Behavior
* All calls succeed atomically or all fail together
* Uses currently connected chain if `chainId` not specified
* Returns batch ID immediately for status tracking
* Background task monitors completion
* Gas is sponsored by paymaster if configured
* Users offered native ERC-20 paymaster option for gas fees (no configuration needed)
### Errors
| Code | Description |
| ------ | -------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params |
### Example
#### Transfer Multiple ERC-20 Tokens
```typescript
import { encodeFunctionData } from 'viem';
const transferAbi = [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
}];
const account = '0x1234...';
const recipient = '0x5678...';
// Encode both transfers
const usdcTransfer = encodeFunctionData({
abi: transferAbi,
functionName: 'transfer',
args: [recipient, 1000000n],
});
const uniTransfer = encodeFunctionData({
abi: transferAbi,
functionName: 'transfer',
args: [recipient, 1000000000000000000n],
});
// Send both transfers atomically
const result = await jaw.provider.request({
method: 'wallet_sendCalls',
params: [{
calls: [
{
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
data: usdcTransfer,
},
{
to: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
data: uniTransfer,
},
],
}],
});
console.log('Batch ID:', result.id);
```
#### Check Status
```typescript
// Get status of the batch
const status = await jaw.provider.request({
method: 'wallet_getCallsStatus',
params: [result.id],
});
console.log('Status:', status.status);
```
#### Execute with Permission (Delegated Execution)
When a permission has been granted via `wallet_grantPermissions`, you can execute calls using that permission. This allows the spender to execute transactions on behalf of the account within the permission's constraints.
```typescript
import { encodeFunctionData } from 'viem';
// First, get the permission ID from a previous wallet_grantPermissions call
const permissionId = '0x1234...'; // The permission ID returned from grantPermissions
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: ['0x5678...', 1000000n],
});
// Execute the transfer using the permission
const result = await jaw.provider.request({
method: 'wallet_sendCalls',
params: [{
calls: [
{
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
data: transferData,
},
],
capabilities: {
permissions: {
id: permissionId,
},
},
}],
});
console.log('Batch ID:', result.id);
```
When using the `permissions` capability:
* The calls are executed through the JustaPermissionManager contract
* The permission's call and spend limits are enforced
* The spender must have been granted appropriate permissions for the calls being made
### Related Methods
* [wallet\_getCallsStatus](/api-reference/wallet_getCallsStatus) - Get batch status
* [wallet\_showCallsStatus](/api-reference/wallet_showCallsStatus) - Show status UI
* [wallet\_grantPermissions](/api-reference/wallet_grantPermissions) - Grant permissions for delegated execution
## wallet\_showCallsStatus
Show the status UI popup for a batch of calls sent via `wallet_sendCalls`.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_showCallsStatus',
params: ['0x123abc...'],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | -------- | -------- | ---------------------------------------- |
| `batchId` | `string` | Yes | Batch ID returned from wallet\_sendCalls |
#### Example
```json
["0x123abc456def..."]
```
### Response
Returns `void` (no return value).
**Type:** `void`
### Behavior
* Opens popup showing transaction status UI
* Displays pending, completed, or failed status
* Shows individual call details in the batch
* Auto-updates as status changes in real-time
* User can close popup at any time
### Errors
| Code | Description |
| ------ | ----------------------------------- |
| 4001 | User closed the popup |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params (batch ID not found) |
### Example
```typescript
await jaw.provider.request({
method: 'wallet_showCallsStatus',
params: [result.id],
});
```
### Related Methods
* [wallet\_sendCalls](/api-reference/wallet_sendCalls) - Send atomic batch of calls
* [wallet\_getCallsStatus](/api-reference/wallet_getCallsStatus) - Get status programmatically
## wallet\_sign
Unified signing method supporting multiple message formats (ERC-7871). Delegates to `personal_sign` or `eth_signTypedData_v4` based on the request type.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_sign',
params: [{
request: {
type: '0x45', // or '0x01'
data: { message: 'Hello World' } // or TypedData object for 0x01
},
}],
});
```
#### Parameters
| Name | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------------------------------------------------------- |
| `request.type` | `string` | Yes | Signature type: `"0x45"` (personal\_sign) or `"0x01"` (signTypedData\_v4) |
| `request.data` | `object` | Yes | For 0x45: `{ message: string }`. For 0x01: TypedData object (EIP-712) |
#### Supported Types
| Type | EIP Standard | Description | Data Format |
| ------ | ------------ | ----------------------------- | ----------------------------------------------------------------- |
| `0x45` | EIP-191 | Personal message signing | `{ message: string }` - UTF-8 message string |
| `0x01` | EIP-712 | Structured typed data signing | TypedData object with `types`, `domain`, `primaryType`, `message` |
### Response
Returns the signature as a hexadecimal string.
**Type:** `0x${string}`
#### Example
```typescript
"0x1234567890abcdef..."
```
### Behavior
* **Type 0x45**: Delegates to `personal_sign` - adds EIP-191 prefix and signs the UTF-8 message
* **Type 0x01**: Delegates to `eth_signTypedData_v4` - signs structured EIP-712 data
* Opens popup for user approval
* Returns signature in same format as the underlying method
### Errors
| Code | Description |
| ------ | --------------------------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| -32602 | Invalid params (unsupported type or malformed data) |
### Example
#### Personal Message (Type 0x45)
```typescript
const message = 'Hello World';
const signature = await jaw.provider.request({
method: 'wallet_sign',
params: [{
request: {
type: '0x45',
data: {
message: message, // UTF-8 message string
},
},
}],
});
```
#### Typed Data (Type 0x01)
```typescript
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
primaryType: 'Person',
domain: {
name: 'My DApp',
version: '1',
chainId: 1,
},
message: {
name: 'Alice',
wallet: '0x...',
},
};
const signature = await jaw.provider.request({
method: 'wallet_sign',
params: [{
request: {
type: '0x01',
data: typedData, // TypedData object directly
},
}],
});
```
#### SIWE (Sign-In With Ethereum)
```typescript
const siweMessage = `example.com wants you to sign in with your Ethereum account:
0x1234...
Sign in to Example DApp
URI: https://example.com
Version: 1
Chain ID: 1
Nonce: ${crypto.randomUUID()}
Issued At: ${new Date().toISOString()}`;
const signature = await jaw.provider.request({
method: 'wallet_sign',
params: [{
request: {
type: '0x45',
data: {
message: siweMessage, // UTF-8 message string
},
},
}],
});
```
### Related Methods
* [personal\_sign](/api-reference/personal_sign) - Direct EIP-191 message signing (type 0x45)
* [eth\_signTypedData\_v4](/api-reference/eth_signTypedData_v4) - Direct EIP-712 typed data signing (type 0x01)
## wallet\_switchEthereumChain
Switch to a different chain.
**Authentication Required:** Yes
### Request
```typescript
await jaw.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }],
});
```
#### Parameters
| Name | Type | Required | Description |
| --------- | ------------- | -------- | ------------------------------------- |
| `chainId` | `0x${string}` | Yes | Target chain ID in hexadecimal format |
#### Example
```json
[{ "chainId": "0x89" }]
```
### Response
Returns `null` on success.
**Type:** `null`
### Behavior
* Switches to the specified chain if supported
* Emits `chainChanged` event with the new chain ID
* All subsequent transactions use the new chain
* Only supported chains can be switched to
### Errors
| Code | Description |
| ---- | -------------------------------- |
| 4001 | User rejected the request |
| 4100 | Unauthorized (not authenticated) |
| 4902 | Chain not supported |
### Example
#### Basic Chain Switch
```typescript
try {
await jaw.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }], // Polygon
});
console.log('Switched to Polygon');
} catch (error) {
if (error.code === 4902) {
console.error('Polygon is not supported');
}
}
```
#### Switch with Event Listener
```typescript
// Listen for chain changes
jaw.provider.on('chainChanged', (chainId) => {
console.log('Chain changed to:', parseInt(chainId, 16));
});
// Switch chain
await jaw.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x2105' }], // Base
});
```
### Related Methods
* [eth\_chainId](/api-reference/eth_chainId) - Get current chain ID
## Custom UI Handler
Build your own UI for app-specific mode by implementing the `UIHandler` interface. This is useful for non-React applications or when you need complete control over the user experience.
### Overview
When using `Mode.AppSpecific`, the SDK needs a way to display approval dialogs for wallet operations. The `UIHandler` interface defines how your application handles these UI requests.
```typescript
import { JAW, Mode } from '@jaw.id/core';
import { MyCustomUIHandler } from './my-ui-handler';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new MyCustomUIHandler(),
},
});
```
### UIHandler Interface
```typescript
import {
UIHandler,
UIRequest,
UIResponse,
UIHandlerConfig,
} from '@jaw.id/core';
class MyCustomUIHandler implements UIHandler {
private config?: UIHandlerConfig;
/**
* Initialize the handler with SDK configuration.
* Called by the SDK before any requests are made.
*/
init(config: UIHandlerConfig): void {
this.config = config;
// Use config.apiKey, config.defaultChainId, config.paymasters, etc.
}
/**
* Request user approval for an action.
* This is the main method you must implement.
*/
async request(request: UIRequest): Promise> {
// Display UI based on request.type
// Wait for user interaction
// Return the response
}
/**
* Optional: Check if this handler supports a request type.
*/
canHandle(request: UIRequest): boolean {
return true;
}
/**
* Optional: Clean up when handler is no longer needed.
*/
async cleanup(): Promise {
// Close any open dialogs, release resources
}
}
```
### UIHandlerConfig
The SDK passes configuration to your handler via the `init()` method:
```typescript
interface UIHandlerConfig {
/** JAW API key for RPC URL resolution */
apiKey?: string;
/** Default chain ID */
defaultChainId?: number;
/** Paymaster configuration per chain for gasless transactions */
paymasters?: Record }>;
/** App name shown in dialogs */
appName?: string;
/** App logo URL */
appLogoUrl?: string | null;
}
```
### Request Types
Your handler will receive different request types based on the wallet operation:
#### wallet\_connect
Connection request when user wants to connect their wallet.
```typescript
interface ConnectUIRequest {
id: string;
type: 'wallet_connect';
timestamp: number;
correlationId?: string;
data: {
appName: string;
appLogoUrl: string | null;
origin: string;
chainId: number;
capabilities?: Record;
};
}
```
**Expected response data:** `WalletConnectResponse` with `accounts` array.
#### personal\_sign
EIP-191 personal message signing.
```typescript
interface SignatureUIRequest {
id: string;
type: 'personal_sign';
timestamp: number;
correlationId?: string;
data: {
message: string; // Hex-encoded message
address: Address;
};
}
```
**Expected response data:** Signature string (hex).
#### eth\_signTypedData\_v4
EIP-712 typed data signing.
```typescript
interface TypedDataUIRequest {
id: string;
type: 'eth_signTypedData_v4';
timestamp: number;
correlationId?: string;
data: {
typedData: string; // JSON string
address: Address;
};
}
```
**Expected response data:** Signature string (hex).
#### wallet\_sendCalls
Transaction batch request (also used for `eth_sendTransaction`).
```typescript
interface TransactionUIRequest {
id: string;
type: 'wallet_sendCalls';
timestamp: number;
correlationId?: string;
data: {
version: '1.0';
from: Address;
calls: Array<{
to: string;
value?: string;
data?: string;
}>;
chainId: number;
atomicRequired?: boolean;
};
}
```
**Expected response data:** `{ id: string; chainId: number }` - the transaction bundle ID.
#### wallet\_grantPermissions
Permission grant request for session keys.
```typescript
interface PermissionUIRequest {
id: string;
type: 'wallet_grantPermissions';
timestamp: number;
correlationId?: string;
data: {
address: Address;
chainId: number | string;
expiry: number;
spender: Address;
permissions: {
spends?: Array<{
limit: string;
period: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year' | 'forever';
token: Address;
}>;
calls?: Array<{
target: Address;
selector: `0x${string}`;
functionSignature?: string;
}>;
};
};
}
```
**Expected response data:** Permission grant response object.
#### wallet\_revokePermissions
Permission revocation request.
```typescript
interface RevokePermissionUIRequest {
id: string;
type: 'wallet_revokePermissions';
timestamp: number;
correlationId?: string;
data: {
permissionId: string;
address: Address;
chainId?: number;
};
}
```
**Expected response data:** Revocation confirmation.
#### wallet\_sign
Unified signing request (type 0x45 for personal sign, 0x01 for EIP-712).
```typescript
interface WalletSignUIRequest {
id: string;
type: 'wallet_sign';
timestamp: number;
correlationId?: string;
data: {
address: Address;
chainId?: number;
request: {
type: '0x45' | '0x01';
data: string;
};
};
}
```
**Expected response data:** Signature string (hex).
### UIResponse Structure
Your handler must return a `UIResponse` object:
```typescript
interface UIResponse {
id: string; // Must match request.id
approved: boolean; // true if user approved, false if rejected
data?: T; // Result data when approved
error?: UIError; // Error when rejected
}
```
#### Approval Response
```typescript
return {
id: request.id,
approved: true,
data: resultData, // Type depends on request type
};
```
#### Rejection Response
```typescript
import { UIError } from '@jaw.id/core';
return {
id: request.id,
approved: false,
error: UIError.userRejected('User declined the request'),
};
```
### UIError Class
Use `UIError` for standardized error handling:
```typescript
import { UIError, UIErrorCode } from '@jaw.id/core';
// User rejected the request
UIError.userRejected('User declined');
// Request timed out
UIError.timeout('Request timed out after 30s');
// Unsupported request type
UIError.unsupportedRequest('wallet_unknown');
// Handler not available
UIError.handlerNotAvailable();
```
Error codes:
* `UIErrorCode.USER_REJECTED` (4001)
* `UIErrorCode.TIMEOUT` (4002)
* `UIErrorCode.UNSUPPORTED_REQUEST` (4003)
* `UIErrorCode.HANDLER_NOT_AVAILABLE` (4004)
### Complete Example
Here's a complete example of a custom UI handler using vanilla JavaScript with DOM manipulation:
```typescript
import {
UIHandler,
UIRequest,
UIResponse,
UIHandlerConfig,
UIError,
ConnectUIRequest,
SignatureUIRequest,
TypedDataUIRequest,
TransactionUIRequest,
} from '@jaw.id/core';
export class VanillaUIHandler implements UIHandler {
private config?: UIHandlerConfig;
private modalContainer: HTMLElement | null = null;
init(config: UIHandlerConfig): void {
this.config = config;
// Create modal container
this.modalContainer = document.createElement('div');
this.modalContainer.id = 'jaw-modal-container';
document.body.appendChild(this.modalContainer);
}
async request(request: UIRequest): Promise> {
switch (request.type) {
case 'wallet_connect':
return this.handleConnect(request as ConnectUIRequest) as Promise>;
case 'personal_sign':
return this.handleSign(request as SignatureUIRequest) as Promise>;
case 'eth_signTypedData_v4':
return this.handleTypedData(request as TypedDataUIRequest) as Promise>;
case 'wallet_sendCalls':
return this.handleTransaction(request as TransactionUIRequest) as Promise>;
default:
throw UIError.unsupportedRequest(request.type);
}
}
private async handleConnect(request: ConnectUIRequest): Promise> {
return new Promise((resolve) => {
const modal = this.createModal(`
Connect to ${request.data.appName}
Origin: ${request.data.origin}
Chain ID: ${request.data.chainId}
`);
modal.querySelector('#approve')?.addEventListener('click', async () => {
this.closeModal();
// Here you would trigger passkey authentication
// and return the account data
resolve({
id: request.id,
approved: true,
data: {
accounts: [{ address: '0x...' }],
},
});
});
modal.querySelector('#reject')?.addEventListener('click', () => {
this.closeModal();
resolve({
id: request.id,
approved: false,
error: UIError.userRejected(),
});
});
});
}
private async handleSign(request: SignatureUIRequest): Promise> {
return new Promise((resolve) => {
// Decode hex message for display
const message = Buffer.from(request.data.message.slice(2), 'hex').toString();
const modal = this.createModal(`
Sign Message
Address: ${request.data.address}
${message}
`);
modal.querySelector('#approve')?.addEventListener('click', async () => {
this.closeModal();
// Here you would sign the message with the user's passkey
const signature = '0x...'; // Your signing logic
resolve({
id: request.id,
approved: true,
data: signature,
});
});
modal.querySelector('#reject')?.addEventListener('click', () => {
this.closeModal();
resolve({
id: request.id,
approved: false,
error: UIError.userRejected(),
});
});
});
}
private async handleTypedData(request: TypedDataUIRequest): Promise> {
// Similar to handleSign but parse and display typed data
// ...
}
private async handleTransaction(request: TransactionUIRequest): Promise> {
// Display transaction details and get approval
// Execute transaction and return bundle ID
// ...
}
private createModal(content: string): HTMLElement {
if (!this.modalContainer) throw new Error('Handler not initialized');
const modal = document.createElement('div');
modal.className = 'jaw-modal';
modal.innerHTML = `
${content}
`;
this.modalContainer.appendChild(modal);
return modal;
}
private closeModal(): void {
if (this.modalContainer) {
this.modalContainer.innerHTML = '';
}
}
canHandle(request: UIRequest): boolean {
return ['wallet_connect', 'personal_sign', 'eth_signTypedData_v4', 'wallet_sendCalls'].includes(request.type);
}
async cleanup(): Promise {
this.closeModal();
this.modalContainer?.remove();
this.modalContainer = null;
}
}
```
### Best Practices
1. **Always match request.id in response** - The SDK uses this to correlate requests and responses.
2. **Handle all request types you support** - Use `canHandle()` to indicate which types your handler supports.
3. **Provide clear user feedback** - Display relevant information about what the user is approving.
4. **Implement cleanup** - Release resources and close dialogs when the handler is cleaned up.
5. **Use UIError for rejections** - This ensures consistent error handling across the SDK.
6. **Consider mobile responsiveness** - Your UI should work well on all device sizes.
7. **Security considerations**:
* Show full transaction details before approval
* Allow users to review typed data structures
* Clearly indicate when gas will be sponsored vs user-paid
### Related
* [preference.mode](/configuration/preference#mode) - Authentication mode configuration
* [preference.uiHandler](/configuration/preference#uihandler) - UIHandler option reference
* [Provider - RPC Reference](/api-reference) - All supported RPC methods
## Advanced
Advanced topics for customizing and extending the JAW SDK.
### Topics
#### [Custom UI Handler](/advanced/custom-ui-handler)
Build your own UI for app-specific mode by implementing the `UIHandler` interface. This is useful for non-React applications or when you need complete control over the user experience.
#### [Custom Passkey Server](/advanced/passkey-server)
Run your own passkey metadata server for managing passkey credentials. This gives you full control over passkey metadata storage and management.
### When to Use Advanced Features
Most applications will work well with the default SDK configuration and the provided `ReactUIHandler` from `@jaw.id/ui`. Consider the advanced topics when:
* **Custom UI Handler**: You're not using React, or you need pixel-perfect control over all approval dialogs
* **Custom Passkey Server**: You want to manage passkey metadata on your own infrastructure
For standard usage, see the [Configuration](/configuration) documentation.
## Custom Passkey Server
Run your own passkey metadata server for managing passkey credentials in app-specific mode.
### Overview
The SDK needs a server to store passkey metadata (credential IDs, public keys, display names). By default, the SDK uses `https://api.justaname.id/wallet/v2/passkeys`, but you can host your own.
:::warning
**Important:** This server does NOT store passkeys themselves—passkeys are always stored by the browser/device. The server only stores metadata to enable passkey lookups and management.
:::
### Configuration
Point the SDK to your custom server:
```typescript
import { JAW, Mode } from '@jaw.id/core';
import { ReactUIHandler } from '@jaw.id/ui';
const jaw = JAW.create({
apiKey: 'your-api-key',
preference: {
mode: Mode.AppSpecific,
uiHandler: new ReactUIHandler(),
serverUrl: 'https://your-server.example.com/passkeys',
},
});
```
### Required Endpoints
Your server must implement the following endpoints:
#### GET / - Lookup Passkeys by Credential IDs
Retrieves passkey information for one or more credential IDs.
**Query Parameters:**
* `credentialIds` (string\[], repeatable): Array of credential IDs to lookup
**Success Response (200):**
```json
{
"statusCode": 200,
"result": {
"data": {
"passkeys": [
{
"credentialId": "string",
"publicKey": "0x...",
"displayName": "string"
}
]
},
"error": null
}
}
```
**Error Response (404):**
```json
{
"statusCode": 404,
"result": {
"data": null,
"error": "Passkeys not found"
}
}
```
#### POST / - Register a New Passkey
Registers a new passkey credential.
**Headers:**
* `Content-Type: application/json`
**Request Body:**
```json
{
"credentialId": "string",
"publicKey": "0x...",
"displayName": "string"
}
```
**Response:**
* Status `200`/`201` for successful registration
* Status `4xx`/`5xx` for errors
### Data Format Requirements
| Field | Format | Description |
| ---------------- | --------------------------- | -------------------------------------- |
| **credentialId** | Base64url string | Standard WebAuthn credential ID format |
| **publicKey** | Hex string with `0x` prefix | The passkey's public key |
| **displayName** | String | Human-readable name for the passkey |
### When to Self-Host
Consider running your own passkey server when:
* **Data sovereignty**: You need full control over user passkey metadata
* **Custom logic**: You want to add additional validation or processing
* **Development/staging**: You need isolated environments for testing
* **Compliance**: Your organization requires self-hosted infrastructure
### Related
* [preference.serverUrl](/configuration/preference#serverurl) - Server URL configuration
* [preference.mode](/configuration/preference#mode) - Authentication mode configuration
## Account.create()
Create a new account with a passkey.
**Type:** `static async`
### Signature
```typescript
static async create(
config: AccountConfig,
options: CreateAccountOptions
): Promise
```
### Parameters
#### config
Type: `AccountConfig`
| Property | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------------------------- |
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
#### options
Type: `CreateAccountOptions`
| Property | Type | Required | Description |
| ---------- | -------- | -------- | ----------------------------------------------------------------- |
| `username` | `string` | Yes | Username/display name for the passkey |
| `rpId` | `string` | No | Relying party identifier (defaults to `window.location.hostname`) |
| `rpName` | `string` | No | Relying party name (defaults to `'JAW'`) |
### Returns
`Promise` - The newly created Account instance
### Behavior
1. Triggers WebAuthn credential creation (user will see browser passkey dialog)
2. Creates the smart account from the passkey
3. Stores the passkey account and authentication state
4. Returns the ready-to-use Account instance
### Errors
| Error | Description |
| --------------- | ------------------------------------------------------------------- |
| WebAuthn errors | User cancelled passkey creation or browser doesn't support WebAuthn |
| Network errors | Failed to communicate with JAW services |
### Examples
#### Basic Usage
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
console.log('Created account:', account.address);
console.log('Username:', account.getMetadata()?.username);
```
#### With Custom RP Settings
```typescript
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{
username: 'alice',
rpId: 'myapp.com',
rpName: 'My Awesome App',
}
);
```
### Usage in Custom UI Handler
```typescript
class MyUIHandler implements UIHandler {
private config?: UIHandlerConfig;
async handleConnect(request: ConnectUIRequest) {
// Show your custom onboarding UI
const username = await this.showUsernameInput();
const account = await Account.create(
{
chainId: request.data.chainId,
apiKey: this.config?.apiKey,
},
{ username }
);
return {
id: request.id,
approved: true,
data: {
accounts: [{ address: account.address }],
},
};
}
}
```
### Related
* [Account.get()](/account/get) - Login with existing account
* [Account.import()](/account/import) - Import from cloud backup
* [Account.getStoredAccounts()](/account/getStoredAccounts) - List created accounts
## account.estimateGas()
Estimate gas for a transaction.
**Type:** `instance async`
### Signature
```typescript
async estimateGas(calls: TransactionCall[]): Promise
```
### Parameters
#### calls
Type: `TransactionCall[]`
Array of transaction calls to estimate.
```typescript
interface TransactionCall {
/** Target contract address */
to: Address;
/** Value to send in wei (bigint or hex string) */
value?: bigint | string;
/** Call data */
data?: Hex;
}
```
### Returns
`Promise` - The estimated gas amount in wei.
### Behavior
Estimates the total gas required for the user operation, including:
* Call execution gas
* Verification gas
* Pre-verification gas
This estimate is used by the bundler to determine if the operation can be submitted.
### Example
#### Getting an Account Instance
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Basic Usage
```typescript
import { parseEther } from 'viem';
const gas = await account.estimateGas([
{ to: '0x...', value: parseEther('0.1') }
]);
console.log('Estimated gas:', gas.toString());
```
### Related
* [account.sendCalls()](/account/sendCalls) - Send batch transactions
* [account.sendTransaction()](/account/sendTransaction) - Send the transaction
## Account.fromLocalAccount()
Create an account from a viem LocalAccount. Ideal for server-side usage or integration with embedded wallet providers.
**Type:** `static async`
### Signature
```typescript
static async fromLocalAccount(
config: AccountConfig,
localAccount: LocalAccount
): Promise
```
### Parameters
#### config
Type: `AccountConfig`
| Property | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------------------------- |
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
#### localAccount
Type: `LocalAccount` (from viem)
A viem LocalAccount instance. Can be created from:
* Private key
* Mnemonic
* Embedded wallet providers (Privy, Dynamic, Magic, Turnkey, etc.)
### Returns
`Promise` - The Account instance
### Behavior
1. Creates a smart account using the LocalAccount as the owner
2. No passkey or WebAuthn involved
3. No authentication state stored (since there's no passkey)
4. `getMetadata()` returns `null` for these accounts
### Examples
#### From Private Key
```typescript
import { Account } from '@jaw.id/core';
import { privateKeyToAccount } from 'viem/accounts';
const localAccount = privateKeyToAccount('0x...');
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
console.log('Smart account address:', account.address);
console.log('Has metadata:', account.getMetadata()); // null
```
### When to Use
Use `fromLocalAccount()` when:
* **Server-side automation** - Backend services that need to execute transactions
* **Embedded wallets** - Integrating with Privy, Dynamic, Magic, Turnkey, etc.
Do **not** use when:
* You want passkey-based authentication (use `create()` or `get()`)
### Related
* [Account.get()](/account/get) - Passkey-based authentication
* [Account.create()](/account/create) - Create passkey account
* [getMetadata()](/account/getMetadata) - Returns null for LocalAccount-based accounts
## Account.get()
Get an account instance - restores an existing session or triggers WebAuthn login.
**Type:** `static async`
### Signature
```typescript
static async get(
config: AccountConfig,
credentialId?: string
): Promise
```
### Parameters
#### config
Type: `AccountConfig`
| Property | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------------------------- |
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
#### credentialId
Type: `string | undefined`
Optional credential ID to login with. If provided and not already authenticated, triggers WebAuthn authentication.
### Returns
`Promise` - The Account instance
### Behavior
This is the primary method to get an Account instance:
1. **If `credentialId` is provided**: Always triggers WebAuthn authentication, even if already authenticated. This ensures user verification when selecting a specific account.
2. **If no `credentialId` and already authenticated**: Restores the account from storage without prompting WebAuthn.
3. **If no `credentialId` and not authenticated**: Throws an error.
### Errors
| Error | Description |
| ------------------------------------ | ---------------------------------------------- |
| `Not authenticated` | Not authenticated and no credentialId provided |
| `No account found for credential ID` | Provided credentialId doesn't exist in storage |
### Examples
#### Restore Existing Session
```typescript
import { Account } from '@jaw.id/core';
// Restore without WebAuthn prompt (if already authenticated)
try {
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
console.log('Restored account:', account.address);
} catch (error) {
console.log('Not authenticated');
}
```
#### Login with Specific Account
```typescript
import { Account } from '@jaw.id/core';
// Get stored accounts
const accounts = Account.getStoredAccounts('your-api-key');
if (accounts.length > 0) {
// Login with first account (triggers WebAuthn)
const account = await Account.get(
{ chainId: 1, apiKey: 'your-api-key' },
accounts[0].credentialId
);
console.log('Logged in as:', account.address);
}
```
### Related
* [Account.create()](/account/create) - Create a new account
* [Account.getAuthenticatedAddress()](/account/getAuthenticatedAddress) - Check authentication status
* [Account.getStoredAccounts()](/account/getStoredAccounts) - List available accounts
## account.getAddress()
Get the smart account address (async).
**Type:** `instance async`
### Signature
```typescript
async getAddress(): Promise
```
### Parameters
None.
### Returns
`Promise` - The smart account address.
### Behavior
This async method returns the smart account address. It's useful for:
1. **Counterfactual addresses** - Getting the address before the account is deployed on-chain
2. **Address verification** - Confirming the computed address matches expectations
For deployed accounts, this returns the same value as the synchronous `address` property.
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
const address = await account.getAddress();
console.log('Address:', address);
```
### Related
* [account.address](/account#instance-properties) - Synchronous address property
* [account.getSmartAccount()](/account/getSmartAccount) - Get underlying smart account
* [account.sendTransaction()](/account/sendTransaction) - Deploys account if needed
## Account.getAuthenticatedAddress()
Get the authenticated account address without fully loading the account.
**Type:** `static`
### Signature
```typescript
static getAuthenticatedAddress(apiKey: string): Address | null
```
### Parameters
#### apiKey
Type: `string`
API key for JAW services. Used to scope the storage lookup.
### Returns
`Address | null` - The account address if authenticated, or `null` if not authenticated.
### Behavior
This is a synchronous method that checks the authentication state stored locally:
1. Checks localStorage for stored authentication state
2. Returns the address if found and valid
3. Returns `null` if not authenticated
This method does **not** trigger WebAuthn or any network requests.
### Example
```typescript
import { Account } from '@jaw.id/core';
const address = Account.getAuthenticatedAddress('your-api-key');
if (address) {
console.log('Authenticated as:', address);
} else {
console.log('Not authenticated');
}
```
### Use Cases
* **Check before restore** - Verify authentication before calling `Account.get()`
* **Conditional UI** - Show different UI based on authentication state
* **Quick status check** - Get address without full account initialization
### Related
* [Account.get()](/account/get) - Load the full account instance
* [Account.getStoredAccounts()](/account/getStoredAccounts) - List all stored accounts
* [Account.logout()](/account/logout) - Clear authentication state
## account.getCallStatus()
Get the status of a previously submitted call batch.
**Type:** `instance sync`
### Signature
```typescript
getCallStatus(batchId: Hash): CallStatusResponse | undefined
```
### Parameters
#### batchId
Type: `Hash`
The batch ID (userOpHash) returned from `sendCalls()`.
### Returns
`CallStatusResponse | undefined` - The call status in EIP-5792 format, or `undefined` if the batch ID is not found.
```typescript
interface CallStatusResponse {
/** EIP-5792 version */
version: string;
/** The batch ID (userOpHash) */
id: `0x${string}`;
/** Chain ID in hex format */
chainId: `0x${string}`;
/** Status code: 100=pending, 200=completed, 400=offchain failure, 500=onchain revert */
status: number;
/** Whether the operation is atomic (always true for ERC-4337) */
atomic: boolean;
/** Transaction receipts (present when completed or reverted) */
receipts?: CallReceipt[];
}
interface CallReceipt {
logs: Array<{
address: `0x${string}`;
data: `0x${string}`;
topics: `0x${string}`[];
}>;
status: `0x${string}`;
blockHash: `0x${string}`;
blockNumber: `0x${string}`;
gasUsed: `0x${string}`;
transactionHash: `0x${string}`;
}
```
### Status Codes
| Code | Name | Description |
| ---- | ---------------- | ------------------------------------------------ |
| 100 | Pending | Not yet completed onchain |
| 200 | Completed | Included onchain without reverts |
| 400 | Offchain Failure | Not included onchain, wallet won't retry |
| 500 | Complete Revert | Reverted completely, has receipt with status 0x0 |
### Behavior
1. Looks up the batch ID in the internal call status store
2. Returns the status in EIP-5792 format
3. Returns `undefined` if the batch ID is not found
4. Status is automatically updated in the background after `sendCalls()`
### Example
#### Getting an Account Instance
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Basic Usage
```typescript
// Send calls and get the batch ID
const { id } = await account.sendCalls([
{ to: '0x...', value: parseEther('0.1') }
]);
// Check status immediately
const status = account.getCallStatus(id);
```
### Related
* [account.sendCalls()](/account/sendCalls) - Send calls without waiting
* [account.sendTransaction()](/account/sendTransaction) - Send and wait for receipt
* [wallet\_getCallsStatus](/api-reference/wallet_getCallsStatus) - EIP-5792 RPC method
## account.getChain()
Get the chain configuration.
**Type:** `instance`
### Signature
```typescript
getChain(): Chain
```
### Parameters
None.
### Returns
`Chain` - The chain configuration object.
#### Chain
```typescript
interface Chain {
/** Chain ID */
id: number;
/** RPC URL for the chain */
rpcUrl: string;
/** Optional paymaster URL for gas sponsorship */
paymasterUrl?: string;
}
```
### Behavior
Returns a copy of the chain configuration used by this account instance. Modifying the returned object does not affect the account.
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
const chain = account.getChain();
console.log('Chain ID:', chain.id);
console.log('RPC URL:', chain.rpcUrl);
console.log('Paymaster URL:', chain.paymasterUrl);
```
### Related
* [account.chainId](/account#instance-properties) - Quick access to chain ID
* [account.getSmartAccount()](/account/getSmartAccount) - Get underlying smart account
* [AccountConfig](/account#accountconfig) - Configuration interface
## account.getMetadata()
Get account metadata (only available for passkey-based accounts).
**Type:** `instance`
### Signature
```typescript
getMetadata(): AccountMetadata | null
```
### Parameters
None.
### Returns
`AccountMetadata | null` - Account metadata or `null` for LocalAccount-based accounts.
#### AccountMetadata
```typescript
interface AccountMetadata {
/** Username/display name */
username: string;
/** ISO date string when the account was created */
creationDate: string;
/** Whether the account was imported from cloud backup */
isImported: boolean;
}
```
### Behavior
* For accounts created via `Account.create()`: Returns full metadata
* For accounts created via `Account.import()`: Returns metadata with `isImported: true`
* For accounts created via `Account.fromLocalAccount()`: Returns `null`
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
const metadata = account.getMetadata();
if (metadata) {
console.log('Username:', metadata.username);
console.log('Created:', metadata.creationDate);
console.log('Imported:', metadata.isImported);
} else {
console.log('Local account - no metadata available');
}
```
### Related
* [Account.create()](/account/create) - Creates account with metadata
* [Account.import()](/account/import) - Imports account (sets isImported: true)
* [Account.fromLocalAccount()](/account/fromLocalAccount) - Creates account without metadata
## account.getPermission()
Get details of a previously granted permission.
**Type:** `instance async`
### Signature
```typescript
async getPermission(permissionId: Hex): Promise
```
### Parameters
#### permissionId
Type: `Hex`
The permission ID (hash) to fetch. This is the `permissionId` field returned from `grantPermissions()`.
### Returns
`Promise` - The permission details.
```typescript
interface WalletGrantPermissionsResponse {
/** Smart account this permission is valid for */
account: Address;
/** Entity that can use this permission */
spender: Address;
/** Timestamp (in seconds) that specifies when this permission becomes valid */
start: number;
/** Timestamp (in seconds) that specifies the time by which this permission expires */
end: number;
/** Salt used for permission uniqueness (as hex string) */
salt: Hex;
/** Array of call permissions */
calls: CallPermissionDetail[];
/** Array of spend permissions */
spends: SpendPermissionDetail[];
/** Permission identifier - the permission hash from the contract */
permissionId: Hex;
/** Chain ID in hex format */
chainId: Hex;
}
```
### Behavior
1. Fetches the permission from the JAW relay by ID
2. Returns the full permission details in the same format as `grantPermissions()`
3. Throws if the permission is not found
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
const permission = await account.getPermission(
'0x1234567890abcdef...' // Permission ID
);
console.log('Spender:', permission.spender);
console.log('Expires:', new Date(permission.end * 1000));
console.log('Call permissions:', permission.calls);
console.log('Spend permissions:', permission.spends);
```
### Related
* [account.grantPermissions()](/account/grantPermissions) - Grant new permissions
* [account.revokePermission()](/account/revokePermission) - Revoke a permission
* [Subscription Payments Guide](/guides/subscription) - Learn about the permission system
## account.getSmartAccount()
Get the underlying viem SmartAccount for advanced operations.
**Type:** `instance`
### Signature
```typescript
getSmartAccount(): SmartAccount
```
### Parameters
None.
### Returns
`SmartAccount` - The raw viem SmartAccount instance.
### Behavior
Returns the underlying viem `SmartAccount` instance, giving you direct access to all viem smart account methods and properties.
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
const smartAccount = account.getSmartAccount();
console.log('SmartAccount type:', smartAccount.type);
```
### Related
* [account.getAddress()](/account/getAddress) - Get account address
* [account.getChain()](/account/getChain) - Get chain configuration
* [Account Overview](/account) - High-level Account API
## Account.getStoredAccounts()
Get all stored passkey accounts.
**Type:** `static`
### Signature
```typescript
static getStoredAccounts(apiKey: string): PasskeyAccount[]
```
### Parameters
#### apiKey
Type: `string`
API key for JAW services. Used to scope the storage lookup.
### Returns
`PasskeyAccount[]` - Array of stored passkey accounts.
#### PasskeyAccount
```typescript
interface PasskeyAccount {
/** WebAuthn credential ID */
credentialId: string;
/** Public key (hex encoded) */
publicKey: Hex;
/** Smart account address */
address: Address;
/** Username/display name */
username: string;
/** ISO date string when created */
creationDate: string;
/** Whether imported from cloud backup */
isImported: boolean;
}
```
### Behavior
This is a synchronous method that retrieves all passkey accounts stored locally:
1. Reads from localStorage
2. Returns all stored accounts regardless of authentication state
3. Returns an empty array if no accounts are stored
### Example
```typescript
import { Account } from '@jaw.id/core';
const accounts = Account.getStoredAccounts('your-api-key');
console.log(`Found ${accounts.length} stored accounts:`);
accounts.forEach(acc => {
console.log(`- ${acc.username}: ${acc.address}`);
});
```
### Use Cases
* **Account selector** - Build UI for selecting which account to login with
* **Onboarding flow** - Check if user has existing accounts
* **Account management** - Display list of all user accounts
* **Pre-fetch account info** - Show account details before authentication
### Related
* [Account.get()](/account/get) - Login with a specific account
* [Account.getAuthenticatedAddress()](/account/getAuthenticatedAddress) - Get current authenticated address
* [Account.create()](/account/create) - Create a new account
* [Account.import()](/account/import) - Import account from cloud backup
## account.grantPermissions()
Grant permissions to a spender.
**Type:** `instance async`
### Signature
```typescript
async grantPermissions(
expiry: number,
spender: Address,
permissions: PermissionsDetail
): Promise
```
### Parameters
#### expiry
Type: `number`
Unix timestamp (in seconds) when the permission expires.
#### spender
Type: `Address`
The address that can use this permission to execute calls on behalf of the account.
#### permissions
Type: `PermissionsDetail`
The permissions to grant.
```typescript
interface PermissionsDetail {
/** Call permissions - which contracts and functions can be called */
calls?: CallPermissionDetail[];
/** Spend permissions - token spending limits */
spends?: SpendPermissionDetail[];
}
interface CallPermissionDetail {
/** Target contract address */
target: Address;
/** Function selector (4 bytes) */
selector: Hex;
}
interface SpendPermissionDetail {
/** Token address (use 0xEee...EEeE for native ETH) */
token: Address;
/** Maximum amount that can be spent per period */
limit: string;
/** Time period for the limit */
period: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year';
}
```
### Returns
`Promise` - The granted permission details.
```typescript
interface WalletGrantPermissionsResponse {
/** Smart account this permission is valid for */
account: Address;
/** Entity that can use this permission */
spender: Address;
/** Timestamp (in seconds) that specifies when this permission becomes valid */
start: number;
/** Timestamp (in seconds) that specifies the time by which this permission expires */
end: number;
/** Salt used for permission uniqueness (as hex string) */
salt: Hex;
/** Array of call permissions */
calls: CallPermissionDetail[];
/** Array of spend permissions */
spends: SpendPermissionDetail[];
/** Permission identifier - the permission hash from the contract */
permissionId: Hex;
/** Chain ID in hex format */
chainId: Hex;
}
```
### Behavior
1. Creates a permission with the specified constraints
2. Signs the permission with the smart account
3. Registers the permission with the JAW relay
4. Returns the permission details including the unique ID
### Example
```typescript
// Grant both call and spend permissions
const permission = await account.grantPermissions(
Math.floor(Date.now() / 1000) + 86400 * 7, // 1 week
'0xSpenderAddress...',
{
calls: [
{
target: ROUTER_ADDRESS,
selector: '0x38ed1739', // swapExactTokensForTokens
},
{
target: ROUTER_ADDRESS,
selector: '0x7ff36ab5', // swapExactETHForTokens
}
],
spends: [
{
token: USDC_ADDRESS,
limit: '1000000000', // 1000 USDC
period: 'week',
}
],
}
);
console.log('Permission ID:', permission.permissionId);
console.log('Expires:', new Date(permission.end * 1000));
```
### Use Cases
* **Session keys** - Allow a temporary key to perform specific actions
* **Subscription services** - Grant recurring spending permissions
* **Automated trading** - Allow a bot to execute specific trades
* **Gaming** - Let a game contract execute moves without prompts
### Related
* [account.revokePermission()](/account/revokePermission) - Revoke a granted permission
* [account.getPermission()](/account/getPermission) - Get permission details
* [Subscription Payments Guide](/guides/subscription) - Learn more about the permission system
## Account.import()
Import a passkey from cloud backup.
**Type:** `static async`
### Signature
```typescript
static async import(config: AccountConfig): Promise
```
### Parameters
#### config
Type: `AccountConfig`
| Property | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------------------------- |
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
### Returns
`Promise` - The imported Account instance
### Behavior
1. Triggers WebAuthn authentication with empty `allowCredentials` (lets user select any available passkey)
2. User selects a passkey from their cloud-synced credentials (iCloud Keychain, Google Password Manager, etc.)
3. Creates the smart account from the imported passkey
4. Stores the passkey account marked as imported
5. Returns the ready-to-use Account instance
### Errors
| Error | Description |
| --------------------------------------------- | -------------------------------------------------- |
| WebAuthn errors | User cancelled or browser doesn't support WebAuthn |
| `Failed to retrieve imported passkey account` | Backend lookup failed |
### Examples
#### Basic Usage
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.import({
chainId: 1,
apiKey: 'your-api-key',
});
console.log('Imported account:', account.address);
const metadata = account.getMetadata();
console.log('Is imported:', metadata?.isImported); // true
```
### Usage in Custom UI Handler
```typescript
class MyUIHandler implements UIHandler {
async handleConnect(request: ConnectUIRequest) {
// Show import option in your UI
const action = await this.showConnectOptions();
if (action === 'import') {
const account = await Account.import({
chainId: request.data.chainId,
apiKey: this.config?.apiKey,
});
return {
id: request.id,
approved: true,
data: {
accounts: [{ address: account.address }],
},
};
}
// ... handle other actions
}
}
```
### Cloud Sync Providers
Passkeys can be synced via:
* **Apple**: iCloud Keychain (iOS, macOS, Safari)
* **Google**: Google Password Manager (Android, Chrome)
* **Microsoft**: Windows Hello (Windows, Edge)
* **1Password, Dashlane, etc.**: Third-party password managers
The import flow allows users to access their JAW account on any device where their passkeys are synced.
### Related
* [Account.create()](/account/create) - Create a new account
* [Account.get()](/account/get) - Login with existing account
* [Account.getStoredAccounts()](/account/getStoredAccounts) - List local accounts
## Account
The `Account` class provides a low-level API for smart account operations. It's the recommended way to interact with JAW smart accounts when building custom UI handlers or server-side applications.
### When to Use Account
The `Account` class is ideal for:
* **Custom UI Handlers** - When implementing your own `UIHandler` for app-specific mode
* **Server-side Applications** - Using `fromLocalAccount()` with embedded wallets (Privy, Dynamic, Turnkey)
* **Direct Integration** - When you need fine-grained control over account operations
For standard client-side usage with the built-in UI, use the provider-based API instead.
### Installation
:::code-group
```bash [npm]
npm install @jaw.id/core viem
```
```bash [pnpm]
pnpm add @jaw.id/core viem
```
```bash [yarn]
yarn add @jaw.id/core viem
```
```bash [bun]
bun add @jaw.id/core viem
```
:::
### Import
The `Account` class is exported from `@jaw.id/core`:
```typescript
import { Account } from '@jaw.id/core';
```
### Static Methods
Factory methods for creating Account instances:
| Method | Description |
| ------------------------------------------------------- | ------------------------------------------------ |
| [Account.get()](/account/get) | Get account - restores session or triggers login |
| [Account.create()](/account/create) | Create new account with passkey |
| [Account.import()](/account/import) | Import passkey from cloud backup |
| [Account.fromLocalAccount()](/account/fromLocalAccount) | Create from viem LocalAccount (server-side) |
Utility methods:
| Method | Description |
| --------------------------------------------------------------------- | --------------------------------- |
| [Account.getAuthenticatedAddress()](/account/getAuthenticatedAddress) | Get current authenticated address |
| [Account.getStoredAccounts()](/account/getStoredAccounts) | Get all stored passkey accounts |
| [Account.logout()](/account/logout) | Clear authentication state |
### Instance Properties
| Property | Type | Description |
| --------- | --------- | --------------------- |
| `address` | `Address` | Smart account address |
| `chainId` | `number` | Current chain ID |
### Instance Methods
#### Information
| Method | Description |
| --------------------------------------------- | ---------------------------------------------- |
| [getMetadata()](/account/getMetadata) | Get account metadata (username, creation date) |
| [getSmartAccount()](/account/getSmartAccount) | Get underlying viem SmartAccount |
| [getChain()](/account/getChain) | Get chain configuration |
| [getAddress()](/account/getAddress) | Get address (async, for counterfactual) |
#### Signing
| Method | Description |
| ----------------------------------------- | --------------------------------- |
| [signMessage()](/account/signMessage) | Sign a personal message (EIP-191) |
| [signTypedData()](/account/signTypedData) | Sign typed data (EIP-712) |
#### Transactions
| Method | Description |
| --------------------------------------------- | ---------------------------------------- |
| [sendTransaction()](/account/sendTransaction) | Send transaction and wait for receipt |
| [sendCalls()](/account/sendCalls) | Send bundled calls (returns immediately) |
| [estimateGas()](/account/estimateGas) | Estimate gas for calls |
#### Permissions
| Method | Description |
| ----------------------------------------------- | ------------------------------ |
| [grantPermissions()](/account/grantPermissions) | Grant permissions to a spender |
| [revokePermission()](/account/revokePermission) | Revoke a permission |
| [getPermission()](/account/getPermission) | Get permission details |
### Interfaces
#### AccountConfig
Configuration for creating or loading an account:
```typescript
interface AccountConfig {
/** Chain ID for the account */
chainId: number;
/** API key for JAW services (required) */
apiKey: string;
/** Custom paymaster URL for gas sponsorship */
paymasterUrl?: string;
}
```
#### CreateAccountOptions
Options for creating a new account:
```typescript
interface CreateAccountOptions {
/** Username/display name for the passkey */
username: string;
/** Relying party identifier (defaults to window.location.hostname) */
rpId?: string;
/** Relying party name (defaults to 'JAW') */
rpName?: string;
}
```
#### TransactionCall
Transaction call structure:
```typescript
interface TransactionCall {
/** Target contract address */
to: Address;
/** Value to send in wei (bigint or hex string) */
value?: bigint | string;
/** Call data */
data?: Hex;
}
```
#### AccountMetadata
Account metadata returned by `getMetadata()`:
```typescript
interface AccountMetadata {
/** Username/display name */
username: string;
/** ISO date string when created */
creationDate: string;
/** Whether imported from cloud */
isImported: boolean;
}
```
### Value Format
The `value` field in `TransactionCall` must be in **wei** (the smallest unit of ETH):
```typescript
import { parseEther } from 'viem';
// BigInt (wei)
{ to: '0x...', value: 1000000000000000000n } // 1 ETH in wei
// Hex string (wei)
{ to: '0x...', value: '0x0de0b6b3a7640000' } // 1 ETH in wei
// Using parseEther for convenience
{ to: '0x...', value: parseEther('1') } // 1 ETH
{ to: '0x...', value: parseEther('0.1') } // 0.1 ETH
```
Use `parseEther()` from viem to convert human-readable ETH amounts to wei.
### Related
* [Custom UI Handler](/advanced/custom-ui-handler) - Build custom UI with Account class
* [Provider - RPC Reference](/api-reference) - Provider-based RPC methods
## Account.logout()
Clear authentication state (logout).
**Type:** `static`
### Signature
```typescript
static logout(apiKey: string): void
```
### Parameters
#### apiKey
Type: `string`
API key for JAW services. Used to scope the storage operation.
### Returns
`void`
### Behavior
This method clears the current authentication state:
1. Removes the authenticated address from storage
2. Clears the current credential ID association
3. Does **not** remove stored passkey accounts (user can still login later)
After logout:
* `Account.getAuthenticatedAddress()` returns `null`
* `Account.get()` without credentialId will throw
* `Account.getStoredAccounts()` still returns all accounts
### Example
```typescript
import { Account } from '@jaw.id/core';
Account.logout('your-api-key');
console.log('Logged out');
```
### Important Notes
* Logout is **local only** - it doesn't affect the smart account
* Stored passkey accounts remain available for future login
* The user can still access their account by logging in again
### Related
* [Account.get()](/account/get) - Login to an account
* [Account.getAuthenticatedAddress()](/account/getAuthenticatedAddress) - Check authentication status
* [Account.getStoredAccounts()](/account/getStoredAccounts) - List available accounts
## account.revokePermission()
Revoke a previously granted permission.
**Type:** `instance async`
### Signature
```typescript
async revokePermission(permissionId: Hex): Promise
```
### Parameters
#### permissionId
Type: `Hex`
The permission ID (hash) to revoke. This is the `id` field returned from `grantPermissions()`.
### Returns
`Promise` - The revocation response.
```typescript
interface RevokePermissionApiResponse {
/** Whether the revocation was successful */
success: boolean;
/** Transaction hash if on-chain revocation was needed */
transactionHash?: Hex;
}
```
### Behavior
1. Sends a revocation request to the JAW relay
2. Signs the revocation with the smart account
3. The permission is invalidated and can no longer be used
4. Returns confirmation of the revocation
### Example
```typescript
import { Account } from '@jaw.id/core';
const account = await Account.get({
chainId: 1,
apiKey: 'your-api-key',
});
// Revoke a permission by its ID
const response = await account.revokePermission(
'0x1234567890abcdef...' // Permission ID from grantPermissions()
);
if (response.success) {
console.log('Permission revoked successfully');
} else {
console.log('Revocation failed');
}
```
### Related
* [account.grantPermissions()](/account/grantPermissions) - Grant new permissions
* [account.getPermission()](/account/getPermission) - Get permission details
* [Subscription Payments Guide](/guides/subscription) - Learn about the permission system
## account.sendCalls()
Send multiple calls as a bundled user operation without waiting for receipt.
**Type:** `instance async`
### Signature
```typescript
async sendCalls(
calls: TransactionCall[],
options?: SendCallsOptions
): Promise
```
### Parameters
#### calls
Type: `TransactionCall[]`
Array of transaction calls to execute.
```typescript
interface TransactionCall {
/** Target contract address */
to: Address;
/** Value to send in wei (bigint or hex string) */
value?: bigint | string;
/** Call data */
data?: Hex;
}
```
#### options (optional)
Type: `SendCallsOptions`
Optional settings for call execution.
```typescript
interface SendCallsOptions {
/** Permission ID to use for executing the calls through the permission manager */
permissionId?: Hex;
}
```
### Returns
`Promise` - The user operation ID and chain ID.
```typescript
interface BundledTransactionResult {
/** User operation hash */
id: Hex;
/** Chain ID where the operation was submitted */
chainId: number;
}
```
### Behavior
1. Bundles all calls into a single user operation
2. Sends the user operation to the bundler
3. **Returns immediately** with the user operation ID
4. Does not wait for transaction inclusion
For waiting until the transaction is mined, use [`sendTransaction()`](/account/sendTransaction).
### Example
#### Getting an Account Instance
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Batch Multiple Calls
```typescript
import { encodeFunctionData } from 'viem';
// Multiple token transfers in one operation
const transfers = recipients.map(({ address, amount }) => ({
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [address, amount],
}),
}));
const { id } = await account.sendCalls(transfers);
console.log('Batch transfer submitted:', id);
```
#### Execute with Permission (Delegated Execution)
When a permission has been granted via `account.grantPermissions()`, you can execute calls using that permission. This allows delegated execution within the permission's constraints.
```typescript
import { encodeFunctionData } from 'viem';
// First, get the permission ID from a previous grantPermissions call
const permissionId = '0x1234...'; // The permission ID returned from grantPermissions
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: ['0x5678...', 1000000n],
});
// Execute the transfer using the permission
const { id } = await account.sendCalls(
[{ to: USDC_ADDRESS, data: transferData }],
{ permissionId }
);
console.log('Batch transfer submitted with permission:', id);
```
When using the `permissionId` option:
* The calls are executed through the JustaPermissionManager contract
* The permission's call and spend limits are enforced
* The spender must have been granted appropriate permissions for the calls being made
### sendTransaction vs sendCalls
| | `sendTransaction()` | `sendCalls()` |
| -------- | ------------------- | ----------------------- |
| Returns | Transaction hash | User operation ID |
| Waits | Yes, until mined | No, returns immediately |
| Use case | Need confirmation | Fire and forget |
### Related
* [account.sendTransaction()](/account/sendTransaction) - Send and wait for receipt
* [account.estimateGas()](/account/estimateGas) - Estimate gas before sending
## account.sendTransaction()
Send a transaction and wait for the receipt.
**Type:** `instance async`
### Signature
```typescript
async sendTransaction(calls: TransactionCall[]): Promise
```
### Parameters
#### calls
Type: `TransactionCall[]`
Array of transaction calls to execute.
```typescript
interface TransactionCall {
/** Target contract address */
to: Address;
/** Value to send in wei (bigint or hex string) */
value?: bigint | string;
/** Call data */
data?: Hex;
}
```
### Returns
`Promise` - The transaction hash after the transaction is included in a block.
### Value Format
The `value` field must be in **wei** (the smallest unit of ETH):
```typescript
import { parseEther } from 'viem';
// BigInt (wei)
{ to: '0x...', value: 1000000000000000000n } // 1 ETH in wei
// Hex string (wei)
{ to: '0x...', value: '0x0de0b6b3a7640000' } // 1 ETH in wei
// Using parseEther for convenience
{ to: '0x...', value: parseEther('1') } // 1 ETH
{ to: '0x...', value: parseEther('0.1') } // 0.1 ETH
```
### Example
#### Getting an Account Instance
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Send ETH
```typescript
import { parseEther } from 'viem';
// Send 0.1 ETH
const hash = await account.sendTransaction([
{ to: '0xRecipient...', value: parseEther('0.1') }
]);
console.log('Transaction hash:', hash);
```
#### Contract Call
```typescript
import { encodeFunctionData } from 'viem';
// Encode the function call
const data = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: ['0xRecipient...', 1000000n], // 1 USDC (6 decimals)
});
const hash = await account.sendTransaction([
{ to: USDC_ADDRESS, data }
]);
```
### Related
* [account.sendCalls()](/account/sendCalls) - Send without waiting for receipt
* [account.estimateGas()](/account/estimateGas) - Estimate gas before sending
## account.signMessage()
Sign a personal message (EIP-191).
**Type:** `instance async`
### Signature
```typescript
async signMessage(message: string): Promise
```
### Parameters
#### message
Type: `string`
The message to sign.
### Returns
`Promise` - The signature as a hex string.
### Behavior
1. Creates an EIP-191 personal sign message
2. Signs with the smart account (passkey or local account)
3. Returns the signature wrapped for smart account validation
### Example
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Basic Usage
```typescript
const signature = await account.signMessage('Hello, World!');
console.log('Signature:', signature);
```
#### Sign-In with Ethereum (SIWE)
```typescript
// Construct SIWE message
const siweMessage = `myapp.com wants you to sign in with your Ethereum account:
${account.address}
Sign in to MyApp
URI: https://myapp.com
Version: 1
Chain ID: 1
Nonce: ${generateNonce()}
Issued At: ${new Date().toISOString()}`;
const signature = await account.signMessage(siweMessage);
// Send to backend for verification
await fetch('/api/verify-siwe', {
method: 'POST',
body: JSON.stringify({
message: siweMessage,
signature,
address: account.address,
}),
});
```
### Related
* [account.signTypedData()](/account/signTypedData) - Sign EIP-712 typed data
* [account.sendTransaction()](/account/sendTransaction) - Send transactions
* [Account Overview](/account) - High-level Account API
## account.signTypedData()
Sign EIP-712 typed data.
**Type:** `instance async`
### Signature
```typescript
async signTypedData(
typedData: TypedDataDefinition
): Promise
```
### Parameters
#### typedData
Type: `TypedDataDefinition`
The EIP-712 typed data to sign, including domain, types, primary type, and message.
```typescript
interface TypedDataDefinition {
domain: {
name?: string;
version?: string;
chainId?: number;
verifyingContract?: Address;
salt?: Hex;
};
types: Record>;
primaryType: string;
message: Record;
}
```
### Returns
`Promise` - The signature as a hex string.
### Behavior
1. Encodes the typed data according to EIP-712
2. Signs with the smart account (passkey or local account)
3. Returns the signature wrapped for smart account validation
### Example
#### Getting an Account Instance
Before calling instance methods, create an account using one of the factory methods:
```typescript
import { Account } from '@jaw.id/core';
// Option 1: Restore existing session or login with passkey
const account = await Account.get({ chainId: 1, apiKey: 'your-api-key' });
// Option 2: Create a new account with passkey
const account = await Account.create(
{ chainId: 1, apiKey: 'your-api-key' },
{ username: 'alice' }
);
// Option 3: Import from cloud backup
const account = await Account.import({ chainId: 1, apiKey: 'your-api-key' });
// Option 4: From a local account (server-side / embedded wallets)
const account = await Account.fromLocalAccount(
{ chainId: 1, apiKey: 'your-api-key' },
localAccount
);
```
#### Basic Usage
```typescript
const signature = await account.signTypedData({
domain: {
name: 'MyApp',
version: '1',
chainId: 1,
verifyingContract: '0x...',
},
types: {
Message: [
{ name: 'content', type: 'string' },
{ name: 'timestamp', type: 'uint256' },
],
},
primaryType: 'Message',
message: {
content: 'Hello, World!',
timestamp: BigInt(Date.now()),
},
});
```
### Related
* [account.signMessage()](/account/signMessage) - Sign personal messages
* [account.sendTransaction()](/account/sendTransaction) - Send transactions
* [EIP-712 Specification](https://eips.ethereum.org/EIPS/eip-712) - Typed data standard