871 lines
26 KiB
Markdown
871 lines
26 KiB
Markdown
# Qortal External Auth
|
||
|
||
Minimal local daemon + shared auth core for Qortal-compatible account import and signing.
|
||
|
||
## Getting Started
|
||
|
||
### Install
|
||
|
||
```bash
|
||
npm install
|
||
```
|
||
|
||
### Dev mode
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
### Build + start (production)
|
||
|
||
```bash
|
||
npm run build
|
||
npm run start
|
||
```
|
||
|
||
### Docker
|
||
|
||
Build image:
|
||
|
||
```bash
|
||
docker build -t qortal-external-auth:latest .
|
||
```
|
||
|
||
Run container (bind host port 3191, persist data):
|
||
|
||
```bash
|
||
docker run --rm -p 3191:3191 \
|
||
-e QORTAL_AUTH_HOST=0.0.0.0 \
|
||
-e QORTAL_AUTH_DATA_DIR=/data \
|
||
-v qortal-auth-data:/data \
|
||
qortal-external-auth:latest
|
||
```
|
||
|
||
Note: use a named volume (as shown) instead of a host bind-mount to keep wallet
|
||
files out of your normal filesystem paths. Named volumes live under Docker's
|
||
internal data directory and are less likely to be accessed accidentally.
|
||
|
||
If the Qortal node runs on the Docker host, set `QORTAL_AUTH_NODE_URL` and add the host gateway:
|
||
|
||
```bash
|
||
docker run --rm -p 3191:3191 \
|
||
-e QORTAL_AUTH_HOST=0.0.0.0 \
|
||
-e QORTAL_AUTH_DATA_DIR=/data \
|
||
-e QORTAL_AUTH_NODE_URL=http://host.docker.internal:12391 \
|
||
--add-host=host.docker.internal:host-gateway \
|
||
-v qortal-auth-data:/data \
|
||
qortal-external-auth:latest
|
||
```
|
||
|
||
### Docker Compose (multi-repo)
|
||
|
||
This repo includes a `compose.yaml` that builds the local auth daemon and
|
||
pulls/builds the Nextcloud integration from your Gitea repo.
|
||
|
||
```bash
|
||
docker compose up --build
|
||
```
|
||
|
||
Notes:
|
||
- The Compose file uses a named volume for wallet data (`qortal-auth-data`).
|
||
- Services can reach each other by service name on the default network, e.g.
|
||
`http://qortal-external-auth:3191`.
|
||
- If you need a Qortal node container, add it and set
|
||
`QORTAL_AUTH_NODE_URL=http://<service-name>:12391`.
|
||
|
||
### Environment
|
||
|
||
Optional environment variables:
|
||
|
||
- `QORTAL_AUTH_PORT` (default `3191`)
|
||
- `QORTAL_AUTH_HOST` (default `127.0.0.1`)
|
||
- `QORTAL_AUTH_NODE_URL` (default node URL if not provided in requests)
|
||
- `QORTAL_AUTH_NODE_API_KEY` (optional API key for restricted node endpoints, sent as `X-API-KEY`)
|
||
- `QORTAL_AUTH_NODE_API_KEY_MODE` (`auto`|`always`|`never`|`paths`|`retry`, default `auto`)
|
||
- `QORTAL_AUTH_NODE_API_KEY_PATHS` (comma-separated path patterns when using `paths`, e.g. `/admin/stop,/render/{service}/{name}`. Use `/` to match all node paths.)
|
||
- `QORTAL_AUTH_BODY_LIMIT_BYTES` (max JSON request body size in bytes; default `262144000` / 250 MiB)
|
||
- `QORTAL_AUTH_SAVE_FILE_DIR` (base directory for `qortalRequest('SAVE_FILE')`; default `<dataDir>/saved-files`)
|
||
|
||
`retry` mode attempts a request without the key and retries once on `401/403`
|
||
with the key, so the key is only sent when required.
|
||
|
||
Regardless of mode, if the node explicitly responds with a missing
|
||
`X-API-KEY` error, the daemon retries once with the configured key.
|
||
|
||
`auto` mode uses a built-in list of Qortal endpoints that call
|
||
`Security.checkApiCallAllowed` or declare `@SecurityRequirement(name = "apiKey")`
|
||
in core. `paths` supports simple `{param}` placeholders and `{path:.*}` wildcards.
|
||
- `QORTAL_AUTH_DATA_DIR` (default `./data`)
|
||
- `QORTAL_AUTH_STORAGE_PASSPHRASE` (used when encrypted backups are enabled)
|
||
- `QORTAL_AUTH_DOCS_HOST` (override Swagger server host if needed)
|
||
|
||
Large inline base64 publishes use JSON request bodies and can exceed this limit.
|
||
For very large content, prefer `uploadType: "file"` with `filePath` so data is
|
||
streamed to the node in chunks instead of being sent as one large JSON payload.
|
||
|
||
You can also create a `.env` file in the repo root (it is gitignored).
|
||
|
||
## API
|
||
|
||
All endpoints are JSON over HTTP. By default the daemon listens on `127.0.0.1:3191`.
|
||
|
||
### API docs (interactive)
|
||
|
||
Swagger UI (try calls in browser):
|
||
|
||
```text
|
||
http://127.0.0.1:3191/docs
|
||
```
|
||
|
||
OpenAPI JSON:
|
||
|
||
```text
|
||
http://127.0.0.1:3191/openapi.json
|
||
```
|
||
|
||
### Authentication
|
||
|
||
Most endpoints require a bearer token:
|
||
|
||
```text
|
||
Authorization: Bearer <TOKEN>
|
||
```
|
||
|
||
If a permission is in `ask` mode, include:
|
||
|
||
```text
|
||
X-Qortal-Approval-Token: <TOKEN>
|
||
```
|
||
|
||
Unauthenticated endpoints:
|
||
|
||
- `GET /` (redirect to docs)
|
||
- `GET /health`
|
||
- `GET /openapi.json`
|
||
- `GET /docs`
|
||
|
||
### Typical flow
|
||
|
||
1. `POST /apps/register` to create an app id/secret.
|
||
2. `POST /sessions` to create a bearer token.
|
||
3. Import a wallet (`/wallet/import-seed` or `/wallet/import-backup`).
|
||
4. Set permissions (`/permissions/set`) or issue one-time approvals (`/permissions/approve`).
|
||
5. Unlock a wallet (`/wallet/unlock`) or pass `password` per request.
|
||
6. Call signing or QDN endpoints.
|
||
|
||
### Endpoint reference (daemon)
|
||
|
||
Apps:
|
||
|
||
```text
|
||
POST /apps/register
|
||
Body: {"name":"my-app"}
|
||
Response: {"appId":"...","appSecret":"..."}
|
||
```
|
||
|
||
Sessions:
|
||
|
||
```text
|
||
POST /sessions
|
||
Body: {"appId":"...","appSecret":"..."}
|
||
Response: {"token":"...","expiresAt":<unix ms>}
|
||
```
|
||
|
||
Wallets:
|
||
|
||
```text
|
||
POST /wallet/import-seed
|
||
Body: {"seedPhrase":"...","password":"..."}
|
||
|
||
POST /wallet/create
|
||
Body: {"password":"..."}
|
||
|
||
POST /wallet/import-backup
|
||
Body: {"password":"...","backup":{...}}
|
||
|
||
POST /wallet/backup/export
|
||
Body: {"walletId":"...","password":"...","reEncryptPassword":"..."}
|
||
|
||
POST /wallet/backup-json
|
||
Body: {"walletId":"...","password":"...","newPassword":"..."}
|
||
|
||
POST /wallet/backup
|
||
Body: {"walletId":"...","password":"..."}
|
||
|
||
GET /wallets
|
||
GET /wallet/<WALLET_ID>
|
||
|
||
POST /wallet/unlock
|
||
Body: {"walletId":"...","password":"...","ttlSeconds":1200}
|
||
|
||
GET /wallet/unlock/status?walletId=<WALLET_ID>
|
||
|
||
POST /wallet/lock
|
||
Body: {"walletId":"..."}
|
||
```
|
||
|
||
Permissions:
|
||
|
||
```text
|
||
POST /permissions/set
|
||
Body: {"walletId":"...","scope":"SIGN_TRANSACTION","mode":"ask|session|persistent","ttlSeconds":3600}
|
||
|
||
POST /permissions/approve
|
||
Body: {"walletId":"...","scope":"SIGN_TRANSACTION"}
|
||
|
||
GET /permissions?walletId=<WALLET_ID>
|
||
```
|
||
|
||
Settings:
|
||
|
||
```text
|
||
GET /settings?walletId=<WALLET_ID>
|
||
POST /settings
|
||
Body: {"scope":"global|app|wallet","walletId":"<WALLET_ID>","settings":{...}}
|
||
```
|
||
|
||
Transactions and QDN:
|
||
|
||
```text
|
||
POST /tx/sign
|
||
Body: {"walletId":"...","unsignedBytes":"<BASE58>","process":false,"password":"...","nodeUrl":"..."}
|
||
|
||
POST /qdn/publish
|
||
Body: {"walletId":"...","data":"<BASE64>","service":"DOCUMENT","registeredName":"name","identifier":"id","password":"..."}
|
||
```
|
||
|
||
Qortal request router and node proxy:
|
||
|
||
```text
|
||
POST /qortal/request
|
||
Body: {"requestType":"GET_NODE_INFO","payload":{...}}
|
||
|
||
POST /node/proxy
|
||
Body: {"method":"GET","path":"/admin/info","query":{...},"body":{...}}
|
||
|
||
GET /node/proxy?path=/addresses/balance/<ADDRESS>&assetId=0
|
||
```
|
||
|
||
### System endpoints
|
||
|
||
Health check:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:3191/health
|
||
```
|
||
|
||
OpenAPI JSON:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:3191/openapi.json
|
||
```
|
||
|
||
### Node proxy (generic)
|
||
|
||
Use `/node/proxy` to pass through a Qortal node call. This is useful for
|
||
endpoints not yet exposed via `/qortal/request`.
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/node/proxy \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"method":"GET","path":"/admin/info"}'
|
||
```
|
||
|
||
GET shortcut for simple read-only requests:
|
||
|
||
```bash
|
||
curl -s -X GET 'http://127.0.0.1:3191/node/proxy?path=/addresses/balance/QiNKXRfnX9mTodSed1yRQexhL1HA42RHHo&assetId=0' \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
### Swagger UI authorization (quick)
|
||
|
||
1. Register app:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/apps/register \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"name":"my-app"}'
|
||
```
|
||
|
||
1. Create session:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/sessions \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"appId":"<APP_ID>","appSecret":"<APP_SECRET>"}'
|
||
```
|
||
|
||
1. In Swagger UI (`/docs`), click **Authorize** and paste:
|
||
|
||
```text
|
||
Bearer <TOKEN>
|
||
```
|
||
|
||
### End-to-end example (import backup + sign)
|
||
|
||
Scenario: An external app wants to import a Qortal backup file, unlock it for 20 minutes, and sign a transaction.
|
||
|
||
1. Register app + create session (same as above).
|
||
|
||
2. Import a backup file:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/import-backup \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"password":"backup-password","backup":{...}}'
|
||
```
|
||
|
||
1. Set permissions for signing (session mode):
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/permissions/set \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"session","ttlSeconds":3600}'
|
||
```
|
||
|
||
1. Unlock the wallet for 20 minutes:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/unlock \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","password":"backup-password","ttlSeconds":1200}'
|
||
```
|
||
|
||
1. Sign a transaction (wallet already unlocked):
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/tx/sign \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","unsignedBytes":"<BASE58>","process":false,"nodeUrl":"http://127.0.0.1:12391"}'
|
||
```
|
||
|
||
### Register app
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/apps/register \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"name":"my-app"}'
|
||
```
|
||
|
||
### Create session
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/sessions \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"appId":"<APP_ID>","appSecret":"<APP_SECRET>"}'
|
||
```
|
||
|
||
### Import seed phrase (creates and saves Qortal backup file)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/import-seed \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"seedPhrase":"seed words here","password":"backup-password"}'
|
||
```
|
||
|
||
### Create new wallet (random seed + save backup)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/create \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"password":"backup-password"}'
|
||
```
|
||
|
||
### Import Qortal backup file
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/import-backup \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"password":"backup-password","backup":{...}}'
|
||
```
|
||
|
||
### Export backup file (password required)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/backup/export \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","password":"backup-password"}'
|
||
```
|
||
|
||
Optional compatibility fields for re-encryption on export:
|
||
- `reEncryptPassword`
|
||
- `newPassword`
|
||
|
||
If both are provided, they must match. Alias routes `/wallet/backup-json` and
|
||
`/wallet/backup` use the same behavior.
|
||
|
||
### List wallets
|
||
|
||
```bash
|
||
curl -s -X GET http://127.0.0.1:3191/wallets \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
### Get wallet
|
||
|
||
```bash
|
||
curl -s -X GET http://127.0.0.1:3191/wallet/<WALLET_ID> \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
## Permissions
|
||
|
||
Permissions gate sensitive operations. Modes:
|
||
|
||
- `ask`: requires a one-time approval token
|
||
- `session`: auto-approve until TTL expires
|
||
- `persistent`: always approved for this app + wallet + scope
|
||
|
||
### Set permission mode
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/permissions/set \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"session","ttlSeconds":3600}'
|
||
```
|
||
|
||
### Approve once (for `ask` mode)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/permissions/approve \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION"}'
|
||
```
|
||
|
||
Use the returned `approvalToken` as header:
|
||
|
||
```bash
|
||
-H 'X-Qortal-Approval-Token: <TOKEN>'
|
||
```
|
||
|
||
### List persistent permissions
|
||
|
||
```bash
|
||
curl -s -X GET http://127.0.0.1:3191/permissions?walletId=<WALLET_ID> \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
## Settings
|
||
|
||
Settings are stored in `data/settings.json` and can be configured at:
|
||
|
||
- global defaults
|
||
- per-app
|
||
- per-wallet
|
||
|
||
These settings control the default auth mode and the default node URL if a request does not supply `nodeUrl`.
|
||
When `authMode` is `session`, the daemon will **auto‑unlock** the wallet when a signing request includes a `password`.
|
||
|
||
### Get settings (resolved)
|
||
|
||
```bash
|
||
curl -s -X GET 'http://127.0.0.1:3191/settings?walletId=<WALLET_ID>' \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
### Set global defaults
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/settings \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"scope\":\"global\",\"settings\":{\"authMode\":\"ask\",\"sessionTtlSeconds\":3600,\"approvalTtlSeconds\":300,\"unlockTtlSeconds\":1200,\"nodeUrl\":\"http://127.0.0.1:12391\"}}'
|
||
```
|
||
|
||
### Set app defaults
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/settings \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"scope\":\"app\",\"settings\":{\"authMode\":\"session\"}}'
|
||
```
|
||
|
||
### Set per-wallet defaults
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/settings \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"scope\":\"wallet\",\"walletId\":\"<WALLET_ID>\",\"settings\":{\"authMode\":\"persistent\",\"nodeUrl\":\"http://127.0.0.1:12391\"}}'
|
||
```
|
||
|
||
**Note:** If `authMode` is set to `session` or `persistent`, the daemon will auto‑grant permissions for that scope without requiring approval tokens.
|
||
|
||
### Encrypted backups (optional)
|
||
|
||
If `encryptedBackups` is enabled in **global** settings, wallet backups are encrypted at rest using a storage passphrase.
|
||
On startup the daemon will prompt for the passphrase if `QORTAL_AUTH_STORAGE_PASSPHRASE` is not set.
|
||
|
||
Enable via settings:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/settings \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"scope\":\"global\",\"settings\":{\"encryptedBackups\":true}}'
|
||
```
|
||
|
||
Then start the daemon with:
|
||
|
||
```bash
|
||
QORTAL_AUTH_STORAGE_PASSPHRASE='your-passphrase' npm run dev
|
||
```
|
||
|
||
## Wallet Unlock
|
||
|
||
Unlocking decrypts the backup once, then stores an **encrypted** private key in memory with a TTL. Signing calls can omit `password` while unlocked.
|
||
|
||
### Unlock
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/unlock \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"walletId\":\"<WALLET_ID>\",\"password\":\"backup-password\",\"ttlSeconds\":1200}'
|
||
```
|
||
|
||
### Unlock status
|
||
|
||
```bash
|
||
curl -s -X GET 'http://127.0.0.1:3191/wallet/unlock/status?walletId=<WALLET_ID>' \
|
||
-H 'Authorization: Bearer <TOKEN>'
|
||
```
|
||
|
||
### Lock
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/wallet/lock \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"walletId\":\"<WALLET_ID>\"}'
|
||
```
|
||
|
||
## Signing
|
||
|
||
### Sign transaction (Base58 unsigned bytes)
|
||
|
||
If the wallet is unlocked, you can omit `password`.
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/tx/sign \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","password":"backup-password","unsignedBytes":"<BASE58>","process":false,"nodeUrl":"http://127.0.0.1:12391"}'
|
||
```
|
||
|
||
## QDN publish (base64)
|
||
|
||
This is a minimal base64 publish flow (mirrors Hub’s `PUBLISH_QDN_RESOURCE`). If the wallet is unlocked, you can omit `password`.
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qdn/publish \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":"<BASE64>","nodeUrl":"http://127.0.0.1:12391"}'
|
||
```
|
||
|
||
## Qortal request router
|
||
|
||
A compatibility endpoint that mirrors Hub-style request types.
|
||
|
||
Request format:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"GET_NODE_INFO","payload":{}}'
|
||
```
|
||
|
||
### Node and status
|
||
|
||
- `GET_NODE_INFO`
|
||
- `GET_NODE_STATUS`
|
||
- `GET_DAY_SUMMARY`
|
||
|
||
### Accounts and names
|
||
|
||
- `GET_ACCOUNT_DATA` (payload: `address` or `walletId`)
|
||
- `GET_ACCOUNT_NAMES` (payload: `address` or `walletId`)
|
||
- `GET_PRIMARY_NAME` (payload: `address` or `walletId`)
|
||
- `GET_NAME_DATA` (payload: `name`)
|
||
- `SEARCH_NAMES` (payload: `query`, `limit`, `offset`, `reverse`)
|
||
|
||
### Balances
|
||
|
||
- `GET_BALANCE` (payload: `address` or `walletId`, returns QORT balance)
|
||
- `GET_WALLET_BALANCE` (payload: `coin` + `address` or `walletId`)
|
||
|
||
`coin` supports `QORT`, an asset name, or an assetId.
|
||
|
||
### Wallet metadata
|
||
|
||
- `GET_USER_WALLET` (payload: `walletId`)
|
||
- `GET_USER_WALLET_INFO` (payload: `walletId`)
|
||
- `GET_USER_ACCOUNT` (payload: optional `walletId`)
|
||
- `WHICH_UI` (payload optional, returns `HUB_WEB` by default; set `QORTAL_AUTH_UI=HUB_ELECTRON` to override)
|
||
|
||
### Encryption
|
||
|
||
- `ENCRYPT_DATA` (payload: `walletId` or `address`, `data64|base64`, optional `publicKeys[]`)
|
||
- `DECRYPT_DATA` (payload: `walletId` or `address`, `encryptedData|data64|base64`, and `publicKey` when decrypting legacy `qortalEncryptedData`)
|
||
- `ENCRYPT_QORTAL_GROUP_DATA` (payload: `walletId` or `address`, `data64|base64`, and either `secretKeyObject` or `groupId`, optional `isAdmins`, optional `typeNumber`, optional `refreshCache`)
|
||
- `DECRYPT_QORTAL_GROUP_DATA` (payload: `walletId` or `address`, `data64|base64`, and either `secretKeyObject` or `groupId`, optional `isAdmins`, optional `refreshCache`)
|
||
|
||
If `secretKeyObject` is omitted and `groupId` is provided, the daemon mirrors Hub behavior:
|
||
it fetches group admins, resolves their primary names, finds the latest matching key publish
|
||
(`symmetric-qchat-group-<groupId>` or `admins-symmetric-qchat-group-<groupId>`), decrypts it
|
||
with the caller wallet key, and uses the resulting key object. Resolved keys are cached for
|
||
20 minutes unless `refreshCache=true`.
|
||
|
||
### Transactions and blocks
|
||
|
||
- `GET_USER_WALLET_TRANSACTIONS` (payload: `walletId`, `limit`, `offset`, `reverse`, `confirmationStatus`, `txType`)
|
||
- `SEARCH_TRANSACTIONS` (payload: `limit`, `offset`, `reverse`, plus any `/transactions/search` params)
|
||
- `DECODE_TRANSACTION` (payload: `bytes|unsignedBytes|signedBytes`, optional `ignoreValidityChecks`)
|
||
- `FETCH_BLOCK` (payload: `height` or `signature`, empty = latest)
|
||
- `FETCH_BLOCK_RANGE` (payload: `start`, `end`)
|
||
|
||
### Groups and ATs
|
||
|
||
- `LIST_GROUPS` (payload: `limit`, `offset`, `reverse`)
|
||
- `LIST_ATS` (payload: `limit`, `offset`, `reverse`)
|
||
- `GET_AT` (payload: `at` or `atAddress`)
|
||
- `GET_AT_DATA` (payload: `at` or `atAddress`)
|
||
|
||
### QDN and hosted data
|
||
|
||
- `PUBLISH_QDN_RESOURCE` (payload: `walletId`, `registeredName`, `service`, plus one of `data` (base64), `stringData`/`text` (plain), or `filePath` (chunked), optional `uploadType`, optional `encrypt` + `publicKeys[]`, `identifier`, `title`, `description`, `category`, `tags`, `fee`)
|
||
- `PUBLISH_MULTIPLE_QDN_RESOURCES` (payload: `walletId`, `resources[]`, optional `continueOnError`, optional top-level `encrypt` + `publicKeys[]`, each resource can include `uploadType`/`filePath` and optional `disableEncrypt`)
|
||
- `GET_QDN_RESOURCE_URL` (payload: `service`, `name`, optional `identifier`)
|
||
- `LINK_TO_QDN_RESOURCE` (alias of `GET_QDN_RESOURCE_URL`)
|
||
- `FETCH_QDN_RESOURCE` (payload: `service`, `name`, optional `identifier`, `build`, `attempt`, `timeoutMs`, `nodeUrl`)
|
||
- `GET_QDN_RESOURCE_STATUS` (payload: `service`, `name`, `identifier`, optional `build`)
|
||
- `GET_QDN_RESOURCE_PROPERTIES` (payload: `service`, `name`, `identifier`, optional `build`)
|
||
- `GET_QDN_RESOURCE_METADATA` (payload: `service`, `name`, optional `identifier`)
|
||
- `LIST_QDN_RESOURCES` (payload: `service`, `name`, `identifier`, `limit`, `offset`, `reverse`)
|
||
- `SEARCH_QDN_RESOURCES` (payload: `query`, `service`, `name`, `identifier`, `limit`, `offset`, `reverse`)
|
||
- `GET_HOSTED_DATA` (payload: `limit`, `offset`, `query`)
|
||
- `DELETE_HOSTED_DATA` (payload: `walletId`, `hostedData[]`)
|
||
- `SAVE_FILE` (payload: `filename` or relative `filePath`, plus `text` or `base64|data64|data`; writes under `QORTAL_AUTH_SAVE_FILE_DIR`)
|
||
|
||
### Crosschain
|
||
|
||
- `GET_CROSSCHAIN_SERVER_INFO` (payload: `coin`)
|
||
- `GET_SERVER_CONNECTION_HISTORY` (payload: `coin`)
|
||
- `GET_TX_ACTIVITY_SUMMARY` (payload: `coin`)
|
||
- `GET_FOREIGN_FEE` (payload: `coin`, `type` where `type` is `feekb` or `feerequired`)
|
||
- `GET_PRICE` (payload: `blockchain`, optional `limit`, `offset`, `reverse`, `inverse`)
|
||
- `GET_ARRR_SYNC_STATUS` (payload optional)
|
||
- `ADD_FOREIGN_SERVER` (payload: `coin`, `serverInfo`)
|
||
- `REMOVE_FOREIGN_SERVER` (payload: `coin`, `serverInfo`)
|
||
- `SET_CURRENT_FOREIGN_SERVER` (payload: `coin`, `serverInfo`)
|
||
- `UPDATE_FOREIGN_FEE` (payload: `coin`, `feeType`=`feekb|feerequired`, `fee`)
|
||
- `SIGN_FOREIGN_FEES` (payload: `signedFees[]`)
|
||
|
||
### Lists
|
||
|
||
- `GET_LIST_ITEMS` (payload: `listName`)
|
||
- `ADD_LIST_ITEMS` (payload: `listName`, `items[]`)
|
||
- `DELETE_LIST_ITEM` (payload: `listName`, `items[]`)
|
||
|
||
### Chat
|
||
|
||
- `SEARCH_CHAT_MESSAGES` (payload: `txGroupId` or `involving[]`, plus search params)
|
||
- `SEND_CHAT_MESSAGE` (build + sign + process; see transaction build list below)
|
||
|
||
### Transaction build + sign + process
|
||
|
||
These build a raw unsigned tx via the Qortal node, sign it with your wallet, and (by default) process it.
|
||
All require `payload.walletId` and a node-format `payload.tx` unless noted.
|
||
Set `payload.process=false` to return signed bytes without processing.
|
||
If `timestamp`, `reference` (or `lastReference`), `fee`, or the required `*PublicKey` field are omitted, the daemon will fill them using the wallet and node.
|
||
|
||
- `REGISTER_NAME` (can auto-build from `name` + optional `description`)
|
||
- `UPDATE_NAME`
|
||
- `SELL_NAME`
|
||
- `CANCEL_SELL_NAME`
|
||
- `BUY_NAME`
|
||
- `CREATE_GROUP`
|
||
- `UPDATE_GROUP`
|
||
- `ADD_GROUP_ADMIN`
|
||
- `REMOVE_GROUP_ADMIN`
|
||
- `BAN_FROM_GROUP`
|
||
- `CANCEL_GROUP_BAN`
|
||
- `KICK_FROM_GROUP`
|
||
- `INVITE_TO_GROUP`
|
||
- `CANCEL_GROUP_INVITE`
|
||
- `JOIN_GROUP`
|
||
- `LEAVE_GROUP`
|
||
- `CREATE_POLL`
|
||
- `VOTE_ON_POLL`
|
||
- `SEND_COIN` (PAYMENT)
|
||
- `TRANSFER_ASSET`
|
||
- `CREATE_ASSET_ORDER`
|
||
- `CANCEL_ASSET_ORDER`
|
||
- `DEPLOY_AT`
|
||
- `SEND_CHAT_MESSAGE` (includes `/chat/compute` PoW step)
|
||
|
||
### REGISTER_NAME (auto-build)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"REGISTER_NAME","payload":{"walletId":"<WALLET_ID>","name":"my-name","description":"Registered Name"}}'
|
||
```
|
||
|
||
### SIGN_TRANSACTION
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"SIGN_TRANSACTION","payload":{"walletId":"<WALLET_ID>","password":"backup-password","unsignedBytes":"<BASE58>","process":false}}'
|
||
```
|
||
|
||
### DECODE_TRANSACTION
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"DECODE_TRANSACTION","payload":{"bytes":"<BASE58_TX_BYTES>"}}'
|
||
```
|
||
|
||
## Approvals (Hub-style consent)
|
||
|
||
If a request needs approval and the wallet/app is in `ask` mode, the daemon returns:
|
||
|
||
```json
|
||
{ "error": "approval_required", "scope": "<SCOPE>", "walletId": "<WALLET_ID>" }
|
||
```
|
||
|
||
To approve once, request a short-lived approval token and retry with
|
||
`X-Qortal-Approval-Token`:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/permissions/approve \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION"}'
|
||
```
|
||
|
||
Then retry the original request with:
|
||
|
||
```bash
|
||
-H 'X-Qortal-Approval-Token: <APPROVAL_TOKEN>'
|
||
```
|
||
|
||
To auto-approve for a scope, set a mode:
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/permissions/set \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"persistent"}'
|
||
```
|
||
|
||
### PUBLISH_QDN_RESOURCE
|
||
|
||
`uploadType` is optional and auto-detected. `filePath` is read from the daemon filesystem (mount it if running in Docker). For zip files with `filePath`, set `isZip: true`.
|
||
|
||
- `file` if `filePath` is provided (chunked upload)
|
||
- `string` if `stringData` or `text` is provided
|
||
- `zip` if `uploadType` is explicitly `zip` (base64 zip via `data`)
|
||
- `base64` otherwise (`data` is base64)
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":"<BASE64>"}}'
|
||
```
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","uploadType":"file","filePath":"/data/uploads/document.pdf"}}'
|
||
```
|
||
|
||
### PUBLISH_MULTIPLE_QDN_RESOURCES
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"PUBLISH_MULTIPLE_QDN_RESOURCES","payload":{"walletId":"<WALLET_ID>","password":"backup-password","continueOnError":false,"resources":[{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"one","data":"<BASE64>"},{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"two","data":"<BASE64>"}]}}'
|
||
```
|
||
|
||
### GET_USER_ACCOUNT
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"GET_USER_ACCOUNT","payload":{"walletId":"<WALLET_ID>"}}'
|
||
```
|
||
|
||
### WHICH_UI
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"WHICH_UI","payload":{}}'
|
||
```
|
||
|
||
Response is a string (`HUB_WEB` or `HUB_ELECTRON`).
|
||
|
||
### SAVE_FILE
|
||
|
||
Writes a file on the daemon host filesystem. Relative paths are resolved under
|
||
`QORTAL_AUTH_SAVE_FILE_DIR` (default `data/saved-files`). Absolute paths are only
|
||
accepted if they are inside that same base directory.
|
||
|
||
```bash
|
||
curl -s -X POST http://127.0.0.1:3191/qortal/request \
|
||
-H 'Authorization: Bearer <TOKEN>' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"requestType":"SAVE_FILE","payload":{"filename":"example.txt","text":"hello world"}}'
|
||
```
|
||
|
||
## Storage
|
||
|
||
Data is stored under `./data/` by default:
|
||
|
||
- `data/state.json` (apps + wallet metadata + persistent permissions)
|
||
- `data/settings.json` (global/app/wallet settings)
|
||
- `data/audit.log` (JSONL audit trail)
|
||
- `data/wallets/qortal_backup_<address>.json` (Qortal-compatible backups)
|
||
|
||
Override with `QORTAL_AUTH_DATA_DIR` if needed. Use `QORTAL_AUTH_NODE_URL` to set the default node URL.
|
||
|
||
## Testing
|
||
|
||
### Run the backup parity test
|
||
|
||
Put a Qortal backup file in `TEST-FILES/` and run:
|
||
|
||
```bash
|
||
QORTAL_TEST_BACKUP_PASSWORD='Password1234x!' \
|
||
QORTAL_TEST_BACKUP_FILE='not-used-test-2.json' \
|
||
npm run test
|
||
```
|
||
|
||
If you omit `QORTAL_TEST_BACKUP_FILE`, it will use the default file already referenced in the test.
|