AssemblyScript SDK
The AssemblyScript SDK is the guest-side library for writing NOOSChain smart contracts that compile to WASM. It wraps the raw noos.* host imports so contract code can work with JSON strings, typed codecs, storage helpers, events, bucket metadata, nested calls, and migration output without manually managing WASM pointer/length calls.
Use this page when you are writing contract code. For the manifest rules around these APIs, see Manifest Methods, Manifest Calls, Manifest Migrations, and Authorization.
Mental Model
An AssemblyScript contract is deterministic guest code. It does not write NOOSChain tables directly. Instead, it calls host imports through SDK helpers. The host stages effects such as:
- contract storage writes;
- encrypted-record metadata writes;
- contract events;
- return values;
- nested contract calls.
Those effects commit only when the exported method returns success. If the method traps, throws a host error, returns a non-zero status, exceeds its fuel budget, or fails authorization/readiness checks, the staged writes, events, and return value are discarded.
The current ABI is intentionally small:
export function methodWithoutArgs(): i32
export function methodWithArgs(ptr: i32, len: i32): i32Return 0 for success. Return a non-zero value for deterministic failure. Use returnValue(...) or returnJson(...) to set the structured result stored in the transaction CALL_CONTRACT receipt.
First Method
This is the smallest useful pattern:
import { emitEventJson, returnJson, storageSetJson } from "../../../assembly";
export function saveReport(ptr: i32, len: i32): i32 {
const reportJson = String.UTF8.decodeUnsafe(ptr, len);
storageSetJson("lastReport", reportJson);
emitEventJson("report.saved", reportJson);
returnJson(reportJson);
return 0;
}The method:
- decodes transaction arguments from WASM memory;
- writes contract-local storage;
- emits an event;
- stores a structured return value;
- returns
0so the staged effects can commit.
The raw JSON form is good for simple examples and pass-through values. For production methods with structured input, prefer typed codecs.
Typed Arguments And Codecs
readArgs<T>(ptr, len, decoder) decodes JSON arguments using a JsonDecoder<T>. Typed storage, events, and returns use the matching JsonEncoder<T>.
import {
emitEventValue,
getJsonField,
i32Codec,
quoteJsonString,
readArgs,
returnValue,
storageKey,
storageSet,
stringCodec,
JsonDecoder,
JsonEncoder,
} from "../../../assembly";
class Report {
constructor(
public id: string = "",
public status: string = "",
public score: i32 = 0,
) {}
}
class ReportCodec implements JsonEncoder<Report>, JsonDecoder<Report> {
encode(value: Report): string {
return "{" +
"\"id\":" + quoteJsonString(value.id) + "," +
"\"status\":" + quoteJsonString(value.status) + "," +
"\"score\":" + value.score.toString() +
"}";
}
decode(json: string): Report {
return new Report(
stringCodec.decode(getJsonField(json, "id")),
stringCodec.decode(getJsonField(json, "status")),
i32Codec.decode(getJsonField(json, "score")),
);
}
}
const reportCodec = new ReportCodec();
export function saveReport(ptr: i32, len: i32): i32 {
const report = readArgs<Report>(ptr, len, reportCodec);
storageSet<Report>(storageKey("reports", report.id), report, reportCodec);
emitEventValue<Report>("report.saved", report, reportCodec);
returnValue<Report>(report, reportCodec);
return 0;
}The SDK includes codecs for common scalar values:
| Helper | Purpose |
|---|---|
stringCodec | JSON string encode/decode. |
boolCodec | JSON boolean encode/decode. |
i32Codec, u32Codec, i64Codec, f64Codec | Numeric encode/decode helpers. |
jsonStringCodec | Treats a string as already-encoded JSON. |
quoteJsonString(value) | Escapes and quotes a JSON string. |
getJsonField(json, field) | Lightweight field extraction for simple JSON objects. |
storageKey(namespace, key) | Builds namespaced storage keys such as reports:report-001. |
getJsonField(...) is a small helper, not a general JSON parser. Keep method inputs simple, versioned, and explicitly documented in the manifest ABI.
Storage
Contract storage is isolated by contract id. One contract cannot directly write another contract's storage.
Typed helpers:
| Helper | Use |
|---|---|
storageGet<T>(key, decoder) | Read and decode an existing value. |
storageGetNullable<T>(key, decoder) | Read a value that may be null. |
storageSet<T>(key, value, encoder) | Encode and stage a storage write. |
Raw JSON helpers:
| Helper | Use |
|---|---|
storageGetJson(key) | Read raw JSON from contract storage. |
storageSetJson(key, json) | Stage raw JSON under a key. |
storageGetString(key) | Read a string-like JSON value. |
storageSetString(key, value) | Store a JSON string. |
Keep keys stable and namespaced. Use prefixes such as reports:<id> instead of ambiguous global keys. Avoid unbounded scans; the SDK exposes point reads and writes, not arbitrary database access.
Events And Returns
Events are durable execution records for observers and indexers. Return values are stored in the transaction CALL_CONTRACT result receipt.
| Helper | Use |
|---|---|
emitEventValue<T>(topic, value, encoder) | Encode and stage a typed event. |
emitEventJson(topic, json) | Stage a raw JSON event. |
emitEventString(topic, value) | Stage a JSON string event. |
returnValue<T>(value, encoder) | Encode and set the structured return value. |
returnJson(json) | Set a raw JSON return value. |
returnString(value), returnBool(value), returnI32(value) | Convenience return helpers. |
Do not emit secrets, private keys, decrypted payloads, authorization tokens, or large unbounded data. Events and return values are part of the chain's durable execution trail.
Host Errors
Host wrappers throw HostError when a host import returns non-zero. The raw host result is available as HostError.rawJson.
In most methods, let the error propagate. The call will fail and staged effects will be discarded. Only catch HostError when you can intentionally convert the failure into a deterministic contract-level response.
Buckets
Contracts can interact with bucket metadata and encrypted-record metadata. They do not receive plaintext encrypted payloads.
| Helper | Use |
|---|---|
bucketGetMetadata<T>(bucketId, decoder) | Read bucket consensus metadata. |
bucketGetRecordMetadata<T>(bucketId, recordId, decoder) | Read encrypted-record metadata. |
bucketAddEncryptedRecordMetadata<T>(value, encoder) | Stage an encrypted-record metadata write. |
bucketGetMetadataJson(bucketId) | Raw JSON bucket metadata read. |
bucketGetRecordMetadataJson(bucketId, recordId) | Raw JSON encrypted-record metadata read. |
bucketAddEncryptedRecordMetadataJson(json) | Raw JSON encrypted-record metadata write. |
Bucket operations require all relevant policy gates:
- the method manifest must declare the bucket and required permission;
- the deployed contract principal must have the bucket access rule;
- the caller must have the required bucket permission where the host operation enforces caller access;
- local encrypted payload availability must satisfy the manifest when required.
Example encrypted-record metadata write:
import {
bucketAddEncryptedRecordMetadataJson,
emitEventJson,
getJsonField,
quoteJsonString,
returnJson,
stringCodec,
} from "../../../assembly";
export function writeRecordMetadata(ptr: i32, len: i32): i32 {
const input = String.UTF8.decodeUnsafe(ptr, len);
const bucketId = stringCodec.decode(getJsonField(input, "bucketId"));
const recordId = stringCodec.decode(getJsonField(input, "recordId"));
const payloadHash = stringCodec.decode(getJsonField(input, "payloadHash"));
const publicIndexes = getJsonField(input, "publicIndexes");
const writeJson =
`{"bucketId":${quoteJsonString(bucketId)},` +
`"recordId":${quoteJsonString(recordId)},` +
`"payloadHash":${quoteJsonString(payloadHash)},` +
`"publicIndexes":${publicIndexes}}`;
bucketAddEncryptedRecordMetadataJson(writeJson);
emitEventJson("encrypted_record.metadata.written", `{"recordId":${quoteJsonString(recordId)}}`);
returnJson(`{"ok":true,"recordId":${quoteJsonString(recordId)}}`);
return 0;
}For the bucket model and permissions, see Data Buckets and Authorization.
Contract-To-Contract Calls
contractCallJson(contractId, method, argsJson) invokes another deployed contract through the deterministic host and returns the nested method's structured return value as raw JSON.
import { contractCallJson, emitEventJson, returnJson } from "../../../assembly";
export function callTarget(): i32 {
const result = contractCallJson(
"contract-report-target",
"summarize",
"{\"limit\":10}",
);
emitEventJson("report.target.called", result);
returnJson(result);
return 0;
}The caller manifest must declare the outbound edge in calls. The target method must allow the caller contract principal in its invoke.allow policy. If the target method declares bucket requirements, the outbound edge must also declare compatible requiredBuckets.
Nested calls remain deterministic:
- target contract must be active;
- target runtime and manifest must be valid;
- target readiness must pass on the executing node;
- nested-call limits and fuel sharing apply;
- staged effects still commit only if the call stack succeeds.
Contract-Defined Migrations
Contract-defined migrations use a WASM export to transform state during a resumable migration job. The target manifest declares the migration under migrations, and the migration job references the same migrationId.
MigrationOutput builds the JSON result expected by the migration engine:
| Helper | Use |
|---|---|
writeJson(key, valueJson) | Write a value to the target contract state. |
deleteSource(key) | Delete a key from the source when the job mode supports moving. |
eventJson(topic, dataJson) | Emit a migration event. |
commit() | Return the migration output to the host. |
Example:
import { getJsonField, MigrationOutput, returnJson, storageSetJson, stringCodec } from "../../../assembly";
export function migrateV1ToV2(ptr: i32, len: i32): i32 {
const batch = String.UTF8.decodeUnsafe(ptr, len);
const output = new MigrationOutput();
output.writeJson("reports:last", `{"formatVersion":2,"source":${batch}}`);
output.deleteSource("lastReport");
output.eventJson("migration.v1_to_v2", "{\"formatVersion\":2}");
output.commit();
return 0;
}The chain executes the export through Wasmtime, validates the returned migration result, and applies writes through the migration job engine. For manifest rules, see Manifest Migrations.
Execution Budget
NOOSChain does not charge gas, but every contract call has a deterministic protocol fuel budget. Contract execution runs through the Rust Wasmtime sidecar using Wasmtime fuel metering. If a call exceeds the budget, it fails with CONTRACT_EXECUTION_LIMIT_EXCEEDED, and no staged storage writes, bucket writes, events, or return values are committed.
Design methods to be bounded:
- avoid unbounded loops;
- keep JSON inputs and outputs small;
- keep host calls small and predictable;
- split large workflows across multiple transactions;
- use manifest
maxFuelfor public or expensive methods; - avoid wall-clock time, randomness, filesystem access, network access, and node-local assumptions.
For production review guidance, see Security Guide and Security Review Checklist.
Templates
Starter templates live in apps/noos-contract-sdk-as/templates.
List templates:
npm --prefix apps/noos-contract-sdk-as run templates:listCopy a template:
npm --prefix apps/noos-contract-sdk-as run templates:copy -- --template basic-storage --out my-contractValidate and build templates:
npm --prefix apps/noos-contract-sdk-as run test:templatesAvailable templates include basic storage, bucket metadata reads, encrypted record metadata writes, C2C caller/target contracts, events and returns, and migration-enabled contracts. See Templates.
Build Examples
Install SDK dependencies:
npm --prefix apps/noos-contract-sdk-as installBuild the basic example:
npm --prefix apps/noos-contract-sdk-as run build:exampleBuild the typed DTO example:
npm --prefix apps/noos-contract-sdk-as run build:typed-exampleThe build scripts compile AssemblyScript with --runtime stub and produce WASM under apps/noos-contract-sdk-as/build.
Package And Sign
Package the typed DTO example:
npm --prefix apps/noos-contract-sdk-as run package:typed-exampleThe package command writes:
apps/noos-contract-sdk-as/examples/typed-report-contract/package/
noos-contract.json
manifest.json
build/contract.wasm
examples/It copies the compiled WASM to build/contract.wasm and writes noos-contract.json with the WASM hash and canonical manifest hash.
Package any AssemblyScript contract:
npm --prefix apps/noos-contract-sdk-as run package:contract -- --manifest examples/typed-report-contract/package/manifest.json --wasm build/typed-report-contract.wasm --out ../../contract-packages/report-store-0.1.0 --package-name report-store --package-version 0.1.0 --description "Stores report metadata and emits report events." --author-name NOOS --author-organization-id org-noos --license UNLICENSED --keyword reports --metadata-json '{"environment":"tutorial"}'Sign the typed package descriptor:
NOOS_CONTRACT_PACKAGE_SIGNER_PUBLIC_KEY="<USER_PUBLIC_KEY_PEM>" \
NOOS_CONTRACT_PACKAGE_SIGNER_PRIVATE_KEY_PEM="$(cat <USER_PRIVATE_KEY_PATH>)" \
NOOS_CONTRACT_PACKAGE_SIGNER_TYPE="user" \
NOOS_CONTRACT_PACKAGE_SIGNER_ID="user-admin" \
npm --prefix apps/noos-contract-sdk-as run sign:typed-exampleSign any package:
NOOS_CONTRACT_PACKAGE_SIGNER_PUBLIC_KEY="<USER_PUBLIC_KEY_PEM>" \
NOOS_CONTRACT_PACKAGE_SIGNER_PRIVATE_KEY_PEM="$(cat <USER_PRIVATE_KEY_PATH>)" \
NOOS_CONTRACT_PACKAGE_SIGNER_TYPE="user" \
NOOS_CONTRACT_PACKAGE_SIGNER_ID="user-admin" \
npm --prefix apps/noos-contract-sdk-as run sign:contract-package -- --package ../../contract-packages/report-store-0.1.0The signing command writes provenance/signatures.json. See Package Provenance for what the signature covers and how validators enforce provenance policy.
Generate Transaction Payloads
Generate unsigned transaction payload JSON for registry approval, code deployment, instantiation, and manifest-derived bucket access rules:
npm --prefix apps/noos-contract-sdk-as run payloads:contract-package -- --package ../../contract-packages/report-store-0.1.0 --contract-id contract-report-store --owner-user-id user-admin --organization-id org-noos --registry-id pkg-report-store-0.1.0 --metadata-json '{"name":"report-store","version":"0.1.0"}'The generated files are payloads for transactions such as REGISTER_CONTRACT_PACKAGE, DEPLOY_CONTRACT_CODE, INSTANTIATE_CONTRACT, and ADD_BUCKET_ACCESS_RULE. Submit them with the normal transaction tooling shown in Publishing.
Checklist
- [ ] Every exported method returns
0only after staging the intended effects. - [ ] Argument JSON is bounded and decoded through a clear codec.
- [ ] Storage keys are stable and namespaced.
- [ ] Events and return values contain no secrets or plaintext payloads.
- [ ] Bucket operations are declared in the manifest and have matching access rules.
- [ ] C2C calls are declared in the caller manifest and allowed by the target.
- [ ] Public or expensive methods have an appropriate manifest
maxFuel. - [ ] Package descriptor, manifest hash, WASM hash, provenance, and registry approval are reviewed before production publishing.