Extension API

  • Extension API Introduction
  • Setup
  • How it works

  • Sending a request

  • Add listener

  • All together- React example

  • Request types
  • Additional listeners

Extension API Introduction

Welcome to the Qortal Browser Extension API documentation!

This section provides comprehensive information and resources for developers looking to build web-applications that connect to Qortal through the Qortal Browser Extension.

You can add the Qortal Extension to your browser by visiting the Chrome extension store

Setup

This section explains how the Qortal Extension communicates with a web-app and how to setup your webapp to enable this communication.

How it works

The way the extension communicates with the webapp is very similar to how q-apps communicate with the UI.

The extension injects a script into the webapp. This script acts as a middleman between the extension and the webapp- relaying information from the webapp to the extension and vice-versa.

The injected script contains an event listener called 'qortalExtensionRequests'. We dispatch events to this listener from the webapp with specific paramaters in order to request certain information/actions from the extension. Once everything is completed, the listener in the script then dispatches an event to the webapp ( we can call this the response). The response event is called 'qortalExtensionResponses'.

webapp request => script listens to request and communicates with the extension => script then sends a response to the webapp

Sending a request

The first step is to send a request to the extension, such as 'REQUEST_CONNECTION'.

Context: The 'REQUEST_CONNECTION' request asks the user visiting the web app for permission to use the extension with the web app. For example, if the web app wants to get the user's Qortal address, it won't be able to do so until the user accepts the 'REQUEST_CONNECTION' request. This is the initial request that web apps should make, as it establishes the necessary connection. Without this connection, the web app won't be able to make other requests, such as those for payments or other actions.

Let's look at the following code

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_CONNECTION',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 30000
7 }
8 })
9);

The type parameter specifies which action you want from the extension. In this case 'REQUEST_CONNECTION'. In the section 'TYPES' we explain all the types and their request/response.

The second thing to note is the 'requestId' param. You give this param a unique id so that the extension can send a response back with that same Id. This allows your application to know which response corresponds with which request.

The third param is timeout. In milliseconds, this value tells the extension how long the user has to perform an action. In this example, the user will have 30000 milliseconds or 30 seconds to accept/decline the connection request.

Add listener

So far we discussed how to send a request to the extension. Now we will focus on how to capture the response from your webapp that the extension will send.

Let's look at this piece of code.

1function handleResponse(event: any) {
2 const { requestId: responseId, data } = event.detail;
3 if (requestId === responseId) {
4 // do what you need to do. Maybe tell the user that they are now connected
5 }
6}
7
8document.addEventListener("qortalExtensionResponses", handleResponse);

The extension sends an event called 'qortalExtensionResponses' so we setup an event listener. One of the fields returned is requestId which is the same requestId that the webapp sent as part of the request. The other field is data which will contain data concerning the request. In the case of 'REQUEST_CONNECTION' the data field will return true if the user accepted and false if the user declined.

All together- React example

Let's create a piece of React code that shows how we can create one async function called "sendRequestToExtension" that we can use for all our extension requests.

This function takes three paramaters. The first one is the requestType, so for example 'REQUEST_CONNECTION'. The payload param is an optional parameter. Some extension requests need additional information to complete the request. You would put that data here. Lastly, the last parameter is a timeout. This is to put a limit on the time that the user has to respond to the request.

Going back to the React side of things. In the useEffect we call the connectUser function which calls the reusable "sendRequestToExtension". If the user accepts the connection, the "sendRequestToExtension" will return true and if declined then false. If the timeout is executed then it will throw an error.

1export function sendRequestToExtension(
2 requestType: string,
3 payload?: any,
4 timeout: number = 20000
5): Promise<any> {
6 return new Promise((resolve, reject) => {
7 const requestId = Math.random().toString(36).substring(2, 15); // Generate a unique ID for the request
8 const detail = {
9 type: requestType,
10 payload,
11 requestId,
12 timeout: timeout / 1000,
13 };
14
15 // Store the timeout ID so it can be cleared later
16 const timeoutId = setTimeout(() => {
17 document.removeEventListener("qortalExtensionResponses", handleResponse);
18 reject(new Error("Request timed out"));
19 }, timeout); // Adjust timeout as necessary
20
21 function handleResponse(event: any) {
22 const { requestId: responseId, data } = event.detail;
23 if (requestId === responseId) {
24 // Match the response with the request
25 document.removeEventListener(
26 "qortalExtensionResponses",
27 handleResponse
28 );
29 clearTimeout(timeoutId); // Clear the timeout upon successful response
30 resolve(data);
31 }
32 }
33
34 document.addEventListener("qortalExtensionResponses", handleResponse);
35 document.dispatchEvent(
36 new CustomEvent("qortalExtensionRequests", { detail })
37 );
38 });
39}
40
41
42async function requestConnection() {
43 try {
44 const response = await sendRequestToExtension("REQUEST_CONNECTION");
45 return response;
46 } catch (error) {
47 console.error("Error requesting user info:", error);
48 }
49}
50
51const connectUser = async () => {
52 try {
53 const response = await requestConnection();
54 if (response === true) {
55 setIsExtensionConnected(true)
56 } else {
57 setIsExtensionConnected(false)
58 }
59 return;
60 } catch (error) {
61 console.error(error);
62 }
63};
64
65useEffect(()=> {
66 connectUser()
67}, [])

Request types

When dispatching a 'qortalExtensionRequests' event (request), a type needs to be provided along with other parameters. This section will provide documentation of all the possible types.

REQUEST_IS_INSTALLED

Use this request to determine if the user has the extension is already installed. If the response is false, then direct them to install the Qortal Extension.

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_IS_INSTALLED',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 750
7 }
8 })
9);

RESPONSE

1// user accepts
2true

RESPONSE

1// user declines
2false

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_CONNECTION

Use this request to get permission for your webapp to use other requests. Without the user accepting this request, other requests such as REQUEST_SEND_QORT won't be accesible.

This action requires user approval

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_CONNECTION',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 30000
7 }
8 })
9);

RESPONSE

1// user accepts
2true

RESPONSE

1// user declines
2false

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_AUTHENTICATION

Use this request to ask the user to authenticate into a Qortal Account. The user can have the extension install but they can still not be authenticated/logged in.

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_AUTHENTICATION',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 30000
7 }
8 })
9);

RESPONSE

1// user accepts
2true

RESPONSE

1// user declines
2false

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_USER_INFO

Use this request to retrieve information on the Qortal account. In order to be able to use this request the user must have: 1. Accepted connection 2. Has Authenticated

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_USER_INFO',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 15000
7 }
8 })
9);

RESPONSE

1{
2 "name": "myname", // this may not exist
3 "address": "QWxEcmZxnM8yb1p92C1YKKRsp8svSVbABs",
4 "reference": "3XzEqsFwP1JmaqXwQJr7fcakX21kVXruKzotBt3K3hpJAHxYMQ4HXFfYJdvejY1v6CRKP4w7PG8rfbiyFMroybTM",
5 "publicKey": "482YWxWXpfP2egNibGspRpV7eBPWRx4zbrGhkvDZgGpa",
6 "defaultGroupId": 369,
7 "flags": 0,
8 "level": 0,
9 "blocksMinted": 6,
10 "blocksMintedAdjustment": 0,
11 "blocksMintedPenalty": 0
12}

RESPONSE

1// for new accounts that have never made a transaction
2{
3 "name": "",
4 "address": "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J"
5}

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_OAUTH

Use this request to confirm that the authenticated user is the owner of the Qortal account. From your webapp's server you should send a private chat message to the user's Qortal address. This request will send back the message's content that you sent to the Qortal address. Only the rightful owner of the Qortal account will be able to decrypt the chat message.

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_OAUTH',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 300000,
7 payload: {
8 nodeBaseUrl: https://api.qortal.org, // recommended that use use the same nodeBaseUrl that you used to send the encrypted chat message
9 senderAddress: "QWxEcmZxnM8yb1p92C1YKKRsp8svSVbABs", // your webapp's Qortal Address that you sent the chat message from
10 senderPublicKey: "482YWxWXpfP2egNibGspRpV7eBPWRx4zbrGhkvDZgGpa", // your webapp's Qortal publicKey that you sent the chat message from
11 timestamp: 1722590547725, // the timestamp in milliseconds of when your app sent the chat message
12 }
13 }
14 })
15);

RESPONSE

178248 // or whatever you encrypted in the chat message

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_SEND_QORT

Use this request to request a payment in QORT.

This action requires user approval

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_SEND_QORT',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 300000,
7 payload: {
8 description: "Send Qort to participate in the game", // thing string will appear in the extension popup
9 amount: 50, // in QORT
10 address: "QWxEcmZxnM8yb1p92C1YKKRsp8svSVbABs", // the Qortal address of the receiver ( the person receiving the QORT )
11 }
12 }
13 })
14);

RESPONSE

1{
2 res: {
3 amount: '50.00000000'
4 approvalStatus: 'NOT_REQUIRED'
5 creatorAddress: 'QMjCNsctvWLoDdPSRpHn6TF2j96iDr9YWm'
6 fee: '0.00100000'
7 recipient: 'Qi3x7zVhN17mcYm9JTrEYaFihmETSZTzPD'
8 reference: '26xJXTxcdXhFUYFkyZ7qKkj94RtaLBevcyQgCwK3W5xt7JkGPrCbvNgdC46CmJA65cjTCXMykwiyYJfVsPdsU1fS'
9 senderPublicKey: 'Bjo1iUHJXbCb4LKabmE6KWNL5jSgCK36ypasoDgJG53U'
10 signature: '4j2iPN5Xwgocs8Z32JB4UB63G87qS43kPyEwFmQMLvWBXtrSQwAfyx8S9CqQvbregnstXFKqXpkPT2dNdAscriT4'
11 timestamp: 1684321310522
12 txGroupId: 0
13 type: 'PAYMENT'
14 }
15 validApi: "https://api.qortal.org" // the node that was used to send the QORT
16}

RESPONSE

1// If payment was not successfully sent
2{
3 res: false
4 validApi: "https://api.qortal.org" // the node that was used to send the QORT
5}

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_CLOSE_POPUP

Use this request to close the Qortal extension's popup.

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_CLOSE_POPUP',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 750
7 }
8 })
9);

RESPONSE

1true

REQUEST_LTC_BALANCE

Use this request to retrieve the user's LTC balance

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_LTC_BALANCE',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 60000
7 }
8 })
9);

RESPONSE

10.43193087

RESPONSE

1// error
2{
3 error: "error message"
4}

REQUEST_BUY_ORDER

Use this request to request a payment in QORT.

This action requires user approval

1document.dispatchEvent(
2 new CustomEvent("qortalExtensionRequests", {
3 detail: {
4 type: 'REQUEST_BUY_ORDER',
5 requestId: Math.random().toString(36).substring(2, 15), // Generate a unique ID for the request
6 timeout: 60000,
7 payload: {
8 qortalAtAddress: "AMQXmk217gonJzLia5KCPWQdVNfwWke8to"
9 }
10 }
11 })
12);

RESPONSE

1{
2 atAddress: "AMQXmk217gonJzLia5KCPWQdVNfwWke8to",
3 chatSignature: "3FJXWUvFrhuQ8C28v956BR8iANcZgwAofL7qCHud7Ba64NyCRUmpD5eYECo8NHgj1FkyaMqqCtnnLsydgJgXFVqB",
4 node: "https://api.qortal.org",
5 qortAddress: "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J" // the extension user's address
6}

RESPONSE

1// if the Qortal account is new and does not have its publicKey on the blockchain yet.
2{
3 atAddress: "AMQXmk217gonJzLia5KCPWQdVNfwWke8to",
4 encryptedMessageToBase58: "base58string", // encrypted message that was sent in base58
5 node: "https://api.qortal.org",
6 qortAddress: "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J",
7 chatSignature: "3FJXWUvFrhuQ8C28v956BR8iANcZgwAofL7qCHud7Ba64NyCRUmpD5eYECo8NHgj1FkyaMqqCtnnLsydgJgXFVqB",
8 senderPublicKey: "482YWxWXpfP2egNibGspRpV7eBPWRx4zbrGhkvDZgGpa",
9 sender: "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J",
10 reference: chatreference // chat reference
11}

RESPONSE

1// error
2{
3 error: "User has declined"
4}

Additional listeners

These are message events sent by the extension. The type 'LOGOUT' will be received when the user clicks the logout icon in the extension. This allows you to know when the user in no longer authenticated. In the React example, the userInfo state and token are cleared. The type 'RESPONSE_FOR_TRADES' will return trade statuses that were requested.

RESPONSE

1const handleMessage = async (event: any) => {
2 if (event.data.type === "LOGOUT") {
3 // reset states
4 setUserInfo(null);
5 localStorage.setItem("token", "");
6 } else if(event.data.type === "RESPONSE_FOR_TRADES"){
7
8 const response = event.data.payload
9 const tradeHasBeenExecuted = response.callResponse // will be true if the node has successfully sent a trade request
10 const atAddressOfTrade = response.extra.atAddress
11 const message = response.extra.message
12 };
13
14 useEffect(() => {
15 window.addEventListener("message", handleMessage);
16
17 return () => {
18 window.removeEventListener("message", handleMessage);
19 };
20 }, [userInfo?.address]);