They enjoy building smart contracts in a familiar, LISP-based language that makes use of a strong static type system and puts a premium on security. Clarity also removes the multitude of frustrations that are introduced with a turing-complete smart contract language like Ethereum’s Solidity. Now that the Stacks developer community is hitting its stride and maturing quickly, we’re beginning to see patterns emerge like best practices, optimal smart contract structure, common design choices, and most frequently used functions & keywords. The keywords and functions in Clarity are the language’s fundamental building blocks, and perform the mechanisms needed to manipulate data and value on the blockchain.
To help developers get started building smart contracts with Clarity more easily, we’ve compiled a list of the most useful and popular Clarity keywords and functions, including: tx-sender, block-height, define-trait & impl-trait, define-map, and define-constant.
tx-sender is far and away the most frequently used Clarity keyword in the language. It’s a global keyword that takes no input, but when used in a Clarity smart contract, tx-sender will output a principal. Principals are a Clarity native type that represents an entity that can have a token balance. There are two types of principals on Stacks: hashed wallet addresses (secured by public/private key) on the blockchain that can send transactions, and Clarity smart contracts living on the network (no private key, cannot send transactions). Therefore, whenever tx-sender is used within a smart contract, it will instantiate that variable with the identifier value associated with the address that originally called the contract. On Stacks, wallet addresses and principals are represented by a string of 40 alphanumeric characters. (For example: STJRM2AMVF90ER6G3RW1QTF85E3HZH37006D5ER1)
For more context, Stacks addresses are based on three components: a prefix (S), a version byte describing the type of address (T = testnet, P = mainnet, M = multisig mainnet, N = multisig testnet), and the rest is a hash160 of the public key, represented as a Crockford Base32 string.
In this basic example, the person calling this contract is attempting to transfer an amount of STX from their own address to a friend’s wallet. The stx-transfer function is invoked, and three logical arguments are required: the amount, the principal sending the STX, and the address that should receive the tokens. It would be inefficient and potentially dangerous to manually enter an address into the contract. It’s an extremely long string of characters that could be easily misspelled, and it would also destroy the public reusability of the contract by eliminating abstraction. By using the tx-sender keyword, the Clarity contract can automatically “autofill” the tx-sender variable with the address of the principal calling the contract. That way the blockchain knows exactly where to withdraw funds from to complete the desired mechanism of the transaction.
tx-sender may seem like a rudimentary way to achieve attribution in smart contracts, but it is an integral building block for any decentralized operation. Think of it almost as the “From” field in an email. tx-sender is a reliable and accessible way to get principals into Clarity smart contracts.
Another critical keyword in any Clarity developers toolbox is block-height. Sometimes it’s useful to have statistics about the blockchain’s operation injected into a smart contract. Blockchains at their core are state machines, and it can be valuable to have a variable that keeps track of the most recent state verified by miners. To solve this problem, using block-height in Clarity will return a uint value representing the current block height of the Stacks blockchain.
It can also be used as an estimator of time. Since blockchains are such secure computer science systems, it can be difficult to get certain real-world information into the network. Handling specific times and durations can be problematic because blockchains don’t have a native, internal clock. Therefore, predictable block numbers and the consistent pending time between them can be used as helpful relative trackers of time elapsing.
The above example is a nifty Clarity contract for creating a registry of miners who have successfully mined a block on the Stacks network. To do so, this developer created a simple map that tracks each new block number, and the principal of the miner that received the block reward. This is super useful functionality for maintaining a living archive and historical record of mining on Stacks. Logically, this map simply needs a tuple of the winning miners principal, and a uint of the associated block number. Using block-height to obtain data about the most recently mined block is an easy and lightweight method to grab important metrics quickly, and add time-based prerequisites to particular operations in a contract.
define-trait and impl-trait
If the classic video of Steve Ballmer was to be recreated today for the blockchain industry, I think he would instead be cheering for “Interoperability! Interoperability! Interoperability!”
Blockchain is still an extremely nascent technology in the experimentation phase. There are thousands of different teams working on pushing the limits of the tech, but it’s important that standards are reached to allow disconnected smart contracts and tokens to, for lack of a better term, “play nice” together. You saw the same type of interoperability emerge in the early internet. HTTP, Bluetooth, RSS, XML, even specific filetypes like .PDF were all efforts to allow different pieces of hardware and software to share a common lexicon.
The trait system in Clarity is a mechanism that allows for simple definition and configuration of a common interface. The define-trait function outlines the necessary name, functions, argument types, and return type required for a contract to be considered an implementation of the trait. This then allows independent smart contracts to trust that there are a common set of features between them. Once a trait is defined and publicly deployed to the blockchain, a simple impl-trait invocation will signal to the network that a smart contract is indeed implementing a preexisting trait’s features.
A perfect example of traits in action would be the recent non-fungible (NFT) token trait deployed onto the Stacks network. During the testnet era, various NFT experiments were popping up in the explorer and on Github as devs wanted to test out the expressive functionality of Clarity. However, a community-accepted, formalized NFT standard had not been established yet. This resulted in all of the NFTs implementing their own specialized, nuanced methods and functions to achieve NFT functionality. Everything changed when the NFT trait was developed by the community and deployed accessibly to the network! Now, as seen above, there are four distinct methods that a smart contract must contain to be considered a standard-conforming NFT on Stacks. Those methods allow the contract to set an original owner, connect metadata, check the last NFT registered, or transfer ownership.
Once a trait is defined, and a developer wants to implement it into their new smart contract, it is merely a single line of code at the top of a contract that signals its adherence to the NFT trait. The above contract is the recreation of the Beeple NFT on the Stacks blockchain. All the creator of this contract had to do was use the function impl-trait, followed by a trait identifier containing the principal of the contract that defined the trait. Once a trait is implemented, that contract is then expected to contain the four required methods we mentioned before that allow NFTs to function correctly. It also allows NFT metadata to be displayed in products like wallets or the explorer. Take a look at the beeple.clar contract in the explorer to investigate the source code and trait implementation yourself!
When it comes to programming languages, data structures are integral for handling and manipulating sets of information. You might be familiar with arrays, lists, trees, graphs, tables, sets, and other data structures for storing information. In Clarity, maps are used to keep track of the location of specific data in memory. They’re a simple but effective way to relate two sets of information.
In the above example, a developer deployed a contract to the Stacks blockchain in an effort to maintain a library of quotes submitted by users. To add a quote to the library, an individual just needs to call the contract’s add-quote method and pass in a string of their choosing. How are these quotes and their associated identifiers stored? A map is defined as a data structure to catalogue the quote’s string, and an arbitrary incrementing ID. Whenever a new quote is added, it is appended to the map using the map-insert function. Due to the state of the map living on the blockchain in the smart contract, any user can then call the get-quote-by-id method, pass in a quote’s identifier, and receive the quote associated with that ID thanks to the map-get function.
While a map may seem like a simple list of tuples, it actually unlocks loads of unique functionality that is valuable to developers. Being able to conveniently store and recall data in an organized fashion will be critical for more robust contracts serving a multitude of users.
define-constant is one of the most frequently used functions in the Clarity reference, and it’s because of its versatility. Sometimes, as a developer you need some persistent, global variables to perform necessary functionality throughout a contract. The safest and most organized method to achieve this is by instantiating constant variables in your Clarity contract using the define-constant function. The expression passed into the definition is evaluated at contract launch, in the order that it is supplied in the contract. Similar to other kinds of definition statements, define-constant may only be used outside of other functions. You cannot put a define-constant statement in the middle of a function body, as then it would not be globally accessible.
One Clarity smart contract that implemented constants particularly well was Boom.Money’s new Stacking NFT: Boomboxes. This is an extremely complex piece of software that is handling cryptocurrency, interacting with Stacks’ core consensus mechanism, and keeping track of the length of various stacking cycles. A handful of global constants were required to achieve the Boombox mechanism.
It defines a constant dplyr, and assigns tx-sender to it. From earlier, we know that tx-sender is simply the address of the user that deployed the contract. This developer might have need for their personal address later in the contract (to allow contract calls for example). Using define-constant to instantiate a principal as a global variable is a quick and easy way to use it throughout various methods.
It also sets the minimum amount of STX someone needs to participate using define-constant. By instantiating minimum-amount to 100000000 uSTX, this developer could then plug that variable in anywhere within the contract for quick input validation.
The most intriguing use of define-constant here though is how it helps establish a sense of time passing in the smart contract. Stacking on the Stacks blockchain is done in cycles, with one stacking cycle lasting approximately ~2,100 Bitcoin blocks or 15 real-world days. Therefore, it is important for this smart contract to be able to discern when a particular cycle has started, is in-progress, or has concluded. First, this developer assigns the time-limit constant a value of u690950. This actually has nothing to do with seconds or minutes, but is the precise Bitcoin block number that the stacking cycle will begin and Stackers must contribute their STX by. Then, when this contract is called to begin disburse NFTs, it checks that the current block height is less than block number defined in time-limit. This is a roundabout, but genius, way of figuring out when a specific time has passed in the real world.
As demonstrated by this contract, defining constants at the start of your contract can be a superpower. This developer instantiated five tactful constant, and then proceeded to use them efficiently and effectively throughout the Clarity contract. Think of them as your friendly, global variables when developing software for Stacks.
These were just a handful of the weapons in your Clarity arsenal that show how truly flexible and customizable the language can be There are numerous types, keywords, and functions in Clarity, all performing their own important operations. Learning the ins and outs of the entire Clarity reference will only help you solve problems quicker, and successfully take an idea from brainstorm to production.
Now it’s time to go and build your own Clarity contracts! There are a wealth of resources available to begin learning, and also a handful of tutorials to dive right in. Remember, you can always use Clarity Search to browse all of the smart contracts that have been deployed to Stacks mainnet. Check out the documentation to start building out your knowledge, move on to tutorials when you’re ready to get your hands dirty, and finally play around with some of the demo apps and ecosystem projects already leveraging Clarity for powerful crypto use cases!