# Smart-wallet login & admin (`user-1271` + EIP-1271 `admin-sig`)

Two additive capabilities for callers whose wallet is a **smart contract** (a Safe, or any
[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) wallet) rather than an EOA: **`user-1271`**
lets a human log in with a smart-contract wallet, the same way `user-sig`/`user-passkey` do;
**smart-wallet `admin-sig`** lets a Safe mint and be verified as an `admin-sig` deed holder
too (Grantor's own dashboard doesn't accept one yet — see below). Both ride the SAME
`Erc1271Reader.isValidSignature` gate rather than `ecrecover`. See [Concepts](concepts.md) and
[Wallet login](wallet-login.md) for the EOA baseline these modes extend.

## User login: `user-1271`

The assembler is pure, cross-language, does no I/O — it assembles a `Deed` from an address and already-obtained signature bytes:

```ts
import { mintUser1271Deed } from "@grantor/sdk";

// signerAddrHex / walletSigHex: whatever your Safe/wagmi glue produced for
// the exact hash your app's verifier will independently rebuild (see above).
const deed = mintUser1271Deed(
  signerAddrHex,
  walletSigHex,
  tenantId,
  audience,
  origin,      // the origin YOU are actually running on
  challenge,   // from your app's own /challenge
  exp,
);
```

Ships as `mintUser1271Deed`/`mint_user_1271_deed`/`MintUser1271Deed`/
`mint_user_1271_deed` (TS/Python/Go/Rust) — the "`user-1271` mint" row in
`sdk/capability_matrix.py`. Your app verifies the resulting deed exactly
like any other, through the SAME `DeedGuard`/`DeedVerifier.verifyAt` call
every other mode uses — `user-1271` needs no special handling on the
verify side beyond having a chain reader wired in (which every sovereign
verifier already has). See [Verify a deed](verify-tokens.md).

## Why this is not "EIP-1271 on the `user-sig` path"

An early note described this work as landing "on the `user-sig` verify
path." That was wrong, and worth being explicit about because the reason is
structural, not a wording nit: `user-sig`'s wallet signature never reaches
the verifier at all. The wallet signs `user_root_binding` **once, holder
side**, to derive a `root_seed`; a brand-new local ECDSA keypair is then
derived from that seed via HKDF, and *that* local key signs the deed the
verifier checks — pure local math, no chain read, no wallet signature in
sight. A smart-contract wallet has no recoverable private key to seed a
local keypair from, and there is no single reproducible signature to hash
even if it did (a Safe's approval is aggregated on-chain, not a
deterministic `personal_sign` output you could sign twice and compare). So
`user-1271` and smart-wallet `admin-sig` are their own verification paths,
built on the on-chain `Erc1271Reader` gate — not a variant of `user-sig`.
Full detail: [Sovereign tier § Smart-wallet
login](../sovereign-tier.md#smart-wallet-login-user-1271).

## Obtaining the wallet's signature is app glue

Neither this SDK nor Grantor talks to a Safe (or any other smart-contract
wallet) directly. Producing the opaque `wallet_sig_hex` bytes a wallet
contract will accept for a given hash is exactly the same kind of glue
`user-sig`'s wallet-signing callback is for an EOA, one layer further out —
a Safe's own SDK (`@safe-global/protocol-kit`) or `wagmi`/`viem`'s
`signMessage`/`readContract` helpers get you there. What this SDK controls
and pins exactly is **the message** a genuine wallet must approve: for
`user-1271` that is the identical `user_binding` string `user-sig` signs
(`grantor_sdk_core::usersig::user_binding(tenant, aud, origin, challenge,
exp)`), EIP-191-hashed; for smart-wallet `admin-sig` it is the identical
human-readable `admin_binding` prose the EOA path signs. The verifier
rebuilds the same hash from *its own* policy and the challenge *it* issued
— never from anything in the token — before ever asking the wallet contract,
so your app-glue code cannot influence what the wallet is actually asked to
approve.

### Read this before adopting `user-1271`: it is not the private option

**Unlike `user-sig`/`user-passkey`, a `user-1271` `sub` is linkable to the
wallet's on-chain address by anyone who already knows that address.**
`user-sig`'s `sub` comes from a locally-derived key that never touches the
chain; `user-passkey`'s comes from a credential public key that lives only
in an authenticator. `user-1271`'s `sub` is a deterministic hash of the
wallet's address — and every EIP-1271 wallet's address is inherently
public (it's a deployed contract), so anyone holding that address can
recompute the same hash for a guessed `(tenant, origin)` and confirm the
link. Pick `user-1271` for what a smart-contract wallet actually buys you —
**social recovery**: a Safe on a 2-of-3 threshold survives one compromised
signer, so a single stolen owner key is not game-over — not for privacy.
`user-sig`/`user-passkey` remain the maximally-private options; use them
when your users hold EOAs or passkeys and privacy matters more than
multisig resilience. Full writeup: [Sovereign tier § The pseudonymity
tradeoff](../sovereign-tier.md#the-pseudonymity-tradeoff--read-before-picking-this-mode).

## Smart-wallet admin: `admin-sig` with a Safe

`admin-sig` now has a smart-wallet variant (see [Sovereign tier § Dashboard
login](../sovereign-tier.md#dashboard-login-admin-sig)) for an admin whose
wallet is a Safe or any other EIP-1271 contract, minted the same shape as
the EOA path:

> **This does not (yet) apply to Grantor's own dashboard.** The variant
> below is a library capability any relying party can adopt — mint +
> verify, both four-language, both proven end to end against a live
> `ERC1271WalletMock`. Grantor's own control-plane dashboard handler
> (`crates/grantor-controlplane/src/http/deed.rs`) still calls the EOA-only
> `verify_admin_deed`; wiring it to also accept a Safe admin is a follow-up,
> not part of this work.

```python
# signer_addr_hex / wallet_sig_hex: your Safe glue's output over the exact
# admin_binding(tenant, aud, challenge, exp) hash the verifier rebuilds.
deed = mint_admin_deed_smartwallet(
    signer_addr_hex, wallet_sig_hex, tenant_id, audience, challenge, exp,
)
```

Ships as `mintAdminDeedSmartwallet`/`mint_admin_deed_smartwallet`/
`MintAdminDeedSmartwallet`/`mint_admin_deed_smartwallet` — the "`admin-sig`
smart-wallet mint" row in `sdk/capability_matrix.py`. `sub` is the same
`0x`-prefixed address format the EOA path produces, so an app's
`is_admin(tenant, sub)` authorization check needs no separate branch for a
smart-wallet admin.

**Verification is a separate entry point from the login path above** —
mirroring how EOA `admin-sig` is already isolated from `verify_deed`:

```js
// DeedVerifier's verifyAdminSmartwalletAt — a distinct method from
// verifyAt, exactly as EOA admin-sig has its own verify_admin_deed
// separate from verify_deed. Import path: the wasm package DeedVerifier
// ships from (see docs/guide/verify-tokens.md's session-JWT excerpt for
// the same import shape).
const sub = await verifier.verifyAdminSmartwalletAt(deedJson, challenge, nowUnix);
// authenticates ONLY — your app still runs its own is_admin(tenant, sub)
// check before granting anything, same as the EOA path.
```

Same deliberate choices as EOA `admin-sig`: no tenant-status check (a
lapsed tenant's admin must still be able to reach the page that lets them
pay), and it **authenticates, not authorizes** — a successful verify proves
control of the wallet contract at `sub`, nothing about whether that address
administers any particular tenant.

## Both sides ship in all four languages

`user-1271` mint, `admin-sig` smart-wallet mint, `admin-sig` smart-wallet verify, and
`user-1271` deed verification all ship in TypeScript, Python, Go and Rust, enforced by
`just capability-matrix`. See [Sovereign tier § Every capability, every
language](../sovereign-tier.md#every-capability-every-language) for the full table alongside
every other mode.

## See also

- [Wallet login](wallet-login.md) — `user-sig`, the EOA baseline.
- [Passkey login](user-passkey.md) — `user-passkey`, the other maximally-private option.
- [Verify a deed](verify-tokens.md) — the relying-party side.
- [Sovereign tier § Smart-wallet login](../sovereign-tier.md#smart-wallet-login-user-1271)
  and [§ Dashboard login § Smart-wallet
  admin](../sovereign-tier.md#smart-wallet-admin-safe-eip-1271) — the full reference,
  including the exact verifier checklists.
- [Errors](errors.md) — every error code a relying party branches on.
