Chain / Stacks SDK

Stacks SDK

Build, sign, and broadcast Stacks transactions from TypeScript. Where @secondlayer/sdk reads decoded chain data, @secondlayer/stacks writes to the chain.

bun add @secondlayer/stacks
import { createWalletClient, http, mainnet } from "@secondlayer/stacks";
import { privateKeyToAccount } from "@secondlayer/stacks/accounts";
import { Cl } from "@secondlayer/stacks/clarity";

const client = createWalletClient({
  account: privateKeyToAccount(process.env.KEY!),
  chain: mainnet,
  transport: http(),
});

const txid = await client.callContract({
  contractAddress: "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9",
  contractName: "usda-token",
  functionName: "transfer",
  functionArgs: [
    Cl.uint(1_000_000),
    Cl.principal(client.account.address),
    Cl.principal("SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7"),
    Cl.none(),
  ],
});

Fees estimated, nonces managed.

Same getContract client against an in-process Clarinet VM (@stacks/clarinet-sdk is an optional peer of @secondlayer/stacks/simnet).

import { initSimnet } from "@stacks/clarinet-sdk";
import { createPublicClient } from "@secondlayer/stacks";
import { getContract } from "@secondlayer/stacks/actions";
import { SIP010_ABI } from "@secondlayer/stacks/clarity";
import { simnet, simnetChain } from "@secondlayer/stacks/simnet";

const session = await initSimnet("./Clarinet.toml");
const client = createPublicClient({
  chain: simnetChain,
  transport: simnet(session),
});
const deployer = session.getAccounts().get("deployer")!;
const token = getContract({
  client,
  address: deployer,
  name: "usda-token",
  abi: SIP010_ABI,
});
await token.read.getBalance({ account: deployer });
LimitBehavior
/extendednot served
watchesthrow SimnetUnsupportedError
feesNoEstimateAvailable; wallet actions use 'min'

On-chain assertions the network enforces: move more than you allowed and the transaction aborts instead of settling. Attach postConditions to any callContract, transferStx, or deployContract call (mode defaults to deny).

import { Pc } from "@secondlayer/stacks/postconditions";

const txid = await client.callContract({
  // …call as above
  postConditions: [
    Pc.origin()
      .willSendLte(1_000_000)
      .ft("SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.usda-token", "usda"),
  ],
});

Or pass plain objects, the same shape stacks.js uses, portable between SDKs:

postConditions: [
  {
    type: "stx-postcondition",
    address: "origin",
    condition: "lte",
    amount: 1_000_000,
  },
]
TypeProtectsCondition
stx-postconditionSTX movementeq, gt, gte, lt, lte + amount
ft-postconditionSIP-010 token movementsame, scoped to asset
nft-postconditionSIP-009 asset movementsent, not-sent + assetId
staking-postconditionHow much STX a tx may stake or re-parameterize (SIP-045): stake, register-for-bond, stake-updateeqlte + amount
pox-postconditionNon-locking PoX state changes (SIP-045): unstake, announce-l1-early-exit, bond-registration updateswill-not-perform, may-perform, will-perform

SIP-045 staking post-conditions

Bound a pox-5 call from both sides:

const txid = await client.callContract({
  contractAddress: "SP000000000000000000002Q6VF78",
  contractName: "pox-5",
  functionName: "register-for-bond",
  functionArgs: [/* … */],
  postConditions: [
    {
      type: "staking-postcondition",
      address: "origin",
      condition: "lte",
      amount: 500_000_000_000n,
    },
    {
      type: "pox-postcondition",
      address: "origin",
      condition: "will-not-perform",
    },
  ],
});

Activates at Epoch 4.0

Wire format ships in @secondlayer/stacks@2.10.0; the network accepts these only after SIP-045 activates.

Decoding is strict: deserializing a transaction with an unknown post-condition type throws instead of silently misreading the bytes after it. Your indexer sees an error, never corrupt data.

The pox-5 calls these guard have their own typed surface; see PoX-5 Bitcoin Staking.

  • Typed contracts: getContract() binds an ABI to a client for typed reads (read.*), broadcasts (call.*), unsigned wallet-signs-later transactions (buildCall.*), and map lookups (maps.*); generate branded ABIs with secondlayer codegen contracts for named-alias hovers.
  • Clarity values: Cl.* constructors and decoding via @secondlayer/stacks/clarity.
  • Bitcoin SPV: merkle proofs and Bitcoin payment verification, see Bitcoin SPV.
  • PoX-5 staking: SIP-045 bonds, staking, L1 lockup scripts, signer grants, see PoX-5 Bitcoin Staking.
  • Accounts: privateKeyToAccount, mnemonicToAccount, WalletConnect providers via @secondlayer/stacks/accounts and @secondlayer/stacks/connect.
  • Multi-sig: createMultiSigClient for m-of-n signing flows.