@stacks/transactions
Construct, decode transactions and work with Clarity smart contracts on the Stacks blockchain.
Info
This reference refers to the 7.x.x
release of Stacks.js—it's the recommended version to use, but not needed for the Stacks Nakamoto release.
Read the migration guide to learn how to update to the latest version.
Installation
$npm install @stacks/transactions
This library supports the creation of the following Stacks transaction types:
- 1STX token transfer
- 2Smart contract deploy
- 3Smart contract function call
Key Generation
1import { randomPrivateKey, privateKeyToPublicKey } from '@stacks/transactions';23// Random key4const privateKey = randomPrivateKey();5const publicKey = privateKeyToPublicKey(privateKey);67// Private key from hex string8const privateKey = 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01';
STX Token Transfer Transaction
Use the makeSTXTokenTransfer
(with a sender private key), or the "unsigned" version makeUnsignedSTXTokenTransfer
(with a sender public key, without yet signing) to create a STX token transfer transaction.
1import { makeSTXTokenTransfer, broadcastTransaction } from '@stacks/transactions';23const txOptions = {4recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159',5amount: 12345n,6memo: 'test memo',78senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',9network: 'testnet', // for mainnet, use 'mainnet'10};1112const transaction = await makeSTXTokenTransfer(txOptions);1314// to see the raw serialized tx15const serializedTx = transaction.serialize(); // hex string1617// broadcast to the network18const response = await broadcastTransaction({ transaction, network: 'testnet' });19console.log(response.txid);
Smart Contract Deploy Transaction
Use the makeContractDeploy
, or the "unsigned" version makeUnsignedContractDeploy
to create a smart contract deploy transaction.
1import { makeContractDeploy, broadcastTransaction } from '@stacks/transactions';2import { readFileSync } from 'fs';34const clarityCode = readFileSync('/path/to/contract.clar').toString();56const txOptions = {7contractName: 'contract_name',8codeBody: clarityCode,9clarityVersion: 3, // optional, defaults to latest deployed version1011senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',12network: 'testnet', // for mainnet, use 'mainnet'13};1415const transaction = await makeContractDeploy(txOptions);1617// broadcast to the network18const response = await broadcastTransaction({ transaction, network: 'testnet' });19console.log(response.txid);
Smart Contract Function Call (On-chain)
If you want to call a readonly function, use fetchCallReadOnlyFunction
instead.
Use the makeContractCall
or the "unsigned" version makeUnsignedContractCall
to create a smart contract function call transaction.
1import { makeContractCall, broadcastTransaction, Cl, Pc } from '@stacks/transactions';23// Add an optional post condition4const condition01 = Pc.principal('SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE')5.willSendGte(1000000n)6.ustx();78const txOptions = {9contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X',10contractName: 'contract_name',11functionName: 'contract_function',12functionArgs: [Cl.bufferFromUtf8('foo string')],13postConditions: [condition01],1415validateWithAbi: true, // check if the arguments given are compatible with the function1617senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',18network: 'testnet', // for mainnet, use 'mainnet'19};2021const transaction = await makeContractCall(txOptions);2223// broadcast to the network24const response = await broadcastTransaction({ transaction, network: 'testnet' });25console.log(response.txid);
In this example we construct a contract-call
transaction with a post condition. We have set the validateWithAbi
option to true
, so the makeContractCall
builder will attempt to fetch this contracts ABI from the specified Stacks network, and validate that the provided functionArgs match what is described in the ABI. This should help you avoid constructing invalid contract-call transactions. If you would prefer to provide your own ABI instead of fetching it from the network, the validateWithAbi
option also accepts ClarityABI objects.
Sponsoring Transactions
To generate a sponsored transaction, first create and sign the transaction as the origin. The sponsored
property in the options object must be set to true.
1import { makeContractCall, Cl } from '@stacks/transactions';23const txOptions = {4contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X',5contractName: 'contract_name',6functionName: 'contract_function',7functionArgs: [Cl.bufferFromUtf8('foo string')],8validateWithAbi: true,910senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',11network: 'testnet', // for mainnet, use 'mainnet'12sponsored: true,13};1415const transaction = await makeContractCall(txOptions);16const serializedTx = transaction.serialize();1718// can't broadcast sponsored transactions before they are sponsored
The serialized transaction can now be passed to the sponsoring party which will sign the sponsor portion of the transaction and set the fee.
1import { sponsorTransaction, broadcastTransaction } from '@stacks/transactions';23const deserializedTx = deserializeTransaction(serializedTx);4const sponsorKey = '770287b9471081c8acd37d57190c7a70f0da2633311cc120853537362d32e67c01';5const fee = 1000n;67const sponsorOptions = {8transaction: deserializedTx,9sponsorPrivateKey: sponsorKey,10sponsorNonce: 0,11fee,12};1314const sponsoredTx = await sponsorTransaction(sponsorOptions);1516// broadcast to the network17const response = await broadcastTransaction({ transaction: sponsoredTx, network: 'testnet' });18console.log(response.txid);
Supporting multi-signature transactions
To generate a multi-sig transaction, first create an unsigned transaction.
The numSignatures
and publicKeys
properties in the options object must be set:
1import {2makeUnsignedSTXTokenTransfer,3createStacksPrivateKey,4deserializeTransaction,5pubKeyfromPrivKey,6publicKeyToString,7TransactionSigner,8standardPrincipalCV,9BytesReader,10AnchorMode,11} from '@stacks/transactions';1213const recipient = standardPrincipalCV('SP3FGQ8...');14const amount = 2500000n;15const fee = 0n;16const memo = 'test memo';1718// private keys of the participants in the transaction19const privKeyStrings = ['6d430bb9...', '2a584d89...', 'd5200dee...'];2021// create private key objects from string array22const privKeys = privKeyStrings.map(createStacksPrivateKey);2324// corresponding public keys25const pubKeys = privKeyStrings.map(pubKeyfromPrivKey);2627// create public key string array from objects28const pubKeyStrings = pubKeys.map(publicKeyToString);2930const transaction = await makeUnsignedSTXTokenTransfer({31recipient,32amount,33fee,34memo,35numSignatures: 2, // number of signature required36publicKeys: pubKeyStrings, // public key string array with >= numSignatures elements37anchorMode: AnchorMode.Any,38});3940const serializedTx = transaction.serialize();
This transaction payload can be passed along to other participants to sign. In addition to meeting the numSignatures requirement, the public keys of the parties who did not sign the transaction must be appended to the signature.
1// deserialize and sign transaction2const bytesReader = new BytesReader(serializedTx);3// Partially signed or unsigned multi-sig tx can be deserialized to add the required signatures4const deserializedTx = deserializeTransaction(bytesReader);56const signer = new TransactionSigner(deserializedTx);78// first signature9signer.signOrigin(privKeys[0]);1011// second signature12signer.signOrigin(privKeys[1]);1314// after meeting the numSignatures requirement, the public15// keys of the participants who did not sign must be appended16signer.appendOrigin(pubKeys[2]);1718// the serialized multi-sig tx19const serializedSignedTx = deserializedTx.serialize();
Calling Read-only Contract Functions
Read-only contract functions can be called without generating or broadcasting a transaction. Instead it works via a direct API call to a Stacks node.
1import { bufferCVFromString, callReadOnlyFunction } from '@stacks/transactions';2import { StacksTestnet } from '@stacks/network';34const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE';5const contractName = 'kv-store';6const functionName = 'get-value';7const buffer = bufferCVFromString('foo');8const network = new StacksTestnet();9const senderAddress = 'ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ';1011const options = {12contractAddress,13contractName,14functionName,15functionArgs: [buffer],16network,17senderAddress,18};1920const result = await callReadOnlyFunction(options);
Constructing Clarity Values
Building transactions that call functions in deployed clarity contracts requires you to construct valid Clarity Values to pass to the function as arguments. The Clarity type system contains the following types:
(tuple (key-name-0 key-type-0) (key-name-1 key-type-1) ...)
: a typed tuple with named fields.(list max-len entry-type)
: a list of maximum length max-len, with entries of type entry-type(response ok-type err-type)
: object used by public functions to commit their changes or abort. May be returned or used by other functions as well, however, only public functions have the commit/abort behavior.(optional some-type)
: an option type for objects that can either be (some value) or none(buff max-len)
: byte buffer or maximum length max-len.principal
: object representing a principal (whether a contract principal or standard principal).bool
: boolean value (true
orfalse
)int
: signed 128-bit integeruint
: unsigned 128-bit integer
This library contains Typescript types and classes that map to the Clarity types, in order to make it easy to construct well-typed Clarity values in Javascript. These types all extend the abstract class ClarityValue
.
1import {2trueCV,3falseCV,4noneCV,5someCV,6intCV,7uintCV,8standardPrincipalCV,9contractPrincipalCV,10responseErrorCV,11responseOkCV,12listCV,13tupleCV,14bufferCV,15} from '@stacks/transactions';16import { utf8ToBytes } from '@stacks/common';1718// construct boolean clarity values19const t = trueCV();20const f = falseCV();2122// construct optional clarity values23const nothing = noneCV();24const something = someCV(t);2526// construct a buffer clarity value from an existing byte array27const bytes = utf8ToBytes('foo'); // Uint8Array(3) [ 102, 111, 111 ]28const bufCV = bufferCV(bytes);2930// construct signed and unsigned integer clarity values31const i = intCV(-10);32const u = uintCV(10);3334// construct principal clarity values35const address = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B';36const contractName = 'contract-name';37const spCV = standardPrincipalCV(address);38const cpCV = contractPrincipalCV(address, contractName);3940// construct response clarity values41const errCV = responseErrorCV(trueCV());42const okCV = responseOkCV(falseCV());4344// construct tuple clarity values45const tupCV = tupleCV({46a: intCV(1),47b: trueCV(),48c: falseCV(),49});5051// construct list clarity values52const l = listCV([trueCV(), falseCV()]);
If you develop in Typescript, the type checker can help prevent you from creating wrongly-typed Clarity values. For example, the following code won't compile since in Clarity lists are homogeneous, meaning they can only contain values of a single type. It is important to include the type variable BooleanCV
in this example, otherwise the typescript type checker won't know which type the list is of and won't enforce homogeneity.
1const l = listCV<BooleanCV>([trueCV(), intCV(1)]);
Post Conditions
Three types of post conditions can be added to transactions:
- 1STX post condition
- 2Fungible token post condition
- 3Non-Fungible token post condition
For details see: https://github.com/stacksgov/sips/blob/main/sips/sip-005/sip-005-blocks-and-transactions.md#transaction-post-conditions
STX post condition
1import {2FungibleConditionCode,3makeStandardSTXPostCondition,4makeContractSTXPostCondition,5} from '@stacks/transactions';67// With a standard principal8const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';9const postConditionCode = FungibleConditionCode.GreaterEqual;10const postConditionAmount = 12345n;1112const standardSTXPostCondition = makeStandardSTXPostCondition(13postConditionAddress,14postConditionCode,15postConditionAmount16);1718// With a contract principal19const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';20const contractName = 'test-contract';2122const contractSTXPostCondition = makeContractSTXPostCondition(23contractAddress,24contractName,25postConditionCode,26postConditionAmount27);
Fungible token post condition
1import {2FungibleConditionCode,3createAssetInfo,4makeStandardFungiblePostCondition,5} from '@stacks/transactions';67// With a standard principal8const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';9const postConditionCode = FungibleConditionCode.GreaterEqual;10const postConditionAmount = 12345n;11const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';12const assetContractName = 'test-asset-contract';13const assetName = 'test-token';14const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);1516const standardFungiblePostCondition = makeStandardFungiblePostCondition(17postConditionAddress,18postConditionCode,19postConditionAmount,20fungibleAssetInfo21);2223// With a contract principal24const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';25const contractName = 'test-contract';26const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';27const assetContractName = 'test-asset-contract';28const assetName = 'test-token';29const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);3031const contractFungiblePostCondition = makeContractFungiblePostCondition(32contractAddress,33contractName,34postConditionCode,35postConditionAmount,36fungibleAssetInfo37);
Non-fungible token post condition
Warning The Stacks blockchain's post-condition processor can NOT check ownership. It checks whether or not a principal will send or will not send an NFT. Post-conditions can NOT verify anything about the recipient of an asset. If you want to verify conditions about asset recipients, you will need to use Clarity.
1import {2NonFungibleConditionCode,3createAssetInfo,4makeStandardNonFungiblePostCondition,5makeContractNonFungiblePostCondition,6bufferCVFromString,7} from '@stacks/transactions';89// With a standard principal10const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';11const postConditionCode = NonFungibleConditionCode.DoesNotSend;12const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';13const assetContractName = 'test-asset-contract';14const assetName = 'test-asset';15const assetId = bufferCVFromString('test-token-asset-id');16const nonFungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);1718const standardNonFungiblePostCondition = makeStandardNonFungiblePostCondition(19postConditionAddress,20postConditionCode,21nonFungibleAssetInfo,22assetId23);2425// With a contract principal26const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';27const contractName = 'test-contract';2829const contractNonFungiblePostCondition = makeContractNonFungiblePostCondition(30contractAddress,31contractName,32postConditionCode,33nonFungibleAssetInfo,34assetId35);
Conversion of Clarity Values to JSON
Clarity Values represent values of Clarity contracts. If a JSON format is required the helper function cvToJSON
can be used.
1import { cvToJSON, hexToCV } from '@stacks/transactions';23cvToJSON(hexToCV(tx.tx_result.hex));