Wallet integration
Connect your web app to Stacks wallets and enable users to authenticate, sign transactions, and interact with smart contracts
Wallet integration transforms your web app from a static interface into a fully interactive blockchain application. Users can authenticate with their Stacks wallets, sign transactions, and interact with smart contracts directly from your app.
Stacks Connect makes this integration straightforward with a single library that works with all major Stacks wallets including Leather, Xverse, and Asigna.
What you'll learn
You'll learn how to:
- Connect and disconnect Stacks wallets
- Authenticate users and manage connection state
- Read user account information and balances
- Enable contract function calls from your frontend
- Deploy contracts through wallet interactions
- Sign messages for authentication and verification
Prerequisites
Before starting, make sure you have:
- A web application (React, vanilla JS, or any framework)
- Basic knowledge of JavaScript and async/await
- Node.js and npm installed
- A Stacks wallet extension for testing (we recommend Leather)
Step 1: Install Stacks Connect
Install the latest version of Stacks Connect in your project:
npm install @stacks/connect@latest
Note: This guide uses Stacks Connect v8.x.x which introduces the new request method pattern. If you're migrating from v7.x.x, the old showConnect, openContractCall methods are deprecated in favor of the unified request approach.
Stacks Connect works with any JavaScript framework or vanilla JS applications.
Step 2: Set up wallet connection
Create connection functions using the new connect, disconnect, and isConnected methods:
import { connect, disconnect, isConnected, getLocalStorage } from '@stacks/connect';// Connect wallet functionasync function connectWallet() {try {const response = await connect({forceWalletSelect: true, // Always show wallet selection modal});console.log('Connected successfully:', response);updateUserInterface();} catch (error) {console.error('Connection failed:', error);}}// Check if user is connectedfunction isUserConnected() {return isConnected();}// Disconnect wallet functionfunction disconnectWallet() {disconnect();updateUserInterface();}// Get stored user data from local storagefunction getUserData() {if (!isConnected()) return null;const data = getLocalStorage();return data?.addresses?.stx?.[0]?.address || null;}
Add connect/disconnect buttons to your UI:
<div id="wallet-section"><button id="connect-wallet" onclick="connectWallet()">Connect Wallet</button><button id="disconnect-wallet" onclick="disconnectWallet()" style="display: none;">Disconnect</button><div id="user-info" style="display: none;"></div></div>
Step 3: Read user account data
Once connected, access user information and display it:
// Get STX balanceasync function getStxBalance(address, network = 'mainnet') {const baseUrl = network === 'mainnet'? 'https://api.hiro.so': 'https://api.testnet.hiro.so';try {const response = await fetch(`${baseUrl}/v2/accounts/${address}?proof=0`);const data = await response.json();return {balance: parseInt(data.balance) / 1000000, // Convert to STXlocked: parseInt(data.locked) / 1000000,nonce: data.nonce,};} catch (error) {console.error('Failed to fetch balance:', error);return { balance: 0, locked: 0, nonce: 0 };}}// Update UI with user dataasync function updateUserInterface() {const userAddress = getUserData();const userInfoDiv = document.getElementById('user-info');const connectBtn = document.getElementById('connect-wallet');const disconnectBtn = document.getElementById('disconnect-wallet');if (userAddress) {const balance = await getStxBalance(userAddress);userInfoDiv.innerHTML = `<p><strong>Address:</strong> ${userAddress}</p><p><strong>Balance:</strong> ${balance.balance} STX</p>`;userInfoDiv.style.display = 'block';connectBtn.style.display = 'none';disconnectBtn.style.display = 'block';enableWalletFeatures();} else {userInfoDiv.style.display = 'none';connectBtn.style.display = 'block';disconnectBtn.style.display = 'none';disableWalletFeatures();}}
Step 4: Make contract calls
Enable users to interact with smart contracts using the new request method:
import { request } from '@stacks/connect';import { Cl } from '@stacks/transactions';// Call a contract functionasync function callContract() {try {const response = await request('stx_callContract', {contract: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.hello-world',functionName: 'set-value',functionArgs: [Cl.uint(42),Cl.stringUtf8('Hello from frontend!')],network: 'testnet', // or 'mainnet'});console.log('Transaction ID:', response.txid);alert(`Transaction submitted! ID: ${response.txid}`);} catch (error) {console.error('Contract call failed:', error);alert('Transaction failed: ' + error.message);}}
Add a button to trigger contract calls:
<button onclick="callContract()" id="contract-call-btn" disabled>Call Contract</button>
Step 5: Deploy contracts
Let users deploy contracts directly from your interface:
// Deploy a contractasync function deployContract() {const contractCode = `;; A simple counter contract(define-data-var counter uint u0)(define-read-only (get-counter)(var-get counter))(define-public (increment)(begin(var-set counter (+ (var-get counter) u1))(ok (var-get counter))))`;try {const response = await request('stx_deployContract', {name: 'my-counter-contract',clarityCode: contractCode,clarityVersion: '2',network: 'testnet',});console.log('Contract deployed! Transaction ID:', response.txid);alert(`Contract deployed! Transaction ID: ${response.txid}`);} catch (error) {console.error('Contract deployment failed:', error);alert('Deployment failed: ' + error.message);}}
Add deployment button:
<button onclick="deployContract()" id="deploy-contract-btn" disabled>Deploy Contract</button>
Step 6: Sign messages
Implement message signing for authentication and verification:
// Sign a simple messageasync function signMessage() {const message = 'Please sign this message to verify your identity';try {const response = await request('stx_signMessage', {message,});console.log('Message signed:', response.signature);console.log('Public key:', response.publicKey);// Store signature for verificationlocalStorage.setItem('userSignature', JSON.stringify(response));alert('Message signed successfully!');} catch (error) {console.error('Message signing failed:', error);alert('Signing failed: ' + error.message);}}// Sign a structured messageasync function signStructuredMessage() {try {// Create structured message using Clarity valuesconst clarityMessage = Cl.tuple({proposal: Cl.stringAscii('Increase block reward'),choice: Cl.stringAscii('Yes'),voter: Cl.stringAscii(getUserData() || ''),});const clarityDomain = Cl.tuple({name: Cl.stringAscii('My dApp'),version: Cl.stringAscii('1.0.0'),'chain-id': Cl.uint(1),});const response = await request('stx_signStructuredMessage', {message: clarityMessage,domain: clarityDomain,});console.log('Structured message signed:', response);alert('Structured message signed successfully!');} catch (error) {console.error('Structured message signing failed:', error);alert('Structured signing failed: ' + error.message);}}
Add signing buttons:
<button onclick="signMessage()" id="sign-message-btn" disabled>Sign Message</button><button onclick="signStructuredMessage()" id="sign-structured-btn" disabled>Sign Structured Message</button>
Step 7: Persist user session
Store wallet connection state across browser sessions:
// Check connection status on page loadfunction initializeApp() {updateUserInterface();}// Enable wallet-dependent featuresfunction enableWalletFeatures() {document.getElementById('contract-call-btn').disabled = false;document.getElementById('deploy-contract-btn').disabled = false;document.getElementById('sign-message-btn').disabled = false;document.getElementById('sign-structured-btn').disabled = false;}// Disable wallet features when disconnectedfunction disableWalletFeatures() {document.getElementById('contract-call-btn').disabled = true;document.getElementById('deploy-contract-btn').disabled = true;document.getElementById('sign-message-btn').disabled = true;document.getElementById('sign-structured-btn').disabled = true;}// Store additional user preferencesfunction storeUserPreferences(preferences) {localStorage.setItem('userPreferences', JSON.stringify(preferences));}function getUserPreferences() {const stored = localStorage.getItem('userPreferences');return stored ? JSON.parse(stored) : {};}// Initialize on page loaddocument.addEventListener('DOMContentLoaded', initializeApp);
Verify it's working
Test your wallet integration:
- 1Open your app in a browser with a Stacks wallet installed
- 2Click "Connect Wallet" and select your preferred wallet
- 3Verify user info displays including address and balance
- 4Test contract calls - transactions should open in the wallet
- 5Try message signing - wallet should prompt for signature
- 6Refresh the page - connection should persist
Connected successfully? Your wallet integration is working!
Troubleshooting
Wallet not detected
- Ensure you have a Stacks wallet extension installed
- Try refreshing the page after installing the wallet
- Check browser console for any JavaScript errors
Connection popup doesn't appear
- Check if popup blockers are disabled for your site
- Verify you're using the latest version of
@stacks/connect - Make sure you're serving over HTTPS in production
Transactions fail
- Verify contract addresses and function names are correct
- Check that the user has sufficient STX for fees
- Ensure you're using the correct network ('testnet' vs 'mainnet')
- Check that Clarity values are properly constructed with
Clhelpers
User session not persisting
- Check localStorage is enabled in the browser
- Verify
isConnected()is called after page load - Make sure
connect()completed successfully
Balance not updating
- API calls may be cached - add timestamp parameters
- Check network configuration matches the user's wallet
- Verify the Stacks API endpoints are accessible
Migration from older versions
- Replace
showConnect()withconnect() - Replace
openContractCall()withrequest('stx_callContract', {}) - Replace
UserSessionusage withisConnected()andgetLocalStorage() - Update error handling from
onFinish/onCanceltotry/catch
Next steps
Now that wallet integration is working:
- Building Transactions for advanced transaction handling
- Interacting with Smart Contracts to build complex dApp functionality
- Working with APIs to add backend data
Want to learn more about Stacks Connect? Check the official documentation for advanced configuration options, wallet compatibility details, and migration guides.