Integrating Solana Name Service (SNS) Identity with MetaMask Embedded Wallets
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
.soldomains 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
- Yarn
- pnpm
- Bun
npm install @web3auth/modal @solana/web3.js @bonfida/spl-name-service @solana/spl-token
yarn add @web3auth/modal @solana/web3.js @bonfida/spl-name-service @solana/spl-token
pnpm add @web3auth/modal @solana/web3.js @bonfida/spl-name-service @solana/spl-token
bun add @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.
- Create a Project: Go to the dashboard and create a new project.
- Copy Client ID: Once created, copy your Client ID from the dashboard. This ID is crucial for initializing the SDK.
- 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.
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>
);
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.
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.
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.
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.
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
.solextension). - 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.