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=headlessquotes 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-executeforwards 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:
intentOpanddigestResult, 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.