Contract Structure
Contracts in NexScript are somewhat similar to classes in object-oriented languages. A notable difference is that there is no mutable state. So once a contract is instantiated with certain parameters, these values cannot change. Instead, functions can be called on the contract that act on the contract's values to spend money from the contract. The extension for NexScript source code files is .nex
, and the structure of these source files is explained below.
Pragma
A contract file may start with a pragma directive to indicate the NexScript version the contract was written for. This ensures that a contract is not compiled with an unsupported compiler version, which could cause unintended side effects.
The pragma directive follows regular semantic versioning rules.
Example
pragma nexscript ^0.1.0;
pragma nexscript >= 0.2.0 < 0.3.4;
Constructor
A NexScript constructor works slightly differently than what you might be used to in regular object-oriented languages. It is not possible to define any statements inside the constructor, as the constructor is only used to store values in the contract. Because of this limited nature, there is no separate constructor
function, but instead the parameters are specified directly on the class definition.
On nexa contract constructor parameters can be visible and hidden (private to the contract body). Visible arguments will be added to the tail of nexa contract address. See more information in the spec.
To indicate that NexScript's contract constructor parameter is visible to all potential spenders, the use of visible
keyword is required.
In addition visible parameters can serve as a commitment message, playing a role when indexing contract addresses or giving a hint about this contract's behaviour. Such commitments might not even be used in the contract itself. To instruct NexScript compiler to ignore such a commitment parameter it shall be attributed with visible unused
modifier. See example below.
contract OneOfTwo(bytes20 pkh1, bytes32 hash1, bytes20 visible pkh2, bytes32 visible unused hash2)
Note, that because visible parameters are pushed to the altstack after private ones, visible parameters should always be declared after private parameters. As shown in the above code snippet.
The following declaration would be wrong and render the script unspendable (because private parameter is declared after visible):
contract OneOfTwo(bytes20 pkh1, bytes32 hash1, bytes20 visible pkh2, bytes32 hash2)
Example
pragma nexscript ^0.1.0;
contract HTLC(pubkey sender, pubkey recipient, int expiration, bytes32 hash) {
...
}
Upon initialization of the contract, constructor parameters are encoded and added to the contract's bytecode in the reversed order of their declaration. This can be important when manually initializing contracts for debugging purposes.
Functions
The main construct in a NexScript contract is the function. A contract can contain one or multiple functions that can be executed to trigger transactions that spend money from the contract. In the basics the result of a function is just a yes or no answer to the question 'Can money be sent out of this contract?'. But by using a technique called covenants, it is possible to specify other conditions, like restricting where money can be sent. To read more about this technique, refer to the NexScript Covenants Guide.
Example
pragma nexscript ^0.1.0;
contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) {
function transfer(sig recipientSig) {
...
}
function timeout(sig senderSig) {
...
}
}
Function parameters are passed in the reversed order of their declaration. This can be important when manually creating contract transactions for debugging purposes.
Statements
NexScript functions are made up of a collection of statements that determine whether money may be spent from the contract.
require()
The most important statement of NexScript contracts is the require
statement. This statement takes a boolean expression and checks that it evaluates to true
. If it evaluates to false
instead, the transaction fails. This statement is used to ensure that the requirements are met to spend money from the contract.
Example
pragma nexscript ^0.1.0;
contract P2PKH(bytes20 pkh) {
function spend(pubkey pk, sig s) {
require(hash160(pk) == pkh);
require(checkSig(s, pk));
}
}
Variable declaration
Variables can be declared by specifying their type and name. All variables need to be initialised at the time of their declaration, but can be reassigned later on - unless specifying the constant
keyword. Since NexScript is strongly typed and has no type inference, it is not possible to use keywords such as var
or let
to declare variables.
NexScript disallows variable shadowing and unused variables.
Example
int myNumber = 3000;
string constant myString = 'Nexa';
Variable assignment
After their initial declaration, any variable can be reassigned later on. However, NexScript lacks any compound assignment operators such as +=
or -=
.
Example
i = i + 1;
hashedValue = sha256(hashedValue);
myString = 'Nexa rocks!';
Control structures
The only control structures in NexScript are if
and else
statements. This is due to limitations in the underlying Script which currently prevent loops, recursion, and return
statements. If-else statements follow usual semantics known from languages like C or JavaScript.
There is no implicit type conversion from non-boolean to boolean types. So if (1) { ... }
is not valid NexScript and should instead be written as if (bool(1)) { ... }
Example
pragma nexscript ^0.1.0;
contract OneOfTwo(bytes20 pkh1, bytes32 hash1, bytes20 pkh2, bytes32 hash2) {
function spend(pubkey pk, sig s, bytes message) {
require(checkSig(s, pk));
bytes20 pkh = hash160(pk);
if (pkh == pkh1) {
require(sha256(message) == hash1);
} else if (pkh == pkh2) {
require(sha256(message) == hash2);
} else {
require(false); // fail
}
}
}
Comments
Comments can be added anywhere in the contract file. Comment semantics are similar to languages like JavaScript or C. This means that single-line comments can be added with // ...
, while multiline comments can be added with /* ... */
.
Example
// This is a single-line comment.
/*
This is a
multi-line comment.
*/