Node specification
Bellchain has exactly one specification for nodes. The 25 official nodes are built exactly the way community nodes will be built later. Once you understand how the RSI works, you can write your own node. The packages are @bellchain-app/node-spec (types, defineNode(), time series, chain format) and @bellchain-app/nodes (the official nodes), both MIT licensed.
A node at a glance
Section titled “A node at a glance”import { defineNode, l, series } from '@bellchain-app/node-spec';
export default defineNode({ id: 'community.hull-ma', version: '1.0.0', category: 'ind', label: l('Hull Moving Average', 'Hull Moving Average'), description: l('Schneller Durchschnitt mit wenig Verzögerung', 'Fast average with little lag'), inputs: [{ id: 'in', type: 'series', label: l('Wert', 'Value') }], outputs: [{ id: 'out', type: 'series', label: l('HMA', 'HMA'), unit: 'input' }], settings: { length: { type: 'number', label: l('Perioden', 'Periods'), default: 21, min: 2, max: 500, integer: true }, }, // How the node reads on the board sentence: ({ settings }) => `HMA ${settings.length}`, // Candles needed before the first reliable value warmup: ({ settings }) => settings.length + Math.floor(Math.sqrt(settings.length)), // Pure: no network, no storage, no clock compute({ inputs, settings }) { return { out: series.hma(inputs.in, settings.length) }; }, // Required: known values every version is tested against tests: [{ fixture: 'binance-btcusdt-1h-500', settings: { length: 21 }, expectLast: { out: 81372.42333679654 } }],});defineNode() checks the definition right away and reports all problems at once. TypeScript derives the types of compute() from pins and settings: settings.length is a number, inputs.in a number series.
Id, version, category
Section titled “Id, version, category”idwith a namespace:core.rsi,community.hull-ma.corebelongs to the official nodes.versionas a semantic version. If the result changes, the major version goes up; chains can pin it withcore.rsi@1.category:srcsources,indindicators,condconditions,logiclogic & timing,datadata & text,actactions,tooltools.
Every visible text exists in German and English, built with l('Kurs', 'Price'): name, description, help, every pin, every setting, every option. If a language is missing, defineNode() rejects the definition.
| Type | Content | In the editor |
|---|---|---|
series |
number series, one value per candle; null means “no value yet” like na in Pine |
round |
signal |
true or false per candle | square |
text |
message with placeholders, only from the message template to an action | diamond |
Inputs can be optional: true or grow: variadic: { min, max } creates the pins in0, in1 … Outputs can carry a unit: price appears as a price, percent with a percent sign, input like the input.
Settings
Section titled “Settings”| Kind | Fields |
|---|---|
number |
default, min, max, step, integer, unit |
select |
default, options: [{ value, label }] |
text |
default, minLength, maxLength, pattern, format (timezone or url), required, multiline, placeholder |
All of them have label, optionally help and advanced: true (shown under “More”). The default must respect its own limits.
Computing with compute()
Section titled “Computing with compute()”compute() receives whole series and returns whole series, one per output. For every computing node:
- Pure and deterministic. No network, no storage, no clock, no randomness. That’s what lets Bellchain share results across chains; the engine computes RSI 14 on BTC/USDT 1h once for everyone.
- No look-ahead. The value at candle i depends only on candles up to i. What a candle shows never changes again (no repainting).
- Don’t modify inputs.
- Every output series has exactly as many values as there are candles.
The time-series functions in series follow Pine: sma, ema, rma, wma, hma, rsi, macd, stdev, bb, highest, lowest, roc, change, crossover, crossunder, cross.
Memory, sources and actions
Section titled “Memory, sources and actions”stateinstead ofcompute()for nodes with memory, such as the cooldown:init()andstep()compute candle by candle, the state is JSON. That’s why replay and live operation match.sourcefor sources:{ kind: 'candles', market(settings) }declares exchange, pair and timeframe; the engine loads the data, never the node.effectfor actions:{ kind: 'notify', channel: 'telegram' }or{ kind: 'log' }. Actions don’t compute; the engine fires on a rising edge and respects the cooldown.
Messages and placeholders
Section titled “Messages and placeholders”Messages are filled in only when they fire, in the recipient’s language and time zone:
| Placeholder | German | Content |
|---|---|---|
{{chain}} |
{{kette}} |
name of the chain |
{{values}} |
{{werte}} |
values of the nodes involved, such as “RSI 14: 27.69 · Price: $77,788” |
{{price}} |
{{kurs}} |
closing price of the candle |
{{pair}} |
{{paar}} |
trading pair such as BTC/USDT |
{{coin}} |
base currency such as BTC | |
{{time}} |
{{zeit}} |
candle close |
{{a}} to {{d}} |
values at inputs A to D of the message template |
Unknown placeholders stay as they are so that typos stand out.
Required tests
Section titled “Required tests”Every node that works with prices ships tests in its manifest: fixed price data in, known values out. The reference values of the official nodes come from an independent recalculation, not from the code they test. The test kit also checks determinism and causality.
Publishing your own nodes, with a scaffolding tool, a playground and provenance, comes in a later stage. The complete description for developers is in the repository at docs/node-spec.md.