Links

Oscript language reference

Autonomous agents

Autonomous agents (AA) are special addresses (accounts) on the ledger that do not belong to anybody. An AA can send transactions only in response to a triggering transaction sent to it and strictly according to a program associated with the AA. This program is open and known in advance, it specifies the AA's reaction to triggering transactions. The reaction depends on:
  • coins sent by the triggering transaction: how much of which asset were sent;
  • data sent by the triggering transaction;
  • environment that exists in the ledger when the trigger is received: balances, data posted by oracles, attestations, state variables.
The AA's reaction can be one or a combination of:
  • sending some other coins back to the sender or to a third party (which can be another AA);
  • changing the environment of the ledger by posting data feeds, attestations, updating state variables, etc.
The behavior of autonomous agents is similar to vending machines: they accept coins and data entered on a keypad (the triggering action), and in response they release a cup of coffee or play a song, or do whatever they are programmed to do. What's common between them, their behavior is predictable, known in advance.
There are no private/public keys associated with AAs. Their transactions do not have signatures. Their transactions are created by all (full) nodes just by following the common protocol rules and executing the code associated with the AA. All nodes always come to the same result and produce exactly the same transaction that should be sent on behalf of the AA. As one comes to expect from a DAG, all nodes arrive at the same view of the ledger state just by following the rules, without any votings, proof-of competitions, or leaders.
The AA-generated transactions are not broadcast to peers as peers are expected to generate the same transactions on their own.
The language used to specify the AA behavior is a domain specific language that we call Oscript. It was designed specifically for AAs to make it easy to write an autonomous agent behavior, make it easy to argue about what is going on in the AA code, and make it hard to make difficult-to-track errors.
To control the resource consumption, the language does not support loops. Resource usage is controlled by setting a cap on the complexity of the program -- too resource heavy programs are simply not allowed. However, the resource caps provide enough room for the vast majority of practical applications.
AAs can "call" other AAs by sending coins to them. All the scripts in the calling AA are finished and all changes committed before passing control on to the next AA, this avoids completely the reentrancy problem common in other smart contract platforms.

Autonomous agent definition

Addresses of autonomous agents follow the same general rules as all other Obyte addresses: their definitions are two-element arrays and the address is a checksummed hash of the array encoded in base32.
AA address is defined as:
["autonomous agent", {
// here goes the AA code
}]
The second element of the above array is an object that defines a template for future units created by the AA. The template's structure follows the structure of a regular unit in general, with some elements dynamic and dependent upon the input and state parameters. The dynamic elements are designated with special markup and include code in a domain specific language called Oscript:
{
address: "{trigger.address}",
amount: "{trigger.output[[asset=base]] - 1000}"
}
The concept is similar to how such languages as PHP, ASP, and JSP work. For example, a PHP script is a HTML file interspersed with fragments of PHP code enclosed between <?php and ?> tags. These fragments make the HTML page dynamic while keeping its HTML structure. While this mix can quickly become messy when PHP is used to generate HTML code, the ease of creating dynamic web pages by just inserting small pieces of code into HTML was one of the main selling points of these programming languages in the early days of the web. And it enabled quick prototyping, iteration, experimentation, and eventually led to the creation of some of the biggest pieces of the modern Internet (wordpress and facebook, to name a few).
Transactions, or storage units as we call them in Obyte, are similarly the basic building units of distributed ledgers. They are usually represented as JSON objects, which are the counterparts of HTML pages on the web. Now, to make it easy to create dynamic, parameterized JSON objects representing transactions, we allow to inject some code into JSON and invite the developers to apply their creativity and do the rest.
Text
Document format
Scripting language
Web
HTML
PHP
Obyte
JSON
Oscript
This is an example of autonomous agent definition:
["autonomous agent", {
bounce_fees: { base: 10000 },
doc_url: "https://example.com/doc_urls/{{aa_address}}.json",
messages: [
{
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "{trigger.address}",
amount: "{trigger.output[[asset=base]] - 1000}"
}
]
}
}
]
}]
Here messages is a template for the AA's response transaction. It has only one message -- a payment message that will send the received amount less 1000 bytes back to sender. Omitting the amount entirely would send everything back (minus the fees). There are code fragments in strings enclosed in curly braces "{}", they are evaluated and the result is inserted in place of the code.
Before sending the resulting response transaction, the nodes on the network will also enhance it with change outputs (if necessary) and add other necessary fields in order to attach the transaction to the DAG: parents, last stable unit, authors, timestamp, fees, etc.
Below is a specification of the unit template format.

JSON format

bounce_fees

["autonomous agent", {
bounce_fees: {
base: 10000,
"n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY=": 100
},
...
}]
This is an optional field of the unit template that specifies the fees charged from sender if the AA execution fails. In this case, all the received money in all assets is automatically bounced back to sender, less the bounce fees. The fees are keyed by asset ID (base for bytes).
The minimum and default bounce fee for bytes is 10000 bytes. The minimum and default bounce fee for all other assets is 0. Non-base bounce fees apply only to those assets that were actually received by the autonomous agent.
Sending to an autonomous agent anything less than the bounce fees will result in no response and the AA silently eating the coins. However this rule applies only to money sent from regular addresses. Bounce fees are not checked when the money is received from another AA.
bounce_fees field is removed from the final unit.

doc_url

Each deployed autonomous agent can have a URL that points to a JSON formatted documentation file, which content will be shown to users in wallet app. URL can contain {{aa_address}} placeholder, which will be replaced with actual AA address before fetching the JSON file. The structure of the JSON file should look like this:
{
"version": "1.0",
"description": "Description shown to users",
"homepage_url": "https://example.com",
"source_url": "https://github.com/byteball/ocore",
"field_descriptions": {
"some_field_name": "Description how to use this parameter",
"some_other_field_name": "Description how to use this other parameter",
...
}
}
Keys in field_descriptions object should match all the keys in trigger.data that users can use with this AA, wallet app will add value of that fields as an explanation what it does. The value of version should be as shown above ("1.0") - it should NOT used as version of AA.

messages

This is the main field of autonomous agent definition. It specifies templates for the messages to be generated, and the templates are parameterized with oscript code.
The messages can be of any type (called app) that Obyte supports. The most common app is payment, it is used to send payments in any asset back to sender or to a third party. Other apps are:
  • asset: used to define a new asset. Different parameters that can be used are documented on Issuing assets page;
  • data: used to send data, this includes sending data parameters to other (secondary) AAs;
  • data_feed: used to send data feeds. By doing this, the AA becomes an oracle;
  • profile: used to send one's own profile. Maybe an AA wants to say something to the world about itself;
  • text: used to save arbitrary text to the DAG;
  • definition: used to post a definition of a new AA;
  • asset_attestors: used to change the attestor list of an asset previously defined by this AA;
  • attestation: used to post information about some other address. By doing this, the AA becomes an attestor;
  • definition_template: used to post a template for smart contract definition;
  • poll: used to create a poll;
  • vote: used to vote in a poll. Every AA has voting rights after all.
Structure of those messages types is documented on Sending data messages section and Sending data to DAG page.
There is also another, special, app called state, which is not possible in regular units but is used only in AAs to produce state changes. More about it in a separate chapter.

base_aa

It is possible to create parameterized Autonomous Agents, which are based on previously deployed templates. Their structure for that kind of Autonomous Agent would look like this:
["autonomous agent", {
base_aa: "ADDRESS_OF_BASE_AA",
params: {
name1: "value1",
name2: "value2",
...
}
}]
The base AA template would need to reference these parameters as params.name1 and params.name2.

Oscript replacements

Any string, number, or boolean in the template can be calculated by a script. The script is evaluated and the result is inserted in place of the script, the type is preserved. For example, if 20000 bytes were sent from address 2QHG44PZLJWD2H7C5ZIWH4NZZVB6QCC7 to an AA, then this code
{
address: "{trigger.address}",
amount: "{trigger.output[[asset=base]] - 1000}"
}
would be replaced with
{
address: "2QHG44PZLJWD2H7C5ZIWH4NZZVB6QCC7",
amount: 19000
}
Object keys can also be parameterized with Oscript:
{
"{trigger.data.key}": "value"
}
See the Oscript language reference below.

cases - template objects

JSON has scalars (strings, numbers, booleans) and objects (objects and arrays). Scalars can be parameterized through Oscript. Objects, on the other hand, are parameterized differently. They can have several alternative versions in the AA template, and only one version is selected based on input parameters and state. The versions and their selection criteria are specified using an object in cases array:
{
messages: {
cases: [
{
if: "{trigger.data.define}",
messages: [
// first version of messages
....
]
},
{
if: "{trigger.data.issue}",
init: "{$amount = trigger.output[[asset=base]];}",
messages: [
// second version of messages
....
]
},
{
messages: [
// default version of messages
....
]
}
]
}
}
The regular value of an object/array is replaced with an object whose single element is an array cases. Each element of the cases array is an object with up to 3 elements:
  • if: an Oscript expression. If the result of its evaluation is truthy then this case is selected. All other cases are not evaluated. if is required for all cases except the last, the last one may or may not have an if. If all previous cases evaluated to a falsy value and the last one is without an if, the last one is selected;
  • init: an optional statements-only Oscript that is evaluated immediately after if if this case is selected;
  • a mandatory element that is named the same as the original field (messages in the above example). If this case is selected, the original (3 levels higher) field is replaced with the value of this element.
In the above example, if the 2nd case were selected, the original object would fold into:
{
messages: [
// second version of messages
....
]
}
Cases can be nested.
Cases can be used for any non-scalar value inside messages, not just messages themselves.

if - conditional objects

Similar to the cases above, any object can have an additional if field. It is evaluated first, and if it is falsy, the entire object is removed from the enclosing object or array. Its internal Oscripts are not evaluated in this case.
{
messages: [
{
app: "data",
payload: {
timestamp: "{timestamp}",
subscriber: "{trigger.address}"
}
},
{
if: "{trigger.data.withdrawal_amount > 0}",
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "{trigger.address}",
amount: "{trigger.data.withdrawal_amount}"
}
]
}
}
]
}
In the above example, the payment message will be generated only if trigger.data.withdrawal_amount is a number greater than 0.
The if field itself is removed from the object.

init - statements object

Similar to the cases above, any object can have an additional init field. It is evaluated immediately after if when if is present and truthy. If there is no if, init is unconditionally evaluated first.
init must be a statements-only Oscript, it does not return a value.
Example:
{
messages: [
{
init: "{ $addr = trigger.address; }",
app: "data",
payload: {
timestamp: "{timestamp}",
subscriber: "{$addr}"
}
},
{
if: "{trigger.data.withdrawal_amount > 1000}",
init: "{ $amount = trigger.data.withdrawal_amount - 1000; }",
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "{trigger.address}",
amount: "{$amount}"
}
]
}
}
]
}
The init field itself is removed from the object.

Conditional object fields and array elements

If the value of any object field or array value evaluates to an empty string, this field or element is removed.
For example, object
{
field1: "{ (1 == 2) ? 'value1' : '' }",
field2: "value2"
}
will become
{
field2: "value2"
}
Array
[ `{ (1 == 2) ? "value1" : "" }`, "value2" ]
will become
[ "value2" ]

Sending all coins

If the amount field in an output within a payment message is omitted or evaluates to an empty string (which results in its removal per the above rules), this output receives all the remaining coins.
{
messages: [
{
if: "{trigger.data.send_all}",
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "2QHG44PZLJWD2H7C5ZIWH4NZZVB6QCC7",
amount: 1000
},
{
address: "{trigger.address}"
}
]
}
}
]
}
In the above example, 1000 bytes are sent to 2QHG44PZLJWD2H7C5ZIWH4NZZVB6QCC7, the rest of the AA's balance in bytes is sent to the address that triggered the AA.

Empty objects and arrays

If an object or array becomes empty as a result of various removals, it is also removed from the enclosing object or array.

State message

A state message is a special message in the messages array that performs state changes. It is the only oscript where state variables are assigned. Unlike regular messages that always have payload, state message has a field named state instead that contains a state changing script:
{
messages: [
{
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "{trigger.address}",
amount: "{trigger.output[[asset=base]] - 1000}"
}
]
}
},
{
app: "state",
state: `{
var['responded'] = 1;
var['total_balance_sent_back'] += trigger.output[[asset=base]] - 1000;
var[trigger.address || '_response_unit'] = response_unit;
}`
}
]
}
The state message must always be the last message in the messages array. It is not included in the final response unit and its script (state script) is evaluated after the response unit is already prepared. It is the only oscript where response_unit variable is available. State script contains only statements, it is not allowed to return any value.

Constants/Variables

Local constants

$name1 = 1;
$name2 = 'value';
${'name' || 3} = $name1 + 10;
Local constant names are always prefixed with $. If the constant name itself needs to be calculated, the expression is enclosed in curly braces.
Local constants exist only during AA evaluation. Each constant can be assigned a value only once (single-assignment rule).
Each local constant is visible only in its own oscript after it was assigned. If it was assigned in if block, it is also available in all adjacent and enclosed oscripts. If it was assigned in init block, it is also available in all adjacent oscripts except if and in all oscripts enclosed in the same object.
[
{
if: `{
$amount = trigger.output[[asset=base]];
// the result of the last expression is the result of if
$amount == 10000
}`,
init: `{
// here we can reference $amount set in if
$half_amount = round($amount / 2);
}`,
messages: [
{
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "{ trigger.address }",
amount: `{
// we are in an enclosed oscript
// and can reference $half_amount set in init
$half_amount
}`
}
]
}
},
{
app: "state",
state: `{
// we are in an enclosed oscript
// and can reference $amount set in if
var['received'] = $amount;
// we are in an enclosed oscript
// and can reference $half_amount set in init
var['sent_back'] = $half_amount;
}`
}
]
},
{
if: "{trigger.data.payout}",
init: `{
// here we cannot reference $amount nor $half_amount from the above
// we can even assign other values to them without breaking the single-assignment rule
$amount = 10;
}`,
...
}
]
If an unassigned local constant is referenced, it is taken as false.
If-else blocks in curly braces do not create separate scopes for local constants:
if (trigger.data.deposit){
$amount = trigger.output[[asset=base]];
}
$fee = round($amount * 0.01); // we can reference the $amount from above
Local constants can hold values of any type: string, number, boolean, or object (including array).

Objects and arrays

When a local constant holds an object, individual fields of the object can be accessed through dot or [] selector:
$data = trigger.data;
$action = $data.params.action;
$player_score = $data.params[$player_name || '_score'];
If the specified object field does not exist, the value is taken as false.
Objects and arrays can be initialized using familiar syntax as in other languages:
$obj = {a: 3, b: 7 };
$arr = [7, 2, 's', {a: 6}];
Although all local constants are single-assignment, objects and arrays can be mutated by modifying, adding, or deleting their fields:
$obj = {a: 3, b: 7 };
$obj.a = 4; // modifies an existing field
$obj.c = 10; // adds a new field
delete($obj, 'b'); // deletes a field
freeze($obj); // prohibits further mutations of the object
$arr = [7, 2, 's', {a: 6}];
$arr[0] = 8; // modifies an existing element
$arr[] = 5; // adds a new element
delete($arr, 1); // removes element 1
freeze($arr); // prohibits further mutations of the array
The left-hand selectors can be arbitrarily long .a.b[2].c.3.d[].e. If some elements do not exist, an empty object or array is created automatically. [] adds to the end of an array. Array indexes cannot be skipped, i.e. if an array has length 5, you cannot assign to element 7. Once you are dome mutating an object, can call freeze() to prevent further accidental mutations.

Local functions

$f = ($x) => {
$a = var['a'];
$x * $a
};
// one-line syntax for functions that have only one expression
$sq = $x => $x^2;
$res = $f(2);
$nine = $sq(3);
Functions are local constants and the same rules apply to them.
The return value is the value of the last expression or a return statement.
A function sees all other local constants and functions declared before it. Because of this, recursion is impossible.
Constants declared before the function cannot be shadowed by its arguments or other local constants declared within the function.
Complexity of a function is the sum of complexities of all operations within it. Every time a function is called, its complexity is added to the total complexity of the AA. If a function is declared but never called, its complexity doesn't affect the complexity of the AA.

Remote functions (getters)

Getters are read-only functions available to other AAs and non-AA code.
They are meant to extract information about an AA state that is not directly available through state vars. E.g. in order to fetch this information one needs to perform some calculation over state vars or access several state vars and have good knowledge about the way information is stored in the AA.
Examples:
  • oswap could expose functions that would calculate the price of a future exchange. Otherwise, clients would have to do non-trivial math themselves.
  • the token registry could expose a single getter $getTokenDescription($asset) for reading a token description, and a similar one for decimals. Otherwise, one has to read first $desc_hash = var['current_desc_' || $asset], then var['description_' || $desc_hash].
Getters are declared in a top-level getters section which is evaluated before everything else.
['autonomous agent', {
getters: `{
$sq = $x => $x^2;
$g = ($x, $y) => $x + 2*$y;
$h = ($x, $y) => $x||$y;
$r = ($acc, $x) => $x + $acc;
}`,
init: `{
// uncomment if the AA serves as library only
// bounce("library only");
...
}`,
...
}]
The code in getters section can contain only function declarations and constants. Request-specific information such as trigger.data, trigger.outputs, etc is not available in getters.
In the AA which declares them, getters can be accessed like normal functions.
Other AAs can call them by specifying the remote AA address before the function name using this syntax:
$nine = MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU.$sq(3);
or
$remote_aa = "MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU";
$nine = $remote_aa.$sq(3);
If $remote_aa is a variable that cannot be evaluated at deploy time, you need to indicate the max complexity of the remote call either explicitly, after a #:
$nine = $remote_aa#5.$sq(3);
or as a variable that can be evaluated at deploy time:
$max_complexity = 5;
$nine = $remote_aa#$max_complexity.$sq(3);
or by indicating a base AA that the $remote_aa is a parameterized AA of:
$base_aa = '3DGWRKKWWSC6SV4ZQDWEHYFRYB4TGPKX';
$nine = $remote_aa#$base_aa.$sq(3);
The complexity of a remote call is the complexity of its function, plus one.
All functions declared in the getters section are publicly available. If some of them are not meant for public use, one can indicate this by a naming convention, e.g. by starting their names with an underscore $_callPrivateFunction() but information hiding cannot be really enforced since all getters operate on public data anyway.
Getters can also be conveniently called from non-AAs. In node.js code:
const { executeGetter } = require('ocore/formula/evaluation.js');
const db = require('ocore/db.js');
const args = ["arg1", "arg2"];
const res = await executeGetter(db, aa_address, getter, args);
For remote clients, there is a light/execute_getter command in WebSocket API, hopefully it will be shortly available through obyte.js.

State variables

State variables are persisted across invocations of autonomous agents.
Accessing state variables:
// assigning current AAs state variable to local constant
$my_var_name1 = var['var_name1'];
// assigning other AAs state variable to local constant
$their_var_name1 = var['JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY']['var_name1'];
Assigning state variables:
var['var_name1'] = 'var_value';
var['var_name2'] = 10;
var['var_name3'] += 10;
var['var_name4'] = false;
var['var_name5'] = {a:8, b:2};
var['var_name5'] ||= {c:6}; // concat an object
var['var_name'] reads the value of state variable var_name stored under current AA.
var['AA_ADDRESS']['var_name'] reads the value of state variable var_name stored under AA AA_ADDRESS. AA_ADDRESS is a valid address or this_address to refer to the current AA.
If there is no such variable, false is returned.
State variables can be accessed in any oscripts, but can be assigned only in state message script. Only state vars of the current AA can be assigned, state vars of other AAs are read-only. State vars can be reassigned multiple times but only the final value will be saved to the database and only if the AA finishes successfully. All changes are committed atomically. If the AA fails, all changes to state vars are rolled back.
State vars can temporarily hold strings, numbers, objects, and booleans but when persisting, true values are converted to 1 and false values result in removal of the state variable from storage. AAs are required to hold a minimum balance in bytes equal to the size of their storage (length of var names + length of var values). This will also incentivize them to free up unused storage. The size of AA’s storage occupied before the current invocation is in variable storage_size.
Internally, objects are stored as JSON strings and their length is limited. Don't try to store a structure in a state var if this structure can grow indefinitely.
In addition to regular assignment =, state variables can also be modified in place using the following operators:
  • +=: increment by;
  • -=: decrement by;
  • *=: multiply by;
  • /=: divide by;
  • %=: remainder of division by;
  • ||=: concatenate string/object/array.
For concatenation, the existing value of the var is converted to string.
For +=, -=, *=, /=, %=, the existing boolean value is converted to 1 or 0, strings result in error.
If the variable didn't exist prior to one of these assignments, it is taken as false and converted to number or string accordingly.
Each read or write operation on a state variable adds +1 to complexity. Assignment with modification also costs 1 in complexity.
Examples:
var['sent_back'] = $half_amount;
var['count_investors'] += 1;
var['amount_owed'] += trigger.output[[asset=base]];
var['pending'] = false;
$x = var['JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY']['var_name1'];

Response variables

response['key'] = 'text';
Adds a key to the response object. Response variables do not affect state, they are meant to only inform the caller, and other interested parties, about the actions performed by the AA.
Response vars can only be assigned, never read. Response vars can be assigned and reassigned multiple times in any oscript. They can hold values of types: string, number, boolean. Attempting to assign an object would result in true being assigned.
Example: assigning these response variables
response['message'] = "set exchange rate to 0.123 tokens/byte";
response['deposit'] = 2250000;
will result in the following response object:
{
"responseVars": {
"message": "set exchange rate to 0.123 tokens/byte",
"deposit": 2250000
}
}

Responses

The AAs are activated and responses are generated when the triggering unit gets stabilized. If the triggering unit triggers several AAs or there are several triggers that are included in the same MC unit and therefore get stabilized at the same time, the triggers are handled in deterministic order to ensure reproducibility on all nodes.
The first response unit has one or two parents:
  • the MC unit that just got stabilized and which includes the triggering unit;
  • the previous AA-generated unit (meaning, generated by any AA, not just the current one) if it is not already included in the first parent.
Any subsequent responses (generated by secondary AAs and in response to other triggers at the same MCI) are chained after the first response and then one after another.
After every response, 4 events are emitted:
  • aa_response
  • aa_response_to_unit- + trigger_unit
  • aa_response_to_address- + trigger_address
  • aa_response_from_aa- + aa_address
Applications, which are based on a full node can subscribe to these events to receive information about responses they are interested in, e.g.:
var trigger_address = '';
const eventBus = require('ocore/event_bus.js');
eventBus.on('aa_response_to_address-' + trigger_address, (objAAResponse) => {
// handle event
});
Applications, which are based on light node will also need to add the address to their watched list in order to subscribe to these events:
var aa_address = '';
const walletGeneral = require('ocore/wallet_general.js');
const eventBus = require('ocore/event_bus.js');
walletGeneral.addWatchedAddress(aa_address, () => {
eventBus.on('aa_response_from_aa-' + aa_address, (objAAResponse) => {
// handle event
});
});
All 4 event handlers receive objAAResponse object as a single argument:
{
mci: 2385,
trigger_address: '2QHG44PZLJWD2H7C5ZIWH4NZZVB6QCC7',
trigger_unit: 'f2S6Q3ufjzDyl9YcB51JUj2z9nE1sL4XL2VoYOrVRgQ=',
aa_address: 'JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY',
bounced: false,
response_unit: 'JCJ1ZGkl2BtUlYoeu6U2yshp97pen/fIkTHvaKYjZa4=',
objResponseUnit: {
version: '2.0dev',
alt: '3',
timestamp: 1560939440,
messages: [ ... ],
authors: [ ... ],
last_ball_unit: 'cxjDfHzWWgW8LqsC8yoDhgCYmwThmFfkdygGnDrxiFg=',
last_ball: 'gjg8W2cE4WGFAIIsU2BvLOQKOpH/03Oo1SDS3/2SQDs=',
witness_list_unit: '3gLI9EnI2xe3WJVPwRg8s4CB24ruetuddS0wYa2EI3c=',
parent_units: [ 'f2S6Q3ufjzDyl9YcB51JUj2z9nE1sL4XL2VoYOrVRgQ=' ],
headers_commission: 267,
payload_commission: 157,
unit: 'JCJ1ZGkl2BtUlYoeu6U2yshp97pen/fIkTHvaKYjZa4='
},
response: {}
}
The object has the following fields:
  • mci: the MCI of the triggering unit. The response unit is attached to the NC unit with the same MCI;
  • trigger_address: the address that sent coins to the AA and thus triggered it;
  • aa_address: AA address
  • bounced: true if the trigger was bounced, false otherwise;
  • response_unit: hash of the response unit or null if there is no response;
  • objResponseUnit: response unit object or null if there is no response;
  • response: response from the script. The object can have up to two fields: error for error message and responseVars for response variables set by scripts.

Secondary AAs

The generated response unit may contain outputs to other AAs. In this case, the receiving AAs are triggered too. They receive the outputs from the previous AA and the data (if any) from its generated data message.
Secondary AAs behave like the primary ones except that they may receive less than the minimum bounce fees.
If there are several secondary AAs triggered by a previous AA, they are handled in a deterministic order to make sure that the results are reproducible on all nodes. If a secondary AA triggers a ternary one in turn, it is handled before going on to the next secondary AA.
If any of the secondary AAs fails, the entire chain fails, all the changes produced by its AAs are rolled back, and the primary AA bounces.
The total number of secondary AAs stemming from a single primary AA cannot exceed 10, otherwise the primary AA fails and bounces.

Failures

If an AA fails for any reason (bad formula, attempt to send an invalid response, attempt to send more money than it has, etc), and attempt is made to bounce all the received coins back to sender, less bounce fees. All state changes are rolled back.
If creation of the bouncing transaction fails too, no transaction is created in response, i.e. the AA eats the coins.
The response object, which is not saved to the ledger, contains an error message explaining the reasons for failure.

Semicolon

Every variable assignment must be terminated by a semicolon ;. return and bounce statements are also terminated with a semicolon.
$amount = trigger.output[[asset=base]];
var['sent_back'] = round($half_amount/2);
response['message'] = "set exchange rate to 0.123 tokens/byte";
if ($amount >= 42000)
bounce("amount too large");
if (balance[base] < 20000)
return;

Two types of Oscripts

There are two types of Oscripts that can be used in AAs:
  • statements-only scripts that consist only of statements (such as assignments) and don't return any value. init script and state message are the only two allowed statements-only scripts;
  • scripts that return a value. They can have 0 or more statements but the last evaluated expression must not be a statement and its result is the result of the script.
Example of a statements-only script:
$amount = trigger.output[[asset=base]];
$action = trigger.data.action;
Example of a script that returns a value:
$amount = trigger.output[[asset=base]];
$action = trigger.data.action;
$amount*2
The result of evaluation of the last expression $amount*2 is returned.

Non-scalar return values

Usually, the return value of oscript is a scalar: string, number, or boolean. It is just inserted in place of the oscript.
If the return value is an object, it is similarly expanded and inserted in place of the original oscript. This can be used to send prepared objects through trigger.data.
For example, if trigger.data is
{
output: {address: "BSPVULUCOVCNXQERIHIBUDLD7TIBIUHU", amount: 2e5}
}
and an AA has this message
{
app: "payment",
payload: {
asset: "base",
outputs: [
`{trigger.data.output}`
]
}
}
The resulting message will be
{
app: "payment",
payload: {
asset: "base",
outputs: [
{
address: "BSPVULUCOVCNXQERIHIBUDLD7TIBIUHU",
amount: 2e5
}
]
}
}

Flow control

return

return expr;
Interrupts the script's execution and returns the value of expr. This syntax can be used only in oscripts that are supposed to return a value.
return;
Interrupts the script's execution without returning any value. This syntax can be used only in statements-only oscripts: init and state.

if else

if (condition){
$x = 1;
$y = 2 * $x;
}
else{
$x = 2;
$z = $x^3;
}
Evaluates the first block of statements if the condition is truthy, the second block otherwise. The else part is optional.
If the block includes only one statement, enclosing it in {} is optional:
if (condition)
$x = 1;

Complexity

Like other smart contract definitions, AAs have a capped complexity which cannot exceed 100. Some operations involve complex computation or access to the database, such operations are counted and add to the total complexity count. Other operations such as +, -, etc, are relatively inexpensive and do not add to the complexity count. The language reference indicates which operations count towards complexity.
Total complexity is the sum of complexities of all oscripts. It is calculated and checked only during deployment and includes the complexity of all branches even though some of them might not be activated at run time.
If the complexity exceeds 100, validation fails and the AA cannot be deployed.

Operators

Arithmetic operators +, -, *, /, %, ^

$amount - 1000
$amount^2 / 4
Operands are numbers or converted to numbers if possible.
Additional rules for power operator ^:
  • e^x is calculated with exact (not rounded) value of e,
  • an exponent greater or equal to MAX_SAFE_INTEGER will cause an error,
  • for non-integer exponents, the result is calculated as x^y = e^(y * ln(x)) with rounding of the intermediary result, which causes precision loss but guaranties reproducible results;
  • this operator adds +1 to complexity count.

Concatenation operator ||

'abc' || 'def' // 'abcdef'
[4, 6] || [3, 1] // [4, 6, 3, 1]
{x: 1, y: 7} || {y: 8, a:9} // {x: 1, y: 8, a:9}
If the same key is found in both objects, the value from the right-hand one prevails.
Trying to concat an array with object results in error.
If either operand is a scalar (strings, numbers, booleans), both are converted to strings and concatenated as strings. Objects/arrays become "true".

Binary logical operators AND, OR

Lowercase names and, or are also allowed.
Non-boolean operands are converted to booleans.
The result is a boolean.
If the first operand evaluates to true, second operand of OR is not evaluated.
If the first operand evaluates to false, second operand of AND is not evaluated.

Unary logical operator NOT

Lowercase name not is also allowed. The operator can be also written as !.
Non-boolean operand is converted to boolean.
The result is a boolean.

Operator OTHERWISE

Lowercase name otherwise is also allowed.
expr1 OTHERWISE expr2
If expr1 is truthy, its result is returned and expr2 is not evaluated. Otherwise, expr2 is evaluated and its result returned.

Comparison operators ==, !=, >, >=, <, <=

If both operands are booleans or both operands are numbers, the result is straightforward.
If both operands are strings, they are compared in lexicographical order.
If both operands are objects, only == and != are allowed, other operators will cause an error.
If operands are of different types:
  • if any of them is an object or boolean, it causes an error,
  • if any of them is a string, only == and != are allowed and non-string operand will be converted to string before comparison, other operators will cause an error,