Skip to main content
Wallet Linking creates a cryptographically verifiable, off-chain association between an embedded wallet and an external wallet (such as a MetaMask or hardware wallet). This is useful for:
  • Compliance and KYC: verify that a user controls an external address without requiring them to move funds.
  • Fund migration: link an external wallet to facilitate moving liquidity to the embedded wallet.
  • Multi-wallet governance: verify token holdings across wallets for access control or disbursement rules.
The association is stored off-chain and signed by both wallets, so neither party can forge the link.

How it works

1

Authenticate with embedded wallet

The user signs in with their embedded wallet (social login, email OTP, or passkey). This becomes the parent wallet.
2

Sign with external wallet

The user connects their external wallet (MetaMask, hardware wallet, etc.) and signs a message attesting to the embedded wallet address. The embedded wallet also signs a message attesting to the external address.
3

Submit both signatures

Both signatures are submitted to the Sequence Linking API. The link is created off-chain and is privately retrievable using a signature from the parent wallet.

API integration

Install

pnpm install @0xsequence/waas wagmi
The source code for the API interface instantiates against the Sequence server:
import { API, LinkedWallet } from "./api.gen";

const api = new API("https://dev-api.sequence.app", fetch);
Two signatures are required: the embedded wallet signing the external address, and the external wallet signing the embedded wallet address.
import { useSignMessage, useAccount } from "wagmi";

function App() {
  const { signMessageAsync } = useSignMessage();
  const { address: externalWalletAddress } = useAccount();

  const getSignaturesForLinking = async () => {
    const parentWalletAddress = await sequenceWaas.getAddress();
    const parentMessage = "linked wallet with address " + externalWalletAddress;
    const linkingMessage = "parent wallet with address " + parentWalletAddress;

    const externalSignature = await signMessageAsync({
      message: "Link to " + linkingMessage,
    });

    const parentSignatureRes = await sequenceWaas.signMessage({
      message: parentMessage,
    });

    return {
      parentWalletAddress,
      linkedWalletAddress: externalWalletAddress,
      parentMessage,
      linkingMessage: "Link to " + linkingMessage,
      parentSignature: parentSignatureRes.data.signature,
      linkedSignature: externalSignature,
    };
  };

  const linkWallet = async () => {
    const sigs = await getSignaturesForLinking();

    const response = await api.linkWallet({
      signatureChainId: "137",
      linkedWalletType: "metamask",
      parentWalletAddress: sigs.parentWalletAddress,
      parentWalletMessage: sigs.parentMessage,
      parentWalletSignature: sigs.parentSignature,
      linkedWalletAddress: sigs.linkedWalletAddress!,
      linkedWalletMessage: sigs.linkingMessage,
      linkedWalletSignature: sigs.linkedSignature,
    });

    console.log("Linked:", response.status);
  };
}

Retrieve linked wallets

Retrieval requires a fresh signature from the parent wallet to protect privacy.
const getLinkedWallets = async () => {
  const parentWalletAddress = await sequenceWaas.getAddress();
  const message = "parent wallet with address " + parentWalletAddress;
  const signature = await sequenceWaas.signMessage({ message });

  const response = await api.getLinkedWallets({
    parentWalletAddress: parentWalletAddress as `0x${string}`,
    parentWalletMessage: message,
    parentWalletSignature: signature.data.signature,
    signatureChainId: "137",
  });

  return response.linkedWallets;
};
The returned objects follow this shape:
interface LinkedWallet {
  id: number;
  walletType?: string;
  walletAddress: string;
  linkedWalletAddress: string;
  createdAt?: string;
}

Remove a linked wallet

const unlinkWallet = async () => {
  const parentWalletAddress = await sequenceWaas.getAddress();
  const parentMessage = "linked wallet with address " + externalWalletAddress;
  const linkingMessage = "parent wallet with address " + parentWalletAddress;

  const externalSignature = await signMessageAsync({
    message: "Unlink from " + linkingMessage,
  });

  const parentSig = await sequenceWaas.signMessage({ message: parentMessage });

  await api.removeLinkedWallet({
    signatureChainId: "137",
    parentWalletAddress,
    parentWalletMessage: parentMessage,
    parentWalletSignature: parentSig.data.signature,
    linkedWalletAddress: externalWalletAddress!,
    linkedWalletMessage: "Unlink from " + linkingMessage,
    linkedWalletSignature: externalSignature,
  });
};

Demo

A full working demo with source code is available at: