Skip to main content

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.

Network

Unlink is live on Base Sepolia.
ResourceValue
NetworkBase Sepolia
Chain ID84532
API URLhttps://staging-api.unlink.xyz
Pool Address0x647f9b99af97e4b79DD9Dd6de3b583236352f482

Base Sepolia faucet

Get testnet ETH for gas.

Prerequisites

Make sure you have:
  • Node.js v18 or later
  • A package manager (npm, pnpm, yarn, or bun)
  • A TypeScript project
mkdir my-app && cd my-app
npm init -y
npm install typescript tsx
1

Install

npm install @unlink-xyz/sdk viem
2

Create a tenant and a session

The SDK splits work into two pieces: a process-wide Tenant (engine URL + API key, cached env info) and a per-user Session (account identity + optional EVM signer). Build the Tenant once, then call tenant.forUser({ account }) once per user — in a multi-user backend, that’s per request.
Use a private key and viem for backends, scripts, bots, or AI agents.
import {
  account,
  createTenant,
  evm,
  registerAccount,
  signSigningRequest,
} from "@unlink-xyz/sdk";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const evmAccount = privateKeyToAccount(
  process.env.EVM_PRIVATE_KEY as `0x${string}`,
);

const walletClient = createWalletClient({
  account: evmAccount,
  chain: baseSepolia,
  transport: http(process.env.RPC_URL),
});

// Process-wide. Construct once (e.g. at module load).
const tenant = createTenant({
  engineUrl: "https://staging-api.unlink.xyz",
  apiKey: process.env.UNLINK_API_KEY!,
});

// Per user. Construct per user/request.
const unlinkAccount = account.fromMnemonic({
  mnemonic: process.env.UNLINK_MNEMONIC!,
});
const accountKeys = await unlinkAccount.getAccountKeys();

await registerAccount({
  engineUrl: "https://staging-api.unlink.xyz",
  apiKey: process.env.UNLINK_API_KEY!,
  accountKeys,
});

const unlink = tenant.forUser({
  account: unlinkAccount,
  evm: evm.fromViem({ walletClient }),
});
Use account.fromSeed(...) or account.fromKeys(...) when you already manage seed material or raw account keys outside the SDK.

Derive identity from a wallet signature (MetaMask)

For browser apps where you don’t want users to manage a separate mnemonic, derive the Unlink identity from a one-time personal_sign signature on the user’s existing EVM wallet. Same EOA + same tenant + same chain ⇒ same unlink1… address, as long as the wallet emits deterministic ECDSA (RFC-6979) — which every mainstream wallet does today (MetaMask, Rabby, Coinbase Wallet, Ledger, Trezor, viem, ethers).
import { createTenant, account, evm } from "@unlink-xyz/sdk";
import { createWalletClient, custom } from "viem";
import { baseSepolia } from "viem/chains";

const provider = window.ethereum;

// One-time signing prompt. The signature is the entropy for the Unlink keys —
// the user sees the canonical "Unlink: derive identity / Tenant: … / Chain: …"
// message in MetaMask. `fromMetaMask` returns both the AccountProvider and the
// EOA address so the caller doesn't need a second eth_requestAccounts round-trip.
const { accountProvider, address: evmAddress } = await account.fromMetaMask({
  provider,
  tenantName: "your-tenant-slug",
  chainId: 84532,
});

const walletClient = createWalletClient({
  account: evmAddress as `0x${string}`,
  chain: baseSepolia,
  transport: custom(provider),
});

// Browser context — see the note in "Browser (viem)" above. For
// production dApps, prefer `createUnlinkSigner` (see "Browser auth").
const tenant = createTenant({
  engineUrl: "https://staging-api.unlink.xyz",
  apiKey: "your-api-key",
  dangerouslyAllowBrowser: true,
});

const unlink = tenant.forUser({
  account: accountProvider,
  evm: evm.fromViem({ walletClient }),
});
The tenantName string lives in the signed message verbatim — the SDK does not lowercase or canonicalise it, so use a stable slug from your tenant config. tenantName and chainId are also bound into the HKDF salt, so reusing a stale signature against a different (tenant, chain) context derives a fresh, empty address rather than silently surfacing the old identity.If you already hold the signature (e.g. you signed via viem’s walletClient.signMessage(...)), use account.fromEthereumSignature({ signature, tenantName, chainId }) instead — pass the same values you used to build the message. See Utilities for the message-format reference.
personal_sign (EIP-191) is suitable for demos and trusted-origin flows. For production, prefer the EIP-712 typed-data variant (follow-up). EOA-only — smart-account wallets (Safe, Argent, Coinbase Smart Wallet) are not supported by this path. For long-term recovery, call account.export(keys) once after first derivation and store the keystore alongside (or independently of) the wallet signature.
What each piece does:
ConceptRole
createWalletClient (viem) / BrowserProvider (ethers)Standard EVM wallet — signs on-chain transactions (deposits, approvals). Not Unlink-specific.
account.fromMnemonic()Derives your Unlink account (private spending & viewing keys) from a BIP-39 mnemonic. A different mnemonic = a different Unlink identity.
registerAccount()Registers the account once so the backend can build deposits, transfers, and withdrawals for that Unlink identity.
evm.fromViem() / evm.fromEthers()Bridges your EVM wallet into the SDK so Unlink can submit on-chain transactions on your behalf.
createTenant()Process-wide handle (engine URL + API key). Construct once at app startup.
tenant.forUser({ account, evm })Per-user Session. Every SDK operation (deposit, transfer, withdraw, balances) goes through this session.
3

Get your Unlink address

Your Unlink address (unlink1...) is your identity inside the privacy pool. Share it to receive private transfers — it does not reveal your EVM wallet.
const address = await unlink.getAddress();
console.log(address); // "unlink1..."
This address is deterministically derived from your mnemonic. The same mnemonic always produces the same address.
4

Get test tokens

Fund your Unlink account directly via the faucet. No EVM approval or gas needed.
const seeded = await unlink.faucet.requestPrivateTokens({
  token: "0x7501de8ea37a21e20e6e65947d2ecab0e9f061a7",
});
You can verify the balance arrived:
const { balances } = await unlink.getBalances();
console.log(balances);
See Faucet for other options like minting public ERC-20 tokens to an EVM wallet.
5

Transfer

Send tokens privately to another Unlink address. pollTransactionStatus polls until the transaction reaches a terminal state; see Utilities for options.
const transfer = await unlink.transfer({
  recipientAddress: "unlink1recipient...",
  token: "0x7501de8ea37a21e20e6e65947d2ecab0e9f061a7",
  amount: "250000000000000000",
  signSigningRequest: (req) =>
    signSigningRequest(req, accountKeys.spendingPrivateKey),
});

const confirmed = await unlink.pollTransactionStatus(transfer.txId);
See Transfer for multi-recipient transfers.
6

Withdraw

Move tokens back to any EVM address:
const withdrawal = await unlink.withdraw({
  recipientEvmAddress: "0xRecipient",
  token: "0x7501de8ea37a21e20e6e65947d2ecab0e9f061a7",
  amount: "500000000000000000",
  signSigningRequest: (req) =>
    signSigningRequest(req, accountKeys.spendingPrivateKey),
});

const confirmed = await unlink.pollTransactionStatus(withdrawal.txId);
See Withdraw for all parameters.

Going further

Deposit from an EVM wallet

To move ERC-20 tokens from an on-chain wallet into the unlink contract, use depositWithApproval(). It runs the one-time Permit2 approval per token, waits for confirmation, and then runs the deposit — one call instead of three.
const deposit = await unlink.depositWithApproval({
  token: "0x7501de8ea37a21e20e6e65947d2ecab0e9f061a7",
  amount: "1000000000000000000",
});

const confirmed = await unlink.pollTransactionStatus(deposit.txId);
For full control over the approval flow (custom receipt polling, integrating with an existing pre-approval pipeline), call ensureErc20Approval() plus deposit() directly. See Deposit for the full parameter reference, approval helpers, and EVM provider setup.