Accounts & addresses
Learn how to get an address from an account
Stacks uses the concept of an "account" to represent a user's identity on the blockchain. An account is identified by a unique address derived from the account's public key.
Address formats
Stacks addresses use different prefixes to indicate the network they belong to, making it easy to distinguish between mainnet and testnet addresses.
// Mainnet address starts with 'SP'const mainnetAddress = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159';// Testnet address starts with 'ST'const testnetAddress = 'ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ';
The address format ensures that tokens on testnet cannot be accidentally sent to mainnet addresses and vice versa.
Getting an address
There are several ways to obtain a Stacks address depending on your use case and what cryptographic material you have available.
Using Stacks Connect
When building user-facing applications, you'll typically get addresses from users who connect their wallets through Stacks Connect.
import { connect, getLocalStorage, request } from '@stacks/connect';async function handleWalletConnection() {// Connect to walletconst response = await connect();// Get stored addressesconst data = getLocalStorage();const stxAddresses = data.addresses.stx;if (stxAddresses && stxAddresses.length > 0) {const address = stxAddresses[0].address;console.log('STX Address:', address);// 'SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF'}// Get detailed account info if neededconst accounts = await request('stx_getAccounts');console.log('Account details:', accounts.addresses[0]);}
Stacks Connect stores the connected addresses in local storage, allowing your app to persist the connection across page reloads.
Using a seed phrase
For programmatic wallet generation or when restoring accounts from backup, you can derive addresses from a seed phrase (also known as a mnemonic).
import { generateWallet, generateSecretKey } from '@stacks/wallet-sdk';async function createWalletFromSeed() {// Generate a new 24-word seed phraseconst secretKey = generateSecretKey();// Or use an existing seed phrase// const secretKey = 'already owned seed phrase ...';const wallet = await generateWallet({secretKey,password: 'optional-encryption-password',});// Get the first account's addressconst account = wallet.accounts[0];const mainnetAddress = account.address;console.log('Address:', mainnetAddress);console.log('Private key:', account.stxPrivateKey);}
Each wallet can contain multiple accounts, all derived from the same seed phrase using different derivation paths.
Using a private key
If you already have a private key, you can directly derive the corresponding address without going through the wallet generation process.
import { privateKeyToAddress, TransactionVersion } from '@stacks/transactions';function getAddressFromPrivateKey() {// Compressed private key (64 or 66 characters)const privateKey = 'your-private-key-here';// For mainnetconst mainnetAddress = privateKeyToAddress(privateKey,TransactionVersion.Mainnet);// For testnetconst testnetAddress = privateKeyToAddress(privateKey,TransactionVersion.Testnet);console.log('Mainnet:', mainnetAddress);console.log('Testnet:', testnetAddress);}
The same private key will generate different addresses for mainnet and testnet due to the network-specific version bytes.
Using a public key
When you only have access to a public key (for example, in a watch-only wallet scenario), you can still derive the corresponding address.
import { publicKeyToAddress, TransactionVersion } from '@stacks/transactions';function getAddressFromPublicKey() {// Compressed public key (66 characters starting with 02 or 03)const publicKey = '03b3e0a76b292b2c83fc0ac14ae6160d0438ebe94e14bbb7d0ded3c217f3d29ba7';// For mainnetconst mainnetAddress = publicKeyToAddress(publicKey,TransactionVersion.Mainnet);// For testnetconst testnetAddress = publicKeyToAddress(publicKey,TransactionVersion.Testnet);console.log('Mainnet:', mainnetAddress);// 'SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF'}
This is useful for creating watch-only wallets or verifying addresses without access to private keys.
Address validation
Before sending transactions, it's important to validate that addresses are properly formatted.
import { validateStacksAddress } from '@stacks/transactions';function isValidAddress(address: string): boolean {try {// Check if it's a valid mainnet addressif (address.startsWith('SP')) {return validateStacksAddress(address);}// Check if it's a valid testnet addressif (address.startsWith('ST')) {return validateStacksAddress(address);}return false;} catch (error) {return false;}}// Examplesconsole.log(isValidAddress('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159')); // trueconsole.log(isValidAddress('invalid-address')); // false
Always validate addresses before using them in transactions to prevent loss of funds due to typos or formatting errors.