Extension API
How it works
Sending a request
Add listener
All together- React example
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 request6 timeout: 300007 }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 connected5 }6}78document.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 = 200005): Promise<any> {6 return new Promise((resolve, reject) => {7 const requestId = Math.random().toString(36).substring(2, 15); // Generate a unique ID for the request8 const detail = {9 type: requestType,10 payload,11 requestId,12 timeout: timeout / 1000,13 };1415 // Store the timeout ID so it can be cleared later16 const timeoutId = setTimeout(() => {17 document.removeEventListener("qortalExtensionResponses", handleResponse);18 reject(new Error("Request timed out"));19 }, timeout); // Adjust timeout as necessary2021 function handleResponse(event: any) {22 const { requestId: responseId, data } = event.detail;23 if (requestId === responseId) {24 // Match the response with the request25 document.removeEventListener(26 "qortalExtensionResponses",27 handleResponse28 );29 clearTimeout(timeoutId); // Clear the timeout upon successful response30 resolve(data);31 }32 }3334 document.addEventListener("qortalExtensionResponses", handleResponse);35 document.dispatchEvent(36 new CustomEvent("qortalExtensionRequests", { detail })37 );38 });39}404142async 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}5051const 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};6465useEffect(()=> {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 request6 timeout: 7507 }8 })9);
RESPONSE
1// user accepts2true
RESPONSE
1// user declines2false
RESPONSE
1// error2{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 request6 timeout: 300007 }8 })9);
RESPONSE
1// user accepts2true
RESPONSE
1// user declines2false
RESPONSE
1// error2{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 request6 timeout: 300007 }8 })9);
RESPONSE
1// user accepts2true
RESPONSE
1// user declines2false
RESPONSE
1// error2{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 request6 timeout: 150007 }8 })9);
RESPONSE
1{2 "name": "myname", // this may not exist3 "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": 012}
RESPONSE
1// for new accounts that have never made a transaction2{3 "name": "",4 "address": "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J"5}
RESPONSE
1// error2{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 request6 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 message9 senderAddress: "QWxEcmZxnM8yb1p92C1YKKRsp8svSVbABs", // your webapp's Qortal Address that you sent the chat message from10 senderPublicKey: "482YWxWXpfP2egNibGspRpV7eBPWRx4zbrGhkvDZgGpa", // your webapp's Qortal publicKey that you sent the chat message from11 timestamp: 1722590547725, // the timestamp in milliseconds of when your app sent the chat message12 }13 }14 })15);
RESPONSE
178248 // or whatever you encrypted in the chat message
RESPONSE
1// error2{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 request6 timeout: 300000,7 payload: {8 description: "Send Qort to participate in the game", // thing string will appear in the extension popup9 amount: 50, // in QORT10 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: 168432131052212 txGroupId: 013 type: 'PAYMENT'14 }15 validApi: "https://api.qortal.org" // the node that was used to send the QORT16}
RESPONSE
1// If payment was not successfully sent2{3 res: false4 validApi: "https://api.qortal.org" // the node that was used to send the QORT5}
RESPONSE
1// error2{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 request6 timeout: 7507 }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 request6 timeout: 600007 }8 })9);
RESPONSE
10.43193087
RESPONSE
1// error2{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 request6 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 address6}
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 base585 node: "https://api.qortal.org",6 qortAddress: "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J",7 chatSignature: "3FJXWUvFrhuQ8C28v956BR8iANcZgwAofL7qCHud7Ba64NyCRUmpD5eYECo8NHgj1FkyaMqqCtnnLsydgJgXFVqB",8 senderPublicKey: "482YWxWXpfP2egNibGspRpV7eBPWRx4zbrGhkvDZgGpa",9 sender: "Qe3eHrAMSZ2BWtqGTNDPMZooNPLacUAF6J",10 reference: chatreference // chat reference11}
RESPONSE
1// error2{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 states4 setUserInfo(null);5 localStorage.setItem("token", "");6 } else if(event.data.type === "RESPONSE_FOR_TRADES"){78 const response = event.data.payload9 const tradeHasBeenExecuted = response.callResponse // will be true if the node has successfully sent a trade request10 const atAddressOfTrade = response.extra.atAddress11 const message = response.extra.message12 };1314 useEffect(() => {15 window.addEventListener("message", handleMessage);1617 return () => {18 window.removeEventListener("message", handleMessage);19 };20 }, [userInfo?.address]);