npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nexa-wallet-sdk

v1.0.0

Published

Wallet SDK for the Nexa blockchain

Readme

Nexa Wallet SDK

Production-ready TypeScript SDK for building Nexa blockchain wallets and dApps.

Features

  • Wallet Management: Create wallets from seed phrases or private keys
  • Account Types: Support for multiple account types (Default, Vault, DApp)
  • Transaction Building: Fluent API for building and signing transactions
  • Token Operations: Create, mint, melt, and transfer tokens, NFTs, and SFTs
  • Watch-Only Wallets: Monitor addresses without storing private keys
  • Rostrum Provider: Direct access to Electrum RPC endpoints
  • Network Support: Mainnet and testnet compatibility
  • Multiple Formats: CommonJS, ES modules, and browser bundles

Installation

npm install nexa-wallet-sdk

Quick Start

Basic Wallet Setup

import { Wallet, rostrumProvider } from 'nexa-wallet-sdk'

// Connect to the default mainnet node
await rostrumProvider.connect()

// Connect to a specific network (mainnet or testnet)
await rostrumProvider.connect('testnet')  // Uses predefined testnet node
await rostrumProvider.connect('mainnet')  // Uses predefined mainnet node

// Connect to a custom node
await rostrumProvider.connect({
    host: 'your-custom-node.example.com',
    port: 30004,
    scheme: 'wss' // or 'ws' for unencrypted connection
})

// Create wallet from seed phrase
const wallet = new Wallet(
    'your twelve word seed phrase goes here for wallet creation',
    'testnet' // or 'mainnet'
)

// Initialize wallet — discovers accounts and loads balances
await wallet.initialize()

// Get the first default account (store key '0')
const account = wallet.accountStore.getAccount('0')

Simple Transaction

const tx = await wallet.newTransaction(account)
    .sendTo('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc', '10000')
    .populate()
    .sign()
    .build()

// Pass the account to auto-refresh balances after broadcast
const txHash = await wallet.sendTransaction(tx, account)
console.log('Transaction hash:', txHash)

Account Types

The SDK supports three account types, each designed for different use cases. All account types share the same base API for balances, transactions, and addresses.

Default Account (NEXA_ACCOUNT)

Standard BIP44 HD account with multiple receive and change addresses. Best for general wallet use.

  • Derives from BIP44 path m/44'/29223'/{account}'
  • Multiple receive keys and change keys (20 of each by default)
  • Change outputs go to dedicated change addresses
  • Store key: just the account index (e.g., '0', '1')
import { AccountType } from 'nexa-wallet-sdk'

// Create a new default account
const account = await wallet.newAccount(AccountType.NEXA_ACCOUNT)

// Get a receive address (async — checks if current address is used before generating new)
const address = await account.getNewAddress()

// Get a change address (different from receive)
const changeAddress = await account.getNewChangeAddress()

// Check if the account supports change addresses
account.hasChangeAddresses() // true

// List all addresses in the account (receive + change)
const allAddresses = account.getAddresses()
for (const addrKey of allAddresses) {
    console.log(addrKey.address, 'balance:', addrKey.balance)
}

// Access receive and change keys separately
const { receiveKeys, changeKeys } = account.accountKeys
console.log('Receive addresses:', receiveKeys.length)  // 20 by default
console.log('Change addresses:', changeKeys.length)     // 20 by default

Address generation modes:

  • Discovery mode (default): Reuses unused addresses — only generates a new address when the current one has been used on-chain
  • Sequential mode (forceSequentialIndexing = true): Always generates new addresses, useful for multi-user apps where each user needs a unique address

Each default account manages its own pool of receive and change addresses internally. You don't need separate store keys to access individual addresses — use getNewAddress() to get the next available receive address, or accountKeys.receiveKeys / accountKeys.changeKeys to access the full list.

DApp Account (DAPP_ACCOUNT)

Single-key account for application-specific use. All DApp accounts derive from BIP44 account 2.

  • Single address — no change keys
  • Change outputs return to the same address
  • Store key: '2.{index}' (e.g., '2.0', '2.1')
const dappAccount = await wallet.newAccount(AccountType.DAPP_ACCOUNT)

// Single address for everything
const address = dappAccount.getNewAddress()
dappAccount.getNewChangeAddress() // same as getNewAddress()
dappAccount.hasChangeAddresses()  // false

Vault Account (VAULT_ACCOUNT)

Single-key account for secure storage. All Vault accounts derive from BIP44 account 1.

  • Single address — no change keys
  • Store key: '1.{index}' (e.g., '1.0', '1.1')
const vaultAccount = await wallet.newAccount(AccountType.VAULT_ACCOUNT)
const vaultAddress = vaultAccount.getNewAddress()

Account Store

The AccountStore manages all accounts in the wallet. After calling wallet.initialize(), discovered accounts are available via their store keys.

// List all accounts (returns a Map<string, BaseAccount>)
const allAccounts = wallet.accountStore.listAccounts()

// Get a specific account by store key
const defaultAccount = wallet.accountStore.getAccount('0')     // first default account
const vaultAccount = wallet.accountStore.getAccount('1.0')     // first vault account
const dappAccount = wallet.accountStore.getAccount('2.0')      // first dapp account

// Get all accounts of a specific type
const allDappAccounts = wallet.accountStore.getAccountsByType(AccountType.DAPP_ACCOUNT)
const allVaultAccounts = wallet.accountStore.getAccountsByType(AccountType.VAULT_ACCOUNT)
const allDefaultAccounts = wallet.accountStore.getAccountsByType(AccountType.NEXA_ACCOUNT)

// Find the private key for any address across all accounts
const addressKey = wallet.accountStore.findKeyForAddress('nexa:nqtsq5g5...')

// Remove an account
wallet.accountStore.removeAccount('2.0')

Sequential Indexing (Multi-User)

By default, account creation uses discovery-based indexing (scans the blockchain for existing activity). For multi-user scenarios where each user needs a unique account immediately, enable sequential indexing:

wallet.forceSequentialIndexing = true

// Accounts are created with sequential indexes regardless of blockchain activity
const alice = await wallet.newAccount(AccountType.DAPP_ACCOUNT)   // 2.0
const bob = await wallet.newAccount(AccountType.DAPP_ACCOUNT)     // 2.1
const charlie = await wallet.newAccount(AccountType.DAPP_ACCOUNT) // 2.2

wallet.forceSequentialIndexing = false // back to discovery-based

Balances

Balances are loaded during wallet.initialize() and when calling account.loadBalances(). They are not automatically refreshed after sending transactions — call loadBalances() to get the latest state.

NEXA Balance

// Loaded during initialize(), or refresh manually:
await account.loadBalances()

const balance = account.balance
console.log('Confirmed:', balance.confirmed)
console.log('Unconfirmed:', balance.unconfirmed)

Token Balances

// Token balances are loaded alongside NEXA balance
await account.loadBalances()

const tokenBalances = account.tokenBalances
// Record<string, Balance> — keyed by token ID
for (const [tokenId, balance] of Object.entries(tokenBalances)) {
    console.log(`${tokenId}: confirmed=${balance.confirmed}, unconfirmed=${balance.unconfirmed}`)
}

Token Authorities

// Authorities are also loaded with loadBalances()
const authorities = account.tokenAuthorities
// Record<string, ITokenUtxo[]> — keyed by token ID

for (const [tokenId, authUtxos] of Object.entries(authorities)) {
    console.log(`Token ${tokenId} has ${authUtxos.length} authority UTXO(s)`)
}

Refreshing Balances After Transactions

Pass the source account to sendTransaction() and balances are refreshed automatically:

const tx = await wallet.newTransaction(account)
    .sendTo(recipient, '50000')
    .populate()
    .sign()
    .build()

// Account balances are refreshed automatically after broadcast
const txHash = await wallet.sendTransaction(tx, account)

// Balance is now up to date
console.log('Balance:', account.balance)
console.log('Tokens:', account.tokenBalances)

If you broadcast without passing the account, you can still refresh manually:

await wallet.sendTransaction(tx)
await account.loadBalances() // manual refresh

Transaction History

// Get all transactions for the account
const transactions = await account.getTransactions()

// Get transactions from a specific block height
const recentTxs = await account.getTransactions(800000)

// Get transactions for a specific address
const addressTxs = await account.getTransactions(undefined, 'nexa:nqtsq5g5...')

// Get the latest transaction for an address
const latestTx = await rostrumProvider.getLatestTransaction('nexa:nqtsq5g5...')

// Paginate transaction history
const page = await rostrumProvider.getTransactionHistory(address, { limit: 10, offset: 0 })

// Filter by block height range
const filtered = await rostrumProvider.getTransactionHistory(address, {
    from_height: 800000,
    to_height: 900000
})

// Token-only or exclude-token history
const tokenHistory = await rostrumProvider.getTransactionHistory(address, { tokens_only: true })
const nexaHistory = await rostrumProvider.getTransactionHistory(address, { exclude_tokens: true })

Transaction Building

All transactions follow the pattern: create -> configure -> populate -> sign -> build

const tx = await wallet.newTransaction(account)
    .sendTo(address, amount)       // 1. Configure outputs
    .addOpReturn(data)             // 2. Add optional data
    .populate()                    // 3. Find inputs and calculate fees
    .sign()                        // 4. Sign the transaction
    .build()                       // 5. Get final transaction hex

Basic Send Transaction

const tx = await wallet.newTransaction(account)
    .sendTo('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc', '50000')
    .populate()
    .sign()
    .build()

Multiple Outputs

const tx = await wallet.newTransaction(account)
    .sendTo('nexatest:address1', '10000')
    .sendTo('nexatest:address2', '20000')
    .sendTo('nexatest:address3', '30000')
    .addOpReturn('Multi-output transaction')
    .populate()
    .sign()
    .build()

Fee From Amount

// Deduct transaction fee from the send amount
const tx = await wallet.newTransaction(account)
    .sendTo(recipient, '50000')
    .feeFromAmount()  // Fee will be subtracted from the 50000
    .populate()
    .sign()
    .build()

Consolidate UTXOs

// Consolidate all UTXOs to a single address
const tx = await wallet.newTransaction(account)
    .consolidate('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc')
    .populate()
    .sign()
    .build()

Token Operations

Create a Fungible Token

const tx = await wallet.newTransaction(account)
    .token(
        'MyToken',                    // Token name
        'MTK',                        // Ticker symbol
        8,                            // Decimal places
        'https://mytoken.com/info',   // Documentation URL
        'sha256hash'                  // Documentation hash
    )
    .populate()
    .sign()
    .build()

Create an NFT Collection

const tx = await wallet.newTransaction(account)
    .collection(
        'My NFT Collection',
        'MNC',
        'https://mycollection.com/metadata',
        'collectionhash'
    )
    .populate()
    .sign()
    .build()

Mint an NFT

const parentCollectionId = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'

const tx = await wallet.newTransaction(account)
    .nft(
        parentCollectionId,
        'https://mynft.com/content.zip',
        'contenthash123'
    )
    .populate()
    .sign()
    .build()

Mint an SFT (Semi-Fungible Token)

const parentCollectionId = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'

const tx = await wallet.newTransaction(account)
    .sft(
        parentCollectionId,
        'https://mysft.com/content.zip',   // Content URL
        'contenthash456',                   // Content hash
        100n                                // Quantity of SFTs to create
    )
    .populate()
    .sign()
    .build()

Token Transfers

const tokenId = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'

// Send tokens
const tx = await wallet.newTransaction(account)
    .sendToToken('nexatest:recipient', '500', tokenId)
    .populate()
    .sign()
    .build()

// Send NEXA and tokens in the same transaction
const tx2 = await wallet.newTransaction(account)
    .sendTo('nexatest:recipient', '1000')
    .sendToToken('nexatest:recipient', '500', tokenId)
    .populate()
    .sign()
    .build()

Mint Additional Tokens

const tx = await wallet.newTransaction(account)
    .mint(tokenId, '1000000')  // Mint 1,000,000 token units
    .populate()
    .sign()
    .build()

Burn (Melt) Tokens

const tx = await wallet.newTransaction(account)
    .melt(tokenId, '500000')  // Burn 500,000 token units
    .populate()
    .sign()
    .build()

Authority Management

Renew Token Authorities

const tx = await wallet.newTransaction(account)
    .renewAuthority(
        tokenId,
        ['mint', 'melt'],  // Permissions to renew
        'nexatest:nqtsq5g5...'  // Optional: new authority address
    )
    .populate()
    .sign()
    .build()

Send Authority to Another Address

const tx = await wallet.newTransaction(account)
    .sendAuthority(
        tokenId,
        ['mint', 'melt'],       // Permissions to send
        'nexatest:nqtsq5g5...'  // Recipient address
    )
    .populate()
    .sign()
    .build()

Delete Token Authority

const tx = await wallet.newTransaction(account)
    .deleteAuthority(tokenId, 'abc123:0')  // outpoint of authority to delete
    .populate()
    .sign()
    .build()

Watch-Only Wallets

Watch-only wallets allow you to monitor addresses and create unsigned transactions without storing private keys.

Create Watch-Only Wallet

import { WatchOnlyWallet } from 'nexa-wallet-sdk'

const watchOnlyWallet = new WatchOnlyWallet([
    { address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
], 'testnet')

Create Unsigned Transaction

const unsignedTx = await watchOnlyWallet.newTransaction()
    .sendTo('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc', '100000')
    .addOpReturn("Watch-only transaction")
    .populate()
    .build()

Sign Watch-Only Transaction

// Pass the unsigned transaction to a wallet with private keys
const signedTx = await wallet.newTransaction(account, unsignedTx)
    .sign()
    .build()

Subscribe to Address Updates

const myCallback = (notification) => {
    console.log('Address activity:', notification)
}

await watchOnlyWallet.subscribeToAddressNotifications(myCallback)

// Later, to prevent memory leaks:
await watchOnlyWallet.unsubscribeFromAddressNotifications(myCallback)

Rostrum Provider (Electrum RPC)

The rostrumProvider is a singleton that provides direct access to all Electrum RPC endpoints. It is used internally by the SDK but is also exported for direct use.

Connection

import { rostrumProvider } from 'nexa-wallet-sdk'

// Connect to predefined nodes
await rostrumProvider.connect()            // mainnet default
await rostrumProvider.connect('testnet')   // testnet default
await rostrumProvider.connect('mainnet')   // mainnet default

// Connect to a custom node
await rostrumProvider.connect({
    host: 'my-node.example.com',
    port: 20004,
    scheme: 'wss'  // 'ws' | 'wss' | 'tcp' | 'tcp_tls'
})

// Disconnect
await rostrumProvider.disconnect()

Network Info

// Server version
const version = await rostrumProvider.getVersion()

// Current block tip
const tip = await rostrumProvider.getBlockTip()
console.log('Height:', tip.height)

// Connection latency (ms)
const latency = await rostrumProvider.getLatency()

Address Queries

// NEXA balance (excludes tokens)
const balance = await rostrumProvider.getBalance(address)
// { confirmed: number, unconfirmed: number }

// Token balances
const tokenBalances = await rostrumProvider.getTokensBalance(address)
const specificToken = await rostrumProvider.getTokensBalance(address, tokenId)
// { confirmed: Record<string, bigint|number>, unconfirmed: Record<string, bigint|number> }

// NEXA UTXOs (excludes token UTXOs)
const nexaUtxos = await rostrumProvider.getNexaUtxos(address)
// IListUnspentRecord[] — { outpoint_hash, value, height, tx_hash, tx_pos, has_token }

// Token UTXOs for a specific token
const tokenUtxos = await rostrumProvider.getTokenUtxos(address, tokenId)
// ITokenUtxo[] — { outpoint_hash, value, group, token_amount, height, ... }

// First use of an address
const firstUse = await rostrumProvider.getFirstUse(address)
// { block_hash, block_height, tx_hash }

// Transaction history
const history = await rostrumProvider.getTransactionHistory(address)
// ITXHistory[] — { tx_hash, height, fee? }

// Transaction history with filters (pagination, height range, token filtering)
const page = await rostrumProvider.getTransactionHistory(address, {
    limit: 10,        // max results
    offset: 0,        // skip first n
    from_height: 0,   // include from height (inclusive)
    to_height: -1,    // include up to height (-1 = include mempool)
    tokens_only: true, // only txs with tokens
    exclude_tokens: true, // only txs without tokens
})

// Get latest transaction for an address (returns full ITransaction or null)
const latestTx = await rostrumProvider.getLatestTransaction(address)

Transaction & UTXO Queries

// Get a full transaction by hash
const tx = await rostrumProvider.getTransaction(txHash)
// ITransaction — { txid, txidem, hex, vin, vout, confirmations, height, ... }

// Get a specific UTXO by outpoint
const utxo = await rostrumProvider.getUtxo(outpointHash)
// IUtxo — { addresses, amount, group, scriptpubkey, spent, ... }

Token Queries

// Token genesis information
const genesis = await rostrumProvider.getTokenGenesis(tokenId)
// ITokenGenesis — { name, ticker, decimal_places, document_url, document_hash, ... }

Subscriptions

The subscription callback receives a status hash (a hash of the address's current UTXO state), not a transaction hash. Use it as a signal that something changed, then fetch the actual transactions:

// Subscribe to address activity
await rostrumProvider.subscribeToAddresses(
    ['nexa:address1'],
    async (statusHash) => {
        // statusHash is a state change signal, NOT a tx hash
        // Fetch transaction history to see what changed
        const history = await rostrumProvider.getTransactionHistory('nexa:address1')
        const latestTx = history[history.length - 1]
        console.log('New transaction:', latestTx.tx_hash)
    }
)

// Unsubscribe
await rostrumProvider.unsubscribeFromAddresses(addresses, callback)

Broadcasting

// Broadcast a signed transaction, returns the transaction ID
const txId = await rostrumProvider.broadcast(signedTxHex)

Advanced Features

Address Notifications

// Define your callback function
const myCallback = (notification) => {
    console.log('Address notification:', notification)
}

// Subscribe to a single address
await wallet.subscribeToAddressNotifications(
    'nexa:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
    myCallback
)

// Subscribe to multiple addresses
await wallet.subscribeToAddressNotifications(
    ['nexa:address1', 'nexa:address2'],
    myCallback
)

// Subscribe to all wallet addresses
const accounts = wallet.accountStore.listAccounts()
const allAddresses = Array.from(accounts.values()).flatMap(account =>
    account.getAddresses().map(addr => addr.address)
)
await wallet.subscribeToAddressNotifications(allAddresses, myCallback)

// Always unsubscribe when done to prevent memory leaks
await wallet.unsubscribeFromAddressNotifications(allAddresses, myCallback)

Parse Existing Transactions

// From hex string
const tx = await wallet.newTransaction(account)
    .parseTxHex('0100000001...')
    .sign()
    .build()

// From buffer
const txBuffer = Buffer.from('0100000001...', 'hex')
const tx2 = await wallet.newTransaction(account)
    .parseTxBuffer(txBuffer)
    .sign()
    .build()

Message Signing

// Sign a message
const address = account.getNewAddress()
const signature = wallet.signMessage('Hello Nexa', address)

// Verify a message
const isValid = wallet.verifyMessage('Hello Nexa', signature, address)

Export Wallet Data

const walletData = wallet.export()
// { phrase, masterKey, accounts }

Available Exports

import {
    // Core classes
    Wallet,
    WatchOnlyWallet,

    // Account types
    BaseAccount,
    DefaultAccount,
    DappAccount,
    VaultAccount,
    AccountStore,

    // Transaction creators
    WalletTransactionCreator,
    WatchOnlyTransactionCreator,

    // Network provider
    rostrumProvider,

    // Rostrum types & constants
    RostrumScheme,          // { WS, WSS, TCP, TCP_TLS }
    // type: RostrumParams, RostrumTransportScheme, BlockTip, IFirstUse,
    //       ITokenGenesis, ITokensBalance, ITokenListUnspent, ITokenUtxo,
    //       IListUnspentRecord, IUtxo, ISpent, ITransaction, ITXInput,
    //       ITXOutput, IScriptSig, IScriptPubKey, IHistoryFilter, ITXHistory

    // Signing
    SighashType,

    // Utility functions & classes
    ValidationUtils,
    isValidNexaAddress,
    AccountKeysUtils,

    // Enums
    AccountType,
    TxTokenType,

    // Types
    // AccountKeys, AccountIndexes, AddressKey, Balance,
    // WatchOnlyAddress, TxStatus, HodlStatus, TxEntityState,
    // TxTemplateData, TxOptions, TokenAction, PermissionLabel,
    // TransactionEntity
} from 'nexa-wallet-sdk'

Error Handling

try {
    const tx = await wallet.newTransaction(account)
        .sendTo('invalid-address', '1000')
        .populate()
        .sign()
        .build()
} catch (error) {
    if (error.message.includes('Invalid Address')) {
        console.error('Invalid Nexa address provided')
    } else if (error.message.includes('Not enough Nexa balance')) {
        console.error('Insufficient NEXA balance')
    } else if (error.message.includes('Not enough token balance')) {
        console.error('Insufficient token balance')
    } else if (error.message.includes('authority not found')) {
        console.error('Required token authority not found')
    } else {
        console.error('Transaction failed:', error.message)
    }
}

Network Configuration

// Testnet (for development)
const testnetWallet = new Wallet(seedPhrase, 'testnet')

// Mainnet (for production)
const mainnetWallet = new Wallet(seedPhrase, 'mainnet')

// The wallet remembers its network — transactions inherit it automatically
const tx = await wallet.newTransaction(account)
    .sendTo(address, amount)
    .populate()
    .sign()
    .build()

Best Practices

  1. Always Connect First: Call await rostrumProvider.connect() before wallet operations
  2. Network Consistency: Ensure wallet and transaction networks match
  3. Refresh Balances: Call account.loadBalances() after sending transactions to get updated state
  4. Amount Precision: Use strings for amounts to avoid floating-point precision issues
  5. Error Handling: Wrap wallet operations in try-catch blocks
  6. Private Key Security: Never log or expose private keys or seed phrases
  7. Address Validation: Validate addresses before sending transactions
  8. Memory Management: Always unsubscribe from address notifications when done to prevent memory leaks

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For issues and questions: