GitHub
Clarigen

Clarigen is a developer tool that automatically generates TypeScript code that makes it easy to interact with Clarity smart contracts.

There are two huge benefits to using Clarigen:

  • Less boilerplate
  • Complete type safety

Clarigen is built to work alongside the libraries and tooling you already use. For unit tests, it works perfectly with Clarinet and Clarinet SDK. When you're building Clarity apps, it works seamlessly with Stacks.js.

If you'd like to get started using Clarigen in your project, head over to the getting started with Clarigen page.

See the difference

To see how Clarigen can make building Stacks apps easier, compare the typical code you'll write with and without Clarigen.

When writing contract tests with Clarinet:

When using Clarigen, all of your function's arguments and results are strictly typed. In this example, passing a non-integer to increment will result in a TypeScript error.

const alice = accounts.wallet_1.address; // `wallet_1` is typed
const receipt = txOk(contracts.counter.increment(2n), alice);
console.log(receipt.value); // 2n

Or, when building web apps:

const { openContractCall } = useOpenContractCall();
 
async function handleOpenContractCall() {
  await openContractCall({
    ...nftContract.transfer({
      id: 1234,
      sender: 'ST1X6M947Z7E58CNE0H8YJVJTVKS9VW0PHEG3NHN3',
      recipient: 'ST1X6M947Z7E58CNE0H8YJVJTVKS9VW0PHEG3NHN3.vault',
    }),
    onFinish: () => {},
  });
}

From Clarity types to JS types

Because Clarigen knows the types of your contract's functions, it can convert Clarity types to JavaScript types behind the scenes. This means you can pass arguments and check results just like you would with any JavaScript library.

Here's how each Clarity type is converted back and forth with a JavaScript type:

  • uint and int: bigint
  • buff: Uint8Array
  • bool: boolean
  • principal: string
  • string-ascii and string-utf8: string
  • (list <Type>): Type[]
  • (optional <Type>): Type | null

The response Clarity type uses a union type to represent the possible return values of a function. For example, the Clarity type (response principal uint) would be:

{
  isOk: true;
  value: string;
} | {
  isOk: false;
  value: bigint;
};

Clarity tuple types are represented as objects. For example, the Clarity tuple type { topic: (string-ascii 12), value: uint } would be:

{
  topic: string;
  value: bigint;
}

Why?

When you're building Clarity contracts, and Clarity apps, there is a ton of boilerplate code that you need to write. Similarly, you need to use a different library, with a different API, depending on if you're writing tests, web apps, or node.js code.

On the other hand, Clarity's designs mean that we shouldn't have to write lots of boilerplate. Clarity code is fully type-safe, and isn't compiled, so it's easy to generate a type interface for every single Clarity contract.

Clarigen is designed to harness Clarity's architecture and type safety to remove as much boilerplate as possible in your JavaScript projects. Ultimately, it makes Clarity development much more productive and easy.

How it works

The magic behind Clarigen starts with the fact that any Clarity contract can be represented as a machine-readable interface, exposed in JSON format. In other blockchains, this is commonly referred to as an ABI. The interface for a contract looks something like this:

{
  "functions": [
    {
      "name": "decrement",
      "access": "public",
      "args": [],
      "outputs": {
        "type": {
          "response": {
            "ok": "int128",
            "error": "none"
          }
        }
      }
    }
  ]
}

Clarigen will take the JSON interface for each of your projects, lightly annotate it, and generate a TypeScript file inside your project. When you're writing JS code (whether for testing or building apps), Clarigen's libraries will utilize these types to make interacting with contracts a breeze.

The end result is that you'll be able to write code that looks like native JavaScript, but is converted under-the-hood to proper Clarity types.

Here's an example of what your code will look like when using Clarigen. This is an example of writing unit tests with Clarinet.

tests/counter_test.ts
import { CoreNodeEventType, cvToValue, projectFactory } from '@clarigen/core';
import { accounts, project } from '../src/clarigen-types';
import { rov, rovOk, txOk, txErr, ro, varGet, mapGet, filterEvents, tx } from '@clarigen/test';
import { describe, test, it, expect } from 'vitest';
 
const contracts = projectFactory(project, 'simnet');
const { counter } = contracts;
const alice = accounts.wallet_1.address;
 
describe('counter contract tests', () => {
  it('has a default value', () => {
    const initial = counter.constants.counter;
    expect(initial).toEqual(1n);
    const value = rov(counter.getCounter());
    expect(value).toEqual(initial);
    expect(ro(counter.getCounter()).value).toEqual(1n);
  });
 
  it('can increment', () => {
    const receipt = txOk(counter.increment(1n), alice);
    expect(receipt.value).toEqual(2n);
  });
 
  it('can increment more than 1', () => {
    const receipt = txOk(counter.increment(5n), alice);
    expect(receipt.value).toEqual(7n);
  });
 
  it('cannot increment by more than 5', () => {
    const receipt = txErr(counter.increment(6n), alice);
    expect(receipt.value).toEqual(100n);
 
    const responseReceipt = tx(counter.increment(6n), alice);
    expect(responseReceipt.value.isOk).toEqual(false);
  });
 
  it('can get the counter variable', () => {
    const value = varGet(counter.identifier, counter.variables.counter);
    expect(value).toEqual(7n);
  });
 
  it('can get info from the last-increment map', () => {
    const value = mapGet(counter.identifier, counter.maps.lastIncrement, alice);
    expect(value).toEqual(5n);
  });
 
  it('can get events from a tx', () => {
    const receipt = txOk(counter.increment(1n), alice);
    const printEvents = filterEvents(receipt.events, CoreNodeEventType.ContractEvent);
    expect(printEvents.length).toEqual(1);
    const [print] = printEvents;
    const printData = cvToValue<{ action: string; object: string; value: bigint }>(
      print.data.value
    );
    expect(printData).toEqual({
      action: 'incremented',
      object: 'counter',
      value: 8n,
    });
  });
});

Clarity has it's own set of built-in types, but Clarigen will convert them to JavaScript native values behind the scenes. This way, you can pass arguments and check results just like you would with any JavaScript library.