Getting started guide
Autonomous agents allow to quickly create decentralized finance applications on a distributed ledger.
They operate based on code that is deployed on the ledger once and can never be changed. The code is open and is executed by all nodes on the network.
Anybody can activate an AA just by sending a transaction to its address. Here is an example of a simple AA:
messages
is a template of the response transaction that will be generated by the AA. It follows the structure of a regular Obyte transaction but its sections between curly braces {} contain code that makes the resulting transaction dependent on the triggering (activating) transaction. This is similar to how PHP code can be inserted into HTML to make the resulting page dependent on the request. The language autonomous agents are written in is called Oscript.
The above example of an AA just sends the received money less 1000 bytes back to the sender. trigger.address
is the address of the sender who activated the AA. trigger.output
allows to find the amounts in different currencies sent to the AA, trigger.output[[asset=base]]
is the amount in the base asset -- bytes.
Another example that sells tokens for bytes at 1.5 tokens per byte:
This code needs to be deployed on the ledger before it can be used. Visit oscript.org, copy/paste the above code, and click "Deploy". You'll then get the address of your new autonomous agent. Anybody can now send money to this address to trigger execution of this AA's code.
Passing data to AA
AA's response can also depend on data it receives in the triggering transaction.
In Obyte, any transaction can contain one or more messages of different types. These types are called apps. The most common app is payment
. At least one payment is mandatory in every transaction, it is necessary to at least pay fees. Another app is data
. This type of message delivers an arbitrary json object that can contain any data:
The object in payload
is the data this message delivers.
When an AA receives a data message (along with a payment message, of course) in the triggering transaction, it can access the data through trigger.data
and its action can depend on the data received:
The above AA tries to send trigger.data.withdrawal_amount
bytes back to the sender. withdrawal_amount
is a field in the data message of the triggering transaction:
AAs can be triggered with data by manually entering the key/value pairs to GUI wallet, prompting users to submit pre-filled Send screen, making the AA to trigger another AA or with the script from headless wallet.
Handling errors
In the above example, if the withdrawal_amount
is greater than the balance that the AA has (including the amount received from the triggering transaction), the AA's response will fail. When an AA fails to handle a trigger transaction for any reason, it rolls back all changes and attempts to send back to sender all the received funds, in all assets, less the bounce fees. By default, the bounce fees are 10000 bytes for "base" asset and 0 for all other assets. This is also the minimum. The default can be overridden by adding a bounce_fees
field in the autonomous agent definition:
The above AA will charge 20000 bytes and 100 units of the asset "n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY="
if it has to bounce back a triggering transaction. Sending less than 20000 bytes to the AA will result in the AA silently eating the coins (they are not enough even for the bounce fees). When non-zero amount of asset "n9y3VomFeWFeZZ2PcSEcmyBb/bI7kzZduBJigNetnkY="
is sent to the AA, it should also be at least 100, otherwise the AA eats the coins.
Sending data messages
As said above, payment
is the most common message but other messages can be sent as well by any address, AA included.
Sending data feeds
AA can act as an oracle by sending a data feed:
This example shows that object keys can also be parameterized through Oscript, like object values.
The above AA doesn't have a payment message, it will be added automatically just to pay for the fees.
Sending data
An AA can also send any structured data:
Unlike data_feed
messages, data
messages:
can have any structure with any nesting depth (data feeds are flat key-value pairs);
are not indexed for search;
can be used to pass data to other AAs.
The above AA forwards the received money (less 1000 bytes) to another address, which might be an AA. It also passes data, which includes the initially received data parameters. The value of trigger.data
will be expanded into an object and added as value of initial_data
field.
If the secondary recipient (JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY
) happens to be an AA, its execution will start only after the execution of the current AA is finished. There is no reentrancy issue.
Sending texts
This AA will create a simple text message and store it on the DAG. ||
is the operator for string concatenation.
Defining new assets
The above AA creates a definition of a new asset based on parameters passed in data
.
Some syntax elements that we see here for the first time:
!
is a logicalNOT
operator;otherwise
is an operator that returns the first operand if it is truthy (anything except false, 0, and empty string) or the second operand otherwise;when an object or array value evaluates to an empty string, the corresponding object/array element is removed. This means that if
cap
parameter is not passed thencap
inpayload
evaluates to an empty string, thereforecap
is removed from asset definition and an asset with infinite supply will be defined;fields whose value is an empty array/object are also removed. Therefore, if none of the
attestor1
,attestor2
,attestor3
fields was set, theattestors
array becomes empty and will be excluded from the final definition.
Querying state
The AA's behavior can also depend on various state variables that describe the ledger as a whole.
Balance
Balance of other AAs (but not regular addresses) can also be queried:
The above AA forwards any received payment (less 1000 bytes) to another AA JVUJQ7OPBJ7ZLZ57TTNFJIC3EW7AE2RY if its balance is less than 1,000,000 bytes, or returns to sender otherwise.
Data feeds
The above AA pays to 7XZSBB32I5XPIAVWUYCABKO67TZLHKZW
if Charlotte Hornets won and to FDZVVIOJZZVFAP6FLW6GMPDYYHI6W4JG
otherwise TKT4UESIKTTRALRRLWS4SENSTJX6ODCW
is the address of a sports oracle that posts the results of sports events.
Before the data feed is posted, any attempt to evaluate it will fail the AA and bounce the triggering transaction.
Note that the amount
field in the output is omitted, which means that the entire AA balance will be paid out.
Attestations
The above AA pays to the triggering user 50000 bytes if his address is attested by Steem attestation bot JEDZYC2HMGDBIDQKG3XSTXUSHMCBK725
and his Steem reputation is at least 50. Otherwise, it "pays" 0 bytes. Trying to pay 0 amount results in the output being removed. Since it is the only output in this case and it is removed, the resulting response transaction would produce no effect, therefore it is not created at all.
Variables
State variables
An autonomous agent can have its own state saved between invocations. Use var
to access and assign state variables:
The above AA saves the address that sent the previous triggering transaction, waits for the next invocation, and sends the received money less 1000 bytes to the previous user.
Assigning state variables is only allowed in the special message with app set to state
. This message must have a single other field called state
which is a set of Oscript statements. These statements are allowed to assign state variables. The state message is not included in the final response transaction, it can only affect state by modifying the state variables.
When an uninitialized var
is accessed, it evaluates to false
.
The above example has an if
field in the first message. When var['previous_address']
is not initialized yet (first call), the if
oscript evaluates to false
and the entire first message is excluded from the transaction.
State variables of other AAs can also be read, but not assigned:
Local constants
Use local constants to save intermediate results during script execution.
Local constant names are prefixed with $. Unlike state variables, they are not saved between invocations.
To make it easier to argue about a local constant's value, there is one restriction that is not common in other languages: single-assignment rule. A local constant can be assigned a value only once and that value remains constant until the end of the script. An attempt to re-assign a value will fail the script and make the AA bounce.
Conditional sections
As we've seen in the State variables example before, some parts of the response transaction generated by the AA can be excluded by adding an if
field. If the Oscript in the if
evaluates to true or any truthy value (anything except false, 0, and empty string), the enclosing object is included in the final response, otherwise, it is excluded.
The if
field itself is of course always removed from the final response.
Initialization code
Along with if
, any object can have an init
field that includes initialization code:
The init code contains only statements, it does not return any value unlike most other Oscripts. The other exception is state message described in State variables above, it is also statements-only. All other Oscripts can contain 0 or more statements but must end with an expression (without a ;
) whose value becomes the value of the Oscript.
Init code is evaluated immediately after if
(if it is present and evaluated to true
of course) and before everything else. It is often used to set local constants that are used later.
Local constants in if and init
Local constants that are set in if
and init
are also available in all other Oscripts of the current and nested objects. In the above example, $amount1
and $amount2
were set in init
of the message object, and they are used in payload/outputs/output-0/amount of this message.
Local constants set in if
are also available in the corresponding init
.
Statements-only vs return-value Oscripts
As said above, there are two types of Oscripts: those that contain only statements and those that return a value.
There are only two statements-only Oscripts: init and state script. Here is an example of an init script that we've seen before:
All other Oscripts return a value. They either contain a single expression:
or a set of statements and an expression at the end:
The value of the last expression is the value of the Oscript.
Cases
When you need to route the code execution along several mutually exclusive paths depending on input and environment parameters, use cases
:
The regular messages
array is replaced with an object with a single element called cases
. cases
is an array that lists several mutually exclusive alternatives. Every alternative except the last must have an if
field. The first alternative whose if
evaluates to true
defines the messages
that will take the place of the higher-level messages
field.
The conditions in the if
s may not be mutually exclusive but the alternative that is listed earlier in the list takes precedence. Only one alternative is always taken.
In the above example, if trigger.data
has no define
field but has a truthy issue
field, the 2nd alternative is selected, and the original object folds into:
Cases can be nested. Everything said above about if
, init
, and local constants, applies to cases
as well.
if else
The above examples showed how execution can be branched along different parts of json template.
Oscript code itself can be branched as well by using if/else:
return
If you want to interrupt a script's execution and return from it, with or without a value, use return
.
Here is an example of return
in a return-value Oscript, it must return a value:
A return
in a statements-only Oscript, such as init, doesn't return any value, it just interrupts the script:
bounce/require
If you find an error condition and want to prematurely stop the AA execution and bounce, use bounce
:
or require
:
In this example, timestamp
is roughly the current timestamp (number of seconds since Jan 1, 1970 00:00:00 UTC).
All applied changes will be rolled back and any received coins will be bounced back to the sender less the bounce fees.
_________________
This was a quick introduction to programming of autonomous agents. Read the full language reference to learn more, and enjoy your journey through decentralized finance and beyond!
Last updated