Skip to main content

Integrating Solana Name Service (SNS) Identity with MetaMask Embedded Wallets

embedded walletssolanasnsmetamaskMetaMask Developer Relations | November 15, 2025

This tutorial is designed for developers utilizing MetaMask Embedded Wallets and demonstrates how to integrate core functionalities of the Solana Name Service (SNS). This integration creates a seamless onboarding experience by pairing simple social logins with a powerful, human-readable on-chain identity layer.

As an overview, this integration allows users to:

  • Log in using familiar Web2 social providers (Google, Apple, etc.)
  • Register new .sol domains with their embedded wallet
  • Resolve domain names to wallet addresses for easy transfers
  • Fetch and display their primary domain as a user identity
  • Manage domain records for social media integration

For those who want to skip straight to the code, you can find the complete implementation examples in our GitHub repository.

This guide follows the implementation demonstrated in our livestream:

How to set up the Dashboard

If you haven't already, sign up on the MetaMask Embedded Wallets platform. It is free and gives you access to the base plan. After the basic setup, explore other features and functionalities offered by the dashboard. It includes custom verifiers, whitelabeling, analytics, and more. Head to the documentation page for detailed instructions on setting up the dashboard.

Prerequisites and Setup

Before diving into the code, ensure you have the necessary libraries installed and your project configured.

Installation

You'll need the following libraries in your project:

  • @web3auth/modal: The SDK for embedded wallet integration with social logins.
  • @solana/web3.js: For interacting with the Solana blockchain and constructing transactions.
  • @bonfida/spl-name-service: The official SNS SDK for domain registration, resolution, and management.
  • @solana/spl-token: For handling token operations when processing domain registration payments.
npm install @web3auth/modal @solana/web3.js @bonfida/spl-name-service @solana/spl-token

Dashboard Configuration

MetaMask Embedded Wallets enable users to log in using familiar Web2 social logins by using Shamir Secret Sharing (MPC) to ensure the wallet key is distributed and non-custodial.

  1. Create a Project: Go to the dashboard and create a new project.
  2. Copy Client ID: Once created, copy your Client ID from the dashboard. This ID is crucial for initializing the SDK.
  3. Enable Solana Chain: In the dashboard, navigate to "Chains and Network" and enable Solana, Solana Devnet and Solana Testnet. Ensure all the RPC URLs are configured.

Integrating MetaMask Embedded Wallets in React

Once you have set up the dashboard and created a new project, it's time to integrate MetaMask Embedded Wallets in your React application. For the implementation, we'll use the @web3auth/modal SDK. This SDK facilitates integration, allowing you to easily manage embedded wallets in your React application.

Initialize Provider

Wrap your application components with a Web3AuthProvider to configure the SDK with your Client ID.

src/main.tsx
import "./index.css";

import ReactDOM from "react-dom/client";
import { Web3AuthProvider } from "@web3auth/modal/react";
import web3AuthContextConfig from "./web3authContext";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<Web3AuthProvider config={web3AuthContextConfig}>
<App />
</Web3AuthProvider>
);
src/web3authContext.tsx
import { WEB3AUTH_NETWORK } from '@web3auth/modal'
import { type Web3AuthContextConfig } from '@web3auth/modal/react'

// Dashboard Registration
const clientId =
'BFcLTVqWlTSpBBaELDPSz4_LFgG8Nf8hEltPlf3QeUG_88GDrQSw82fSjjYj5x4F3ys3ghMq8-InU7Azx7NbFSs' // get from https://dashboard.web3auth.io

// Instantiate SDK
const web3AuthContextConfig: Web3AuthContextConfig = {
web3AuthOptions: {
clientId,
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
},
}

export default web3AuthContextConfig

Accessing Wallet Information & User Details

Once initialized, you can access wallet information and user details through the Solana hooks.

src/components/walletInfo.tsx
import { useSolanaWallet } from "@web3auth/modal/react/solana";
import { PublicKey } from "@solana/web3.js";
import { useEffect, useState } from "react";

export function WalletInfo() {
const { accounts, connection } = useSolanaWallet();
const [userAddress, setUserAddress] = useState<string | null>(null);

useEffect(() => {
if (accounts && accounts.length > 0) {
setUserAddress(accounts[0]);
}
}, [accounts]);

return (
<div>
<h2>Wallet Information</h2>
<div>
{userAddress && (
<div>
<p><strong>Address:</strong> {userAddress}</p>
<p><strong>Public Key:</strong> {new PublicKey(userAddress).toBase58()}</p>
</div>
)}
</div>
</div>
)
}

Integrating Solana Name Service (SNS)

SNS enables users to register human-readable domain names that map to Solana wallet addresses. This section will show you how to implement the core SNS functionalities: domain resolution, registration, and management.

Required Imports

Ensure you import the necessary components from the installed libraries:

import { resolveDomainName, registerDomainName, getPrimaryDomain } from '@bonfida/spl-name-service'
import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js'
import { useSolanaWallet } from '@web3auth/modal/react/solana'

Domain Resolution

Domain resolution converts a .sol domain name to its corresponding wallet address. This is useful for enabling users to send payments using human-readable names instead of long wallet addresses.

src/components/domainResolver.tsx
import { Connection, PublicKey } from "@solana/web3.js";
import { resolveDomainName } from "@bonfida/spl-name-service";
import { useSolanaWallet } from "@web3auth/modal/react/solana";
import { useState } from "react";

export function DomainResolver() {
const { connection } = useSolanaWallet();
const [domainInput, setDomainInput] = useState("");
const [resolvedAddress, setResolvedAddress] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const resolveDomain = async () => {
if (!connection || !domainInput.trim()) {
setError("Please enter a domain name");
return;
}

try {
setIsLoading(true);
setError(null);

// Normalize domain input (remove .sol if present, convert to lowercase)
const normalizedDomain = domainInput.toLowerCase().replace('.sol', '');

// Resolve the domain to get the owner's public key
const ownerKey = await resolveDomainName(connection, normalizedDomain);

setResolvedAddress(ownerKey.toBase58());
} catch (err) {
setError(
err instanceof Error
? err.message
: "Failed to resolve domain. Domain may not exist or be listed for sale."
);
setResolvedAddress(null);
} finally {
setIsLoading(false);
}
};

return (
<div>
<h2>Domain Resolver</h2>
<div className="flex flex-col gap-4">
<input
type="text"
placeholder="Enter domain (e.g., mydomain.sol)"
value={domainInput}
onChange={(e) => setDomainInput(e.target.value)}
className="px-4 py-2 border rounded-lg text-black"
/>
<button
onClick={resolveDomain}
disabled={isLoading || !domainInput.trim()}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
>
{isLoading ? "Resolving..." : "Resolve Domain"}
</button>

{resolvedAddress && (
<div className="p-4 bg-green-100 rounded-lg">
<p className="text-green-800">
<strong>Resolved Address:</strong> {resolvedAddress}
</p>
</div>
)}

{error && (
<div className="p-4 bg-red-100 rounded-lg">
<p className="text-red-800">Error: {error}</p>
</div>
)}
</div>
</div>
);
}

Domain Registration

Domain registration allows users to claim new .sol domains. The registration process involves creating and sending a transaction with the appropriate instructions.

src/components/domainRegistration.tsx
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { registerDomainName } from "@bonfida/spl-name-service";
import { useSolanaWallet } from "@web3auth/modal/react/solana";
import { useState } from "react";

export function DomainRegistration() {
const { accounts, connection, signAndSendTransaction } = useSolanaWallet();
const [domainInput, setDomainInput] = useState("");
const [isRegistering, setIsRegistering] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);

const registerDomain = async () => {
if (!connection || !accounts?.[0] || !domainInput.trim()) {
setError("Please connect wallet and enter a domain name");
return;
}

try {
setIsRegistering(true);
setError(null);
setSuccess(null);

// Normalize and validate domain input
const normalizedDomain = domainInput.toLowerCase().replace('.sol', '');

if (normalizedDomain.length < 1 || normalizedDomain.length > 32) {
setError("Domain must be between 1 and 32 characters");
return;
}

const buyerPublicKey = new PublicKey(accounts[0]);

// Create registration instruction
const instruction = await registerDomainName(
connection,
normalizedDomain,
buyerPublicKey,
buyerPublicKey // buyer pays for the domain
);

// Create and send transaction
const transaction = new Transaction().add(instruction);
const signature = await signAndSendTransaction(transaction);

setSuccess(`Domain registered successfully! Transaction: ${signature}`);
setDomainInput(""); // Clear input on success
} catch (err) {
setError(
err instanceof Error
? err.message
: "Failed to register domain. Domain may already exist or you may have insufficient funds."
);
} finally {
setIsRegistering(false);
}
};

return (
<div>
<h2>Domain Registration</h2>
<div className="flex flex-col gap-4">
<input
type="text"
placeholder="Enter domain name (without .sol)"
value={domainInput}
onChange={(e) => setDomainInput(e.target.value)}
className="px-4 py-2 border rounded-lg text-black"
/>
<button
onClick={registerDomain}
disabled={isRegistering || !domainInput.trim() || !accounts?.[0]}
className="px-6 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50"
>
{isRegistering ? "Registering..." : "Register Domain"}
</button>

{success && (
<div className="p-4 bg-green-100 rounded-lg">
<p className="text-green-800">{success}</p>
</div>
)}

{error && (
<div className="p-4 bg-red-100 rounded-lg">
<p className="text-red-800">Error: {error}</p>
</div>
)}
</div>
</div>
);
}

Primary Domain Display

A primary domain serves as the user's main identity. This component fetches and displays the user's primary domain, providing instant personalization for your application.

src/components/primaryDomain.tsx
import { Connection, PublicKey } from "@solana/web3.js";
import { getPrimaryDomain } from "@bonfida/spl-name-service";
import { useSolanaWallet } from "@web3auth/modal/react/solana";
import { useState, useEffect } from "react";

export function PrimaryDomain() {
const { accounts, connection } = useSolanaWallet();
const [primaryDomain, setPrimaryDomain] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const fetchPrimaryDomain = async () => {
if (!connection || !accounts?.[0]) {
return;
}

try {
setIsLoading(true);
setError(null);

const ownerPublicKey = new PublicKey(accounts[0]);

// Fetch the user's primary domain
const domain = await getPrimaryDomain(connection, ownerPublicKey);

setPrimaryDomain(domain ? `${domain}.sol` : null);
} catch (err) {
// No primary domain set is not really an error, just no domain
setPrimaryDomain(null);
setError("No primary domain set");
} finally {
setIsLoading(false);
}
};

useEffect(() => {
fetchPrimaryDomain();
}, [connection, accounts]);

return (
<div>
<h2>Primary Domain</h2>
<div className="flex flex-col gap-4">
{isLoading && <span className="text-gray-500">Loading primary domain...</span>}

{primaryDomain && (
<div className="p-4 bg-blue-100 rounded-lg">
<p className="text-blue-800">
<strong>Your Primary Domain:</strong> {primaryDomain}
</p>
</div>
)}

{error && !isLoading && (
<div className="p-4 bg-yellow-100 rounded-lg">
<p className="text-yellow-800">{error}</p>
</div>
)}

<button
onClick={fetchPrimaryDomain}
disabled={isLoading || !accounts?.[0]}
className="px-6 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 disabled:opacity-50"
>
{isLoading ? "Loading..." : "Refresh Primary Domain"}
</button>
</div>
</div>
);
}

Testing and Best Practices

Development Environment

  • DevNet for Testing: Always develop and test on DevNet or TestNet. You can use the Solana Faucet to get test SOL for your new account.
  • Environment Variables: Store your Client ID and other sensitive configuration in environment variables.

User Experience

  • Input Validation: Always validate and normalize domain input (lowercase, trim .sol extension).
  • Loading States: Implement proper loading states while processing domain operations.
  • Error Handling: Provide clear error messages when operations fail or when domains don't exist.
  • Fallback for Listed Domains: When resolving domains that are listed for sale, implement fallback functionality to prevent incorrect transfers.

Production Considerations

  • Domain Availability: Before registration, check domain availability using SNS APIs or SDK methods.
  • Transaction Confirmation: Implement proper transaction confirmation handling to ensure operations complete successfully.
  • Rate Limiting: Consider implementing rate limiting for domain operations to prevent spam.
  • Security: Always validate domain operations server-side when dealing with financial transactions.

Conclusion

This guide demonstrates how to integrate MetaMask Embedded Wallets with Solana Name Service (SNS) to create a seamless domain management experience. By combining MetaMask Embedded Wallets' familiar Web2-like social logins with SNS's human-readable domain functionality, you can enable users to register, resolve, and manage .sol domains directly from their embedded wallet.

The integration provides a smooth, familiar experience for your users while leveraging the power of decentralized identity and human-readable addresses on Solana.

For more advanced SNS features like domain records, subdomains, and social integrations, check out the comprehensive SNS SDK documentation.

If you are interested in learning more about MetaMask Embedded Wallets, please check out our documentation for Web SDK or explore our SNS integration example.