> For the complete documentation index, see [llms.txt](https://docs.alignedlayer.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.alignedlayer.com/wallet-as-a-service/quickstart.md).

# Quickstart

Add a wallet to a web app: sign the user in, create their wallet, send a transaction. Everything below runs in the browser, with a passkey as the user's key.

## 1. Get a publishable key

Create an app in the Aligned dashboard and copy its publishable key (`aligned_pk_test_...`). The key identifies your app. It is not a secret and is meant to ship in your frontend.

Add every origin your app runs on to the key's allowed origins, including `http://localhost:5173` or whatever your dev server uses. Requests from any other origin are rejected with `403 origin_not_allowed`.

## 2. Install the SDK

```sh
npm install @aligned-waas/sdk
```

## 3. Create a client

```ts
import { AlignedClient, localStorageAdapter, webPasskeyAdapter } from '@aligned-waas/sdk';

export const aligned = new AlignedClient({
  publishableKey: 'aligned_pk_test_...',
  storage: localStorageAdapter(), // keeps the session across reloads
  passkey: webPasskeyAdapter(),   // browser WebAuthn
});
```

The client connects to the Aligned testnet by default. Pass `baseUrl` to point it at a backend you run yourself.

Construct it once, at module scope. A new client on every render restarts session restoration and throws away its caches.

## 4. Sign the user in

Email one-time code, no password:

```ts
await aligned.auth.startEmailOtp({ email });
const user = await aligned.auth.verifyEmailOtp({ email, code });
```

The first successful verification creates the account. Google sign-in is available too, in a popup:

```ts
const user = await aligned.auth.loginWithGooglePopup();
```

## 5. Create the wallet

```ts
const { wallet, created } = await aligned.wallets.createWithPasskey({
  userId: user.id, // stable and opaque, never an email
  userName: email, // what the system passkey prompt shows
});

console.log(wallet.address);
```

That one call registers a passkey on the device, creates the account on chain, and selects that passkey as the signing key.

If the user already has a wallet it is returned as is, with `created: false` and no new passkey. Adding a second device to an existing wallet is a different flow, see [Keys and recovery](/wallet-as-a-service/keys-and-recovery.md).

## 6. Send a transaction

```ts
const { tx_hash, status } = await aligned.wallets.sendTransaction({
  to: '0x...',
  value: 1_000_000_000n, // wei, defaults to 0
  data: '0x',            // calldata, defaults to '0x'
});
```

The SDK asks the backend for the digest bound to the wallet's current nonce, prompts for the passkey, and submits. Aligned relays the transaction and pays the gas, so your users never need the chain's native token. `status` is `1` when the transaction succeeded on chain.

## 7. Sign a message

```ts
const { signature } = await aligned.wallets.signMessage('hello');
```

Nothing is submitted on chain. The result is a signature any verifier can check by calling `isValidSignature` (ERC-1271) on the wallet.

Plain strings are hashed as `keccak256(utf8(message))`, which is *not* what `personal_sign` (EIP-191) does. For EIP-191 or EIP-712 semantics, compute the digest yourself and pass `{ hash }`.

## Handling errors

Failed calls throw `ApiError`, carrying the HTTP `status`, a stable `code`, and the parsed response `body`. Codes worth handling explicitly:

| Code                 | What to do                                                                                                    |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `unauthenticated`    | Session expired or missing. Send the user back through sign-in.                                               |
| `origin_not_allowed` | The page origin is not on the publishable key's allowlist. Fix it in the dashboard.                           |
| `rate_limited`       | Back off and retry.                                                                                           |
| `step_up_required`   | The endpoint needs a fresh step-up proof, see [Keys and recovery](/wallet-as-a-service/keys-and-recovery.md). |

Passkey ceremonies throw whatever the browser throws, and a cancelled prompt is a normal outcome rather than a failure. Treat it as "the user said no".

## A note on Node

The SDK is a browser and mobile library. The publishable-key path requires a real `Origin` header, which Node's `fetch` does not send, so calls from Node are rejected. Use it in Node only for tests or for building signing material that a browser will send.

## Where next

* [How it works](/wallet-as-a-service/how-it-works.md), what the contracts guarantee and why.
* [Keys and recovery](/wallet-as-a-service/keys-and-recovery.md), multiple devices and losing them.
* [React](/wallet-as-a-service/framework-guides/react.md) and [React Native](/wallet-as-a-service/framework-guides/react-native.md) for the framework adapters.
