Skip to main content

Install SDK

npm install @unlink-xyz/sdk@canary
pnpm add @unlink-xyz/sdk@canary
yarn add @unlink-xyz/sdk@canary
bun add @unlink-xyz/sdk@canary
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 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 for the details.

Create an API key

Create an API key before wiring your backend examples below:
  1. Sign in at 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
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.
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 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.
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.
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 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.
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.
await client.faucet.requestPrivateTokens({
  token: testToken,
});

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

Transfer

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

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

await tx.wait(); // resolves at user-facing confirmation by default
See 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.