> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# v14.7.0 — External-rooted SHHH: passkey, MetaMask & Phantom

> What v14.7.0 adds: createWallet deploys SHHH wallets whose first owner is an external key (passkey / MetaMask), and Phantom (Ed25519) can co-sign — no Chipi-held key in either case.

<Info>
  v14.7.0 completes external-rooted SHHH signing. A SHHH wallet's first
  owner can now be a **passkey** (`WEBAUTHN_P256`) or a **MetaMask** key
  (`EIP191_SECP256K1`) at `createWallet` time — Chipi holds no key — and a
  **Phantom** wallet (`ED25519`) can co-sign an OutsideExecution from the
  signature its `signMessage` returns. Additive only: existing STARK and
  Chipi-held paths are unchanged.
</Info>

## TL;DR — does this affect me?

| Your setup                                                      | Action you need to take                                                                                       |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Pinned to `@chipi-stack/backend@14.6.x` (or earlier)            | **None.** These are additive APIs; nothing existing changes.                                                  |
| You create SHHH wallets with `signerKind: "STARK"` (or omit it) | **None.** The Chipi-held STARK path is untouched.                                                             |
| You want a passkey / MetaMask to be the wallet's first owner    | Pass `signerKind: "WEBAUTHN_P256"` / `"EIP191_SECP256K1"` + `ownerPubkeyFelts` to `createWallet` (see below). |
| You onboard Phantom signers into a threshold wallet             | Use the new `buildEd25519EnvelopeFromSignature` to co-sign.                                                   |

## External-rooted `createWallet` (passkey, MetaMask)

Before v14.7.0, `createWallet` shipped only `STARK` and `ED25519` and always
expected a key Chipi could hold or generate. Now `WEBAUTHN_P256` and
`EIP191_SECP256K1` deploy a SHHH wallet whose first owner is an **external**
key: you supply the owner's pubkey felts, and no `encryptedPrivateKey` is ever
created.

```ts theme={null}
// Passkey-as-owner (zero Chipi-held key). The 4 felts come from the
// passkey's P-256 pubkey — e.g. via createShhhPasskey + the pubkey helpers.
const wallet = await sdk.createWallet({
  params: {
    externalUserId: "treasury-owner-1",
    chain: Chain.STARKNET,
    walletType: "SHHH",
    signerKind: "WEBAUTHN_P256",
    ownerPubkeyFelts, // [x_low, x_high, y_low, y_high]
  },
});
// wallet.signerKind === "WEBAUTHN_P256"
// wallet.encryptedPrivateKey === null   ← Chipi holds nothing
```

The same shape works for `signerKind: "EIP191_SECP256K1"` (MetaMask). The
create payload sends `ownerPublicKey` (not `encryptedPrivateKey`) as the
credential source; the wallet address is derived from `ownerPubkeyFelts`.

`JWT_ES256_APPLE_SUB` is still gated — it needs an Apple-Developer context.

## Phantom co-sign — `buildEd25519EnvelopeFromSignature`

Phantom (and any external Ed25519 / Solana wallet) never exposes its private
key, so `buildEd25519Envelope` — which requires the raw key — couldn't be used.
The new from-signature builder wraps the 64-byte signature Phantom returns into
a V2\_SNIP12 envelope, mirroring `buildEip191EnvelopeFromSignature` for MetaMask.

```ts theme={null}
import {
  buildEd25519EnvelopeFromSignature,
  ed25519SignedBytes,
} from "@chipi-stack/backend";

await window.solana.connect();
const publicKey = window.solana.publicKey.toBytes();      // 32-byte pubkey

// Sign the EXACT bytes the verifier reconstructs — NOT the raw 32-byte hash.
const msg = ed25519SignedBytes(oeHash);
const { signature } = await window.solana.signMessage(msg); // 64-byte R‖s

const envelope = await buildEd25519EnvelopeFromSignature({
  signature,
  publicKey,
  messageHash: oeHash,
});
```

Two notes that matter:

* **It's async.** Unlike the synchronous EIP-191 builder, the Ed25519 path
  initializes the Garaga WASM hint builder on first call. `await` it.
* **Sign the encoded bytes.** The wallet must sign
  `ed25519SignedBytes(messageHash)` (the 64-byte hex-ASCII encoding), not the
  raw hash — the on-chain verifier reconstructs those exact bytes.

See [Threshold & co-signing](/services/gasless/threshold) for using this in an
N-of-M wallet.
