Qortal Core - Main Code Repository Decentralized Data Network - Blockchain - TRUE Cross-Chain Trading - Application and Website Hosting - Much More - Qortal is the future internet infrastructure for the global digital world.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

422 lines
11 KiB

# Qortal Project - Q-Apps Documentation
## Introduction
Q-Apps are static web apps written in javascript, HTML, CSS, and other static assets. The key difference between a Q-App and a fully static site is its ability to interact with both the logged-in user and on-chain data. This is achieved using the API described in this document.
## Making a request
Qortal core will automatically inject a `qortalRequest()` javascript function (a Promise) to all websites/apps. This can be used to fetch or publish data to or from the Qortal blockchain. This functionality supports async/await, as well as try/catch error handling.
```
async function myfunction() {
try {
let res = await qortalRequest({
action: "GET_ACCOUNT_DATA",
address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
});
console.log(JSON.stringify(res)); // Log the response to the console
} catch(e) {
console.log("Error: " + JSON.stringify(e));
}
}
myfunction();
```
## Timeouts
By default, all requests will timeout after 10 seconds, and will throw an error - `The request timed out`. If you need a longer timeout - e.g. when fetching large QDN resources that may take a long time to be retried, you can use `qortalRequestWithTimeout(request, timeout)` as an alternative to `qortalRequest(request)`.
```
async function myfunction() {
try {
let timeout = 60000; // 60 seconds
let res = await qortalRequestWithTimeout({
action: "FETCH_QDN_RESOURCE",
name: "QortalDemo",
service: "THUMBNAIL",
identifier: "qortal_avatar"
}, timeout);
// Do something with the avatar here
} catch(e) {
console.log("Error: " + JSON.stringify(e));
}
}
myfunction();
```
## Supported methods
Here is a list of currently supported methods:
- GET_ACCOUNT_ADDRESS
- GET_ACCOUNT_PUBLIC_KEY
- GET_ACCOUNT_DATA
- GET_ACCOUNT_NAMES
- GET_NAME_DATA
- SEARCH_QDN_RESOURCES
- GET_QDN_RESOURCE_STATUS
- FETCH_QDN_RESOURCE
- PUBLISH_QDN_RESOURCE
- GET_WALLET_BALANCE
- GET_BALANCE
- SEND_COIN
- SEARCH_CHAT_MESSAGES
- SEND_CHAT_MESSAGE
- LIST_GROUPS
- JOIN_GROUP
- DEPLOY_AT
- GET_AT
- GET_AT_DATA
More functionality will be added in the future.
## Example Requests
Here is some example requests for each of the above:
### Get address of logged in account
_Will likely require user approval_
```
let address = await qortalRequest({
action: "GET_ACCOUNT_ADDRESS"
});
```
### Get public key of logged in account
_Will likely require user approval_
```
let pubkey = await qortalRequest({
action: "GET_ACCOUNT_PUBLIC_KEY"
});
```
### Get account data
```
let res = await qortalRequest({
action: "GET_ACCOUNT_DATA",
address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
});
```
### Get names owned by account
```
let res = await qortalRequest({
action: "GET_ACCOUNT_NAMES",
address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
});
```
### Get name data
```
let res = await qortalRequest({
action: "GET_NAME_DATA",
name: "QortalDemo"
});
```
### Search QDN resources
```
let res = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "THUMBNAIL",
identifier: "qortal_avatar", // Optional
default: true, // Optional
nameListFilter: "FollowedNames", // Optional
includeStatus: false,
includeMetadata: false,
limit: 100,
offset: 0,
reverse: true
});
```
### Fetch QDN single file resource
Data is returned in the base64 format
```
let res = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: "QortalDemo",
service: "THUMBNAIL",
identifier: "qortal_avatar", // Optional. If omitted, the default resource is returned, or you can alternatively use the keyword "default"
rebuild: false
});
```
### Fetch file from multi file QDN resource
Data is returned in the base64 format
```
let res = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: "QortalDemo",
service: "WEBSITE",
identifier: "default", // Optional. If omitted, the default resource is returned, or you can alternatively request that using the keyword "default", as shown here
filepath: "index.html", // Required only for resources containing more than one file
rebuild: false
});
```
### Get QDN resource status
```
let res = await qortalRequest({
action: "GET_QDN_RESOURCE_STATUS",
name: "QortalDemo",
service: "THUMBNAIL",
identifier: "qortal_avatar" // Optional
});
```
### Publish QDN resource
_Requires user approval_
```
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: "Demo", // Publisher must own the registered name - use GET_ACCOUNT_NAMES for a list
service: "WEBSITE",
data64: "base64_encoded_data",
title: "Title",
description: "Description",
category: "TECHNOLOGY",
tags: ["tag1", "tag2", "tag3", "tag4", "tag5"]
});
```
### Get wallet balance (QORT)
_Requires user approval_
```
await qortalRequest({
action: "GET_WALLET_BALANCE",
coin: "QORT"
});
```
### Get wallet balance (foreign coin)
_Requires user approval_
```
await qortalRequest({
action: "GET_WALLET_BALANCE",
coin: "LTC"
});
```
### Get address or asset balance
```
let res = await qortalRequest({
action: "GET_BALANCE",
address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
});
```
```
let res = await qortalRequest({
action: "GET_BALANCE",
assetId: 1,
address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
});
```
### Send coin to address
_Requires user approval_
```
await qortalRequest({
action: "SEND_COIN",
coin: "QORT",
destinationAddress: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
amount: 100000000, // 1 QORT
fee: 10000 // 0.0001 QORT
});
```
### Send coin to address
_Requires user approval_
```
await qortalRequest({
action: "SEND_COIN",
coin: "LTC",
destinationAddress: "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y",
amount: 100000000, // 1 LTC
fee: 20 // 0.00000020 LTC per byte
});
```
### Search or list chat messages
```
let res = await qortalRequest({
action: "SEARCH_CHAT_MESSAGES",
before: 999999999999999,
after: 0,
txGroupId: 0, // Optional (must specify either txGroupId or two involving addresses)
// involving: ["QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2", "QSefrppsDCsZebcwrqiM1gNbWq7YMDXtG2"], // Optional (must specify either txGroupId or two involving addresses)
// reference: "reference", // Optional
// chatReference: "chatreference", // Optional
// hasChatReference: true, // Optional
limit: 100,
offset: 0,
reverse: true
});
```
### Send a group chat message
_Requires user approval_
```
await qortalRequest({
action: "SEND_CHAT_MESSAGE",
groupId: 0,
message: "Test"
});
```
### Send a private chat message
_Requires user approval_
```
await qortalRequest({
action: "SEND_CHAT_MESSAGE",
destinationAddress: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
message: "Test"
});
```
### List groups
```
let res = await qortalRequest({
action: "LIST_GROUPS",
limit: 100,
offset: 0,
reverse: true
});
```
### Join a group
_Requires user approval_
```
await qortalRequest({
action: "JOIN_GROUP",
groupId: 100
});
```
### Deploy an AT
_Requires user approval_
```
let res = await qortalRequest({
action: "DEPLOY_AT",
creationBytes: "12345",
name: "test name",
description: "test description",
type: "test type",
tags: "test tags",
amount: 100000000, // 1 QORT
assetId: 0,
fee: 20000 // 0.0002 QORT
});
```
### Get AT info
```
let res = await qortalRequest({
action: "GET_AT",
atAddress: "ASRUsCjk6fa5bujv3oWYmWaVqNtvxydpPH"
});
```
### Get AT data bytes (base58 encoded)
```
let res = await qortalRequest({
action: "GET_AT_DATA",
atAddress: "ASRUsCjk6fa5bujv3oWYmWaVqNtvxydpPH"
});
```
### List ATs by functionality
```
let res = await qortalRequest({
action: "LIST_ATS",
codeHash58: "4KdJETRAdymE7dodDmJbf5d9L1bp4g5Nxky8m47TBkvA",
isExecutable: true,
limit: 100,
offset: 0,
reverse: true
});
```
## Sample App
Here is a sample application to display the logged-in user's avatar:
```
<html>
<head>
<script>
2 years ago
async function showAvatar() {
try {
// Get QORT address of logged in account
let address = await qortalRequest({
action: "GET_ACCOUNT_ADDRESS"
});
console.log("address: " + address);
2 years ago
// Get names owned by this account
let names = await qortalRequest({
action: "GET_ACCOUNT_NAMES",
address: address
});
console.log("names: " + JSON.stringify(names));
if (names.size == 0) {
console.log("User has no registered names");
return;
}
// Download the avatar of the first registered name
let avatar = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: names[0].name,
service: "THUMBNAIL",
identifier: "qortal_avatar"
});
console.log("avatar: " + JSON.stringify(avatar));
// Display the avatar image on the screen
document.getElementsById("avatar").src = "data:image/png;base64," + avatar;
} catch(e) {
console.log("Error: " + JSON.stringify(e));
}
}
2 years ago
showAvatar();
</script>
</head>
<body>
<img width="500" id="avatar" />
</body>
</html>
```
## Testing and Development
Publishing an in-development app to mainnet isn't recommended. There are several options for developing and testing a Q-app before publishing to mainnet:
### Preview mode
All read-only operations can be tested using preview mode. It can be used as follows:
1. Ensure Qortal core is running locally on the machine you are developing on. Previewing via a remote node is not currently possible.
2. Make a local API call to `POST /render/preview`, passing in the API key (found in apikey.txt), and the path to the root of your Q-App, for example:
```
curl -X POST "http://localhost:12391/render/preview" -H "X-API-KEY: apiKeyGoesHere" -d "/home/username/Websites/MyApp"
```
3. This returns a URL, which can be copied and pasted into a browser to view the preview
4. Modify the Q-App as required, then repeat from step 2 to generate a new preview URL
This is a short term method until preview functionality has been implemented within the UI.
### Single node testnet
For full read/write testing of a Q-App, you can set up a single node testnet (often referred to as devnet) on your local machine. See [Single Node Testnet Quick Start Guide](TestNets.md#quick-start).