Wallet Integration
Documentation for Tezos wallet integration using Beacon SDK and Taquito.
Overview
The wallet integration layer handles:
- Wallet connection (Beacon SDK)
- Transaction signing
- Message authentication
- Balance queries
- Domain name resolution
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ ClientsProvider │
│ (context/clients-context.tsx) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ TezosWallet │ │ Global Atoms (Jotai) │ │
│ │ │ │ │ │
│ │ - TezosToolkit │ │ - walletStateAtom │ │
│ │ - BeaconWallet │ │ - walletLoadingAtom │ │
│ │ - DomainsClient │ │ - tezNetworkSelected │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Beacon SDK │
│ (@airgap/beacon-sdk) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Temple Wallet │ │ Kukai Wallet │ │ Other Wallets │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘TezosWallet Class
File: apps/emprops-studio/wallet.ts
Initialization
typescript
class TezosWallet implements WalletInterface {
private tezos: TezosToolkit
private wallet: BeaconWallet
private domainsClient: DomainsClient
async createClients(): Promise<void> {
const rpcUrl = getRPCFromRegistry(getChainIdByEnvironment("TEZOS"))
// Create Taquito toolkit
this.tezos = new TezosToolkit(rpcUrl)
// Create Beacon wallet
this.wallet = new BeaconWallet({
name: process.env.NEXT_PUBLIC_DAPP_NAME,
preferredNetwork: process.env.NEXT_PUBLIC_NETWORK as NetworkType
})
// Set wallet as provider with timeout
this.tezos.setWalletProvider(this.wallet)
this.tezos.setProvider({
config: { confirmationPollingTimeoutSecond: 120 }
})
// Create domains client for .tez resolution
this.domainsClient = new DomainsClient(rpcUrl, chainId)
}
}Setup (Check Existing Connection)
typescript
async setup(): Promise<WalletState | null> {
// Check for existing active account
const activeAccount = await this.wallet.client.getActiveAccount()
if (activeAccount) {
const address = await this.wallet.getPKH()
const balanceMutez = await this.tezos.tz.getBalance(address)
const balance = simpleFixPrice(balanceMutez.toNumber(), "TEZOS")
return {
address,
connected: true,
blockchain: "TEZOS",
network: activeAccount.network?.type,
balance
}
}
return null
}Connect
typescript
async connect(): Promise<WalletState> {
// Request wallet permissions
const permissions = await this.wallet.requestPermissions({
network: {
type: process.env.NEXT_PUBLIC_NETWORK as NetworkType
}
})
const address = await this.wallet.getPKH()
const balanceMutez = await this.tezos.tz.getBalance(address)
const balance = simpleFixPrice(balanceMutez.toNumber(), "TEZOS")
return {
address,
connected: true,
blockchain: "TEZOS",
network: permissions.network?.type,
balance
}
}Disconnect
typescript
async disconnect(): Promise<void> {
await this.wallet.clearActiveAccount()
}Authenticate (Sign Message)
typescript
async authenticate(message: string): Promise<string> {
const dappUrl = process.env.NEXT_PUBLIC_DAPP_URL
const timestamp = new Date().toISOString()
// Format message per Tezos signing standard
const formattedMessage = [
"Tezos Signed Message:",
dappUrl,
timestamp,
message
].join(" ")
// Request signature (no on-chain transaction)
const signature = await this.wallet.client.requestSignPayload({
signingType: SigningType.MICHELINE,
payload: formattedMessage
})
// Store in localStorage
localStorage.setItem("tezos-auth", JSON.stringify({
signature: signature.signature,
timestamp,
message
}))
return signature.signature
}Get Network
typescript
async getNetwork(): Promise<string> {
const activeAccount = await this.wallet.client.getActiveAccount()
return activeAccount?.network?.type || ""
}Wallet State Management
Jotai Atoms
File: apps/emprops-studio/globals/wallet.ts
typescript
import { atom } from 'jotai'
import { WalletState } from '@/types/wallet'
// Current wallet state
export const walletStateAtom = atom<WalletState | null>(null)
// Loading state during connection
export const walletLoadingAtom = atom<boolean>(true)
// Selected Tezos network
export const tezNetworkSelected = atom<string>("")WalletState Interface
typescript
interface WalletState {
address: string | undefined
connected: boolean
blockchain: "TEZOS" | "ETHEREUM" | "BASE"
network?: string // Chain ID or network name
balance?: number // In native units (XTZ, ETH)
}ClientsProvider
File: apps/emprops-studio/context/clients-context.tsx
Context Definition
typescript
interface Clients {
wallet: TezosWallet | null
}
const ClientsContext = createContext<Clients | null>(null)
export function useClients(): Clients | null {
return useContext(ClientsContext)
}Provider Implementation
typescript
export function ClientsProvider({ children }: { children: ReactNode }) {
const [clients, setClients] = useState<Clients | null>(null)
useEffect(() => {
// Dynamic import to avoid SSR issues
import('@/wallet').then(({ TezosWallet }) => {
const wallet = new TezosWallet()
wallet.createClients().then(() => {
setClients({ wallet })
})
})
}, [])
return (
<ClientsContext.Provider value={clients}>
{children}
</ClientsContext.Provider>
)
}WalletProviderLayout
File: apps/emprops-studio/layouts/WalletProviderLayout/index.tsx
Handles automatic wallet setup on app load:
typescript
const WalletProviderLayout: React.FC<Props> = ({ children }) => {
const clients = useClients()
const [, setWalletState] = useAtom(walletStateAtom)
const [, setWalletLoading] = useAtom(walletLoadingAtom)
const [, setTezNetworkSelected] = useAtom(tezNetworkSelected)
const setupTezosWallet = useCallback(async () => {
try {
if (!clients?.wallet) return
// Check for existing connection
const state = await clients.wallet.setup()
setWalletState(state)
// Get network
const chainId = await clients.wallet.getNetwork()
setTezNetworkSelected(chainId)
} catch (e) {
console.error(e)
} finally {
setWalletLoading(false)
}
}, [clients, setWalletLoading, setWalletState, setTezNetworkSelected])
useEffect(() => {
setupTezosWallet()
}, [setupTezosWallet])
return <>{children}</>
}Tezos Domain Resolution
File: apps/emprops-studio/clients/tezos-client.ts
DomainsClient Class
typescript
class DomainsClient {
private client: TaquitoTezosDomainsClient
constructor(rpcUrl: string, chainId: ChainId) {
const toolkit = new TezosToolkit(rpcUrl)
toolkit.addExtension(new Tzip16Module())
// Determine network from chain ID
const network = chainId === ChainId.TezosMainnet
? SupportedNetwork.Mainnet
: SupportedNetwork.Ghostnet
this.client = new TaquitoTezosDomainsClient({
tezos: toolkit,
network,
caching: { enabled: true }
})
}
async resolveDomain(address: string): Promise<string | null> {
try {
const domain = await this.client.resolver.resolveAddressToName(address)
return domain || null
} catch {
return null
}
}
}Usage:
typescript
// Resolve tz1... address to .tez domain
const domain = await domainsClient.resolveDomain("tz1abc...")
// Returns: "myname.tez" or nullBeacon SDK Integration
Wallet Types Supported
The Beacon SDK supports multiple Tezos wallets:
- Temple Wallet (browser extension)
- Kukai Wallet (web-based)
- Umami Wallet
- AirGap Wallet
- Galleon Wallet
- Spire Wallet
Connection Flow
1. User clicks "Connect Wallet"
│
├─▶ wallet.requestPermissions() called
│
├─▶ Beacon shows wallet selection modal
│
├─▶ User selects wallet (e.g., Temple)
│
├─▶ Wallet extension prompts for approval
│
├─▶ User approves network connection
│
├─▶ Beacon returns permissions object
│
└─▶ App stores wallet stateSigning Types
typescript
enum SigningType {
RAW = "raw", // Plain bytes
OPERATION = "operation", // Tezos operation
MICHELINE = "micheline" // Micheline-encoded data
}MICHELINE is used for:
- Message signing (authentication)
- Structured data signing
- Wert payment widget integration
Using Wallet in Components
Getting Wallet Instance
typescript
function MyComponent() {
const clients = useClients()
const [walletState] = useAtom(walletStateAtom)
// Check if connected
if (!walletState?.connected) {
return <ConnectWalletButton />
}
// Use wallet for transactions
const handleMint = async () => {
const wallet = clients?.wallet
if (!wallet) return
const toolkit = wallet.getTezosToolkit()
const contract = await toolkit.wallet.at(contractAddress)
// ... execute transaction
}
}Getting TezosToolkit
typescript
// For transactions (requires wallet)
const toolkit = clients.wallet.getTezosToolkit()
const contract = await toolkit.wallet.at(address)
await contract.methods.mint(params).send()
// For queries (no wallet needed)
const queryToolkit = getQuerierClientTez()
const contract = await queryToolkit.contract.at(address)
const storage = await contract.storage()Chain ID Reference
File: apps/emprops-studio/types/wallet.ts
typescript
export enum ChainId {
// Tezos
TezosMainnet = "NetXdQprcVkpaWU",
TezosGhostnet = "NetXnHfVqm9iesp",
// Ethereum
EthereumMainnet = "1",
EthereumSepolia = "11155111",
// Base
BaseMainnet = "8453",
BaseSepolia = "84532"
}Environment Variables
bash
# Dapp Identity
NEXT_PUBLIC_DAPP_NAME="EmProps Studio"
NEXT_PUBLIC_DAPP_URL="https://emprops.io"
# Network Configuration
NEXT_PUBLIC_NETWORK=mainnet # or ghostnet
NEXT_PUBLIC_RPC_URL=https://mainnet.tezos.marigold.dev
NEXT_PUBLIC_NETWORKS_REGISTRY='{
"NetXdQprcVkpaWU": ["https://mainnet.tezos.marigold.dev"],
"NetXnHfVqm9iesp": ["https://ghostnet.tezos.marigold.dev"]
}'Error Handling
Common Errors
| Error | Cause | Resolution |
|---|---|---|
WALLET_NOT_CONNECTED | No active account | Call connect() first |
USER_REJECTED | User cancelled | Show retry option |
NETWORK_MISMATCH | Wrong network selected | Prompt network switch |
RPC_ERROR | RPC node issue | Retry with different RPC |
Example Error Handling
typescript
async function connectWallet() {
try {
const state = await wallet.connect()
setWalletState(state)
} catch (error) {
if (error.message.includes('USER_REJECTED')) {
toast.error('Connection cancelled')
} else if (error.message.includes('NETWORK')) {
toast.error('Please switch to mainnet')
} else {
toast.error('Failed to connect wallet')
}
}
}Dependencies
json
{
"@taquito/taquito": "16.0.1",
"@taquito/beacon-wallet": "16.2.0",
"@taquito/tzip16": "16.1.2",
"@tezos-domains/taquito-client": "1.24.0",
"@airgap/beacon-sdk": "4.6.2"
}Related Documentation
- Tezos Implementation - Full system overview
- Contract Client - Contract interactions
- Minting Components - UI components
