NEO•ONE includes a library of standard smart contract values, functions, interfaces and classes.
This chapter will go into detail on some of the more common values and types that we will use throughout the guide. While working through the rest of the chapters you may want to keep this page handy.
For more details, check out the Smart Contract reference.
The standard library includes several specialized value types which are defined in a way that makes them difficult to use incorrectly. For example, it’s a static compile time error to pass an Address
where a Hash256
is expected, even though both are really just Buffer
s underneath the hood.
Address
- a Buffer
that represents a NEO address.Hash256
- a Buffer
that represents a NEO 256 bit hash, most commonly used for asset ids like NEO
or GAS
asset ids.PublicKey
- a Buffer
that represents a public key.Each of the value types can be created from a string literal using the from
static method, for example, Address.from('APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR')
. Hash256
also contains static properties for the NEO
and GAS
Hash256
values.
The Address
type has a commonly used static method, isCaller
, which is used to check that the passed Address
directly called and approved the current method invocation.
Address.isCaller(address);
Address.isCaller
should be used whenever you want to take an action for a given Address
. For example, you would want to check Address.isCaller(address)
before transferring tokens from the address
to another party.
Tagged types are the same as their underlying type, but we’ve "tagged" them with a piece of compile-time data. NEO•ONE currently contains two tagged types, and their tags are used exclusively for generating the corresponding NEO•ONE client APIs for the smart contract. The most common tagged type and the only one we’ll use throughout the guide is the Fixed
type.
The Fixed<T>
type tags a number
with the number of decimals that it represents. All number
s in TypeScript smart contracts are integers since there are no floating point values in smart contracts. However, we typically consider the values we’re working with to have decimals from the user’s point of view and Fixed<T>
helps capture that notion.
For example, the Fixed<2>
type tells the NEO•ONE toolchain that the integer value represents a fixed point decimal with 2 places. In other words, when we have the value 1250
in a smart contract, it really means 12.50
to the user. The NEO•ONE toolchain uses this information to generate client APIs that automatically convert from the integer representation to the decimal point representation and visa-versa.
This makes it easy to do things like display the result of a smart contract method invocation that returns a Fixed<2>
. Simply convert it to a string since the client APIs have already converted it to the decimal representation. Similarly, for dapp inputs, simply take the user’s decimal value and pass it directly to the NEO•ONE client APIs, under the hood it will convert appropriately.
The Blockchain
value contains several properties pertaining to the current state of the blockchain, the current transaction and the current invocation:
Blockchain.currentBlockTime
- the timestamp of the Block
that this Transaction
will be included in.Blockchain.currentHeight
- index of the last Block
persisted to the blockchain.Blockchain.currentTransaction
- the current InvocationTransaction
.Blockchain.currentCallerContract
- the Address
of the smart contract that directly invoked this contract. May be undefined
if the current invocation was not from another smart contract.If you look at the definition file for the standard library, you might notice a property OpaqueTagSymbol0
, OpaqueTagSymbol1
, one0
or one1
that is present on all types, including global types like Array
or Map
. In order to emit the most efficient NEO VM bytecode possible, we have specialized implementations of all of the standard library types. One limitation of this approach, however, is that you must explicitly use the types. For example, you can’t pass an Array
where an Array
-like but not Array
interface is expected. Adding the OpaqueTagSymbol0
, OpaqueTagSymbol1
, one0
, or one1
properties helps enforce this as a static type error, but it does not catch all cases (though we’re working on improving this).
As a rule of thumb, don’t rely on "Duck Typing", instead always be explicit about the types you’re using.
The NEO•ONE compiler is as close to regular TypeScript as possible. But there are a few areas where our compiler does not exactly match normal JavaScript semantics. These areas almost never come up, but could cause strange behavior if you are expecting your Smart Contract code to behave exactly like JavaScript. One of those is when using native Map
s and Set
s. In normal JavaScript if you use a structural type as a key in a Map
or a member in a Set
, you can only retrieve that key/member later if it’s the same reference. Trying to retrieve that key/member with the same value of a different reference will not work in regular JavaScript. But it WILL work in our compiler. The best way to demonstrate this is with examples.
Set
ExampleThis test would pass in regular TS:
const x = new Set<ReadonlyArray<number>>();
const y = [0];
const z = [0];
x.add(y).add(z);
assertEqual(x.has(y), true);
assertEqual(x.has(z), true);
assertEqual(x.has([0]), false); // notice difference here
assertEqual(x.size, 2); // notice difference here
x.delete(y);
assertEqual(x.delete(z), true); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.has(y), false);
assertEqual(x.has(z), false);
But this test will pass in our TS compiler:
const x = new Set<ReadonlyArray<number>>();
const y = [0];
const z = [0];
x.add(y).add(z);
assertEqual(x.has(y), true);
assertEqual(x.has(z), true);
assertEqual(x.has([0]), true); // notice difference here
assertEqual(x.size, 1); // notice difference here
x.delete(y);
assertEqual(x.delete(z), false); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.has(y), false);
assertEqual(x.has(z), false);
Map
ExampleThis test would pass in regular TS:
const x = new Map<ReadonlyArray<number>, string>();
const y = [0];
const z = [0];
x.set(y, 'bar').set(z, 'baz');
assertEqual(x.get(y), 'bar'); // notice difference here
assertEqual(x.has(y), true);
assertEqual(x.get(z), 'baz');
assertEqual(x.has(z), true);
assertEqual(x.get([0]), undefined); // notice difference here
assertEqual(x.has([0]), false); // notice difference here
assertEqual(x.size, 2); // notice difference here
x.delete(y);
assertEqual(x.delete(z), true); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.get(y), undefined);
assertEqual(x.has(y), false);
assertEqual(x.get(z), undefined);
assertEqual(x.has(z), false);
But this test will pass in our TS compiler:
const x = new Map<ReadonlyArray<number>, string>();
const y = [0];
const z = [0];
x.set(y, 'bar').set(z, 'baz');
assertEqual(x.get(y), 'baz'); // notice difference here
assertEqual(x.has(y), true);
assertEqual(x.get(z), 'baz');
assertEqual(x.has(z), true);
assertEqual(x.get([0]), 'baz'); // notice difference here
assertEqual(x.has([0]), true); // notice difference here
assertEqual(x.size, 1); // notice difference here
x.delete(y);
assertEqual(x.delete(z), false); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.get(y), undefined);
assertEqual(x.has(y), false);
assertEqual(x.get(z), undefined);
assertEqual(x.has(z), false);