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
- Using Connect
- Using a mnemonic
import { showConnect } from '@stacks/connect';
showConnect({
appDetails,
userSession,
onFinish: () => {
console.log(userSession.loadUserData().profile.stxAddress.mainnet);
},
});
import { TransactionVersion } from "@stacks/transactions";
import { generateSecretKey, generateWallet, getStxAddress } from "@stacks/wallet-sdk";
const mnemonic = generateSecretKey();
// aunt birth lounge misery utility blind holiday walnut fuel make gift parent gap picnic exact various express sphere family nerve oil drill engage youth
const wallet = await generateWallet({
secretKey: mnemonic, // the same mnemonic will always generate the same wallet accounts
password: 'optional-password', // if you want to encrypt the wallet
});
const account = wallet.accounts[0]; // first account in the wallet
const mainnetAddress = getStxAddress({ account, transactionVersion: TransactionVersion.Mainnet });
// SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF
Transactions​
The following shows how to create a simple transaction (STX transfer) using Stacks.js in different environments.
- Using Connect
- Using a private key
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'),
});
For full manual transaction signing, we need to provide the sender's private key. Treat the private key as a secret and never expose it to the public!
import { makeSTXTokenTransfer } from '@stacks/transactions';
const tx = await makeSTXTokenTransfer({
recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address we are sending to
amount: 10000, // tokens, denominated in micro-STX
anchorMode: 'any',
senderKey: 'c3a2d3...0b1c2', // private key (typically derived from a mnemonic)
});
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
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.
In either case, all post-conditions will still be checked.
By default, transactions are set to Deny
mode for additional security.
Broadcasting​
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