Upload and call a module
Upload
Section titled “Upload”The Hub stores the artifact, recomputes its content address and rejects any mismatch. Uploading needs a
session token or an API key with modules:write; it does not need developer enrollment.
Develop → Create → Work → Upload .hbc (max 16 MiB). The module then appears in your module list and in the Explorer search.
import base64from necter_dev import api, login # helper from "Sign in and enroll"
tok = login()hbc = open("dist/my_module.hbc", "rb").read()m = api("POST", "/v1/modules", tok, json={"hbc_b64": base64.b64encode(hbc).decode()})print(m["manifest_address"], m["stateless"], m["functions"])# $TOKEN = a session token or an nk_test_… API key with modules:writejq -n --arg b "$(base64 < dist/my_module.hbc | tr -d '\n')" '{hbc_b64: $b}' \ | curl -s -X POST https://testnet-rpc.necter.network/v1/modules \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' --data-binary @-The response is the module record:
{ "manifest_address": "0xa99c85c5c7c08f302256cad48b51d7c363a6fcea09d5d48e570c711eed8e6d90", "name": "hashchain_worker", "language": "rust", "functions": ["work"], "hbc_size": 79150, "stateless": true, "uploader": "0x78248efc7f73d98b5eba157d2c719a5aa9e9c1ff", "exec_count": 1, "deployed_at": 1791464337, "manifest": { "compiler": "hivec-rs/0.2.0", "functions": ["work"], "language": "rust", "name": "hashchain_worker", "runtime": "hive-wasm-v1", "manifest_address": "0xa99c…6d90" }}409 already_exists means a module with this exact address is already deployed: it is ready to use.
stateless: true means it can be a committee worker. Read any module with
GET /v1/modules/{address}, list them with GET /v1/modules?developer=0x…, and download the artifact with
GET /v1/modules/{address}/hbc (verify the address yourself).
Call through validator consensus
Section titled “Call through validator consensus”POST /v1/execute{"module": "0x…", "function": "name", "input": <string or JSON>, "gas_limit": 1000000}- No authentication needed (60 requests per minute per IP); with an API key (
tasks:submit) the key’s budget applies. input: a string is passed byte for byte; any other JSON value is passed as canonical JSON (no floats).gas_limit: default 1,000,000, maximum 50,000,000.
curl -s -X POST https://testnet-rpc.necter.network/v1/execute -H 'content-type: application/json' \ -d '{"module":"0x5b89b7c4f37ab4cf68f30999424ee32775d5c137d35c066e8ce9ce75563887bb","function":"multiply","input":{"a":7,"b":6}}'{ "success": true, "output": "{\"result\":42}", "error": null, "events": [], "gas_used": 100472, "receipt_hash": "0x05bda0ee3c3791dabc033c3e5686059e9895ad07df86cdaf1d11896a1bd577b4", "round_id": "exec:0xd28e56c2a1a7ea740e68e9f07f3706a65dc7528f87d97594ee9dd84846a9ba21", "round": { "issued_at": 1791472859, "nonce": "604d35257779249f4d01d558442e07bd" }, "agreeing": 3, "quorum": 3, "consensus": { "state": "finalized", "validators": 4, "quorum": 3, "agreeing_nodes": ["ndsr-11c5d098b068068d", "ndsr-246d0603c8cf3d65", "ndsr-b5afa746863644ff"], "tally": { "0x05bda0ee…77b4": 3 }, "nodes": { "ndsr-64e1cb9f4fef3c60": "still running (quorum already reached)", "…": "voted 0x05bda0ee…" }, "ordering": { "scope": "pure", "fence": null } }}A call whose module fails (abort, trap, out of gas) still returns 200 with "success": false and the
error message: it was executed and agreed on. Errors of the request itself:
| Status | error |
Meaning |
|---|---|---|
| 400 | invalid_field, bad_request |
Missing input, bad address, gas_limit out of range, function not exported |
| 404 | not_found |
Unknown module |
| 413 | too_large |
Body over 256 KiB |
| 429 | rate_limited |
Wait Retry-After seconds |
| 502 | no_consensus |
Validators disagreed (a non-deterministic module) or votes were invalid |
| 503 | unavailable |
Module busy (another round holds it) or validators syncing |
| 504 | timeout |
No quorum of answers in time |
From a backend: hivejs
Section titled “From a backend: hivejs”hivejs wraps /api/execute, which the RPC host serves with the same behaviour as /v1/execute. It runs in
Node 18+ (any runtime with fetch):
import { Hive, HiveExecutionError } from 'hivejs'
const math = new Hive('0x5b89b7c4f37ab4cf68f30999424ee32775d5c137d35c066e8ce9ce75563887bb', { gatewayUrl: 'https://testnet-rpc.necter.network',})const res = await math.call('multiply', { a: 7, b: 6 })console.log(res.data, res.gasUsed, res.receiptHash)try { await math.call('divide', { a: 1, b: 0 })} catch (e) { if (e instanceof HiveExecutionError) console.log('failed on-chain:', e.message, e.gasUsed) else throw e}{ result: 42 } 100472 0x05bda0ee3c3791dabc033c3e5686059e9895ad07df86cdaf1d11896a1bd577b4failed on-chain: HiveJS: execution failed: guest abort: division by zero 72890res.data is the output parsed as JSON, res.output the raw string, plus res.events, res.receipt,
res.nodeId, res.timestamp. Errors: HiveExecutionError (the module ran and failed; .receipt,
.gasUsed), HiveRequestError (4xx; .status, .body), HiveNetworkError (5xx, network, timeout).
call is never retried automatically. Options: gasLimit, timeout (30 s), retries (GETs only),
headers, fetch.
Calls carry no identity
Section titled “Calls carry no identity”Neither /v1/execute nor hivejs sends a caller identity, so a module cannot know who called it. If a function
must be restricted, have the user sign a message (EIP-191) and verify the signature inside the module
(see Data validation & attestation) or in your backend before calling.