Stacks.js integration with devnet
Build a frontend that interacts with your smart contracts on devnet using the Stacks.js libraries.
What you'll learn
In this guide, you'll learn how to interacts with your smart contracts on devnet using the Stacks.js libraries.
Configure Stacks.js for local devnet connection
Make STX transfers between devnet accounts
Call smart contract functions from JavaScript
Deploy contracts programmatically
Quickstart
Install Stacks.js packages
Add the required Stacks.js packages to your frontend project:
Terminal
$npm install @stacks/transactions @stacks/network
Configure for devnet
Set up a devnet network configuration:
devnet-config.ts
import { StacksDevnet } from '@stacks/network';// Configure for local devnetexport const devnet = new StacksDevnet({url: 'http://localhost:3999'});// Get devnet accounts (matches Clarinet wallets)export const accounts = {deployer: {address: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',key: 'cb3df38053d132895220b9ce471f6b676db5b9bf0b4adefb55f2118ece2478df01'},wallet1: {address: 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5',key: '7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801'}};
Test STX transfers
Create a simple STX transfer between devnet accounts:
stx-transfer.ts
import { makeSTXTokenTransfer, broadcastTransaction, AnchorMode } from '@stacks/transactions';import { devnet, accounts } from './devnet-config';async function transferSTX() {const tx = await makeSTXTokenTransfer({amount: 1000000n, // 1 STX in microSTXrecipient: accounts.wallet1.address,senderKey: accounts.deployer.key,network: devnet,anchorMode: AnchorMode.Any,});const result = await broadcastTransaction(tx, devnet);console.log('Transaction ID:', result.txid);// Transaction will be mined in next devnet blockreturn result.txid;}transferSTX().catch(console.error);
Run the transfer:
Terminal
$ts-node stx-transfer.tsTransaction ID: 0x3d5f8a70c4821e0f8c985b1f5b9d77f8c4b6d1a2e7f9c3b8a4d5e6f1c8d9b7a3
Call smart contracts
Interact with contracts deployed on devnet:
contract-call.ts
import { makeContractCall, broadcastTransaction, AnchorMode, Cl } from '@stacks/transactions';import { devnet, accounts } from './devnet-config';async function callContract() {const tx = await makeContractCall({contractAddress: accounts.deployer.address,contractName: 'counter',functionName: 'increment',functionArgs: [],senderKey: accounts.wallet1.key,network: devnet,anchorMode: AnchorMode.Any,});const result = await broadcastTransaction(tx, devnet);console.log('Contract call ID:', result.txid);return result.txid;}// Read contract stateasync function readCount() {const response = await fetch(`${devnet.coreApiUrl}/v2/contracts/call-read/${accounts.deployer.address}/counter/get-count`,{method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({sender: accounts.wallet1.address,arguments: []})});const data = await response.json();console.log('Current count:', data.result);}callContract().then(() => readCount()).catch(console.error);
Deploy contracts from frontend
Deploy new contracts to devnet programmatically:
deploy-contract.ts
import { makeContractDeploy, broadcastTransaction, AnchorMode } from '@stacks/transactions';import { devnet, accounts } from './devnet-config';async function deployContract() {const contractCode = `(define-data-var count uint u0)(define-public (increment)(ok (var-set count (+ (var-get count) u1))))(define-read-only (get-count)(var-get count))`;const tx = await makeContractDeploy({contractName: 'my-counter',codeBody: contractCode,senderKey: accounts.deployer.key,network: devnet,anchorMode: AnchorMode.Any,});const result = await broadcastTransaction(tx, devnet);console.log('Contract deployed:', result.txid);return result.txid;}deployContract().catch(console.error);
Common patterns
Watching for transaction confirmation
Monitor when transactions are confirmed on devnet:
transaction-monitor.ts
async function waitForTransaction(txid: string) {let attempts = 0;const maxAttempts = 10;while (attempts < maxAttempts) {const response = await fetch(`${devnet.coreApiUrl}/extended/v1/tx/${txid}`);const tx = await response.json();if (tx.tx_status === 'success') {console.log('Transaction confirmed!');return tx;}if (tx.tx_status === 'abort_by_response') {throw new Error(`Transaction failed: ${tx.tx_result.repr}`);}// Wait for next blockawait new Promise(resolve => setTimeout(resolve, 5000));attempts++;}throw new Error('Transaction timeout');}
Post conditions for safety
Add post conditions to ensure contract calls behave as expected:
safe-transfer.ts
import { Pc } from '@stacks/transactions';const postConditions = [// Ensure sender's balance decreases by exactly the amountPc.principal(accounts.wallet1.address).willSendEq(1000000n).ustx(),// Ensure recipient receives the amountPc.principal(accounts.deployer.address).willReceiveEq(1000000n).ustx()];const tx = await makeSTXTokenTransfer({amount: 1000000n,recipient: accounts.deployer.address,senderKey: accounts.wallet1.key,network: devnet,postConditions,anchorMode: AnchorMode.Any,});
Next steps
- Learn about transaction signing
- Explore post conditions for safer transactions
- Set up Chainhook integration for event monitoring