Build on Asentum
Writing Contracts
Start here · Estimated read time: 12 minutes
TL;DR
Asentum contracts are plain JavaScript files. Top-level named functions become callable methods. A handful of injected globals — storage, emit, msg, E — give you state, logs, sender info, and cross-contract calls. Everything else is just JavaScript, running inside a Hardened JavaScript sandbox.
Anatomy of a contract
The smallest useful contract:
function init() {
storage.set('count', 0n);
}
function increment() {
const next = storage.get('count') + 1n;
storage.set('count', next);
emit('Incremented', { newValue: next });
}
function get() {
return storage.get('count');
}- Every top-level
functionis exposed as a callable method. - Functions that read storage but don't write are view methods — free, callable via
asentum_viewCall, no transaction required. - Functions that write to
storageor callemitare send methods — require a signed transaction and cost gas.
The storage API
Each contract gets its own keyed key/value store. Keys are strings, values are anything JSON-serializable (including BigInt and nested objects).
storage.get(key)— read a value (undefined if unset).storage.set(key, value)— write a value.storage.delete(key)— remove a value.storage.has(key)— check existence.
Contract state is a Sparse Merkle Tree under the hood — individual key writes are cheap, but large payloads pay per-byte. See the fee market for the cost model.
Emitting events
emit('Transfer', { from, to, amount });Events are indexed and queryable via eth_getLogs. The first argument is the event name (topic 0); object keys become additional indexed topics. This is how explorers and dapps listen for on-chain activity.
The msg object
Inside a method body, msg exposes the transaction context:
msg.sender— the caller's address.msg.value— ASE attached (in wei, as a BigInt).msg.block.number— current block height.msg.block.timestamp— current block time (unix seconds).
Calling other contracts
Cross-contract calls are async message-passing, not synchronous. That's what makes reentrancy structurally impossible. Use the injected E() helper:
async function payout(token, to, amount) {
const balance = await E(token).balanceOf(to);
if (balance < amount) throw new Error('insufficient');
await E(token).transfer(to, amount);
}Every E(addr).method() returns a Promise; the next block commits after the call completes. For the mental model, see Smart Contracts.
Constructors and init
Asentum contracts don't have a special constructor keyword. Convention is to export an init function and guard it against replay:
function init(owner) {
if (storage.has('initialized')) throw new Error('already initialized');
storage.set('initialized', true);
storage.set('owner', owner ?? msg.sender);
}The deployer typically calls init in a second transaction immediately after deploy. The SDK can bundle deploy + init into one flow.
Gotchas
- Use BigInt for amounts. Numbers are never safe for token math. Always use
0n,amount + 1n, etc. - No
Date.now()orMath.random(). Both break determinism — they're removed from the contract sandbox. Usemsg.block.timestampfor time; there is deliberately no randomness primitive in v1. - No
fetch, nosetTimeout, no filesystem. Contracts are deterministic pure functions over their inputs. - Throw to revert. Any uncaught exception reverts the transaction and refunds unused gas. Use descriptive messages.
For the complete list of allowed language features, see Hardened JavaScript.
A worked example: tip jar
function init() {
storage.set('owner', msg.sender);
storage.set('total', 0n);
}
function tip() {
if (msg.value === 0n) throw new Error('send some ASE');
storage.set('total', storage.get('total') + msg.value);
emit('Tipped', { from: msg.sender, amount: msg.value });
}
function withdraw() {
if (msg.sender !== storage.get('owner')) throw new Error('not owner');
const total = storage.get('total');
storage.set('total', 0n);
transfer(msg.sender, total);
emit('Withdrawn', { to: msg.sender, amount: total });
}
function getTotal() { return storage.get('total'); }Try it live in the playground — it's one of the built-in templates. Ready to deploy for real? See Deploy Your First Contract.
Read next