# 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://: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 `/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 ``` If a permission is in `ask` mode, include: ```text X-Qortal-Approval-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":} ``` 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/ POST /wallet/unlock Body: {"walletId":"...","password":"...","ttlSeconds":1200} GET /wallet/unlock/status?walletId= 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= ``` Settings: ```text GET /settings?walletId= POST /settings Body: {"scope":"global|app|wallet","walletId":"","settings":{...}} ``` Transactions and QDN: ```text POST /tx/sign Body: {"walletId":"...","unsignedBytes":"","process":false,"password":"...","nodeUrl":"..."} POST /qdn/publish Body: {"walletId":"...","data":"","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/
&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 ' \ -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 ' ``` ### 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":"","appSecret":""}' ``` 1. In Swagger UI (`/docs`), click **Authorize** and paste: ```text Bearer ``` ### 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 ' \ -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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","unsignedBytes":"","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":"","appSecret":""}' ``` ### 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 ' \ -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 ' \ -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 ' \ -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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","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 ' ``` ### Get wallet ```bash curl -s -X GET http://127.0.0.1:3191/wallet/ \ -H 'Authorization: Bearer ' ``` ## 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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","scope":"SIGN_TRANSACTION"}' ``` Use the returned `approvalToken` as header: ```bash -H 'X-Qortal-Approval-Token: ' ``` ### List persistent permissions ```bash curl -s -X GET http://127.0.0.1:3191/permissions?walletId= \ -H 'Authorization: Bearer ' ``` ## 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=' \ -H 'Authorization: Bearer ' ``` ### Set global defaults ```bash curl -s -X POST http://127.0.0.1:3191/settings \ -H 'Authorization: Bearer ' \ -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 ' \ -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 ' \ -H 'Content-Type: application/json' \ -d '{\"scope\":\"wallet\",\"walletId\":\"\",\"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 ' \ -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 ' \ -H 'Content-Type: application/json' \ -d '{\"walletId\":\"\",\"password\":\"backup-password\",\"ttlSeconds\":1200}' ``` ### Unlock status ```bash curl -s -X GET 'http://127.0.0.1:3191/wallet/unlock/status?walletId=' \ -H 'Authorization: Bearer ' ``` ### Lock ```bash curl -s -X POST http://127.0.0.1:3191/wallet/lock \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{\"walletId\":\"\"}' ``` ## 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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","password":"backup-password","unsignedBytes":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":"","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 ' \ -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-` or `admins-symmetric-qchat-group-`), 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 ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"REGISTER_NAME","payload":{"walletId":"","name":"my-name","description":"Registered Name"}}' ``` ### SIGN_TRANSACTION ```bash curl -s -X POST http://127.0.0.1:3191/qortal/request \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"SIGN_TRANSACTION","payload":{"walletId":"","password":"backup-password","unsignedBytes":"","process":false}}' ``` ### DECODE_TRANSACTION ```bash curl -s -X POST http://127.0.0.1:3191/qortal/request \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"DECODE_TRANSACTION","payload":{"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": "", "walletId": "" } ``` 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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","scope":"SIGN_TRANSACTION"}' ``` Then retry the original request with: ```bash -H 'X-Qortal-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 ' \ -H 'Content-Type: application/json' \ -d '{"walletId":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":""}}' ``` ```bash curl -s -X POST http://127.0.0.1:3191/qortal/request \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"","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 ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"PUBLISH_MULTIPLE_QDN_RESOURCES","payload":{"walletId":"","password":"backup-password","continueOnError":false,"resources":[{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"one","data":""},{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"two","data":""}]}}' ``` ### GET_USER_ACCOUNT ```bash curl -s -X POST http://127.0.0.1:3191/qortal/request \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"requestType":"GET_USER_ACCOUNT","payload":{"walletId":""}}' ``` ### WHICH_UI ```bash curl -s -X POST http://127.0.0.1:3191/qortal/request \ -H 'Authorization: Bearer ' \ -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 ' \ -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_
.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.