Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fae61c2
feat(core): add React Native passkey adapter support for Account class
anthony23991 Feb 24, 2026
6dec183
fix(core): update signTypedData to use hash signing (#125)
LeoFranklin015 Feb 26, 2026
7d922bb
chore(release): publish [skip ci]
github-actions[bot] Feb 26, 2026
ae3b4dd
chore(keys): content security policy implementation in keys-jaw-id (#…
Ghadi8 Feb 26, 2026
e9556c2
fix(core): add celo mainnet + sepolia (#126)
Ghadi8 Feb 27, 2026
78da585
chore(release): publish [skip ci]
github-actions[bot] Feb 27, 2026
96f12a0
fix(ui): reverse resolve addresses in decoded calldata screens (#127)
Ghadi8 Mar 2, 2026
75c0611
chore(release): publish [skip ci]
github-actions[bot] Mar 2, 2026
326330f
feat: beta release (#128)
Ghadi8 Mar 3, 2026
e94f898
chore(release): publish [skip ci]
github-actions[bot] Mar 3, 2026
36250bc
refactor(ui): update SignatureDialog and SiweDialog layouts (#129)
LeoFranklin015 Mar 3, 2026
b14a8dc
feat: removed x from dialog (#130)
LeoFranklin015 Mar 3, 2026
586615b
chore(release): publish [skip ci]
github-actions[bot] Mar 3, 2026
ad101e9
fix: cached wallet response (#131)
LeoFranklin015 Mar 4, 2026
bdd4d8b
chore(release): publish [skip ci]
github-actions[bot] Mar 4, 2026
11bfa91
fix(core): test coverage, in memory storage and missing getfn/rpid
anthony23991 Mar 4, 2026
b2388f4
fix(core): typecheck
anthony23991 Mar 4, 2026
4f7263b
fix(core): rpId defaults to "localhost" in non-browser environments, …
anthony23991 Mar 6, 2026
3d79b6e
fix(core): feedbacks for account class
anthony23991 Mar 9, 2026
d1f0948
fix(core): removed any type for credentials
anthony23991 Mar 9, 2026
5c22906
Merge remote-tracking branch 'origin/main' into feat/rn-passkey-account
Ghadi8 Mar 9, 2026
605a2fb
refactor(core): abstraction layer for RN dev (#133)
Ghadi8 Mar 9, 2026
e8254d8
fix(cli, core): add @jaw.id/cli package with MCP server and browser a…
Ghadi8 Mar 10, 2026
ae947b2
chore(release): publish [skip ci]
github-actions[bot] Mar 10, 2026
bcb90ea
fix(keys): more leniant csp (#137)
Ghadi8 Mar 10, 2026
c932ad7
fix(cli): preflight handling (#138)
Ghadi8 Mar 10, 2026
3f22429
chore(release): publish [skip ci]
github-actions[bot] Mar 10, 2026
8e24f7b
fix(cli): oclif error (#139)
anthony23991 Mar 11, 2026
9641dbd
chore(release): publish [skip ci]
github-actions[bot] Mar 11, 2026
7205aa5
fix(cli): new cli architecture using a relay as blind pipe (#141)
Ghadi8 Mar 11, 2026
1415f05
chore(release): publish [skip ci]
github-actions[bot] Mar 11, 2026
7d673d6
fix(cli): dependency (#143)
anthony23991 Mar 11, 2026
7f98378
chore(release): publish [skip ci]
github-actions[bot] Mar 11, 2026
c6333ac
fix(core): pulled from main
anthony23991 Mar 12, 2026
5df7d60
Merge branch 'feat/rn-passkey-account' into feat/rn-cross-platform-mo…
anthony23991 Mar 13, 2026
011a7c2
feat(ui-native): added playground and cross platform rn
anthony23991 Mar 16, 2026
a3bd761
chore(ui-native): restore tsconfig project reference for nx sync
anthony23991 Mar 16, 2026
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
4 changes: 2 additions & 2 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Run affected build, test, and lint tasks, excluding docs app
# Run affected build, test, and lint tasks, excluding docs app and ui-native (peer deps not installed)
# This compares against the remote branch you're pushing to
nx affected -t build,test,lint --base=origin/main --head=HEAD --exclude=docs
nx affected -t build,test,lint --base=origin/main --head=HEAD --exclude=docs,ui-native
2 changes: 2 additions & 0 deletions .nxignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# Directories to ignore from Nx processing
playground/demo-native
# Ignore Foundry git submodule dependencies
contracts/**/lib/**
30 changes: 29 additions & 1 deletion apps/docs/docs/pages/account/create.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Type: `AccountConfig`
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
| `storage` | `SyncStorage` | No | Custom storage implementation (defaults to localStorage on web, in-memory fallback in React Native) |

### options

Expand All @@ -34,8 +35,10 @@ Type: `CreateAccountOptions`
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `username` | `string` | Yes | Username/display name for the passkey |
| `rpId` | `string` | No | Relying party identifier (defaults to `window.location.hostname`) |
| `rpId` | `string` | No | Relying party identifier (defaults to `window.location.hostname`). **Required in React Native.** |
| `rpName` | `string` | No | Relying party name (defaults to `'JAW'`) |
| `nativeGetFn` | `NativePasskeyGetFn` | No | Native passkey get function for React Native (e.g. `Passkey.get`). See [React Native Usage](#react-native-usage). |
| `nativeCreateFn` | `NativePasskeyCreateFn` | No | Native passkey create function for React Native (e.g. `Passkey.create`). See [React Native Usage](#react-native-usage). |


## Returns
Expand Down Expand Up @@ -119,6 +122,31 @@ class MyUIHandler implements UIHandler {
```


## React Native Usage

In React Native, `window` and `navigator.credentials` are unavailable. Pass `Passkey.get` and `Passkey.create` from [`react-native-passkey`](https://github.com/f-23/react-native-passkey) directly — JAW handles all format conversion, challenge generation, and public key extraction internally.

```typescript
import { Account } from '@jaw.id/core';
import { Passkey } from 'react-native-passkey';
import type { SyncStorage } from '@jaw.id/core';

// Your SyncStorage backed by MMKV or similar
const storage: SyncStorage = { /* ... */ };

const account = await Account.create(
{ chainId: 8453, apiKey: 'your-api-key', storage },
{
username: 'alice',
rpId: 'myapp.com',
rpName: 'My App',
nativeGetFn: Passkey.get,
nativeCreateFn: Passkey.create,
}
);
```


## Related

- [Account.get()](/account/get) - Login with existing account
Expand Down
38 changes: 37 additions & 1 deletion apps/docs/docs/pages/account/get.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Get an account instance - restores an existing session or triggers WebAuthn logi
```typescript
static async get(
config: AccountConfig,
credentialId?: string
credentialId?: string,
options?: GetAccountOptions
): Promise<Account>
```

Expand All @@ -26,13 +27,23 @@ Type: `AccountConfig`
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
| `storage` | `SyncStorage` | No | Custom storage implementation (defaults to localStorage on web, in-memory fallback in React Native) |

### credentialId

Type: `string | undefined`

Optional credential ID to login with. If provided and not already authenticated, triggers WebAuthn authentication.

### options

Type: `GetAccountOptions | undefined`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `nativeGetFn` | `NativePasskeyGetFn` | No | Native passkey get function for React Native (e.g. `Passkey.get`). |
| `rpId` | `string` | No | Relying party identifier. **Required in React Native.** |


## Returns

Expand Down Expand Up @@ -96,6 +107,31 @@ if (accounts.length > 0) {
```


## React Native Usage

Pass `Passkey.get` from [`react-native-passkey`](https://github.com/f-23/react-native-passkey) as `nativeGetFn` and an explicit `rpId`. JAW handles all base64url ↔ ArrayBuffer conversion internally.

```typescript
import { Account } from '@jaw.id/core';
import { Passkey } from 'react-native-passkey';

// Restore existing session (no passkey prompt)
const account = await Account.get(
{ chainId: 8453, apiKey: 'your-api-key', storage },
undefined,
{ nativeGetFn: Passkey.get, rpId: 'myapp.com' }
);

// Login with specific credential (triggers native passkey prompt)
const stored = Account.getStoredAccounts('your-api-key', storage);
const account = await Account.get(
{ chainId: 8453, apiKey: 'your-api-key', storage },
stored[0].credentialId,
{ nativeGetFn: Passkey.get, rpId: 'myapp.com' }
);
```


## Related

- [Account.create()](/account/create) - Create a new account
Expand Down
30 changes: 29 additions & 1 deletion apps/docs/docs/pages/account/import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ Import a passkey from cloud backup.
## Signature

```typescript
static async import(config: AccountConfig): Promise<Account>
static async import(
config: AccountConfig,
options?: ImportAccountOptions
): Promise<Account>
```


Expand All @@ -23,6 +26,16 @@ Type: `AccountConfig`
| `chainId` | `number` | Yes | Chain ID for the account |
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
| `storage` | `SyncStorage` | No | Custom storage implementation (defaults to localStorage on web, in-memory fallback in React Native) |

### options

Type: `ImportAccountOptions | undefined`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `nativeGetFn` | `NativePasskeyGetFn` | No | Native passkey get function for React Native (e.g. `Passkey.get`). |
| `rpId` | `string` | No | Relying party identifier. **Required in React Native.** |


## Returns
Expand Down Expand Up @@ -105,6 +118,21 @@ Passkeys can be synced via:
The import flow allows users to access their JAW account on any device where their passkeys are synced.


## React Native Usage

Pass `Passkey.get` from [`react-native-passkey`](https://github.com/f-23/react-native-passkey) as `nativeGetFn` and an explicit `rpId`. JAW handles all base64url ↔ ArrayBuffer conversion internally.

```typescript
import { Account } from '@jaw.id/core';
import { Passkey } from 'react-native-passkey';

const account = await Account.import(
{ chainId: 8453, apiKey: 'your-api-key', storage },
{ nativeGetFn: Passkey.get, rpId: 'myapp.com' }
);
```


## Related

- [Account.create()](/account/create) - Create a new account
Expand Down
25 changes: 24 additions & 1 deletion apps/docs/docs/pages/account/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The `Account` class is ideal for:

- **AI Agents** - Issue programmable smart wallets for AI agents
- **Headless Integration** - No UI required, direct programmatic access
- **React Native** - Native mobile passkey support via `nativeGetFn` and `nativeCreateFn`
- **Server-side Applications** - Using `fromLocalAccount()` with embedded wallets (Privy, Dynamic, Turnkey)
- **Custom UI Handlers** - When implementing your own `UIHandler` for app-specific mode
- **Direct Integration** - When you need fine-grained control over account operations
Expand Down Expand Up @@ -46,6 +47,9 @@ The `Account` class is exported from `@jaw.id/core`:

```typescript
import { Account } from '@jaw.id/core';

// React Native: also import adapter types
import type { NativePasskeyGetFn, NativePasskeyCreateFn, SyncStorage } from '@jaw.id/core';
```


Expand Down Expand Up @@ -130,6 +134,8 @@ interface AccountConfig {
paymasterUrl?: string;
/** Custom paymaster context for gas sponsorship */
paymasterContext?: Record<string, unknown>;
/** Custom storage implementation (defaults to localStorage on web, in-memory fallback in React Native) */
storage?: SyncStorage;
}
```

Expand All @@ -141,10 +147,27 @@ Options for creating a new account:
interface CreateAccountOptions {
/** Username/display name for the passkey */
username: string;
/** Relying party identifier (defaults to window.location.hostname) */
/** Relying party identifier (defaults to window.location.hostname). Required in React Native. */
rpId?: string;
/** Relying party name (defaults to 'JAW') */
rpName?: string;
/** Native passkey get function for React Native (e.g. Passkey.get) */
nativeGetFn?: NativePasskeyGetFn;
/** Native passkey create function for React Native (e.g. Passkey.create) */
nativeCreateFn?: NativePasskeyCreateFn;
}
```

### GetAccountOptions / ImportAccountOptions / RestoreAccountOptions

Options for `get()`, `import()`, and `restore()`:

```typescript
interface GetAccountOptions {
/** Native passkey get function for React Native (e.g. Passkey.get) */
nativeGetFn?: NativePasskeyGetFn;
/** Relying party identifier. Required in React Native. */
rpId?: string;
}
```

Expand Down
33 changes: 32 additions & 1 deletion apps/docs/docs/pages/account/restore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Restore an account from existing credential data without triggering a WebAuthn p
static async restore(
config: AccountConfig,
credentialId: string,
publicKey: `0x${string}`
publicKey: `0x${string}`,
options?: RestoreAccountOptions
): Promise<Account>
```

Expand All @@ -28,6 +29,7 @@ Type: `AccountConfig`
| `apiKey` | `string` | Yes | API key for JAW services |
| `paymasterUrl` | `string` | No | Custom paymaster URL for gas sponsorship |
| `paymasterContext` | `Record<string, unknown>` | No | Custom paymaster context |
| `storage` | `SyncStorage` | No | Custom storage implementation (defaults to localStorage on web, in-memory fallback in React Native) |

### credentialId

Expand All @@ -41,6 +43,15 @@ Type: `` `0x${string}` ``

The public key of the passkey (hex encoded).

### options

Type: `RestoreAccountOptions | undefined`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `nativeGetFn` | `NativePasskeyGetFn` | No | Native passkey get function for React Native (e.g. `Passkey.get`). |
| `rpId` | `string` | No | Relying party identifier. **Required in React Native.** |


## Returns

Expand Down Expand Up @@ -100,6 +111,26 @@ async function handleSignRequest(credentialId: string, publicKey: `0x${string}`)
```


## React Native Usage

Pass `Passkey.get` from [`react-native-passkey`](https://github.com/f-23/react-native-passkey) as `nativeGetFn` and an explicit `rpId` so signing operations use the native passkey prompt. JAW handles all base64url ↔ ArrayBuffer conversion internally.

```typescript
import { Account } from '@jaw.id/core';
import { Passkey } from 'react-native-passkey';

const account = await Account.restore(
{ chainId: 8453, apiKey: 'your-api-key', storage },
credentialId,
publicKey,
{ nativeGetFn: Passkey.get, rpId: 'myapp.com' }
);

// Signing triggers the native passkey prompt via nativeGetFn
const signature = await account.signMessage('Hello');
```


## Related

- [Account.get()](/account/get) - Get account with optional WebAuthn prompt
Expand Down
41 changes: 16 additions & 25 deletions apps/keys-jaw-id/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//@ts-check

const path = require('path');
const { composePlugins, withNx } = require('@nx/next');


Expand All @@ -11,32 +10,24 @@ const nextConfig = {
// Use this to set Nx-specific options
// See: https://nx.dev/recipes/next/next-config-setup
nx: {},
poweredByHeader: false,
productionBrowserSourceMaps: false,
compiler: {
removeConsole: process.env.NODE_ENV === 'production'
? { exclude: ['error', 'warn'] }
: false,
},
transpilePackages: ['@jaw.id/ui'],
experimental: {
transpilePackages: ['@jaw.id/ui', '@jaw.id/core'],
experimental: {
optimizePackageImports: ['lucide-react'],
},
webpack: (config) => {
// Replace the UI package's CSS with an empty override to prevent
// conflicting oklch CSS variables and duplicate Tailwind utility classes.
// The UI package's CSS uses oklch color values and var(--color) directly,
// while this app uses HSL params with hsl(var(--color) / <alpha-value>)
// for opacity modifier support. This app's own Tailwind config generates
// all needed utilities from the UI package's source files.
const overrideCss = path.resolve(__dirname, 'src/app/ui-overrides.css');
config.resolve.alias[
path.resolve(__dirname, '../../packages/ui/src/styles.css')
] = overrideCss;
config.resolve.alias[
path.resolve(__dirname, '../../packages/ui/dist/index.css')
] = overrideCss;
return config;

// Serve AASA file with correct content-type for iOS passkeys
async headers() {
return [
{
source: '/.well-known/apple-app-site-association',
headers: [
{
key: 'Content-Type',
value: 'application/json',
},
],
},
];
},
};

Expand Down
6 changes: 4 additions & 2 deletions apps/keys-jaw-id/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"dependencies": {
"@jaw.id/core": "workspace:*",
"@jaw.id/ui": "workspace:*",
"@justaname.id/sdk": "^0.2.204",
"@tanstack/react-query": "^5.62.16",
"next": "~15.5.10",
"next": "~15.2.6",
"react": "19.0.1",
"react-dom": "19.0.1",
"tailwindcss-animate": "^1.0.7",
"viem": "^2.38.2"
}
},
"devDependencies": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"webcredentials": {
"apps": [
"9234ZPYS2R.id.jaw.demo.native"
]
}
}
Loading
Loading