@stacks/transactions

Construct, decode transactions and work with Clarity smart contracts on the Stacks blockchain.


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

Terminal
$
npm install @stacks/transactions

This library supports the creation of the following Stacks transaction types:

  1. 1STX token transfer
  2. 2Smart contract deploy
  3. 3Smart contract function call

Key Generation

1
import { randomPrivateKey, privateKeyToPublicKey } from '@stacks/transactions';
2
3
// Random key
4
const privateKey = randomPrivateKey();
5
const publicKey = privateKeyToPublicKey(privateKey);
6
7
// Private key from hex string
8
const 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.

1
import { makeSTXTokenTransfer, broadcastTransaction } from '@stacks/transactions';
2
3
const txOptions = {
4
recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159',
5
amount: 12345n,
6
memo: 'test memo',
7
8
senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',
9
network: 'testnet', // for mainnet, use 'mainnet'
10
};
11
12
const transaction = await makeSTXTokenTransfer(txOptions);
13
14
// to see the raw serialized tx
15
const serializedTx = transaction.serialize(); // hex string
16
17
// broadcast to the network
18
const response = await broadcastTransaction({ transaction, network: 'testnet' });
19
console.log(response.txid);

Smart Contract Deploy Transaction

Use the makeContractDeploy, or the "unsigned" version makeUnsignedContractDeploy to create a smart contract deploy transaction.

1
import { makeContractDeploy, broadcastTransaction } from '@stacks/transactions';
2
import { readFileSync } from 'fs';
3
4
const clarityCode = readFileSync('/path/to/contract.clar').toString();
5
6
const txOptions = {
7
contractName: 'contract_name',
8
codeBody: clarityCode,
9
clarityVersion: 3, // optional, defaults to latest deployed version
10
11
senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',
12
network: 'testnet', // for mainnet, use 'mainnet'
13
};
14
15
const transaction = await makeContractDeploy(txOptions);
16
17
// broadcast to the network
18
const response = await broadcastTransaction({ transaction, network: 'testnet' });
19
console.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.

1
import { makeContractCall, broadcastTransaction, Cl, Pc } from '@stacks/transactions';
2
3
// Add an optional post condition
4
const condition01 = Pc.principal('SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE')
5
.willSendGte(1000000n)
6
.ustx();
7
8
const txOptions = {
9
contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X',
10
contractName: 'contract_name',
11
functionName: 'contract_function',
12
functionArgs: [Cl.bufferFromUtf8('foo string')],
13
postConditions: [condition01],
14
15
validateWithAbi: true, // check if the arguments given are compatible with the function
16
17
senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',
18
network: 'testnet', // for mainnet, use 'mainnet'
19
};
20
21
const transaction = await makeContractCall(txOptions);
22
23
// broadcast to the network
24
const response = await broadcastTransaction({ transaction, network: 'testnet' });
25
console.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.

1
import { makeContractCall, Cl } from '@stacks/transactions';
2
3
const txOptions = {
4
contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X',
5
contractName: 'contract_name',
6
functionName: 'contract_function',
7
functionArgs: [Cl.bufferFromUtf8('foo string')],
8
validateWithAbi: true,
9
10
senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01',
11
network: 'testnet', // for mainnet, use 'mainnet'
12
sponsored: true,
13
};
14
15
const transaction = await makeContractCall(txOptions);
16
const serializedTx = transaction.serialize();
17
18
// 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.

1
import { sponsorTransaction, broadcastTransaction } from '@stacks/transactions';
2
3
const deserializedTx = deserializeTransaction(serializedTx);
4
const sponsorKey = '770287b9471081c8acd37d57190c7a70f0da2633311cc120853537362d32e67c01';
5
const fee = 1000n;
6
7
const sponsorOptions = {
8
transaction: deserializedTx,
9
sponsorPrivateKey: sponsorKey,
10
sponsorNonce: 0,
11
fee,
12
};
13
14
const sponsoredTx = await sponsorTransaction(sponsorOptions);
15
16
// broadcast to the network
17
const response = await broadcastTransaction({ transaction: sponsoredTx, network: 'testnet' });
18
console.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:

1
import {
2
makeUnsignedSTXTokenTransfer,
3
createStacksPrivateKey,
4
deserializeTransaction,
5
pubKeyfromPrivKey,
6
publicKeyToString,
7
TransactionSigner,
8
standardPrincipalCV,
9
BytesReader,
10
AnchorMode,
11
} from '@stacks/transactions';
12
13
const recipient = standardPrincipalCV('SP3FGQ8...');
14
const amount = 2500000n;
15
const fee = 0n;
16
const memo = 'test memo';
17
18
// private keys of the participants in the transaction
19
const privKeyStrings = ['6d430bb9...', '2a584d89...', 'd5200dee...'];
20
21
// create private key objects from string array
22
const privKeys = privKeyStrings.map(createStacksPrivateKey);
23
24
// corresponding public keys
25
const pubKeys = privKeyStrings.map(pubKeyfromPrivKey);
26
27
// create public key string array from objects
28
const pubKeyStrings = pubKeys.map(publicKeyToString);
29
30
const transaction = await makeUnsignedSTXTokenTransfer({
31
recipient,
32
amount,
33
fee,
34
memo,
35
numSignatures: 2, // number of signature required
36
publicKeys: pubKeyStrings, // public key string array with >= numSignatures elements
37
anchorMode: AnchorMode.Any,
38
});
39
40
const 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 transaction
2
const bytesReader = new BytesReader(serializedTx);
3
// Partially signed or unsigned multi-sig tx can be deserialized to add the required signatures
4
const deserializedTx = deserializeTransaction(bytesReader);
5
6
const signer = new TransactionSigner(deserializedTx);
7
8
// first signature
9
signer.signOrigin(privKeys[0]);
10
11
// second signature
12
signer.signOrigin(privKeys[1]);
13
14
// after meeting the numSignatures requirement, the public
15
// keys of the participants who did not sign must be appended
16
signer.appendOrigin(pubKeys[2]);
17
18
// the serialized multi-sig tx
19
const 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.

1
import { bufferCVFromString, callReadOnlyFunction } from '@stacks/transactions';
2
import { StacksTestnet } from '@stacks/network';
3
4
const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE';
5
const contractName = 'kv-store';
6
const functionName = 'get-value';
7
const buffer = bufferCVFromString('foo');
8
const network = new StacksTestnet();
9
const senderAddress = 'ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ';
10
11
const options = {
12
contractAddress,
13
contractName,
14
functionName,
15
functionArgs: [buffer],
16
network,
17
senderAddress,
18
};
19
20
const 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 or false)
  • int : signed 128-bit integer
  • uint: 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.

1
import {
2
trueCV,
3
falseCV,
4
noneCV,
5
someCV,
6
intCV,
7
uintCV,
8
standardPrincipalCV,
9
contractPrincipalCV,
10
responseErrorCV,
11
responseOkCV,
12
listCV,
13
tupleCV,
14
bufferCV,
15
} from '@stacks/transactions';
16
import { utf8ToBytes } from '@stacks/common';
17
18
// construct boolean clarity values
19
const t = trueCV();
20
const f = falseCV();
21
22
// construct optional clarity values
23
const nothing = noneCV();
24
const something = someCV(t);
25
26
// construct a buffer clarity value from an existing byte array
27
const bytes = utf8ToBytes('foo'); // Uint8Array(3) [ 102, 111, 111 ]
28
const bufCV = bufferCV(bytes);
29
30
// construct signed and unsigned integer clarity values
31
const i = intCV(-10);
32
const u = uintCV(10);
33
34
// construct principal clarity values
35
const address = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B';
36
const contractName = 'contract-name';
37
const spCV = standardPrincipalCV(address);
38
const cpCV = contractPrincipalCV(address, contractName);
39
40
// construct response clarity values
41
const errCV = responseErrorCV(trueCV());
42
const okCV = responseOkCV(falseCV());
43
44
// construct tuple clarity values
45
const tupCV = tupleCV({
46
a: intCV(1),
47
b: trueCV(),
48
c: falseCV(),
49
});
50
51
// construct list clarity values
52
const 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.

1
const l = listCV<BooleanCV>([trueCV(), intCV(1)]);

Post Conditions

Three types of post conditions can be added to transactions:

  1. 1STX post condition
  2. 2Fungible token post condition
  3. 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

1
import {
2
FungibleConditionCode,
3
makeStandardSTXPostCondition,
4
makeContractSTXPostCondition,
5
} from '@stacks/transactions';
6
7
// With a standard principal
8
const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';
9
const postConditionCode = FungibleConditionCode.GreaterEqual;
10
const postConditionAmount = 12345n;
11
12
const standardSTXPostCondition = makeStandardSTXPostCondition(
13
postConditionAddress,
14
postConditionCode,
15
postConditionAmount
16
);
17
18
// With a contract principal
19
const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';
20
const contractName = 'test-contract';
21
22
const contractSTXPostCondition = makeContractSTXPostCondition(
23
contractAddress,
24
contractName,
25
postConditionCode,
26
postConditionAmount
27
);

Fungible token post condition

1
import {
2
FungibleConditionCode,
3
createAssetInfo,
4
makeStandardFungiblePostCondition,
5
} from '@stacks/transactions';
6
7
// With a standard principal
8
const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';
9
const postConditionCode = FungibleConditionCode.GreaterEqual;
10
const postConditionAmount = 12345n;
11
const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';
12
const assetContractName = 'test-asset-contract';
13
const assetName = 'test-token';
14
const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);
15
16
const standardFungiblePostCondition = makeStandardFungiblePostCondition(
17
postConditionAddress,
18
postConditionCode,
19
postConditionAmount,
20
fungibleAssetInfo
21
);
22
23
// With a contract principal
24
const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';
25
const contractName = 'test-contract';
26
const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';
27
const assetContractName = 'test-asset-contract';
28
const assetName = 'test-token';
29
const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);
30
31
const contractFungiblePostCondition = makeContractFungiblePostCondition(
32
contractAddress,
33
contractName,
34
postConditionCode,
35
postConditionAmount,
36
fungibleAssetInfo
37
);

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.

1
import {
2
NonFungibleConditionCode,
3
createAssetInfo,
4
makeStandardNonFungiblePostCondition,
5
makeContractNonFungiblePostCondition,
6
bufferCVFromString,
7
} from '@stacks/transactions';
8
9
// With a standard principal
10
const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE';
11
const postConditionCode = NonFungibleConditionCode.DoesNotSend;
12
const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ';
13
const assetContractName = 'test-asset-contract';
14
const assetName = 'test-asset';
15
const assetId = bufferCVFromString('test-token-asset-id');
16
const nonFungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName);
17
18
const standardNonFungiblePostCondition = makeStandardNonFungiblePostCondition(
19
postConditionAddress,
20
postConditionCode,
21
nonFungibleAssetInfo,
22
assetId
23
);
24
25
// With a contract principal
26
const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X';
27
const contractName = 'test-contract';
28
29
const contractNonFungiblePostCondition = makeContractNonFungiblePostCondition(
30
contractAddress,
31
contractName,
32
postConditionCode,
33
nonFungibleAssetInfo,
34
assetId
35
);

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.

1
import { cvToJSON, hexToCV } from '@stacks/transactions';
2
3
cvToJSON(hexToCV(tx.tx_result.hex));