Skip to content

CLI

The SDK is the programmatic surface. The hsuite CLI is the human one: a packaged command-line tool that wraps the SDK so you can bootstrap a wallet, subscribe, and deploy without writing a line of code. It ships as @hsuite/smart-engines-cli and installs the hsuite binary onto your PATH.

Terminal window
npm install -g @hsuite/smart-engines-cli

This puts the hsuite command on your path. Run hsuite with no arguments to see the full help. The current line is @hsuite/[email protected] — check yours with hsuite --version, and keep it current with npm install -g @hsuite/smart-engines-cli@latest. The CLI tracks the SDK major: 3.x pairs with SDK 10 / 11 / 12 / 13, 2.x paired with SDK 8 / 9.

What has shipped since the 2.0.0 major (which added hsuite subscribe --pay-with xrp — pay a subscription in XRP — and dropped hsuite governance simulate / hsuite hist-balance query, after the validator raw tier was retired from the public SDK in 8.0.0):

Release What changed
2.1.0 hsuite status / hsuite list surface the app’s public URL and newest-first deploy history; hsuite billing prints the billing history; before paying any deposit, subscribe / topup / upgrade verify the deposit wallet’s address through an independent validator and refuse (nothing paid) on a mismatch
3.0.0 major hsuite verify now checks certs only against the validators’ HCS-anchored registry rows (new --network / --registry-anchor; --registry-hosts / --min-agree removed); new hsuite customer-scope; subscribe / upgrade / usage quote prices in US dollars first, then the HST figure at its conversion rate
3.0.1 / 3.0.2 Patches — deploy image-copy and --skip-push login fixes, and a Node-engines floor bump (^22.23.4 || ^24.22.1 || >=26.12.1)
3.1.0 / 3.2.0 / 3.3.0 SDK range moved to ^11, ^12, then ^13; no CLI command, flag, or output changed — no CLI command calls a method SDK 11 / 12 / 13 altered

The CLI authenticates, subscribes and pays with an XRPL wallet, so as a smart-app developer you never need Hedera credentials.

HTTPS by default, one explicit insecure opt-in

Section titled “HTTPS by default, one explicit insecure opt-in”

The CLI is the tool that mints and carries your credentials (hsuite init, hsuite subscribe), so it refuses to send them over plain http:// to a public host. Every command that takes a --server / --gateway routes through one shared transport rule — nothing derives “is this safe?” from the URL scheme alone.

Target Behaviour
https://… Accepted
http://localhost, http://127.0.0.1, http://[::1] Accepted — the local-dev flow needs no flag
http://<service>.svc / .cluster.local Accepted — an in-cluster service name that can’t exist on the public internet
any other http://… Refused before the client is built and before any credential is signed

A refusal names the opt-in. When you genuinely need plaintext to a host you operate yourself, opt in explicitly — one global mechanism, either spelling:

Terminal window
hsuite --insecure deploy --manifest ./app.json # global flag, before or after the subcommand
HSUITE_ALLOW_INSECURE=1 hsuite deploy # env-var form; only the exact value 1 counts

--insecure is registered once on the root program, so it applies to every command in the same invocation.

Terminal window
hsuite init # generate XRPL testnet wallet + fund it with test XRP + write .env.local
hsuite subscribe --tier <tier> # register/mint a subscription against the cluster
hsuite deploy --manifest <file> # deploy a smart-app from a JSON manifest
flowchart LR
  A["hsuite init"] -->|writes .env.local| B["hsuite subscribe"]
  B -->|active subscription| C["hsuite deploy"]
  C --> D["Smart-app running"]

Generates an XRPL testnet wallet, funds it with test XRP from the XRPL test network’s own faucet (run by Ripple — not the HSUITE faucet), and writes credentials to an env file.

Option Default Purpose
--out <file> .env.local Output env file path
--network <network> testnet XRPL network the faucet funds from (testnet | devnet)
--hsuite-network <name> $HSUITE_NETWORK, else testnet Which HSUITE cluster/gateway to target (testnet | mainnet | devnet) — orthogonal to --network above
--gateway <url> resolved from --hsuite-network Explicit gateway URL override; wins over --hsuite-network / $HSUITE_NETWORK
--no-fund — Skip the XRPL faucet step
--force — Overwrite an existing env file

Mints a subscription NFT against the cluster’s /api/v3/subscription/* surface.

Option Default Purpose
--env <file> .env.local Env file with XRPL creds
--gateway <url> VALIDATOR_URL from env Validator/gateway URL
--tier <tier> builder builder | growth | scale | enterprise
--app-name <name> — Smart-app display name
--app-id <id> SUBSCRIPTION_APP_ID from env Resume an existing entity instead of creating one (see below)
--no-autopay auto-pay Create the entity and print the deposit instructions, but do not pay; pay by hand, then re-run hsuite subscribe
--pay-with <asset> hsuite Asset to pay the deposit in — hsuite (the default), xrp, or a swap asset (btc, eth, usdt, …). XRP and swap-asset payments are built, not yet enabled: a validator with that payment switched off refuses (xrp_quote_unavailable for XRP) and nothing is created. With xrp the validator quotes the price in XRP and the deposit is an XRP payment carrying the order’s Destination Tag, which the platform converts into HST. With a swap asset the CLI prints where you send it; it never pays it
--pay-network <network> — Swap assets only: the network the asset travels on when it has several (e.g. --pay-with usdt --pay-network trx)
--refund-address <address> — Required for a swap asset: your address on that asset’s network, where funds return if the swap cannot complete
--refund-extra-id <memo> — Swap assets only: the memo / tag of --refund-address, for assets whose addresses take one
--nft-issuer <address> SUBSCRIPTION_NFT_ISSUER from env Optional override for the XRPL subscription-NFT issuer; normally resolved automatically

Re-running subscribe resumes instead of erroring. Pass --app-id <id> (or set SUBSCRIPTION_APP_ID, which subscribe saves for you) to re-run against an entity that already exists. The CLI says it is resuming, asks /status where the subscription stands and carries on from there: an already-active subscription or minted NFT skips the payment and goes on to accept the subscription NFT offer, a credited deposit skips the payment and mints, a pending deposit is paid — so an interrupted run (e.g. the NFT offer left unaccepted) can be finished by simply running the command again. A resumed entity keeps the tier and name it was created with: --tier and --app-name are ignored. The CLI also accepts the specific subscription NFT by its NFTokenID, so a dev wallet that already holds other NFTs no longer trips a false “already accepted” and skips the accept.

A create whose outcome is unknown does not orphan the entity either. Creating the entity is a long ceremony and the client gives up after 180 seconds, so a timeout or a dropped connection can leave an entity the CLI never heard back about. subscribe records the create (SUBSCRIPTION_PENDING_CREATE in your env file) before it sends it; the next run asks the platform whether that entity exists and resumes it if it does. It creates a fresh entity only when the platform shows none and the earlier signed create should have run out (about 13 minutes); until then it refuses and says when to retry. A create the platform plainly rejected, or one that never left your machine, leaves nothing pending, so the next run creates at once.

A failed payment does not orphan the entity. subscribe saves SUBSCRIPTION_APP_ID (and APP_ID) the moment the smart-app entity is created — before any payment is attempted — so if the payment fails (say, a wallet holding no HST) the next hsuite subscribe resumes that entity instead of creating a second one, and the error names the entity and the exact command to resume it. Before a resumed run pays anything it asks the platform where the subscription stands (GET /status) and does not pay a deposit the platform reports as credited. That check is advisory, not a guarantee: only the cluster that holds the deposit account verifies a payment on read, so through the gateway another cluster can still answer “pending” for a payment made a minute ago. So, before it signs a payment, the CLI also reads the deposit account’s HST balance on the ledger (which every cluster agrees on) and refuses to pay when the account already holds what the quote asks. If either check fails, or the platform answers that it has no such subscription, nothing is paid. So after --no-autopay you can pay by hand and re-run hsuite subscribe — but give the payment about a minute to be credited first.

The instructions --no-autopay prints carry the order’s Destination Tag (and the deeplink’s dt=): the platform attributes a payment to its order by that tag, so a hand-made payment must carry it. A quote past its deadline is re-quoted before it is shown.

The balance check cannot tell whose HST an account holds, so the CLI also records the hash of the payment it signs (SUBSCRIPTION_PENDING_PAYMENT in your env file) before it submits it, and a resumed run asks the ledger what became of that payment first: one that validated is never paid again, even when the quote was re-priced in the meantime; one that failed, or that can no longer validate, is paid afresh.

If a payment was submitted but its outcome is unknown (a timeout, a dropped connection), or it validated but a later step failed, the error says so and offers hsuite subscribe --app-id <id> --env <file> --no-autopay: that run can never pay, and still mints and accepts the NFT as soon as the deposit is credited.

hsuite deploy is the single, end-to-end path from a JSON manifest to a smart-app live on every cluster. One command: parse + validate the manifest, gate on a framework/SDK check, tar and upload any frontend SPA bundle, build and push the backend image, fan the pushed image out to every cluster, sign a build attestation, then deploy. It writes the resulting app id to DEPLOYED_APP_ID in your env file.

Terminal window
hsuite deploy --manifest smart-app.json
flowchart LR
  M["Parse + validate manifest"] --> F["tar + upload frontend bundle"]
  F --> B["docker build + push backend"]
  B --> D["fan image out to every cluster"]
  D --> A["sign build attestation"]
  A --> W["wait for runtime to settle"]
  W --> L["report RUNNING + live URL"]
Option Default Purpose
--manifest <file> required JSON manifest path
--env <file> .env.local Env file with auth creds
--project <dir> . Smart-app project root (SDK/framework detection)
--gateway <url> the $HSUITE_NETWORK gateway, else testnet Smart-engines gateway URL
--subscription-app-id <id> SUBSCRIPTION_APP_ID Subscription appId to deploy to (also the auth identity)
--strategy <strategy> rolling rolling or recreate. The platform currently rolls every update; recreate is accepted and applied as a rolling update
--docker-host <host> $DOCKER_HOST Docker host for build/push
--skip-build false Image already built locally
--skip-push false Image already at the registry
--skip-distribute false Skip the multi-cluster image fan-out (single-cluster / dev registry)
--skip-cluster-check false Skip the post-deploy per-cluster convergence check. Only for a cluster set you know is degraded
--allow-open-mode false Deploy on purpose with no XRPL_SEED in the container (open mode). Without it, a subscribed deploy with no seed fails
--no-wait wait Exit as soon as deploy() is accepted; skip blocking on RUNNING/FAILED
--non-interactive false CI mode (no-op — the flow is already headless)

Why the fan-out? The CLI distributes the pushed image to every cluster itself — synchronously, before reconciling — and re-verifies the exact digest on each one, aborting if any cluster can’t serve it. Use --skip-distribute only against a single-cluster or dev registry.

Your .env.local rides along. deploy (and redeploy) forward every variable in your env file into the deployed container (encrypted at rest), so any custom app var “just works” with no CLI flag. That includes XRPL_SEED: the deployed app authenticates with it, and a subscribed deploy without it fails unless you pass --allow-open-mode. The CLI withholds only the platform-resolved connection URLs (SMART_HOST_URL / VALIDATOR_URL / GATEWAY_URL), local-only toggles (DEV_MODE / ALLOW_INSECURE) and its own pending-subscription markers (SUBSCRIPTION_PENDING_*). In their place the platform injects the in-cluster host URL plus an APP_TOKEN for BaasClient.fromEnv(process.env); host-side acceptance of APP_TOKEN is built, not yet enabled. See Authentication → deployment-context auth.

A deploy manifest is one canonical JSON shape: name, a services[] array (the BaaS services the app uses), a required backend block, and an optional frontend block for a SPA.

smart-app.json
{
"name": "my-smart-app",
"degree": 3,
"services": ["database", "storage"],
"backend": {
"port": 3000,
"tag": "v1",
"replicas": 1,
"context": ".",
"dockerfile": "Dockerfile",
"limits": { "cpu": "500m", "memory": "512Mi" },
"egress": [{ "host": "api.example.com", "port": 443 }]
},
"frontend": {
"build": "ng build",
"dir": "dist/app"
}
}
Field Required Notes
name yes Smart-app name
services yes BaaS services the app uses (array)
degree no Clusters to deploy across (integer, >= 3)
backend.port yes Container listen port (1..65535, never 8080 — reserved for the edge sidecar)
backend.tag yes Image tag (the part after the colon in the registry ref)
backend.replicas no Pods per cluster
backend.context / backend.dockerfile / backend.build / backend.limits no Build context, Dockerfile path, pre-build command, CPU/memory caps
backend.egress no External hosts the app needs to reach — see below
frontend.dir / frontend.bundlePath conditional A frontend needs one (bundlePath — a pre-built tarball — wins); frontend.build runs first

Reaching the outside world (backend.egress)

Section titled “Reaching the outside world (backend.egress)”

A deployed smart-app’s network egress is default-deny: without a declaration it can resolve DNS and talk to the platform BaaS endpoint, and nothing else. To call an external API, an RPC endpoint or an inference service, name it:

"egress": [
{ "host": "api.example.com", "port": 443 },
{ "host": "rpc.example.net", "port": 8545 }
]
  • host is a bare lowercase hostname — no scheme, no path, no :port suffix, no wildcard, no IP literal. port defaults to 443.
  • How many you may declare depends on your subscription tier (builder 3 → enterprise 50). A longer list is rejected at deploy with the cap named.
  • Declaring a destination is a request, not a grant: each cluster operator keeps an allow-list of destinations permitted there, and a host outside it is refused. If a declared host is not reachable from your running app, ask the operator whether it is on the cluster’s allow-list.
  • Omitting egress leaves your previous declaration in place — redeploying from an older manifest never silently revokes working egress. Declare an explicit "egress": [] to clear it.

Once an app is deployed, manage it across its lifecycle. Most share --env, --gateway, and --app-id (flag → DEPLOYED_APP_ID → SUBSCRIPTION_APP_ID); hsuite list takes no --app-id.

Command Does
hsuite redeploy --manifest <f> Redeploy an existing app (same appId). --tag override, --strategy, the --skip-* flags, --allow-open-mode, --no-wait (waits for the runtime to settle by default, same as deploy)
hsuite update Patch runtime config: --cpu, --memory, --degree, --replicas, --strategy, --env-set KEY=VAL
hsuite suspend Suspend the app
hsuite resume Resume a suspended app
hsuite delete Retire the app. Signs an owner retire proof with your wallet (XRPL_SEED / XRPL_ADDRESS from your env, as written by hsuite init) and sends it with the request; the host re-verifies it against the app’s registered owner before tearing anything down. Asks for confirmation; --yes skips it. Without an interactive terminal it refuses unless you pass --yes
hsuite status Lifecycle + runtime status (--watch to poll until settled, --json)
hsuite list List your deployed apps (--json)

The CLI mirrors the SDK’s SubscriptionClient for day-2 operations. Most accept --env, --gateway, and --app-id:

Command Does
hsuite renew Extend the subscription lock period (--additional-days <n>). When renewal is paid it shows the renewal order instead (amount, deadline, tag); --pay-with hsuite|xrp pays it
hsuite topup Re-fund a drained prepaid float in place — same tier, no tier change (--amount <int>, --no-autopay)
hsuite upgrade --to-tier <T> Upgrade tier (may require a top-up: prints the amount, the Destination Tag — also in the deeplink as dt= — and the deadline; an expired quote is re-quoted first)
hsuite downgrade --to-tier <T> Schedule a downgrade for the next renewal
hsuite tier-change cancel Cancel a pending tier change
hsuite usage Print today’s usage + overage info
hsuite billing Print the billing history (deposits, upgrades, top-ups, renewals)

Your subscription deposit is a prepaid float: it’s seeded once when the app activates and only ever drops as usage settles against it. When it hits zero the platform scales the app to zero replicas (“subscription not active”) — and nothing re-funds it automatically (renew extends the lock but takes no fresh deposit). hsuite topup is the way back without changing tier: it re-funds the existing float in place.

Terminal window
# Re-fund one month of the current tier's credit and auto-pay it in HST.
hsuite topup
# Top up a specific amount (HSUITE smallest-units, non-negative integer).
hsuite topup --amount 5000000
# Just get the deposit address + amount back; pay it yourself later.
hsuite topup --no-autopay

It POSTs to the ownership-guarded /api/v3/subscription/topup, which records the pending credit and returns the app’s existing deposit wallet plus the amount to send. By default the CLI then auto-pays that credit in HST — the SendMax covers the 0.25% transfer fee — and polls until the float is back above zero. Pass --no-autopay to only request the credit and print the deposit address + amount instead of paying.

Option Default Purpose
--amount <int> one month of the current tier Credit in HSUITE smallest-units (non-negative integer)
--no-autopay auto-pay Only request the credit + print the deposit address; don’t pay
--gateway <url> VALIDATOR_URL from env Validator/gateway URL
--env <file> .env.local Env file with XRPL creds + subscription appId
Command Does
hsuite faucet Drip testnet HSUITE (HST) to your dev wallet; sets the HST trust line if needed (--chain, default xrpl). See Faucet
hsuite build / hsuite sign / hsuite push CI building blocks behind deploy — run the project build, assemble + sign a build attestation, and docker login + push the backend image
hsuite personhood verify Personhood (HPP) proof verification against the cluster
hsuite verify Verify a post-quantum attestation. As of CLI 3.0.0 it checks certs only against the validators’ HCS-anchored registry rows (read from the public Mirror node): choose the anchor with --network <name> (default $HSUITE_NETWORK, else testnet) or a trusted --registry-anchor <file>; the old --registry-hosts / --min-agree are gone. SDK 8.1 onward also requires --max-age <ms|infinity>
hsuite customer-scope Print the app’s effective customer-scope policy (strict | permissive) and whether it is explicit or the platform default; hsuite customer-scope strict|permissive sets it. hsuite deploy reads the same policy and prints a non-blocking warning when it is permissive