Macros specification

This page describes an overview of all macros that a Nana indexer should implement and make available to a program's method. Some macros described here are of type view. Any methods that are of type view are restricted to only being able to call other "view" macros. If a method is of type "view" and attempts to call state-changing macros, the NIP is invalid and will not be whitelisted.

View macros are prefixed with $ (just like NIP methods)

alloc()

The alloc() macro takes in 3 parameters:

alloc(sender, recipient, nana, amount)

struct NanaId{
  block: u32,
  tx: u16,
}

struct ProgramId {
  block: u32,
  tx: u16,
}

struct BoxId { 
    program: ProgramId,
    id: u32
}

enum Destination {
    Str(String),
    ProgramId,
    BoxId
}
enum Asset {
    NanaId,
    Str(String)
}

struct Params{
    sender: Destination,
    recipient: Destination,
    nana: NanaId,
    amount: Asset
}

This function unallocates nanas from sender and allocates them to recipient

nana and amount specify how much (amount ) of the nana (nana )should be sent from the sender to the recipient (which is the opposite of whatever sender is) If sender does not have enough of nana to satisfy the transfer of amount , the function should error and therefore produce a rotten banana.

If nana is set to all , every nana balance from sender is unallocated and allocated to recipient with the amount field being ignored. If a nana is set to all_recursive and sender is a programId, all of the Nanas from the program and its boxes are unallocated and allocated to recipient. If sender is not of type programId, all_recursive 's logic is identical to all .

If amount is set to all , all of nana from sender is unallocated and allocated to recipient

The following edge cases for parameters are considered invalid and produce a rotten banana:

  • nana must be a valid Nana existing in sender 's balance or be a string of "all" or "all_recursive". If neither present it is considered invalid.

  • An amount that overflows an LEB128 or is not "all" is considered invalid.

  • recipient is a program that wasn't included in the NIPs use return field.

set()

The set() macro takes 2 parameters:

set(key, value)

enum value {
    Str(String), 
    Int(u128)
}

struct Params{
    key: String
    value: value 
}

Under the hood every program on creation is assigned a JSON object - which they can alter by setting a key , which sets a field in this object. A key can be separated with :, which causes the key string to be split with : with the items in the array referring to a field tree.

Example:

set("parentfield:childfield", 2)

Is stored as:

{
    "parentfield": {
        "childfield": 2
    }
}

If any parent specified by key does not exist, the parent field and all child fields are recursively created. Furthermore, the following edge cases should error and produce a rotten banana:

  • A set's key parameter starts with :

  • A set's key parameter ends with :

  • A set's key parameter has any repeating :

  • A set's key parameter attempts to assign a value (or a field tree) to an existing field already specifying a field tree (rather than just a value)

  • A set's key parameter attempts to assign a field tree to an already existing value.

  • A set's value parameter is not a string or integer.

  • A set's value parameter is an integer that overflows an LEB128.

  • A field tree has a depth of more than 10 fields

The indexer also needs to keep track of the total byte size used by all value parameters on all sets in a transaction's call tree. If at any point this tracker goes over 1000 bytes, set() should error a rotten banana should be produced.

call()

set(program, method, params)

struct ProgramId {
    block: u32,
    tx: u16
}

enum value {
    Int(u8),
    Int(u16),
    Int(u32),
    Int(u64),
    Int(u128)
}

struct Params{
    program: ProgramId
    method: u8,
    params:  data: (
    Option<value>, 
    Option<value>, 
    Option<value>,
    Option<value>
    Option<value>
  ),

}

The call() method allows any state_changing method to call any other state_changing method from another program or itself. 0:0 can be used to call a state_changing method from itself (using the actual program id of self to call itself is considered invalid and such NIPs will not be whitelisted) while 0:nip_id can be used to call a method from another NIP.

If params is excluded, the default of [0,0,0,0,0] will be set instead

The call's return is inherited from whatever program or method it is calling (which can only be a String or u128)

Example:

// program 1 with NIP id of 2
const exampleChildMethod = (Nana, params) => {
    //Has method id of "3"
    return params[0]
}
// program 2
const exampleParentMethod = (Nana, params) => {
    const {
        call
    } = Nana;
    
    let exampleValue = call("0:2", 3)
    
    /*
        exampleValue here would be 0 because exampleChildMethod (which is being called
        with call({block: 0, tx: 2}, 3) returns the first item of the params array -  which is 0 since
        params defaults to [0,0,0,0,0] as it was not provided
    */
    return "something"
}

Furthermore, the following edge cases should error and produce a rotten banana:

  • Every state_changing function has the same type for the params field (vector of 5 uints) - anything other than this is considered invalid and error.

  • If the method being called was not imported in the programs use export field, the method is invalid and will error.

  • In the future ProgramId will be block:tx, with the pointer being to an inscription specifying the Programs code. However, since this is currently disabled ( Programs ) - any "block" that is non-0 will be considered invalid and error.

inherit()

The inherit() method takes in 1 parameter:

inherit(program)

struct ProgramId {
    block: u32,
    tx: u16
}

struct Params{
    program: ProgramId
}

This method deep clones another programs storage into its own storage, overriding and deleting anything that previously existed there.

Furthermore, the following edge cases should error and produce a rotten banana:

  • A valid program is not provided (parameter is malformed, or program does not exist)

  • Program wasnt imported with use

$assert()

The assert view method takes in one parameter:

balance(expression)

struct Params{
    expression: bool
}

This method evaluates an expression to a boolean, exiting and erroring the program (and therefore producing a rotten banana) if it evaluates to false , or continuing with execution and having no effect if it evaluates to true.

Furthermore, the following edge cases should error and produce a rotten banana:

  • expression is not of type boolean

$exit()

The assert view method takes in one parameter:

exit(expression)

struct Params{
    expression: bool
}

This method will forcefully exit the execution of a method. The method will be valid and change the Nana state if expression evaluates to true or produce a rotten banana and revert changes from the call if expression evaluates to false.

Furthermore, the following edge cases should error and produce a rotten banana:

  • expression is not of type boolean

$balance()

The balance view method takes in two parameters: balance(sender, nana)

struct NanaId{
  block: u32,
  tx: u16,
}

struct Params{
    sender: String,
    nana: NanaId
}

This view method gets the total balance of nana from to the programs balance (self) or the unallocated nana payload (unallocated) or vice versa. You can switch between the check by using the string self or the string nana

Returns -> This method returns a u128 repreenting the nana balance of sender.

Furthermore, the following edge cases should error and produce a rotten banana:

  • The Nana being referred to with NanaID doesn't exist (note, if the nana does exist but simply isnt in the balance this will return "0", but will not error)

  • Sender is not a string that is equivalent to self or unallocated

$sha256elided()

The hash view method takes in two parameter: $sha256elided(value)

struct Params{
    value: String
}

value can be any input string into the sha256 hash function

-> Returns

The return value is a u128 representation of the hash, constructed from output bytes with the last 16 being elided.

Furthermore, the following edge cases should error and produce a rotten banana:

  • Value is not a string

$random()

Uses math.random() to produce a random integer.... just kidding hehe

The random view method takes in two parameters: $random(min, max)

struct Params{
    min: u128
    max: u128
}

This view method gets a seed by running the following macros:

const seed = $sha256elided($block("hash"))

Then a range is determined with min and max:

const range = max - min

Then using range seed is normalized to an integer between min and max

const normalized = (seed * range) / MAX_U128 + min;

This normalized integer represents a random int within the scope of the block. Note - blockhashe's can be manipulated by miners, which the function depends on for the seed . This should not be used in scenarios where a "correct value" encourages MEV. Such NIPs will be discarded.

Implementation in Javascript (returns BigInt):

const random = (min, max, blockHash = "") => {
  const MAX_U128 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn;

  //Resulting value from $sha256elided($block("hash"))
  const hash = createHash("sha256").update(blockHash).digest("hex");
  const seed = BigInt("0x" + hash.slice(32)); //elide last 16 bytes

  //Normalize seed betwen min and max
  const range = BigInt(max - min);
  return (seed * range) / MAX_U128 + BigInt(min);
};

-> Returns

u128

Furthermore, the following edge cases should error and produce a rotten banana:

  • Any of the parameters are malformed (min and max are not u128s)

$get()

The get view method takes in one parameter: get(key)

struct Params{
    key: String 
}

Returns a value from a programs local storage.

A key can be separated with :, which causes the key string to be split with : with the items in the array referring to a field tree.

-> Returns

enum value {
    Str(String),
    Int(u128)
}

//Return object
struct Return {
    exists: bool,
    value: value,
}

The exists field is a boolean that checks for the existence of a non-fieldtree value at key .

The value field represents a string or a u128 that represents the value stored at that specific field on the programs storage. If exists is false, this field will be set to NULL.

Furthermore, the following edge cases should error and produce a rotten banana:

  • A get's key parameter starts with :

  • A get's key parameter ends with :

  • A get's key parameter has any repeating :

  • A field tree has a depth of more than 10 fields

$block()

The block view method takes in one parameter: $block(key)

struct Params{
    key: String 
}

Is able to fetch headers from the current block with key . A map of available values can be found below:

-> Returns

enum ReturnValue {
    Str(String),
    Int(u64),
    Int(u32),
    Int(u16)
}

Furthermore, the following edge cases should error and produce a rotten banana:

  • The key field isn't a valid property listed in the table above (or is not of type string)

$sender()

The sender view method takes in no parameters: $sender()

Returns the owning address of the first vin UTXO in a transaction's inputs that had Nanas allocated . In the case no input utxo had nanas present, output will be "GENESIS"

Furthermore, the following edge cases should error:

  • The view function is called outside the scope of a Banana process. This macro cannot produce a rotten banana, however, since if called within the processing of a Banana it will always have a sender assigned.

Last updated