Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion src/pages/controller/examples/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,104 @@ export function MultiAuthConnectWallet() {
}
```

### 4. Performing Transactions
### 4. Headless Authentication

For programmatic authentication without opening any UI, you can use headless mode in your React components:

```tsx
import { useCallback, useState } from 'react'
import { useConnect } from '@starknet-react/core'
import { ControllerConnector } from '@cartridge/connector'

export function HeadlessLogin() {
const { connectAsync, connectors } = useConnect()
const [username, setUsername] = useState('')
const [loading, setLoading] = useState(false)
const controller = connectors[0] as ControllerConnector

const handleHeadlessLogin = useCallback(async (signer: string) => {
if (!username) {
alert('Please enter a username')
return
}

setLoading(true)
try {
// Ensure we start fresh
if (controller.account) {
await controller.disconnect()
}

// Headless authentication
const account = await controller.connect({
username,
signer,
})

if (!account) {
throw new Error('Failed to authenticate')
}

// Sync with starknet-react state
await connectAsync({ connector: controller })

alert(`Successfully authenticated as ${username}!`)
} catch (error) {
console.error('Headless authentication failed:', error)
alert('Authentication failed: ' + (error as Error).message)
} finally {
setLoading(false)
}
}, [username, controller, connectAsync])

return (
<div className="space-y-4">
<div>
<label htmlFor="username">Username:</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter your username"
disabled={loading}
/>
</div>

<div className="grid gap-2">
<button
onClick={() => handleHeadlessLogin('webauthn')}
disabled={loading || !username}
>
Login with Passkey
</button>

<button
onClick={() => handleHeadlessLogin('metamask')}
disabled={loading || !username}
>
Login with MetaMask
</button>

<button
onClick={() => handleHeadlessLogin('google')}
disabled={loading || !username}
>
Login with Google
</button>
</div>

{loading && <p>Authenticating...</p>}
</div>
)
}
```

:::warning
Headless mode requires that the user already has the specified signer (passkey, OAuth account, EVM wallet) associated with their Cartridge username. For new user registration, use the regular `connect()` flow which opens the UI.
:::

### 5. Performing Transactions

Execute transactions using the `account` object from `useAccount` hook:

Expand Down
35 changes: 35 additions & 0 deletions src/pages/controller/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ const account = await controller.connect();
const phantomAccount = await controller.connect(["phantom-evm"]);
```

### Headless Authentication

For programmatic authentication without opening any UI, you can use headless mode by providing a `username` and `signer`:

```ts
import Controller from "@cartridge/controller";

const controller = new Controller({});

// Headless authentication with WebAuthn/Passkey
const account = await controller.connect({
username: "alice",
signer: "webauthn",
});

// Headless authentication with password
const account = await controller.connect({
username: "alice",
signer: "password",
password: "your-secure-password",
});

// Headless authentication with OAuth providers
await controller.connect({ username: "alice", signer: "google" });
await controller.connect({ username: "alice", signer: "discord" });

// Headless authentication with EVM wallets
await controller.connect({ username: "alice", signer: "metamask" });
await controller.connect({ username: "alice", signer: "phantom-evm" });
```

:::info
Headless mode performs authentication in a hidden iframe without displaying any UI. However, if session policies need approval or verification, the UI will open automatically to request user consent.
:::

When `connect()` is called, users will see an improved controller creation interface with username autocomplete functionality. As users type their username, they'll see matching existing accounts with user profiles, making it easier to connect to existing controllers or choose unique usernames for new accounts.

For session-based applications, users will see permissions organized into clear sections: an expandable "Authorize [game]" card containing contract methods, followed by dedicated spending limit cards for token approvals, making it easy to understand what they're authorizing.
Expand Down
Loading