Skip to content
OpenRaiPublic

About

🚧WORK IN PROGRESS🥦 - A Nano ($XNO) payment runtime that turns raw node RPC into reliable payment detection, settlement, and events.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

194 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Important

This project is still under heavy development. The vision is set, but the API & SDK is still not stable. Please don't use for production quite yet.

RaiFlow

RaiFlow is the runtime layer Nano app developers should have had from the start.

RaiFlow sits between your app and one or more Nano nodes and turns that low-level mess into an application-facing runtime.

Your app -(RaiFlow SDK)-> Runtime -(JSON-RPC)-> Nano node(s)
                          |
                          |- invoice domain
                          |- wallet domain
                          |- custody engine
                          |- persisted events
                          `- RPC failover + confirmations

Adnan showing the move from raw Nano JSON-RPC to the RaiFlow runtime layer

The Pitch

Without RaiFlow, applications often ends up owning too much Nano-specific machinery:

  • raw JSON-RPC calls
  • node failover and reconnect logic
  • WebSocket confirmation handling
  • account frontier tracking
  • work generation and block publish flow
  • receivable detection and receive flow
  • payment matching and webhook plumbing

With RaiFlow (through its SDK) your app talks to a runtime that already understands those concerns:

  • create invoices
  • operate managed or watched accounts
  • send XNO safely with idempotency
  • publish pre-signed blocks
  • subscribe to persisted events
  • deliver webhooks from one place

The idea is simple: you build app logic. RaiFlow carries the Nano runtime logic for you.

Adnan (عدنان), the Nano camel above, is the repo mascot for that shift.

What RaiFlow IS

RaiFlow is a self-hostable Nano runtime (typically deployed as an app container) for two jobs that instinsically belong together:

  • getting paid
  • operating a wallet

Those jobs map to two domains in one runtime:

  • Invoice domain: create payment expectations, detect matching payments, manage lifecycle, and sweep funds
  • Wallet domain: manage derived accounts, watch external accounts, send funds, publish pre-signed blocks, and generate work

Both domains share the same storage, event system, RPC layer, and custody engine.

RaiFlow, just like a trusty camel, intentionally tries to stay as thin but also fit as possible. It adds orchestration, persistence, event routing, and reliability guarantees. It does not try to become your catalog, checkout, customer database, or business logic layer.

What it IS NOT

RaiFlow is not:

  • a consumer wallet UI
  • a hosted gateway or SaaS product
  • a Nano node
  • a block explorer
  • a fiat payments platform
  • an e-commerce framework
  • something you deploy externally as a public service

Typed Client SDK

Use @openrai/raiflow-sdk when your application talks to RaiFlow over HTTP:

pnpm add @openrai/raiflow-sdk

The SDK provides a typed RaiFlowClient with resource classes for accounts, deposits and refunds, sends, invoices, webhooks, blocks, and work, plus re-exports of all canonical types from @openrai/model.

See packages/raiflow-sdk/README.md for the full API reference.

Deployment Quickstart

The fastest way to run RaiFlow is with Docker Compose.

Quick Start

  1. Copy the example compose file (or use the one in the repository root):
cp docker-compose.yml docker-compose.override.yml
  1. Edit docker-compose.override.yml and set the required environment variables:
environment:
  NANO_RPC_URL: "https://rpc.nano.org"
  RAIFLOW_MODE: "custodial"           # or "non-custodial"
  RAIFLOW_API_KEY: "your-secret-key"  # required
  1. Start the container:
docker compose up -d

RaiFlow will boot, run SQLite migrations, and start in the configured mode.

Required Environment Variables

Variable Required Description
NANO_RPC_URL Yes Nano node RPC endpoint (e.g. https://rpc.nano.org)
RAIFLOW_MODE Yes "custodial" or "non-custodial" — see Modes
RAIFLOW_API_KEY Yes API key for Bearer auth. You set this yourself.
RAIFLOW_OWS_WALLET Custodial only OWS wallet name or ID.
RAIFLOW_OWS_CREDENTIAL Custodial only OWS wallet passphrase or wallet-scoped API token.
RAIFLOW_OWS_VAULT_PATH Custodial only Path to the OWS encrypted vault.
RAIFLOW_CUSTODY_REP Custodial only Default representative for managed accounts.

Modes

RaiFlow operates in one of two modes, set at startup via RAIFLOW_MODE:

Custodial mode — RaiFlow asks an OWS wallet to derive accounts and sign canonical state blocks, while RaiFlow manages orchestration and PoW. RaiFlow never receives raw seed material. Configure custody.wallet, custody.credential, custody.vaultPath, and custody.representative in raiflow.yml.

Custodial mode uses @open-wallet-standard/core 1.3.2 from the pinned OpenRai OWS revision 56184be, @openrai/nano-core 3.6.0, and nano-rspow-node 0.17.2. The OWS dependency builds its Node native binding during installation. Building from source requires Rust stable, a C/C++ toolchain, pkg-config, OpenSSL development headers, and a supported target: macOS x64/arm64 or Linux x64/arm64 with GNU libc. The Docker builder installs these tools and compiles the binding. Custodial startup checks indexed derivation and signs a canonical state-block probe through OWS before serving requests; RaiFlow does not load wallet key material.

Non-custodial mode — RaiFlow acts as a relay and monitor. All signing happens client-side. Watched accounts, block publishing, and work generation are available. Managed accounts, sends, and invoices are not — those require custody.

Persistent Data

The /data volume persists the SQLite database (raiflow.db) across restarts.

Port Binding & Security

RaiFlow is designed as an app-private service. Do NOT forward port 3100 to the public internet. The compose file binds to 127.0.0.1 so only local application code can reach it. The API key authenticates internal service requests, not public traffic.

Custom Config

For advanced settings (webhooks, auto-sweep, multi-node RPC), mount your own raiflow.yml:

services:
  raiflow:
    volumes:
      - ./my-raiflow.yml:/app/raiflow.yml:ro

Minimum Inbound Amount

RaiFlow admits inbound transfers of at least 0.000001 XNO by default. Configure the inclusive minimum per transfer in raiflow.yml:

receiving:
  minimumAmountXno: "0.001"

RAIFLOW_MINIMUM_RECEIVE_AMOUNT_XNO=0.001 overrides the YAML value at startup. The YAML setting also accepts an env:YOUR_VARIABLE reference. Use a quoted, positive decimal XNO string with up to 30 decimal places. To explicitly admit every positive transfer, set "0.000000000000000000000000000001" (1 raw). Embedded runtimes accept new Runtime({ minimumReceiveAmountXno: "0.001" }); HTTP SDK clients use the policy configured on their runtime instance.

Transfers below the minimum create no deposit, payment, observation event, or receive job. They do not contribute to invoice totals, even if many small transfers would add up to the invoice amount. Unreceived transfers remain on the Nano node as receivables. New invoices below the minimum are rejected. Outgoing sends have no minimum imposed by this setting.

Restart to apply a changed minimum. Existing records remain, and recovery of already signed or published operations continues. Raising the minimum also affects payments to existing invoices; lowering it does not rebuild previously skipped historical payments. Choose the minimum before issuing invoices. This setting reduces dust processing; sustained transfers above the minimum still need the storage budgets below.

Inbound Storage Budgets

RaiFlow keeps confirmed deposit, payment, event, webhook, and recovery evidence. It does not expire old records. New inbound transfers pause before persistence when either storage limit is reached; their receivables remain on the Nano node for recovery after capacity is raised. Existing deposits and operation recovery continue to work. Check GET /health/storage to see current usage and pause status. Raising limits requires a runtime restart.

storage:
  driver: "sqlite"
  path: "./raiflow.db"
  maxDatabaseBytes: 1073741824       # default: 1 GiB
  maxDepositsPerAccount: 100000      # default: 100,000 per destination address

RAIFLOW_STORAGE_MAX_DATABASE_BYTES and RAIFLOW_STORAGE_MAX_DEPOSITS_PER_ACCOUNT override the YAML values. The database byte limit caps SQLite pages and the inbound pause begins before the limit to leave room for WAL activity and recovery writes. The per-account limit counts retained confirmed deposit records; it never deletes existing history.

Running The Current Code

  1. Install dependencies:
pnpm install
  1. Create a config file:
cp raiflow.yml.example raiflow.yml
  1. Set the required environment variables:
export RAIFLOW_MODE=custodial          # or non-custodial
export RAIFLOW_API_KEY=your-secret-key
export NANO_RPC_URL=https://rpc.nano.org
  1. If using custodial mode, also set:
export RAIFLOW_OWS_WALLET=runtime-wallet
export RAIFLOW_OWS_CREDENTIAL=ows_key_... # or the wallet passphrase
export RAIFLOW_OWS_VAULT_PATH=/secure/ows-vault
export RAIFLOW_CUSTODY_REP=nano_1...
  1. Build the workspace:
pnpm -r build
  1. Run tests:
pnpm -r test
  1. Start the runtime:
pnpm --filter @openrai/runtime start

Status

RaiFlow is actively developed. The v2 foundation is solid and the wallet domain is coming online.

Capability Status
Multi-node RPC with failover Shipped
WebSocket confirmation tracking Shipped
Custody engine (seed, derivation, signing, PoW) Shipped
Persisted event store & bus Shipped
Managed accounts (derive, sign, track) Shipped
Watched accounts (monitor external addresses) Shipped
Sends (idempotent queue → publish → confirm) Shipped
Invoices (create, detect, complete) Shipped
Webhooks (HMAC signed delivery) Shipped
Operator dashboard (SSR) Shipped
Block publish escape hatch Shipped
Work generation escape hatch Shipped
Pre-signed block publishing Shipped
Durable webhook outbox and retry history Shipped
Send and receive restart recovery Shipped
Explicit deposit refunds through the send pipeline Shipped
Minimum inbound amount policy Shipped
Keep-all inbound storage budgets and pause status Shipped

Storage budgets preserve existing history and pause inbound persistence before database or per-destination deposit limits. The minimum inbound amount rejects dust before persistence; the budgets bound sustained above-minimum traffic. See progress and the roadmap for implementation status.

If you are evaluating the repository today, the safest reading is:

The runtime handles accounts, deposits, refunds, sends, invoices, durable events, and webhooks. Configured inbound minima and storage budgets control which transfers are persisted; history is retained and ingestion pauses at a limit.

Current Runtime Surface

The runtime exposes the following HTTP surface:

Static & Dashboard

  • GET / — wayfinder page
  • GET /dashboard — SSR operator dashboard (with ?view= and ?showInternal= toggles)

System

  • GET /health/live
  • GET /health/ready
  • GET /health/storage — storage budget and inbound pause status
  • GET /v1/version

Accounts

  • POST /v1/accounts — create managed or watched account
  • GET /v1/accounts — list accounts (optional ?type=managed|watched)
  • GET /v1/accounts/:id
  • PATCH /v1/accounts/:id
  • DELETE /v1/accounts/:id — remove an empty, inactive account
  • GET /v1/accounts/:id/receivable — pending receivable blocks from the node
  • POST /v1/accounts/:id/sends — queue a send from this account
  • GET /v1/accounts/:id/sends — list sends for this account

Sends

  • GET /v1/sends/:id — global send lookup

Deposits and refunds

  • GET /v1/accounts/:id/deposits — confirmed inbound deposit history
  • GET /v1/deposits/:depositId/refunds
  • POST /v1/deposits/:depositId/refunds — requires explicit destination and idempotency key

Blocks (escape hatch for pre-signed flows)

  • POST /v1/blocks — publish a pre-signed block JSON string

Work (escape hatch for non-custodial flows)

  • POST /v1/work — generate PoW for a hash

Invoices

  • POST /v1/invoices
  • GET /v1/invoices (optional ?status=)
  • GET /v1/invoices/:id
  • POST /v1/invoices/:id/cancel
  • GET /v1/invoices/:id/payments
  • GET /v1/invoices/:id/events (optional ?after=)

Webhooks

  • POST /v1/webhooks
  • GET /v1/webhooks
  • DELETE /v1/webhooks/:id

Events

  • GET /v1/events — ordered polling with a monotonic cursor
  • GET /v1/events/stream — server-sent event stream

All mutating operations require an Idempotency-Key header where documented. Sends require an idempotency key — rejection is correct behavior if missing.

Design Constraints

These are deliberate constraints, not marketing copy:

  • self-hostable first
  • idempotency on mutating operations
  • persist-first events
  • one runtime for invoice and wallet domains
  • multi-node RPC instead of single-node dependence
  • namespace separation for invoice and managed-account derivation
  • framework-agnostic runtime API built on web Request/Response
  • Nano protocol primitives delegated to @openrai/nano-core

Developer Experience Philosophy

RaiFlow is designed so that Nano protocol mechanics are invisible to the developer:

  • PoW is not your problem. RaiFlow generates work internally. You never call work_generate in normal usage.
  • Signing is not your problem. In custodial mode, OWS signs blocks without exposing wallet key material to RaiFlow. Your app sends high-level intents like "send 1 XNO to this address."
  • Frontiers are not your problem. RaiFlow tracks account frontiers and constructs blocks correctly.
  • Confirmations are not your problem. RaiFlow watches for confirmations via WebSocket and updates send status automatically.

If you find yourself reaching for WorkResource or BlocksResource in the SDK, it indicates one of these use cases:

  1. You are building a non-custodial flow where blocks are signed client-side (e.g., a browser wallet or thin client-side wallet). This is a first-class, supported integration path.
  2. You are building an app-specific protocol layer that needs one-off PoW without external work provider configuration.

For custodial flows, use SendsResource — RaiFlow handles signing and PoW automatically.

Repository Layout

apps/site/            documentation site
packages/
  model/              canonical types and contracts
  config/             YAML config loader with env resolution
  storage/            SQLite adapter and migrations
  rpc/                Nano RPC + WebSocket primitives
  events/             event bus and persisted event access
  custody/            derivation, signing, PoW, frontier-related logic
  runtime/            HTTP runtime and orchestration
  webhook/            webhook signing and delivery
  raiflow-sdk/        typed JS/TS client
examples/             reference integrations
rfcs/                 architecture decisions
docs/                 progress and implementation notes

Repository Truth Sources

If you want the current state rather than the intended end state, read these first:

  • docs/progress.md — current implementation frontier
  • ROADMAP.md — milestone map
  • rfcs/0001-project-framing.md — project framing and scope
  • rfcs/0003-event-model.md — current v2 resource/event model

Release Flow

RaiFlow uses a solo-developer Changesets flow for the public packages in this workspace:

  • @openrai/model
  • @openrai/webhook
  • @openrai/raiflow-sdk

Those three published packages release in lockstep and share the same version on each repo release. @openrai/nano-core remains separate in its own repository and release line.

Local package development stays on workspace:* links, including the examples, so in-repo changes are exercised without publishing prereleases.

Typical release steps:

  1. Add a changeset:
pnpm changeset
  1. Push the changeset to main. The Release workflow builds and tests, applies the version bumps, publishes eligible packages through npm Trusted Publishing, then pushes the release commit and package tags directly to main.

The manual escape hatch applies the version bumps, creates a release commit, and creates one tag per bumped package:

pnpm release:version
  1. Push the commit and tags:
git push origin HEAD:main --atomic --tags

The workflow publishes with OIDC; it does not open a version PR.

About

🚧WORK IN PROGRESS🥦 - A Nano ($XNO) payment runtime that turns raw node RPC into reliable payment detection, settlement, and events.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages