Skip to content

fromSafeWebauthn: support per-op isInit for mixed-init multichain bundles #164

Description

@Sednaoui

Problem

fromSafeWebauthn takes a single `isInit: boolean` at construction and returns a `Signer` with a fixed `address` resolved against that `isInit` (shared signer for `true`, deterministic verifier-proxy for `false`).

This works for the homogeneous-init case — all UserOps in a multichain bundle share the same `isInit` value (e.g., a brand-new Safe being deployed on N chains in one bundle, or an already-deployed Safe sending follow-up ops on N chains).

It does not work for the mixed-init case — chain A is already deployed (`isInit=false`, owner = verifier proxy at one address) while chain B is fresh (`isInit=true`, owner = shared signer at a different address). The two ops need different per-op signer addresses, but `SafeMultiChainSigAccountV1.signUserOperationsWithSigners` only accepts a flat `signers` array — one signer applied uniformly to every op.

This is a realistic scenario: any time a passkey-owned multichain Safe expands to a new chain, the first cross-chain bundle on that new chain has the deployment op (isInit=true) alongside follow-up ops on the older chains (isInit=false).

Affected code

  • `src/account/Safe/adapters.ts` — `fromSafeWebauthn()`
  • `src/account/Safe/SafeMultiChainSigAccount.ts` — `signUserOperationsWithSigners()`

Possible API shapes

Option A: signer factory

`fromSafeWebauthn` returns a function that the plural method calls per-op:

```ts
const signerFactory = fromSafeWebauthn({ publicKey, accountClass, getAssertion })
// SDK invokes per UserOperationToSign:
// const signer = signerFactory({ isInit: uop.userOperation.nonce === 0n })
```

Option B: union type for the signers param

`signUserOperationsWithSigners` accepts either a fixed signer (homogeneous) or a per-op resolver (mixed):

```ts
signUserOperationsWithSigners(
opsToSign: UserOperationToSign[],
signers: ReadonlyArray<Signer | ((op: UserOperationToSign) => Signer)>,
)
```

Option A is cleaner; Option B is less invasive. Both compose with the existing #155 fix.

Workaround today

Bypass `signUserOperationsWithSigners` entirely: compute `getMultiChainSingleSignatureUserOperationsEip712Hash` yourself, call the adapter's `signHash` directly, then use `formatSignaturesToUseroperationsSignatures` with per-op `webAuthnSignatureOverrides.isInit`. This is what `abstractionkit-examples/chain-abstraction/add-owner-passkey.ts` did before #155 landed; an earlier comment on #153 (#153 (comment)) already flagged this scope gap.

Out of scope for this issue

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions