Skip to main content

Learning the Stacks.js Basics

Looking to build a Stacks-ready web application? Read more on Stacks Connect

There are two main ways developers build applications on the Stacks blockchain:

🔒 Without Direct Private Key Access

For example, a web app that allows users to interact with the Stacks blockchain using their Stacks wallet (browser extension or mobile).

Most users interact via their favorite Stacks wallet. Developers can build web apps, which prompt the user for an action (e.g. sign a transaction), and then the wallet will handle the rest. The wallet will act in the security, and best interest of the user, and the user will be able to review the transaction before signing. Read more

🔑 With Private Key Access

For example, managing funds with the Stacks.js CLI, building a backend (which can sign transactions directly).

Nevertheless, direct private key access is needed for some use cases. Developers can build simple scripts and tools intended for "offline" use. Users may use the Stacks.js CLI directly to send a transaction. Backends may need to automate signing without direct user interaction. In these cases, developers can use the same libraries used by Stacks wallets for account handling and transaction signing. Read more

Concepts

To introduce the different functionality offered by Stacks.js, we'll walk through a few examples and concepts important to building on the Stacks blockchain.

Networks

Typically, we speak of "mainnet" and "testnet" as the networks of Stacks. Most wallets will be configured to "mainnet" by default, this is the actual blockchain that holds real STX tokens. As the name suggests, "testnet" is a network for testing. It's a separate blockchain state that holds test tokens, which have no value.

Developers are encouraged to use testnet for testing before rolling out applications and contracts to mainnet. There is even Devnet/Mocknet for working in a local development environment for development. Stacks.js functions can be configured to use whichever network you want.

import { StacksMainnet, StacksTestnet } from '@stacks/network';
const mainnet = new StacksMainnet();
const testnet = new StacksTestnet();

The constructors can also be passed a custom URL to an API, if you want to use a different API than the default.

import { StacksMainnet } from '@stacks/network';
const network = new StacksMainnet({ url: 'https://www.mystacksnode.com/' });

Accounts and Addresses

Stacks uses the concept of an "account" to represent a user's identity on the blockchain. An account is identified by a unique address. The address is derived from the account's public key, which is derived from the account's private key.

A normal mainnet address starts with SP, and a testnet address starts with ST. e.g. SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159, ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ

import { showConnect } from '@stacks/connect';

showConnect({
appDetails,
userSession,
onFinish: () => {
console.log(userSession.loadUserData().profile.stxAddress.mainnet);
},
});

Transactions

The following shows how to create a simple transaction (STX transfer) using Stacks.js in different environments.

import { openSTXTransfer } from '@stacks/connect';
import { StacksTestnet } from '@stacks/network';
import { AnchorMode } from '@stacks/transactions';

openSTXTransfer({
network: new StacksTestnet(),

recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address we are sending to
amount: 10000, // tokens, denominated in micro-STX
anchorMode: AnchorMode.Any,

onFinish: response => console.log(response.txid),
onCancel: () => console.log('User canceled'),
});

Anchor Mode / Block Type

In the examples above, we used AnchorMode.Any to indicate that the transaction can be "mined" in different ways. Stacks has two types of blocks: microblocks and (anchor) blocks.

  • Microblocks (off-chain) are faster, but less reliable. Microblocks can be confirmed quickly but are not final until the microblock is included in an anchor block.
  • Anchor Blocks (on-chain) are the normal Stacks blocks. They are slower, but more reliable. Anchor blocks are final and cannot be reverted.
// AnchorMode options
anchorMode: "offChainOnly" | "onChainOnly" | "any",

Post-Conditions

In Stacks, transactions can have "post-conditions". These are additional security to ensure the transaction was executed as expected.

More precisely, adding post-conditions to a transaction can ensure that:

  • STX tokens were transferred from an address
  • FTs/NFTs we transferred from an address
caution

Post-conditions aren't perfect. They can't say anything about the end-state after a transaction. So, they can't guarantee the receival of FTs/NFTs, since they only check for sending.

An example adding a post-condition (of an address sending 1000 uSTX).

import { Pc } from '@stacks/transactions';

const tx = await makeContractCall({
// ...
postConditions: [
Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6').willSendEq(1000).ustx(),
],
});

Post-Condition Mode

...aka "allow transfer of unspecified assets?"

In addition to the post-conditions itself, we can also specify a "mode" for the transaction to verify asset transfers. The mode can be either Allow or Deny.

  • Allow means that the transaction can transfer any asset (assuming no conflicting post-conditions).
  • Deny means the transaction will fail if any asset transfers (not specified in the post-conditions) are attempted.
note

In either case, all post-conditions will still be checked. By default, transactions are set to Deny mode for additional security.

Broadcasting

Connect

For web applications, the users' wallet can broadcast transactions created via Stacks Connect. Read more

A finalized transaction can be broadcasted to the network or serialized (to a byte representation) using Stacks.js.

import { bytesToHex } from '@stacks/common';
import { makeSTXTokenTransfer, broadcastTransaction, AnchorMode } from '@stacks/transactions';

const broadcastResponse = await broadcastTransaction(transaction);
const txId = broadcastResponse.txid;

const serializedTx = tx.serialize(); // Uint8Array
const serializedTxHex = bytesToHex(serializedTx); // hex string