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

# Quickstart

> Install the SDK and make your first private transaction in minutes.

## Install SDK

<CodeGroup>
  ```bash npm theme={null}
  npm install @unlink-xyz/sdk@canary
  ```

  ```bash pnpm theme={null}
  pnpm add @unlink-xyz/sdk@canary
  ```

  ```bash yarn theme={null}
  yarn add @unlink-xyz/sdk@canary
  ```

  ```bash bun theme={null}
  bun add @unlink-xyz/sdk@canary
  ```
</CodeGroup>

The current SDK surface is published on the `canary` npm dist-tag.

## Choose a chain

The SDK connects to a hosted deployment by `environment` name. The examples below
use `base-sepolia`. See [Supported chains](/supported-chains) for the full list.

## Choose custody model

* Non-custodial browser apps import from `@unlink-xyz/sdk/browser`.
* Custodial servers, CLIs, workers, and agents import from `@unlink-xyz/sdk/client`.

Both models use `@unlink-xyz/sdk/admin` on your backend for registration,
authorization tokens, and backend reads. See [Custody models](/custody-models) for
the details.

## Create an API key

Create an API key before wiring your backend examples below:

1. Sign in at [dashboard.unlink.xyz](https://dashboard.unlink.xyz).
2. Create or select an organization.
3. Create a project and choose the target chain.
4. Open the project's **API Keys** page.
5. Click **Create key**, enter a label, and copy the revealed key.
6. Store it as `UNLINK_API_KEY` in your backend or server environment.

API keys are scoped to one project. The full key is shown exactly once, so copy
it before closing the dialog. Never ship this key to browser bundles.

## Browser non-custodial client

The browser owns the user's spending key. Your backend exposes two routes:

* `POST /api/unlink/register`
* `POST /api/unlink/authorization-token`

```ts theme={null}
import { account, createUnlinkClient } from "@unlink-xyz/sdk/browser";

const { account: unlinkAccount } = await account.fromWallet({
  provider: window.ethereum,
  appId: "your-app-id",
  chainId: 84532,
});

const client = createUnlinkClient({
  environment: "base-sepolia",
  account: unlinkAccount,
});

await client.ensureRegistered();
```

By default, `createUnlinkClient` posts registration to `/api/unlink/register`
and authorization-token requests to `/api/unlink/authorization-token` on your
backend. Pass `registerUrl` or `authorizationToken.url` only if your app mounts
those routes somewhere else.

## App backend routes

Use the backend-only API key from [Create an API key](#create-an-api-key).

```ts theme={null}
import { createUnlinkAdmin, createUnlinkAuthRoutes } from "@unlink-xyz/sdk/admin";

const admin = createUnlinkAdmin({
  environment: "base-sepolia",
  apiKey: process.env.UNLINK_API_KEY!,
});

const routes = createUnlinkAuthRoutes({
  admin,
  authenticate: async (request) => getAppSession(request),
  onRegister: async ({ session, registration }) => {
    await db.linkUnlinkAddress(session.userId, registration.address);
  },
  authorizeUnlinkAddress: async ({ session, unlinkAddress }) =>
    db.userOwnsUnlinkAddress(session.userId, unlinkAddress),
});

app.post("/api/unlink/register", (c) => routes.register(c.req.raw));
app.post("/api/unlink/authorization-token", (c) =>
  routes.authorizationToken(c.req.raw),
);
```

Keep this API key on your backend. See
[Custody models](/custody-models#app-backend) for the canonical explanation of
these routes.

## Local demo routes

Browser clients call the register and authorization-token routes even in local
demos. For throwaway UI work, you can stub the routes, then replace the stubs
with `createUnlinkAuthRoutes` before testing real funds.

```ts theme={null}
app.post("/api/unlink/register", (c) => c.body(null, 204));

app.post("/api/unlink/authorization-token", (c) =>
  c.json({
    token: "demo.authorization.token",
    expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
  }),
);
```

This is only a local mock. A real backend should authenticate the app user,
call `admin.users.register`, check address ownership, and issue the token
through `admin.authorizationTokens.issue`.

## Server or custodial client

When your server is allowed to hold user accounts, wire the user client directly
to the admin client. Use the backend-only API key from
[Create an API key](#create-an-api-key).

```ts theme={null}
import { createUnlinkAdmin } from "@unlink-xyz/sdk/admin";
import { account, createUnlinkClient } from "@unlink-xyz/sdk/client";

const admin = createUnlinkAdmin({
  environment: "base-sepolia",
  apiKey: process.env.UNLINK_API_KEY!,
});

const unlinkAccount = account.fromMnemonic({ mnemonic });
const unlinkAddress = await unlinkAccount.getAddress();

const client = createUnlinkClient({
  environment: "base-sepolia",
  account: unlinkAccount,
  register: (payload) => admin.users.register(payload),
  authorizationToken: {
    provider: () => admin.authorizationTokens.issue({ unlinkAddress }),
  },
});

await client.ensureRegistered();
```

The account passed to `createUnlinkClient` signs private actions. See
[Account constructors](/accounts-and-keys#account-constructors) for which helper
supports `execute()`.

## EVM provider

Deposits and `execute()` need an EVM provider. Use the adapter that matches your
wallet stack. Browser apps can start with the injected EIP-1193 provider.

```ts theme={null}
import { evm } from "@unlink-xyz/sdk/browser";

await window.ethereum.request({
  method: "eth_requestAccounts",
});

const evmProvider = evm.fromEip1193({
  provider: window.ethereum,
});
```

Pass `evm: evmProvider` when constructing the client, or pass it per call.
The SDK also includes `evm.fromViem`, `evm.fromEthers`, and `evm.fromSigner`
for apps that already use those stacks.

## Fund with test tokens

Faucet helpers are available only on configured testnet environments.
Use a test token configured for the environment you selected.

```ts theme={null}
await client.faucet.requestPrivateTokens({
  token: testToken,
});

const { balances } = await client.getBalances();
console.log(balances);
```

## Transfer

```ts theme={null}
const tx = await client.transfer({
  recipientAddress: "unlink1recipient...",
  token: testToken,
  amount: "250000000000000000",
});

const confirmed = await tx.wait();
console.log(confirmed.confirmationStatus); // "confirmed" | "processed" | "failed"
```

## Deposit from an EVM wallet

```ts theme={null}
const tx = await client.depositWithApproval({
  token: testToken,
  amount: "1000000000000000000",
});

await tx.wait(); // resolves at user-facing confirmation by default
```

See [Deposit](/deposit) for the full parameter reference.

## Derive account from a wallet signature

`account.fromWallet` (shown above) asks the wallet to sign a deterministic
message and derives the Unlink account from that signature. For the message
format, `fromEthereumSignature`, and recovery guidance, see
[Accounts and keys](/accounts-and-keys#derive-account-from-a-wallet-signature).
