@0xsingularity/cli
v0.4.1
Published
The Singularity command line app. A guided terminal experience for wallets, funds, transfers and program deploys on the Singularity chain.
Maintainers
Readme
@0xsingularity/cli
The Singularity command line app — a guided terminal experience for the Singularity chain.
[!WARNING] Support ceiling (2026-08-29): This README describes CLI and repository behavior, not a live public service. The audit used base
bc7b834de9bf22aa35d0294f3dd16212c72d7500. Current remediation is an unreleased candidate with no published or deployed SHA. The last public-edge audit found the configured endpoint unavailable. Public reads and SVM transfer/deploy are PREVIEW. EVM, WASM, and Move writes are DISABLED. UTH identity and supply work remain incomplete. Agents, A2A, MCP, and validator joining are TARGET. Historical finality, latency, faucet, and rate-limit observations are not current release evidence. See the capability matrix.
.····::::::::·····
. .···::::--:::::···.
.··················:::--------:::····
.·····::::--=++*+=--:-=*oo*++===---::···
..····::::--=+#@@@o+-····:=o@@@@o*+=--::···
.·····:::--=+o@@@%*-·. ·-*@@@@%*+=-::··.
▄▀▀▀▀ ▀▀█▀▀ █▄ █ ▄▀▀▀▄ █ █ █ ▄▀▀▀▄ █▀▀▀▄ ▀▀█▀▀ ▀▀█▀▀ █ █
▀▄▄▄ █ █ █ █ █ ▄▄ █ █ █ █▄▄▄█ █▄▄▄▀ █ █ ▀▄▀
▄▄▄▄▀ ▄▄█▄▄ █ ▀█ ▀▄▄▄▀ ▀▄▄▄▀ █▄▄▄▄ █ █ █ ▀▄ ▄▄█▄▄ █ █
.············.. .·:----:::····::::-==:··
..········.. ·:=o%%%%#####%%%#*-:··..
...····.. .·:=o@@@@@@@@@@%*=-:·······...............
....... ··:-=*o%@@@#o*+=--:::················....
.... .··::-==++++===---:::::··············...
.··:::----==-----::::::·············..
entropy to order · v0.3.0That is one frame of many. It fills the whole terminal, and the eye moves.
npm install -g @0xsingularity/cli
singularityNode.js 18 or later is the only install prerequisite. The CLI retains a
historical endpoint default, but the last audit found it unavailable. Use
--endpoint with an operator-provided endpoint for controlled testing.
What you see on launch
Run singularity. Three things happen, in order.
1. The eye turns. The Singularity gravity well fills the whole terminal and spins, sixty frames a second. The core stays dark. The ring around it burns white. Spiral arms sweep out of the ring and fade into the dark.
2. The wordmark resolves. After about a second and a half the letters of SINGULARITY appear out of the dark core. The field opens around them. The tagline and the version settle underneath.
3. Setup begins. The intro hands over to the guided screens.
Press any key to skip to the end of that.
Why it looks like this
The field is not decoration invented for a terminal. well() — the gravity
well, with its void core at radius 0.12, its glowing ring at 0.30, and arms
driven by time — is ported straight from the Singularity ASCII art engine. The
terminal and the marketing surfaces draw the same shape from the same maths,
down to the density ramp.
It is monochrome, because the brand is. Bright white is the signal register. Depth comes from ASCII density, never from hue. Sparse glyphs sit back in gray, dense glyphs burn white.
When it does not animate
NO_COLOR, SINGULARITY_NO_ANIMATION, and any output that is not a terminal
all get one static frame of the eye with the wordmark already resolved, and no
timer at all. The intro can never spin against a log file, and it can never
hold a pipe open.
What you get
Run singularity with no arguments and it opens the guided app.
On a fresh machine it creates a wallet, asks you to back it up, and tries the SVM preview faucet when the configured endpoint is reachable. Then it opens the main menu.
On a machine that already has a wallet it opens straight to the main menu, with your address and the configured endpoint's balance response at the top. The read can fail when that endpoint is unavailable.
◆ SINGULARITY · Main menu · 25aHPB…b3PM
╭──────────────────────────────────────────────────────────────╮
│ │
│ Your address │
│ 25aHPBn9kToSxe7fKSj5rnwTdMjnFFGBjH13ThfQb3PM │
│ │
│ Balance 2 SING │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ ▸ Balance │
│ Fund │
│ Send │
│ Look up an account │
│ Chain stats │
│ Recent transaction │
│ Deploy a program │
│ Run a node │
│ Settings │
│ Quit │
│ │
│ What you hold right now. │
│ │
╰──────────────────────────────────────────────────────────────╯
↑ ↓ move enter select esc quitArrow keys move, enter selects, escape goes back.
Fund, Send, and Deploy are SVM technical-preview actions. Do not treat their presence in the menu as public availability.
Scripting
Give it a subcommand and it runs that command, prints the result, and exits. No screen, no prompt, no terminal required.
singularity wallet new
singularity fund 2
singularity balance
singularity transfer <TO> 1.5
singularity account <ADDRESS>
singularity stats
singularity tx <SIGNATURE>
singularity check-program target/deploy/my_program.so
singularity deploy target/deploy/my_program.so --yes
singularity node init --mode replica --disk /mnt/nvme1 --runtime-manifest <URL>
singularity node start --service
singularity doctorEvery command exits non-zero on failure and writes the reason to stderr.
deploy wasm, deploy move, and move execute are intentionally disabled
in this release. They exit before reading a module, loading a wallet, opening an
RPC session, building a transaction, signing it, or submitting it.
validator join, validator leave, agent register, and mcp invoke are
TARGET writes and also fail before those operations.
| Option | Effect |
|---|---|
| --endpoint <URL> | use another endpoint for this run |
| --url <URL> | the same, spelled the way RPC tools spell it |
| --wallet <FILE> | use another wallet file for this run |
| --force, -f | overwrite something that already exists |
| --yes, -y | answer every confirmation with yes |
| --runtime-manifest <URL> | node init: the runtime release manifest (or set SINGULARITY_RUNTIME_MANIFEST) |
The whole RPC surface
The node serves 289 JSON-RPC methods across 13 namespaces, and every one of
them is reachable from the command line. rpc calls any method by its wire
name; each namespace group calls that namespace's methods by a friendlier
kebab-case name. The lists come from the SDK's RPC manifest, which mirrors the
node's dispatch tables.
singularity rpc eto_getStats # any method, wire name
singularity rpc eth_getBalance 0xabc… latest
singularity evm get-balance 0xabc… latest # same call, friendly name
singularity evm block-number
singularity move-rpc move-view '{"function":"0x1::coin::balance"}'
singularity wasm wasm-query <CONTRACT> '{"balance":{}}'
singularity onebook get-order <ORDER_ID>
singularity hilsa chain-catalog
singularity agent-rpc a2a-list-cards
singularity explorer get-stats
singularity admin validator-set
singularity evm list # every method in a namespaceThe namespaces are svm, evm, explorer, wasm, move, admin, hilsa,
cil (deprecated eto_cil* aliases), token, onebook, workflow,
crossvm, and agent. The move and agent groups run as move-rpc and
agent-rpc, because move and agent are already commands. A parameter is read as JSON when it parses as JSON —
numbers, booleans, arrays, objects — and as a string when it does not, so
addresses need no quoting. Results print as pretty JSON, one object per run,
so the output pipes straight into jq.
To change the endpoint or the wallet file for every run, open Settings in
the app. It saves them to ~/.config/singularity/settings.json.
Offline Bombil codecs
Encode or inspect the versioned prototype formats without a wallet, settings file or RPC connection:
singularity bombil encode lifecycle-profile '{"version":1,"watchSlots":2,"maximumFillCapacity":64,"controlCapacity":1,"maximumFillsPerCommand":4}'
singularity bombil decode lifecycle-profile 0200000040010401
singularity bombil encode lifecycle-command '{"kind":"reserve","account":1,"nonce":"0","fillCapacity":2}'
singularity bombil helpencode <SCHEMA> <JSON> prints lowercase hex and decode <SCHEMA> <HEX> prints
JSON. Supply JSON as one quoted argument. Hex must have an even number of digits,
with no 0x prefix or whitespace. Uppercase input hex is accepted; output is
lowercase. Unknown or duplicate JSON fields, extra arguments, invalid integer
ranges and noncanonical wire fields fail with a nonzero exit code.
| Schema | JSON fields |
|---|---|
| lifecycle-profile | version, watchSlots, maximumFillCapacity, controlCapacity, maximumFillsPerCommand |
| lifecycle-command | kind, account, nonce, plus the operation fields below |
| registered-svm | base58 program, version, base58 tradeGrant, inputHex (0–4096 bytes) |
| request-record | Decode only: canonical BRQ1 owner-request accounting record |
| conversion-request | Decode only: canonical 96-byte BLM1 conversion request |
| representation-record | Decode only: canonical 96-byte BRP1 accounting record |
Every u64 (nonce, token, sequence, registered program version) must be a
decimal string, such as "18446744073709551615"; signs, leading zeroes and
exponents are rejected. Other integers, including lifecycle profile version,
are JSON numbers. Lifecycle operation fields are:
| kind | Additional fields |
|---|---|
| reserve | fillCapacity |
| ackFill | token, cursor, sequence, fillIndex |
| ackControl | token, sequence |
| replenish | token, fillCapacity |
| retire | token |
These commands call the SDK's canonical BLC1/profile/BSG1 codecs. They do not reserve capacity, authorize an acknowledgement, sign or submit a transaction, deploy code or activate an instance. BSG1 requires an explicitly installed registered SVM adapter; native configuration and authority checks still apply.
Owner-request records
singularity bombil decode request-record "$RECORD_HEX"
singularity bombil request inspect "$RECORD_ADDRESS" --endpoint "$RPC_URL"Offline decoding checks the exact BRQ1 version-1 layout: 252 header bytes plus
0–1024 returned bytes. It rejects reserved bytes, inconsistent lengths, zero
identities, invalid height/deadline/retention order, and compute usage above its
limit. request-record cannot be encoded through the CLI.
request inspect uses the existing endpoint settings and standard
getAccountInfo read. It does not load a wallet or send a transaction. It checks
the OneBook account owner, non-executable metadata, and the canonical PDA derived
from the record's chain domain, instance, owner and request ID. Missing accounts,
foreign accounts and RPC errors produce a nonzero exit code.
Both forms print JSON: u64 fields are decimal strings, owner is base58, and
byte fields use lowercase *Hex values. A returned terminal includes outputHex;
a reverted terminal includes its numeric code, exact Rust accounting name
and raw financialReason byte. Only code 9 (FinancialRejected) permits a nonzero
reason. These local codes are separate from the guest error ABI.
computeUsed records actual consumed compute, including failed execution; this
record does not itself charge fees. retainUntil is a retention bound, not a
promise of pruning. BRQ1 contains no replay flag. RPC inspection labels the result
rpc-account and retains the server's rpcSlot; neither offline bytes nor this
ordinary account response independently proves QMDB durability. The command does
not activate a Bombil request submission route.
What a transaction costs
Repository execution charges these technical-preview fees to the signing wallet:
| Charge | Amount | |---|---| | Per signature | 5,000 lamports (0.000005 SING) | | Per compute unit consumed | 1 lamport |
A repository test prices a plain transfer at 5,450 lamports (one signature, 450 compute units). This is not a current public fee quote.
The app never guesses the fee. Before it sends anything it asks the chain:
getFeeForMessage for the signature term and simulateTransaction for the
compute units (getFeeForMessage alone omits them). Then it checks your
balance against amount + fee and refuses with one sentence when you are short,
instead of letting the chain bounce the transaction. A simulation error stops
the send too, with the program logs, because the node runs the same preflight
on sendTransaction.
Your wallet
One file, at ~/.config/singularity/id.json, written with owner-only
permissions.
That file is the only copy of your key. The app says so in a screen you cannot skip past without acknowledging it. Nobody can restore it for you.
@0xsingularity/web3.js reads and writes the same file, so a wallet created
here works in a script and the other way round.
SVM deployment technical preview
singularity deploy <file.so> runs three gates, in order:
- The program check. This is the most valuable thing in the package. See below.
- The build tools. Missing tools are installed once, on your confirmation,
into
~/.config/singularity/toolchain. Nothing outside that directory changes and no shell profile is edited. - The deploy, driven through those managed tools over plain RPC.
Use this workflow only on a controlled endpoint whose tested SHA is known. The last audited public endpoint was unavailable, and SVM deployment is not LIVE.
The program check
The loader accepts ELF e_flags of 0x0 (SBPF v1) or the legacy 0x20
marker. It rejects the plain version encodings (1, 2, 3) that
current build tools emit by default.
Prior end-to-end evidence found that a program built with default settings deployed and then did not execute. The deploy reported no error. This is historical private evidence, not proof of a current public deploy.
singularity check-program target/deploy/my_program.soThe check runs automatically before every deploy and aborts before anything reaches the chain. This is chain issue #763.
Build so the program carries e_flags 0x0:
cargo build-sbf --arch v0--arch v0 writes 0x0. The other arch values (v1/v2/v3) write
0x1/0x2/0x3, which the chain rejects. (Older toolchains spelled this
--arch sbfv1; current cargo build-sbf rejects that and wants --arch v0.)
Disabled WASM and Move workflows
The public CLI does not currently support these workflows:
singularity deploy wasm <FILE> DISABLED
singularity deploy move <FILE> DISABLED
singularity move execute ... DISABLEDEach command exits non-zero with an explicit disabled message. It does so before reading the supplied file, loading a wallet, opening an RPC session, or building, signing, or submitting a transaction. Low-level envelope helpers in the SDK are not a promise that a production CLI route is available.
singularity deploy <FILE.so> is an SVM technical preview. EVM contract
deployment is disabled. Ethereum tooling is not a write alternative. Do not
automate any disabled command until the capability matrix records deployed
positive and negative evidence.
Running a node
Run a node in the main menu, or singularity node … from a script, sets a
Singularity node up on this machine and runs it. There are three kinds:
| Kind | What it is | What it needs |
|---|---|---|
| Consensus node (--mode consensus) | PREVIEW node software for the fixed private committee. Public validator joining is TARGET | Linux x86_64 and an NVIDIA GPU with CUDA 12+ |
| Non-consensus node (--mode replica) | Follows the chain and serves RPC. Never executes, never votes. | Linux x86_64. No GPU, no stake. |
| CPU-only node (--mode cpu) | A non-consensus node whose runtime is compiled here, CPU-only. The indexer profile. | Rust and git. Any Linux. |
singularity node init --mode consensus --disk /mnt/nvme1 --p2p-addr 203.0.113.7:19100 \
--runtime-manifest https://<release-host>/manifest.json
singularity node start --service # run it as a system service
singularity node status # height, service, identity, registry status
singularity validator status # experimental registry readRun singularity node init with no --mode (or pick Run a node in the
app) and it asks: what kind, which disk for the data, which disk for the logs,
and — for a consensus node — the public address peers dial.
Where the data goes
A node writes constantly. singularity node disks shows every mounted
filesystem with its free space, filesystem type and whether it is an SSD.
--disk MOUNT keeps the chain data in MOUNT/singularity/<network>/data and
the logs in MOUNT/singularity/<network>/logs (owner-only directories);
--data-dir and --log-dir name exact places, and the guided setup asks for
the data disk and the log disk separately so a runaway log never starves the
state store. Setup warns when the data would land on the boot disk, on a
spinning disk, or on a disk with less than 500 GB free.
Everything else the node owns lives in ~/.config/singularity/node: the
runtime, the config, the key, the environment file and the systemd unit.
When there is no Linux
The runtime is a Linux x86_64 program. singularity node env looks at the
machine and says how a node would run on it:
- Linux — directly.
--isolateputs it in an LXC container (or Docker if LXC is missing) with the chosen data and log disks passed through. - Windows — in a WSL 2 distribution named
singularity-node;node envprints thewsl --install …commands, and Docker is the fallback. - macOS — in Docker Desktop. A Mac has no NVIDIA GPU, so only replica and CPU-only nodes run there.
node start drives the container once it exists.
The runtime
Consensus and replica nodes download a released runtime named by a signed
manifest; the tarball and the genesis file are checked against their published
SHA-256 before anything is unpacked. The manifest URL is required: pass
--runtime-manifest <URL> or set SINGULARITY_RUNTIME_MANIFEST. There is no
built-in default — the host this app used to name has no DNS record, so a
default would only turn a missing setting into a confusing network error, and
node init now says exactly which flag to pass instead. The CPU-only node
clones the source (or uses SINGULARITY_SOURCE_DIR, a developer's checkout)
and runs cargo build --release --no-default-features.
No public release manifest is established by this README. The operator must provide one and verify its resulting runtime SHA against release evidence.
Settings that must match the fleet
node init writes the consensus-relevant epoch length into
~/.config/singularity/node/config/node.env, marked # Must match the fleet.:
| Variable | Default | Why it must match |
|---|---|---|
| SVM_SLOTS_PER_EPOCH | the manifest network's slots_per_epoch, else 432000 | release consensus accepts only 432000; shorter schedules are fresh debug-testnet overrides |
Release consensus pins the value to 432000. Coordination cannot authorize
another value. The CLI fallback matches that release pin. A release node with
an incompatible manifest fails at startup. Short schedules are valid only for
fresh debug testnets.
Protocol v8 has no Hilsa activation setting. Observer/CILS ingestion is part of
the protocol, while durable interop roots, proofs, attestations, mint/burn,
outbox, and bridge value are safety-paused. The CLI therefore does not emit the
legacy ETO_CIL_ENABLED or ETO_CIL_MINT_ENABLED variables. Adding either by
hand does not enable those capabilities.
The consensus key
One 32-byte file, ~/.config/singularity/node/keys/consensus.key, owner-only,
written once. It is the only copy of the validator's identity. node
status prints the public keys derived from it; the secret is never printed.
validator join and validator leave are TARGET and disabled. They fail
before reading this key, loading a wallet, opening a session, building a
transaction, signing, or submitting. validator status retains an experimental
read of eto_validatorStatus and eto_validatorSet. It does not make joining
publicly available.
Other things worth knowing
- The token is SING. Every amount this app prints is in SING.
- Prior private runs observed one-hop, sub-second finality. No current public deployed-SHA artifact proves it.
- SVM preview deploys use plain RPC. This is repository behavior, not a current public service claim.
- Preview fee rules are 5,000 lamports per signature plus 1 per compute unit. See What a transaction costs.
- Repository caps are 1 MiB per transaction, 65,535 bytes per instruction, 100 addresses per multiple-account call, and 256 calls per batch.
Develop
The CLI depends on @0xsingularity/web3.js by path in this repository
(file:../singularity-web3), so build the library before you install the
CLI — npm install here takes the library's dist/ as it is on disk:
(cd ../singularity-web3 && npm install && npm run build)
npm install
npm run typecheck
npm test # 513 tests, none of them touch the network
npm run buildRebuilt the library? Run npm install here again to pick the new dist/
up. Publishing swaps the path for a registry range; see ../RELEASING.md.
The logic lives in plain TypeScript modules under src/core and
src/commands. The screens under src/ui are presentation only, built with
ink. That split is why the wallet, the
amounts, the faucet errors, the program check and the deploy path are all
covered by tests that need neither a terminal nor a network. Focused command
tests also prove that disabled WASM, Move, validator, Agent, and MCP write
routes never reach their wallet, session, build, or submission layers.
Licence
MIT.
Local representation backing
singularity bombil decode conversion-request "$BLM1_HEX"
singularity bombil decode representation-record "$BRP1_HEX"
singularity bombil conversion inspect "$BINDING_JSON" --endpoint "$RPC_URL"BLM1 is a retained-call request, not a binding record or authority. BRP1 records last reconciled accounting and the expected policy's fingerprint. Both formats are exactly 96 bytes and support decoding only. Amounts, nonces, accounted collateral, retirement and U256 surplus print as exact decimal strings; all amounts are raw base units. EVM addresses/hashes print as lowercase hex and SPL public keys as base58.
Supply the expected deployment policy as one flat JSON object with these exact fields. The CLI does not discover or attest an approved policy:
| Fields | JSON values |
|---|---|
| chainDomainHex, instanceIdHex, vaultSha256Hex, tokenKeccak256Hex, tokenBalanceMappingSlotHex | 32-byte hex, without 0x |
| vaultHex, tokenHex, routeHex | Distinct nonzero 20-byte hex addresses |
| administrator, mint | Nonzero canonical base58 public keys |
| evmChainId | Nonzero canonical decimal u64 string |
| decimals | Integer number from 0 to 255 |
Inspection uses existing endpoint configuration and one standard
getMultipleAccounts response, without loading a wallet or sending a
transaction. It derives the singleton record PDA from instance/mint, checks the
OneBook-owned non-executable BRP1 row and complete binding fingerprint, then
checks classic SPL mint metadata/authority, ERC-20 collateral and vault storage,
and actual vault/token code pins. Wrong owners, malformed rows, missing backing,
policy mismatches and RPC errors fail with a nonzero exit code.
Output is tagged observation: "rpc-accounts" and policy: "supplied-binding".
It includes derived addresses, rpcSlot, recorded accounting, current
mintSupply, actualCollateral, and three explicit checks. A successful read
can contain false checks; it does not certify healthy backing. Impossible
retirement/surplus differences are null. lastReconciledSupply is separate
from current supply: direct SPL burns can increase observedRetiredLocked and
pendingRetirement without granting credit. retiredLockedFloor cannot be
reduced or recovered by the v1 adapter. Recorded unallocatedSurplus and current
observedUnallocatedSurplus remain U256 values; donations are not redemption
credit.
The shared RPC slot is not an independent atomic/durable snapshot, deployment attestation or conversion authorization. This tooling does not activate the inactive dispatcher or implement funding transactions.
