Files

871 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 **autounlock** 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 autogrant 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 Hubs `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.