A viem-style SDK for Stacks. One package, zero polyfills, full tree-shaking. 23.8 KB gzipped with 6 runtime dependencies — 11.6x smaller than stacks.js.

Install with bun add @secondlayer/stacks.


Getting started

Create a public client for read-only operations, or a wallet client for signing transactions. Install with bun add @secondlayer/stacks.

import { createPublicClient, createWalletClient, http } from "@secondlayer/stacks"
import { mainnet } from "@secondlayer/stacks/chains"
import { privateKeyToAccount } from "@secondlayer/stacks/accounts"

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
})

const wallet = createWalletClient({
  chain: mainnet,
  transport: http(),
  account: privateKeyToAccount("..."),
})

Clients

Three client types cover every use case. createPublicClient for reads, createWalletClient for signing, and createMultiSigClient for multi-signature transactions. Clients are extensible with .extend() for BNS, PoX, and StackingDAO modules.

Transports are composable: http(), webSocket(), custom(), and fallback() for automatic failover.

import { createPublicClient, http, webSocket, fallback } from "@secondlayer/stacks"
import { mainnet } from "@secondlayer/stacks/chains"

// Fallback transport — tries HTTP first, falls back to WebSocket
const publicClient = createPublicClient({
  chain: mainnet,
  transport: fallback([
    http(),
    webSocket(),
  ]),
})

// Read-only actions
const balance = await publicClient.getBalance("SP1234...")
const height = await publicClient.getBlockHeight()
const nonce = await publicClient.getNonce("SP1234...")
const fee = await publicClient.estimateFee({ ... })

Contracts

Read and call Clarity contracts with full type safety. Method names are automatically converted from kebab-case to camelCase.

import { readContract, callContract } from "@secondlayer/stacks/actions"

// Read-only call
const balance = await readContract(publicClient, {
  contract: "SP2C2YFP12AJZB1MAERTSVAR6NQJHQ5MEH0GH33C.usda-token",
  method: "getBalance",   // auto-camelCased from get-balance
  args: { owner: "SP1234..." },
})

// State-changing call
const txId = await callContract(wallet, {
  contract: "SP2C2YFP12AJZB1MAERTSVAR6NQJHQ5MEH0GH33C.usda-token",
  method: "transfer",
  args: { amount: 1000n, sender: "SP1234...", recipient: "SP5678..." },
})

// Simulate without broadcasting
const result = await simulateCall(publicClient, {
  contract: "SP2C2YFP12AJZB1MAERTSVAR6NQJHQ5MEH0GH33C.usda-token",
  method: "getBalance",
  args: { owner: "SP1234..." },
})

// Batch multiple reads in a single call
const results = await multicall(publicClient, [
  { contract: "SP...::token-a", method: "getBalance", args: { owner: "SP..." } },
  { contract: "SP...::token-b", method: "getBalance", args: { owner: "SP..." } },
])

Transfers

STX transfers with post-conditions for safety.

import { transferStx } from "@secondlayer/stacks/actions"
import { Pc } from "@secondlayer/stacks/postconditions"

const txId = await transferStx(wallet, {
  recipient: "SP5678...",
  amount: 1_000_000n, // 1 STX in microSTX
  memo: "payment",
  postConditions: [
    Pc.principal("SP1234...").willSendLte(1_000_000n).ustx(),
  ],
})

Subscriptions

Subscribe to real-time blockchain events over WebSocket.

import { watchBlocks, watchMempool, watchTransaction,
  watchAddress, watchAddressBalance } from "@secondlayer/stacks/subscriptions"

const unwatch = watchBlocks(publicClient, {
  onBlock: (block) => console.log("New block:", block.height),
})

const unwatchMempool = watchMempool(publicClient, {
  onTransaction: (tx) => console.log("Pending:", tx.txId),
})

// Watch a specific transaction until confirmed
watchTransaction(publicClient, {
  txId: "0x...",
  onStatusChange: (status) => console.log("Status:", status),
})

// Watch an address for activity
watchAddress(publicClient, {
  address: "SP1234...",
  onTransaction: (tx) => console.log("Activity:", tx),
})

BNS

Bitcoin Name System — register and resolve .btc names.

import { resolveName, registerName } from "@secondlayer/stacks/bns"

const address = await resolveName(publicClient, { name: "alice.btc" })

const txId = await registerName(wallet, {
  name: "myname",
  namespace: "btc",
  zonefile: "...",
})

Props

Clients
chainChainrequired
transportTransportrequired
accountAccountwallet client only
Chains
mainnetChainchain ID 0x00000001
testnetChainchain ID 0x80000000
devnetChainlocalhost:3999
Accounts
privateKeyToAccount(key: string) → Account
mnemonicToAccount(mnemonic: string) → Account
providerToAccount(provider) → Account
Transports
http(url?) → Transportchain RPC
webSocket(url?) → Transport
fallback(transports[]) → Transport
custom(handler) → Transport
Modules
actionsreadContract, callContract, transferStx, deployContract, multicall, simulateCall, getContract, getMapEntry, getContractAbi, getBalance, getBlockHeight, getBlock, getNonce, estimateFee, getAccountInfo
transactionsbuildTokenTransfer, buildContractCall, buildContractDeploy, signTransaction, signMessage, sponsorTransaction
subscriptionswatchBlocks, watchMempool, watchTransaction, watchAddress, watchAddressBalance, watchNftEvent
postconditionsPc fluent builder for post-conditions
clarityCl constructors, serialize/deserialize, ABI types
bnsresolveName, registerName
poxstackStx, delegateStx
stackingdaoStackingDAO liquid staking
connectWallet connection, WalletConnect v2
utilsformatStx, parseStx, address validation, encoding