Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
PasskeyWalletClient – 1auth
Skip to content

Overview

PasskeyWalletClient is a viem-compatible wallet client that extends the standard WalletClient with passkey-authenticated transaction signing. It provides a familiar viem API while handling all WebAuthn operations internally.

Use this when you want:

  • A standard viem WalletClient interface
  • Batched transaction support via sendCalls()
  • Output-first token requests (specify what tokens you want to receive)

Creating a Wallet Client

import { createPasskeyWalletClient } from '@rhinestone/1auth';
import { base } from 'viem/chains';
import { http } from 'viem';
 
const walletClient = createPasskeyWalletClient({
  accountAddress: '0x...', // User's smart account address
  username: 'user@example.com',
  chain: base,
  transport: http(),
});

Config Options

OptionTypeRequiredDescription
accountAddressAddressYesUser's smart account address
usernamestringYesUsername for the passkey provider
chainChainYesviem chain configuration
transportTransportYesviem transport (e.g., http())
providerUrlstringNoURL of the 1auth provider (defaults to https://passkey.1auth.box)
dialogUrlstringNoURL for the dialog UI
waitForHashbooleanNoWait for transaction hash before resolving (default: true)
hashTimeoutMsnumberNoMaximum time to wait for hash in ms
hashIntervalMsnumberNoPoll interval for hash in ms

sendCalls

Send multiple calls as a single batched transaction. Opens the passkey modal for user approval.

const hash = await walletClient.sendCalls({
  calls: [
    {
      to: '0x...', // Contract address
      data: '0x...', // Encoded calldata
      value: parseEther('0.1'), // Optional ETH value
      label: 'Swap ETH for USDC', // Optional: shown in signing UI
      sublabel: '0.1 ETH → 250 USDC', // Optional: additional context
      icon: 'https://your-app.example/icons/swap.svg', // Optional fallback icon
    },
  ],
  chainId: 8453, // Optional: override chain
});

Parameters

PropertyTypeRequiredDescription
callsTransactionCall[]YesArray of calls to execute
chainIdnumberNoOverride the default chain ID
tokenRequests{ token: string; amount: bigint }[]NoOutput tokens to deliver

TransactionCall

PropertyTypeRequiredDescription
toAddressYesTarget contract address
dataHexNoEncoded calldata
valuebigintNoETH value in wei
labelstringNoLabel shown in signing UI
sublabelstringNoAdditional context for UI
iconstringNoFallback icon URL (SVG / square PNG / data: URL) shown in the sign dialog when 1auth's built-in token registry can't resolve a logo for this call. See Per-call icons.
abireadonly unknown[]NoOptional ABI used by the sign dialog to render an unverified decode of data (function name + args). See Per-call ABIs.

Returns

Promise<Hash> - The transaction hash once confirmed.

Per-call icons

The sign dialog shows a 48px icon next to every action card. By default 1auth resolves the icon from the token's symbol / address (USDC, ETH, MATIC …) using a built-in registry, and falls back to a neutral letter avatar for unknown contracts.

For demos, custom NFT mints, or any contract 1auth doesn't recognise, pass icon on the call so the dialog renders your glyph instead of the fallback:

await walletClient.sendCalls({
  calls: [
    {
      to: MOCK_NFT_ADDRESS,
      data: encodeFunctionData({ abi: mockNftAbi, functionName: 'mint', args: [price] }),
      label: 'Mint NFT',
      sublabel: '0.10 USDC',
      icon: 'https://your-app.example/nft-glyph.svg',
    },
  ],
  chainId: 421614,
});

Rules

  • Built-in icons always win. If 1auth recognises the call's token (USDC, ETH, …), it uses that icon and ignores icon. This keeps apps from impersonating well-known assets with a custom SVG.
  • Allowed schemes: https://... or data:image/(svg+xml|png|webp|jpeg|gif);.... Other schemes (http:, javascript:, file:, …) are rejected by the server with a 400. Maximum length 8 KB.
  • Same field on every call shape. IntentCall, TransactionCall (walletClient.sendCalls), and the EIP-1193 wallet_sendCalls params all accept the same icon?: string field — mix and match as you would label and sublabel.

Privacy note

HTTPS icon URLs are fetched directly by the user's browser when the sign dialog renders. That leaks the user's IP, User-Agent, and Referer to whatever host you point at — turning the icon URL into a real-time beacon for "user is signing right now." If you don't want to make a third-party request from inside the dialog, use a data:image/svg+xml,… URL instead (the SVG is inlined into the call payload, never fetched).

Per-call ABIs

By default the sign dialog can only decode calls whose contract+selector is in 1auth's ERC-7730 clear-signing registry, or matches the built-in ERC-20 fallback (transfer / approve / transferFrom). Calls to a custom contract — your own marketplace, an NFT mint, a game state update — render as Unverified with just the raw selector.

Pass abi on the call so the dialog can decode data into a human-readable function signature with named arguments:

import { encodeFunctionData } from 'viem';
 
const myAppAbi = [
  {
    name: 'mintWithReferral',
    type: 'function',
    inputs: [
      { name: 'tokenId', type: 'uint256' },
      { name: 'referrer', type: 'address' },
    ],
    outputs: [{ type: 'bool' }],
  },
] as const;
 
await walletClient.sendCalls({
  calls: [
    {
      to: MY_NFT_ADDRESS,
      data: encodeFunctionData({
        abi: myAppAbi,
        functionName: 'mintWithReferral',
        args: [42n, referrerAddress],
      }),
      label: 'Mint NFT',
      sublabel: '0.10 USDC',
      icon: 'https://your-app.example/nft-glyph.svg',
      abi: myAppAbi,
    },
  ],
  chainId: 421614,
});

The sign dialog's expanded action card now shows:

[Unverified]
mintWithReferral(uint256 tokenId, address referrer)
tokenId       42
referrer      0xabcd…1234

Trust model

abi is app-supplied and unverified — the same trust treatment as label, sublabel, and icon. The dialog:

  • Always renders the decoded preview behind an amber "Unverified" badge so users can tell it didn't come from a verified clear-signing entry.
  • Always shows the raw to address alongside the decode, so ground truth is never hidden.
  • Silently drops the preview if decodeFunctionData throws — e.g. when the ABI doesn't include a function with a matching selector. The call still goes through; it just falls back to the unverified-selector display.

This is the same model used by client.grantPermissions({ contracts })'s ABI metadata in the SmartSession review surface.

Rules

  • Verified entries always win. If the call's contract+selector matches a registry entry (ERC-7730 clear-signing data, ERC-20 fallback), that decoded display is used and abi is ignored. An app cannot override USDC's verified transfer row with its own ABI definition.
  • Same field on every call shape. IntentCall, TransactionCall (walletClient.sendCalls), and the EIP-1193 wallet_sendCalls params all accept the same abi?: readonly unknown[] field.
  • Size cap. The server rejects ABIs with more than 256 top-level items to keep prepare-time JSON parsing bounded.

Pairing with viem

The natural pairing is to use the same ABI for both encoding and the preview, so the bytes on the wire and the displayed function are guaranteed to match by construction:

const abi = myAppAbi;
 
await walletClient.sendCalls({
  calls: [
    {
      to: MY_NFT_ADDRESS,
      data: encodeFunctionData({ abi, functionName: 'mint', args: [42n] }),
      abi, // same ABI used for both encoding and display
    },
  ],
});

Token Requests (Output-First Model)

The tokenRequests parameter lets you specify what tokens and amounts you want to receive as the output of your transaction. This is the output-first model - you declare the desired result, and the orchestrator figures out how to achieve it.

import { parseUnits } from 'viem';
 
const hash = await walletClient.sendCalls({
  calls: [
    {
      to: '0xUSDC_ADDRESS',
      data: '0x', // Empty for simple transfers
      label: 'Receive USDC',
    },
  ],
  tokenRequests: [
    {
      token: '0xUSDC_ADDRESS', // Token contract address
      amount: parseUnits('100', 6), // 100 USDC (6 decimals)
    },
  ],
});

Type Signature

tokenRequests?: {
  token: string;   // ERC-20 token contract address
  amount: bigint;  // Amount in base units (use parseUnits)
}[];

When to Use Token Requests

  • Swaps: Specify the output token and amount you want to receive
  • Cross-chain transfers: Declare the destination token and amount
  • Multi-step operations: Let the orchestrator optimize the path

The orchestrator will automatically:

  1. Determine required input tokens from your portfolio
  2. Find the optimal route across chains
  3. Execute bridging and swaps as needed

Example: Token Swap

import { parseUnits } from 'viem';
 
// Swap: receive 100 USDC on Base
const hash = await walletClient.sendCalls({
  calls: [
    {
      to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
      data: '0x',
      label: 'Swap to USDC',
      sublabel: 'Receive 100 USDC',
    },
  ],
  tokenRequests: [
    {
      token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
      amount: parseUnits('100', 6),
    },
  ],
  chainId: 8453, // Base
});
 
console.log('Transaction hash:', hash);

Notes

  • The client extends viem's WalletClient, so standard methods like getAddresses() work as expected
  • sendCalls() opens the passkey modal for user approval before executing
  • Use waitForHash: false if you only need intent confirmation (faster, but no hash)
  • Token amounts use bigint for viem compatibility - use parseUnits() for decimals