Releases: cardano-onboard/platform
Release list
Onboard.Ninja DIY v1.3.0-beta
Measuring onboarding
Claim counts tell you a campaign was used. They don't tell you whether it
worked.
Every campaign now has an Onboarding panel that answers the question the
platform exists for: did this airdrop bring anyone new onto Cardano, and did
they do anything afterwards? For each claimant wallet it reads public chain data
and reports whether the wallet existed before the claim, whether it later sent a
transaction of its own, and whether it delegates to a stake pool.
Activation is deliberately strict. The wallet has to appear as an input to a
later transaction, not just receive one — otherwise anyone could inflate their
own activation rate by sending more tokens to their own claimants.
The observation window sits with the numbers rather than in a footnote, because
an activation rate a week after an event and the same rate three months later
are completely different claims.
Analysis runs in the background and can be re-run as wallets mature. Your own
test claims can be flagged so they stop dragging the percentages down. On a
self-hosted install with no queue worker, run it directly:
php artisan onboard:analyze "My Campaign"It uses Koios, which needs no account, and works on
mainnet, preprod and preview. Set KOIOS_API_TOKEN for a higher rate limit on
large campaigns.
Verification. Checked against an event campaign whose results were already
published:
66 claimant wallets, 5 operator tests, 61 genuine attendees, 34 newly onboarded
(55.7%), 17 of those 34 later transacting for themselves (50%). The built-in
analysis and the standalone tooling those figures came from agree on every
number.
Deleting codes
Codes can be deleted individually, as a selection, or as every unclaimed code in
a campaign — the last being the realistic fix for a bulk import that was wrong
from the start.
Claimed codes are never deleted. Their claims are the record that someone was
paid. In a mixed selection the claimed ones are kept and reported rather than
failing the whole request, so a batch that saw a couple of early claims is still
clearable.
Exporting claims
Export claims downloads every claimed address as CSV, with the onboarding
classification alongside where an analysis has run: new or established, prior
transaction count, activation, delegation and pool.
Dashboard
Campaign search across name, description, network and status. The list also fits
small screens properly now, which is where a campaign actually gets checked
during an event.
Documentation
New FAQ,
troubleshooting guide,
support page and a
guide to
measuring onboarding.
The troubleshooting guide covers installation failures that have actually
happened: a host database holding the MySQL port, a stale route cache blanking
the home page, a mail driver that throws because its HTTP client was never
installed, and assets blocked by the content security policy when served from a
separate domain.
Fixes carried from 1.2.4-beta
- Filesystem operations resolve the configured disk instead of assuming S3, so
self-hosted installs using local storage work as intended. - Bulk code uploads are signed against the configured disk.
Security backlog
Five hardening items from the public backlog are verified and closed in this
release:
content security policy, consolidated frame options, hidden server version,
cross-origin and permissions policies, and content-type options on static
assets. Cross-Origin-Embedder-Policy remains opt-in per deployment, because
require-corp breaks cross-origin assets that do not send their own CORP
header.
Tests
258 PHPUnit (671 assertions) and 73 Vitest, up from 209 and 54 in v1.2.3-beta.
Onboard.Ninja DIY v1.2.3-beta (Milestone 3)
Milestone 3 — the public launch release. This is the largest update since the
project went open source: a full token-metadata system, print-ready QR export,
campaign analytics, a documentation site, and a substantial overhaul of the
self-hosted Docker experience.
If you self-host, read the Upgrade Notes at the bottom — this release
contains breaking changes to the Docker setup, all of them fixes for things
that were quietly broken before.
Getting started just got a lot shorter
New installs are now two commands:
git clone https://github.com/cardano-onboard/platform.git
cd platform
./quickstart.sh
docker compose up -d --buildquickstart.sh generates everything that has to be unique to your install —
the Laravel APP_KEY, both MySQL passwords, and your admin login — writes them
to .env with 600 permissions, and refuses to overwrite an existing config
unless you ask it to. Use --yes for unattended installs and
--port 8082 if 8080 is taken.
The previous instructions told you to copy a template and fill in the blanks.
In practice that produced a container that failed on boot, for reasons covered
in the fixes below.
Token metadata & the known-asset registry
Reward tokens can now be picked by ticker instead of raw policy/asset hex.
known_assetsregistry, backed by the Koios token registry. Lookups are
registry-first: served from the local table, falling back to Koios only for
an unknown token, then cached.- Autocomplete ranked by usage so common tokens surface first.
- Scheduled sync (
assets:sync-registry, every 6 hours) pulls the full
registry — roughly 7,900 mainnet and 500 preprod tokens. The sync retries
with backoff and fails loudly rather than silently truncating. - Decimal-aware amounts throughout. On-chain values are base units; the UI
now divides by the token's decimals for display, and converts human input
back to base units on code creation. No more counting zeros. - Seeded defaults: HOSKY, MIN, SUNDAE, iUSD, DJED, USDM on mainnet;
tDRIP and tUSDM on preprod.
Print-ready QR export
Export a campaign's claim codes as QR codes for printing on cards, flyers, or
badges. Generation is idempotent — re-running won't duplicate or churn stored
files — and works against both local disk and S3-compatible storage.
Campaign analytics & reward visibility
- Performance charts on the campaign page: claims over time, at a glance.
- Expandable reward details per claim code, with token metadata resolved
lazily so the list stays fast. - Branded wallet views showing token logo, human-readable name, and
decimal-formatted amounts, replacing the raw hex tables.
Documentation site
A full VitePress documentation site now covers setup, configuration, campaigns,
codes and rewards, claiming, monitoring, and an API reference — with separate
getting-started paths for self-hosted and SaaS.
License
Onboard.Ninja is now released under the Apache License 2.0 (previously MIT).
Apache 2.0 keeps the same permissive, business-friendly terms while adding an
explicit patent grant and clearer contribution and attribution provisions. See
the LICENSE and NOTICE files for full terms.
Security
- Claim codes are now generated from independent random draws. The previous
scheme derived codes in a way that reduced effective entropy. Existing codes
keep working; newly generated ones are stronger. - Content Security Policy is now configuration-driven
(config/security.php) and enforced in local development rather than
disabled, so violations surface before they reach production.
Self-hosted fixes
Several of these were preventing a clean Docker install from working at all.
APP_KEYis now required, and the container says so. The old entrypoint
claimed to auto-generate a key, but the generated value was written to a file
the running process would never read — so the app cached an empty key and
returned a 500 on every request. Startup now fails immediately with
instructions instead. Auto-generating per boot was deliberately not restored:
a key that changes on restart invalidates sessions and makes previously
encrypted data unreadable.- Blank database passwords no longer produce an unfixable install. Compose
substituted a default whenDB_PASSWORDwas empty, so MySQL initialised with
one password while the app was handed another — a permanent
Access denied for user 'onboard'that no amount of editing.envcould
resolve. Empty values now abort at startup. - Host ports are configurable.
APP_PORT,DB_HOST_PORTand
REDIS_HOST_PORTare set in.env; there is no longer any reason to edit
docker-compose.yml. - MySQL and Redis now bind to
127.0.0.1by default. They were previously
published on all interfaces, which on a public VPS exposed the database to
the internet. See Upgrade Notes if you rely on remote access. .dockerignoreadded. The build context previously included everything,
meaning a local.envcould be baked into image layers.- Fixed a blank home page on self-hosted installs. The self-hosted build
ships a reduced route table, and the frontend referenced routes that only
exist on the hosted edition. Because the route helper throws on an unknown
name, this blanked the landing page and every authenticated page. Those
references are now guarded, with regression tests covering the reduced route
table. - Known-asset endpoints are now routed in the self-hosted build. The
controller shipped but its routes did not, so token lookup failed at runtime. - Corrected repository URLs throughout the README and docs.
Upgrade Notes
APP_KEY must be set. If your .env has a blank APP_KEY, the container
will now stop on startup rather than serving errors. Generate one with
openssl rand -base64 32 and prefix it with base64:, or run
./quickstart.sh. Keep the value stable — changing it logs everyone out and
makes previously encrypted data unreadable.
Database passwords must be non-empty. Blank DB_PASSWORD or
DB_ROOT_PASSWORD now aborts startup instead of silently diverging from what
MySQL was initialised with.
MySQL and Redis are no longer reachable from other machines by default. If
you depend on remote access, set DB_BIND=0.0.0.0 / REDIS_BIND=0.0.0.0 in
.env — but use strong passwords and a firewall first.
Changing database passwords requires care. MySQL only applies
DB_PASSWORD when it initialises an empty data directory. On an existing
install, changing it in .env has no effect and will lock the app out. Either
change it inside MySQL:
docker compose exec mysql mysql -u root -p<OLD_ROOT_PASSWORD> \
-e "ALTER USER 'onboard'@'%' IDENTIFIED BY '<NEW_DB_PASSWORD>';"or start fresh with docker compose down -v (this destroys all data).
Seed the known-asset registry after upgrading, so reward tokens can be
picked by ticker:
docker compose exec app php artisan db:seed --forceKnown issues
- The known-asset registry is not seeded automatically on first boot; run the
command above. This will be folded into container startup in a later release. ADMIN_PASSWORDremains in.envafter the admin account is seeded, and is
re-applied on every container restart — so changing your password in the app
is reverted on the next restart. A dedicated command to manage the admin
account is tracked for the next release.
Full documentation: https://cardano-onboard.github.io/platform/
Onboard.Ninja DIY v1.1.0-beta (Milestone 2)
Onboard.Ninja DIY — v1.1.0-beta
Self-hosted Cardano airdrop platform. Run your own claim portal end-to-end:
upload a CSV of codes, generate QR codes, and let claimants redeem rewards
straight from a wallet.
This release builds on the initial DIY drop with substantial work on testing,
security, and operator ergonomics.
Highlights
- Pluggable transaction backend. The
TransactionBackendcontract lets
you wire in your own Cardano transaction service. Three drivers ship in
the box:phyrhose(the hosted service the SaaS uses),proxy(routes
through a configurable HTTP proxy), andnull(no-op for local
development and CI). - Docker-first deployment. Multi-stage
Dockerfileplus
docker-compose.ymlget you to a working stack with one command. An
.env.dockertemplate covers every knob you'll need. - First-run admin seeding. Set
ADMIN_EMAIL/ADMIN_PASSWORDand the
seeder provisions your owner account. No registration endpoint, no public
signup — DIY operators control who logs in. - Configurable limits. File size, code count, and per-IP / per-campaign
rate limits are all driven by env vars, so you can match your hardware and
audience without touching code. - Node 22 LTS across CI and the Docker image.
Configuration
Everything operator-tunable lives in .env. Notable knobs added in this
release:
| Variable | Default | Purpose |
|---|---|---|
UPLOAD_MAX_FILE_SIZE |
10485760 |
CSV upload size (bytes, default 10 MB) |
UPLOAD_MAX_CODES |
10000 |
Codes per campaign |
CLAIM_RATE_PER_IP |
60 |
Claim requests per minute per IP |
CLAIM_RATE_PER_CAMPAIGN |
120 |
Claim requests per minute per campaign |
ADMIN_EMAIL / ADMIN_PASSWORD |
— | First-run admin credentials |
TRANSACTION_BACKEND |
null |
Backend driver (phyrhose, proxy, null) |
See .env.docker and config/cardano.php for the full set.
Testing & Quality
The DIY release ships with the same test suite the hosted version runs in CI:
- 224 automated tests — PHPUnit (164), Vitest (40), Dusk E2E (20)
- 56% PHP coverage, gated at 50% in CI
- 28 security tests covering XSS, SQL injection, auth bypass, CSRF, mass
assignment, and cross-user authorization on every authenticated endpoint - OWASP ZAP baseline scan clean of high-severity findings
- k6 load tests with reproducible profiles for sizing your own deployment
Run them locally with php artisan test, npm run test, and
php artisan dusk.
Hardware Sizing
Load-tested across three resource profiles so you can pick the right box:
| Profile | RAM | Claims/sec | Dashboard p95 | Concurrent users |
|---|---|---|---|---|
| Minimum | 1.5 GB | 26.6 | 14 ms | ~10 |
| Recommended | 2.5 GB | 72.5 | 16 ms | ~30 |
| Comfortable | 4.5 GB | 126.4 | 22 ms | ~50 |
Numbers are from a sustained k6 run against the claim API; your mileage will
vary with backend latency.
Bech32 Address Validation
Wallet address validation now follows BIP-173 charset
([023456789acdefghjklmnpqrstuvwxyz]) and CIP-19 length rules, rejecting
typos and obviously-malformed addresses before they hit the transaction
backend.
Security Hardening
- ULIDs (not auto-increment IDs) on all user-facing models
- Soft deletes everywhere it matters
- Secret fields (
key,skey,vkey) marked$hiddenso they never leak
through Inertia props CampaignPolicyenforces owner-only access; controllerauthorizeResource
short-circuits cross-user access at the framework level- Security headers on every response:
X-Content-Type-Options: nosniff,
X-Frame-Options: DENY,Referrer-Policy: strict-origin-when-cross-origin,
and HSTS on HTTPS
Getting Started
git clone https://github.com/cardano-onboard/platform.git onboard
cd onboard
cp .env.docker .env
# edit .env — set APP_KEY, DB password, ADMIN_EMAIL/PASSWORD, backend creds
docker compose up -d
docker compose exec app php artisan migrate --seedFull instructions, including running without Docker, are in README.md.
Known Limitations
- The
phyrhosedriver targets Phyrhose, a hosted
transaction-submission service by Andrew Westberg that the Onboard.Ninja
SaaS uses for token distribution. Using it requires your own JWT and
network ID. If you'd rather not depend on a hosted service, implement the
TransactionBackendcontract against your own infrastructure and register
it inAppServiceProvider::resolveBackend(). - Single-operator model — there's no multi-tenant or team-account support in
the DIY build. Each instance is one organization. - Beta software. Battle-tested in CI and load tests, but production
deployments should be monitored closely. Bug reports welcome via GitHub
Issues.
Contributing
CONTRIBUTING.md covers the dev setup, test commands, and PR conventions.
Issue templates for bug reports and feature requests are in
.github/ISSUE_TEMPLATE/.
Built on Laravel 10, Vue 3, Vuetify 3, and Inertia.js.
v1.0.2-beta.3
Changes
- Fix platform test failures (password confirmation/update tests excluded)
v1.0.1-beta.2
Onboard.Ninja DIY — self-hosted Cardano airdrop platform (public beta).