Actions
Standalone action functions for tree-shakeable imports. These are the same functions available on clients, but usable without a client instance.
Public (Read-Only) Actions
import { getBalance, getNonce, readContract, getBlock, getBlockHeight } from "@secondlayer/stacks/actions";
const balance = await getBalance(client, { address: "SP2J6..." });
const nonce = await getNonce(client, { address: "SP2J6..." });
const height = await getBlockHeight(client);getAccountHistory, getMempoolStats, and getNftHoldings cover paginated tx history, mempool stats, and NFT holdings as first-class, standalone-importable actions.
Node-Only Reads
getContractSource and getRawBlock hit a stacks-node's raw RPC (/v2/contracts/source, /v2/blocks/{height}) — not Hiro's extended API and not a tenant proxy, so client needs a direct node transport. getRawBlock is distinct from getBlock (which reads the indexed extended-API shape): it returns node/consensus fields like index_block_hash and miner_txid. Both return null on a 404 or missing data instead of throwing.
import { getContractSource, getRawBlock } from "@secondlayer/stacks/actions";
const src = await getContractSource(client, { contract: "SP2J6....my-contract" });
const block = await getRawBlock(client, { height: 150_000 });Contract Reads
import { readContract } from "@secondlayer/stacks/actions";
import { Cl } from "@secondlayer/stacks/clarity";
const result = await readContract(client, {
contractAddress: "SP2J6...",
contractName: "my-contract",
functionName: "get-balance",
functionArgs: [Cl.principal("SP3FBR...")],
});Typed Contracts
import { getContract } from "@secondlayer/stacks/actions";
const contract = getContract({
client,
address: "SP2J6...",
name: "my-contract",
abi: MY_ABI,
});
// Type-safe reads, calls, map lookups, and unsigned tx builds
const balance = await contract.read.getBalance({ account: "SP3FBR..." });
const txid = await contract.call.transfer({ to: "SP3FBR...", amount: 100n });
const entry = await contract.maps.tokenBalances("SP3FBR..."); // value or null
// Build an unsigned transaction for a wallet to sign later — never broadcasts.
// publicKey defaults to the client account; fee/nonce are auto-resolved when
// omitted (see ContractBuildCallOptions for fee/nonce/postConditions/sponsored).
const tx = await contract.buildCall.transfer(
{ to: "SP3FBR...", amount: 100n },
{ publicKey },
);ABIs generated by sl codegen contracts are branded with TypedAbi, so all
four namespaces surface the generated named type aliases in hovers and errors;
hand-written as const ABIs get the same API via structural inference.
Wallet Actions
import { sendTransaction, transferStx, callContract } from "@secondlayer/stacks/actions";
const { txid } = await sendTransaction(client, { transaction: signedTx });
const txid = await transferStx(client, {
recipient: "SP2J6...",
amount: 1_000_000n,
});Simulation
import { simulateCall, multicall } from "@secondlayer/stacks/actions";
// Dry-run a contract call
const result = await simulateCall(client, {
contractAddress: "SP2J6...",
contractName: "my-contract",
functionName: "transfer",
functionArgs: [Cl.uint(100)],
sender: "SP3FBR...",
});
// Batch multiple reads
const results = await multicall(client, {
calls: [
{ contractAddress: "SP2J6...", contractName: "token-a", functionName: "get-balance", functionArgs: [Cl.principal("SP3FBR...")] },
{ contractAddress: "SP2J6...", contractName: "token-b", functionName: "get-balance", functionArgs: [Cl.principal("SP3FBR...")] },
],
});