Build Your First Smart App
The boilerplate is a working Smart App you copy and make your own. It has two apps:
smart-app/: a NestJS backend. It reaches the cluster only through@hsuite/smart-engines-sdk.ionic-dapp/: an Angular/Ionic frontend with wallet connect, served on port 8105.
The backend runs in two modes. With no credentials it runs in open mode on in-memory stores, so everything works on your machine. With testnet credentials the same code runs in cluster mode against the validators.
Prerequisites
Section titled “Prerequisites”- Node.js 24. The boilerplate’s CI and container use it. The CLI supports Node
^22.23.4,^24.22.1or>=26.12.1. - npm. The boilerplate uses npm, not yarn.
- Docker with buildx. Only
hsuite deployandhsuite redeployneed it. - Access to the boilerplate. See step 1.
Get the boilerplate
Section titled “Get the boilerplate”The boilerplate repository is private for now. Access comes with developer access to the testnet, after the security audit. Clone it, or copy it into your own repository, then install both apps:
Terminal window # Backendcd smart-appnpm install --legacy-peer-deps# Frontendcd ../ionic-dappnpm ci --legacy-peer-depsUse
npm ciinionic-dapp/: its lockfile keeps the@hsuite/native-connect-*packages at matching versions.Install the CLI
Section titled “Install the CLI”Terminal window npm install -g @hsuite/smart-engines-cli@latesthsuite --versionRun every
hsuitecommand fromsmart-app/. The--envand--manifestpaths, and the manifest’s build context, resolve against the current directory.Run it locally in open mode
Section titled “Run it locally in open mode”Terminal window # Terminal 1cd smart-appnpm run dev # http://localhost:3005, Swagger at /api/docs# Terminal 2cd ionic-dappnpm start # http://localhost:8105With no
.env.localthe backend logsOPEN MODE: no credentials configured …once and keeps every plane in memory. Check it:Terminal window curl localhost:3005/baas/status# {"mode":"memory","appId":null}The backend’s other scripts:
npm test(offline unit tests),npm run lint,npm run buildandnpm run build:prod.npm run test:e2eneeds a live cluster and credentials.Make it yours
Section titled “Make it yours”- Name it. Set
nameinsmart-app/smart-app.manifest.json. When you subscribe,--app-namesets the name the network records, and defaults toAPP_NAMEfrom your env file. - Pick the services you use. The manifest’s
serviceslists them, fromauth,database,storage,functionsandmessaging. - Write your code. Backend logic goes in
smart-app/src/, the interface inionic-dapp/src/. Keep the backend’s cluster calls on the SDK: the deploy checks that the project is built on it. - Size it.
backend.replicasandbackend.limits(cpu,memory) in the manifest set how much it runs with.
- Name it. Set
Create a wallet with
Section titled “Create a wallet with hsuite init”hsuite initTerminal window cd smart-apphsuite inithsuite initgenerates a new XRPL testnet wallet, funds it with testnet XRP from the public XRPL testnet faucet, and writes.env.local:- the wallet:
XRPL_NETWORK,XRPL_ADDRESS,XRPL_PUBLIC_KEY,XRPL_SEED; - the cluster:
HSUITE_NETWORK,VALIDATOR_URL,SMART_HOST_URL(bothhttps://gateway.testnet.hsuite.network); - placeholder app values (
APP_ID,APP_NAME,APP_DESCRIPTION) and server settings.
Then edit
.env.local: setPORT=3005.initwritesPORT=3000, and the dapp expects the backend on 3005. If the dapp’s calls are refused with a CORS error, addhttp://localhost:8105toCORS_ORIGINS.Useful flags:
--out <file>(default.env.local),--no-fund, and--force.initrefuses to overwrite a file that already holds a realXRPL_SEEDunless you pass--force.- the wallet:
Switch to cluster mode
Section titled “Switch to cluster mode”Cluster mode needs four values in
.env.local:SMART_HOST_URL,XRPL_ADDRESS,XRPL_SEEDandSUBSCRIPTION_APP_ID.initwrote the first three. The fourth comes from a subscription, and a subscription is paid in testnet HST, whichinitdoes not give you.Terminal window hsuite faucet # testnet HST to your wallethsuite subscribe --app-name "My App" # creates the app and pays the deposithsuite faucetpays testnet HST to the wallet in.env.local. If the wallet has no HST trustline yet, it sets one first. It prints the amount, the transaction hash and what remains. When you are rate-limited it says so and exits with code 1.hsuite subscribesigns in with your wallet, creates the app, pays the HST deposit, then mints the subscription NFT and accepts it into your wallet. Creating the app can take a few minutes, so let it run. It writesSUBSCRIPTION_APP_IDandAPP_IDas soon as the app exists, andSUBSCRIPTION_NFT_SERIALonce the NFT is accepted. The tier defaults tobuilder(--tier builder|growth|scale|enterprise). If it stops part-way, run it again: it resumes the saved app.
Restart the backend after any change to
.env.local, then check it:Terminal window npm run devcurl localhost:3005/api/health# … "baas":{"registered":true,"appId":"<your appId>", … }The boot log shows
BaaS auth step 1/3 … 3/3, thenBound to existing smart-app <appId>. If only some of the four values are set, the backend is not in open mode either: every non-public route answers 503 and the log names what is missing.Develop with the SDK
Section titled “Develop with the SDK”Your business logic goes in
smart-app/src/. The boilerplate already calls the SDK’s data planes, and each call acts for the signed-in customer, throughclient.forCustomer(customerToken). The dapp signs its user in and sends that bearer with every request to your backend.The pattern the boilerplate uses import type { BaasClient } from '@hsuite/smart-engines-sdk';async function saveNote(client: BaasClient, customerToken: string, text: string) {const db = client.forCustomer(customerToken).db;const { document } = await db.insert('notes', { text });const proof = await db.getDocumentProof('notes', document._id);return { id: document._id, proof };}What the boilerplate demonstrates, and where:
Plane SDK calls Backend routes Database db.insert,db.update,db.delete,db.find,db.getDocumentProof/baas/dbStorage storage.upload,storage.downloadWithMeta,storage.listFiles,storage.delete/baas/storageMessaging messaging.publish,messaging.getHistory/baas/messagingFunctions functions.deploy,list,get,getCode,invoke,eval,getLogs,delete,pause,resume/functionsRules rules.publish,rules.listByOwner,rules.get,rules.simulate, with theRulesbuilders/rulesEntities entities.createToken,createAccount,createTopic,prepareCreateAccount,prepareCreateToken,get,delete/entities/api/healthand/baas/statusreport the app’s state. Configuration is read from.env.local; a key thatsrc/config/env-validation.tsdoes not declare is dropped at boot, so add your own keys there first.The deploy does not compile your backend: the image copies a prebuilt
dist/. Build it withbuild:prod:Terminal window cd smart-appnpm run build:prodThe deploy aborts if the image contains
.map,.d.tsor.tsbuildinfofiles, and plainnpm run buildemits them.The frontend needs its production settings. Copy
ionic-dapp/src/environments/environment.prod.example.tstoenvironment.prod.tsin the same folder and replace bothREPLACE_BEFORE_DEPLOY:*values:walletConnectProjectId: your WalletConnect project id.smartAppUrl: the public URL of your deployed backend.
While a placeholder remains, the deploy stops with
frontend still contains REPLACE_BEFORE_DEPLOY placeholders.Deploy with
Section titled “Deploy with hsuite deploy”hsuite deployThe deploy is described by
smart-app/smart-app.manifest.json. The boilerplate’s manifest:smart-app/smart-app.manifest.json {"name": "boilerplate","services": ["database", "storage"],"degree": 3,"backend": {"port": 3200,"tag": "v1","replicas": 1,"context": ".","dockerfile": "Dockerfile","env": {"NODE_ENV": "production","PORT": "3200","API_KEY_ENABLED": "false","RATE_LIMIT_TTL": "60","RATE_LIMIT_MAX": "120","REQUEST_TIMEOUT": "30000"}},"frontend": {"build": "cd ../../ionic-dapp && npm run build -- --output-path ../smart-app/public","dir": "public"}}- Required:
name,services,backend.portandbackend.tag.backend.portis the port your backend listens on inside the container, and8080is reserved. - Tags are immutable. Every new version, and every retry, needs a fresh
backend.tag. - Outbound traffic is blocked by default. A deployed app reaches DNS and the platform’s own endpoint only. To call an external API, request it in
backend.egress, for example[{ "host": "api.example.com", "port": 443 }](bare hostnames, at most 50 entries). The network may refuse a request.
The container’s environment is your
--envfile plusbackend.env. Every non-empty key in the env file is forwarded, encrypted, except the platform URLs (SMART_HOST_URL,VALIDATOR_URL,GATEWAY_URL) and local-only toggles. A non-empty value in the env file wins overbackend.env. Your.env.localholdsPORT=3005andNODE_ENV=development, so deploy from a copy with the production values:Terminal window cd smart-appcp .env.local .env.deploy# in .env.deploy: set PORT=3200 (the manifest's backend.port) and NODE_ENV=productionhsuite deploy --manifest smart-app.manifest.json --env .env.deployThe deploy needs a subscription: without
SUBSCRIPTION_APP_IDit stops at once and tells you to runhsuite subscribe. It also refuses an env file withoutXRPL_SEED. Then it:- checks that the project is built on
@hsuite/smart-engines-sdk; - signs in with your wallet;
- runs the frontend hook, checks the bundle and uploads it;
- builds and pushes the image;
- signs a build attestation with your wallet;
- deploys, and waits until the app is running.
It prints
✓ deployed: <url>and writesDEPLOYED_APP_IDto the env file. See Deployment for what the platform checks on the way.- Required:
Watch it:
Section titled “Watch it: hsuite status and hsuite usage”hsuite statusandhsuite usageTerminal window hsuite status --env .env.deploy --watchhsuite usage --env .env.deployhsuite list --env .env.deployhsuite statusprints the app’s lifecycle state (PENDING_SUBSCRIPTION,ACTIVE,SUSPENDEDorRETIRED) and, once deployed, its URL, runtime state, replicas, image, last error, declared egress and history. It ends with a check of every cluster.--watchpolls until the runtime isRUNNINGorFAILED;--jsonprints the raw status.hsuite usageprints today’s usage by category, then the usage percentage, any overage, and whether the app is blocked or in a grace period.hsuite listlists your deployed apps.
hsuite topuprefills the subscription’s prepaid credit without changing the tier.Ship changes with
Section titled “Ship changes with hsuite redeploy”hsuite redeployhsuite redeployupdates the same app with a new image tag, a new frontend bundle, or both. Rebuild first, then pass a fresh tag:Terminal window npm run build:prodhsuite redeploy --manifest smart-app.manifest.json --env .env.deploy --tag v2It targets
DEPLOYED_APP_IDfrom the env file (or--app-id <id>) and prints✓ redeployed: <url>. If the URLhsuite deployprinted differs from thesmartAppUrlyou set, updateenvironment.prod.tsand redeploy.