Anoma Developer Documentation
  • Overview
  • Build
    • Getting Started
    • Your First Anoma App
      • Define a Resource
      • Write a Transaction Function
      • Write a Projection Function
      • Run your App
    • Anoma App Examples
  • LEARN
    • Overview
    • State Model
      • Model Comparison
    • Resource Machine
      • Information Flow Control
    • Resources
      • State
      • Logic
      • Kind
      • Lifecycle
    • Transactions
      • Delta
      • Actions
      • Balanced Transactions
      • Intents
    • Applications
      • Backend
      • Interface
    • Services
      • Indexing
      • Solving
Powered by GitBook
On this page
  1. Build
  2. Your First Anoma App

Write a Transaction Function

This page walks you through writing a transaction function that initializes our resource object.

PreviousDefine a ResourceNextWrite a Projection Function

Last updated 27 days ago

If you've followed the tutorial, you will have the basic building blocks to write a . Visit the "LEARN" section to read up on and the different interface function types.

We're adding to the HelloWorld.juvix file. Here, we will write two functions which will ultimately initialize our resource and build the transaction necessary to create the resource. We first write a function helloWorldTransaction which passes back an object of type Transaction. We then create a main function which returns a TransactionRequest object.

Alright, let's start with preparing the Transaction object:

HelloWorld.juvix
-- ### Module and previous imports ###

-- ### Helper functions and resource object ###

--- Produces a ;Transaction; that creates a HelloWorld ;Resource;
--- @param nonce A number used to ensure ;Resource; uniqueness.
--- @param message The message to store in the ;Resource;
helloWorldTransaction
  {M : Type -> Type} -- polymorphic function with type parameter M
  {{Monad M}} -- additional information for type parameter M
  {{Tx M}} -- random number generator needs side effects / Monad
  (nonce : Nonce) 
  (label : String) 
  : M Transaction :=
  let
    newResource := mkHelloWorldResource nonce label;
  in mkTransactionNoPublicData@{
       -- A Transaction must be balanced, so we consume an ephemeral resource of
       -- the same kind as the one we're creating.
       consumed := [newResource@Resource{ephemeral := true}];
       created := [newResource];
     };

Let's unpack our helloWorldTransaction function. First and foremost, the function is supposed to produce an object of type Transaction. It takes two arguments, nonce and label which are passed to the function call of mkHelloWorldResource. This allows us to construct a function as shown in Define a Resource - we pass a nonce to ensure the uniqueness of our resource and the label to add an arbitrary message, like "Hello World!".

Now, we break new territory. In line 17, we call mkTransactionNoPublicData which is a helpful Applib function to simplify passing back the Transaction object. As a reminder, the Transaction object is what we expect in line 14 as the return type of the overall helloWorldTransaction function.

Back to mkTransactionNoPublicData - we call this function by specifying the two arguments consumed and created. Those arguments represent two key concept that are at the core of Anoma's magic. In short, an Anoma transaction needs to be balanced, i.e. a resource must be consumed so that another resource can be created. Here, we want to create a non-ephemeral resource, our newResource resource, so we want to consume an ephemeral resource of the same specification, another newResource. You can learn more about why this is necessary under Transactions.

The Applib library hides away most of the complexities of building resources and transactions using Anoma. You can still introspect what's happening by looking at the function definiton (in VSCode, use e.g. F12 to maneuver there directly).

HelloWorld.juvix
-- ### Previous code for modules, imports, helpers and the resource ###

--- Produces a ;TxContext; which injects an identity and the state root of a commitment tree into the ;TransactionRequest;
ctx : TxContext :=
  TxContext.mk@{
    caller := Universal.identity;
    currentRoot := CommitmentRoot.fromNat 0;
  };

This snippet of code, for now, just assigns dummy values to two important parameters. The ctx function takes caller which is an identity and currentRoot which is the current state root of the commitment tree. For now, it's sufficient to remember that the identity can be used to assign ownable resources and to sign messages. The commitment tree root can proof the valid existence of the resource. In this specific case, we assign Universal.identity which is a universally known public key (derived from a zero address 0x0 seed / private key) and create a dummy state root from 0.

Now, we add the main function:

HelloWorld.juvix
-- Previous code for modules, imports, helpers, 
-- the resource, and the context

--- The function that is run to produce a Transaction to send to Anoma.
main : TransactionRequest :=
  TransactionRequest.build 0 ctx (helloWorldTransaction (Nonce.fromAnomaAtom 0) "Hello World!\n");

The main function here serves a straightforward job - it creates a TransactionRequest object by calling TransactionRequest.build with the previously written helloWorldTransaction function as well as the nonce and, finally, the cleartext label "Hello World!".

The completed code of our HelloWorld.juvix file should look like the following:

See the complete HelloWorld.juvix file.
HelloWorld.juvix
module HelloWorld;

import Stdlib.Prelude open;
import Applib open;
import Anoma.Encode open;

--- A logic function that is always valid.
logic : Logic := Logic.mk \{_ := true};

--- Creates a new ;Resource; that stores a ;String; message.
--- @param nonce A number used to ensure resource uniqueness
--- @param message The message to store in the ;Resource;.
mkHelloWorldResource
  (nonce : Nonce)
  (message : String)
  {ephemeral : Bool := false}
  : Resource :=
  Resource.mk@{
    label := Label.fromNat (builtinAnomaEncode message);
    logic := Encoded.encode logic;
    value := AnomaAtom.fromNat 0;
    quantity := 1;
    nonce := Nonce.toRaw nonce;
    ephemeral;
    unusedRandSeed := 0;
    nullifierKeyCommitment := AnomaAtom.fromNat 0;
  };

--- Produces a ;Transaction; that creates a HelloWorld ;Resource;
--- @param nonce A number used to ensure ;Resource; uniqueness.
--- @param message The message to store in the ;Resource;
helloWorldTransaction
  {M : Type -> Type} -- polymorphic function with type parameter M
  {{Monad M}} -- additional information for type parameter M
  {{Tx M}} -- random number generator needs side effects / Monad
  (nonce : Nonce)
  (label : String)
  : M Transaction :=
  let
    newResource := mkHelloWorldResource nonce label;
  in mkTransactionNoPublicData@{
       -- A Transaction must be balanced, so we consume an ephemeral resource of
       -- the same kind as the one we're creating.
       consumed := [newResource@Resource{ephemeral := true}];
       created := [newResource];
     };

--- Produces a ;TxContext; which injects an identity and the state root of a commitment tree into the ;TransactionRequest;
ctx : TxContext :=
  TxContext.mk@{
    caller := Universal.identity;
    currentRoot := CommitmentRoot.fromNat 0;
  };

--- The function that is run to produce a Transaction to send to Anoma.
main : TransactionRequest :=
  TransactionRequest.build 0 ctx (helloWorldTransaction (Nonce.fromAnomaAtom 0) "Hello World!\n");

In the following chapter, we will add a projection function to access our resource label.

Define a Resource
applications
transaction function