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

Headless Mode

Headless mode lets an app submit a 1auth intent with signatures produced outside the 1auth passkey dialog. Use it when the user's ERC-7579 account already has another validator installed and your app can produce the signatures that validator expects.

The important boundary is simple: 1auth prepares the intent and forwards opaque signature bytes, but it does not decide whether those bytes are valid. The installed on-chain validator is the signature gate.

For example, the non-passkey signer might be:

  • an app-managed ECDSA signer,
  • a backend or AWS KMS signing service,
  • another ERC-7579 validator your app has installed out of band.

If you want 1auth to show the user a passkey approval dialog for every intent, use OneAuthClient.sendIntent instead.

What 1auth does

In headless mode, 1auth still provides the intent infrastructure:

  • /api/intent/prepare?signingMode=headless quotes the intent, performs balance checks, and returns the message hashes your validator signer must sign.
  • Sponsorship helpers mint access and extension JWTs for apps that sponsor intents.
  • /api/intent/headless-execute forwards your pre-encoded validator signatures to the orchestrator.
  • Status and wait endpoints work the same way as the passkey flow.

1auth treats the submitted signatures as opaque bytes. It does not open clear signing UI and does not verify the validator signature locally.

Requirements

Before using headless mode:

  • the account must already have a validator installed that can authorize the intent,
  • your app must know how to sign the prepared hashes for that validator,
  • your app must encode the signatures in the validator-prefixed format expected by the orchestrator,
  • submitted calls must stay inside whatever policy or authorization the installed validator enforces.

This guide only covers the prepare-sign-submit execution path. If the validator is a SmartSession validator, put the setup, policy review, and session-key details in the Permissions flow. The permissions flow stores public grant metadata per browser origin, so a registered app origin can later recover its sessionKeyHandle without asking the user to grant the same SmartSession permission again.

Prepare

import { OneAuthHeadlessClient } from "@rhinestone/1auth/headless";
 
const headless = new OneAuthHeadlessClient({
  providerUrl: "https://passkey.1auth.box",
  sponsorship: {
    accessTokenUrl: "/api/sponsorship/access-token",
    extensionTokenUrl: "/api/sponsorship/extension-token",
  },
});
 
const prepared = await headless.prepareIntent({
  accountAddress,
  targetChain: 84532,
  calls: [
    {
      to: vault,
      data: depositCalldata,
      value: "0",
    },
  ],
});

prepareIntent() returns:

  • intentOp and digestResult, which are passed back unchanged at submit time,
  • per-origin message hashes,
  • destination and target-execution message hashes when required,
  • the echoed account, calls, target chain, and expiry.

Sign

Sign the prepared hashes with the non-passkey validator your app controls. 1auth does not prescribe the signer or encoding here; use the encoder for your validator.

const {
  originSignatures,
  destinationSignature,
  targetExecutionSignature,
} = await signWithInstalledValidator({
  prepared,
  validatorContext,
});

The contents of validatorContext depend on the installed validator.

Submit

const result = await headless.submitIntent({
  intentOp: prepared.intentOp,
  digestResult: prepared.digestResult,
  accountAddress: prepared.accountAddress,
  targetChain: prepared.targetChain,
  calls: prepared.calls,
  expiresAt: prepared.expiresAt,
  originSignatures,
  destinationSignature,
  targetExecutionSignature,
});

For sponsored intents, submitIntent() fetches an extension token and binds it to the prepared intentOp. Pass sponsor: false for self-paying intents.

Wait

const final = await headless.waitForIntent(
  result.intentId,
  result.transactionResult,
);

waitForIntent() polls 1auth's wait endpoint and returns the latest orchestrator status. If the orchestrator exposes a transaction hash, the headless client returns it in the submit or wait response.

Trust Model

Headless mode removes the per-intent passkey dialog, so your app becomes responsible for deciding what to sign. 1auth still protects sponsored intents by binding the extension token to the prepared intentOp, but a malicious or buggy app-side signer can still sign the wrong thing.

The safety boundary is the installed validator:

  • if the validator approves the submitted signature, the intent can execute,
  • if the calls drift outside that validator's policy, the validator should reject on-chain.