diff --git a/plugins/plugins/core/components/qdn-action-constants.js b/plugins/plugins/core/components/qdn-action-constants.js index eb9c2aeb..323f6f10 100644 --- a/plugins/plugins/core/components/qdn-action-constants.js +++ b/plugins/plugins/core/components/qdn-action-constants.js @@ -54,3 +54,67 @@ export const mimeToExtensionMap = { "application/x-gzip": ".gz", "application/x-bzip2": ".bz2", } + +export const listOfAllQortalRequests = [ + 'IS_USING_GATEWAY', + 'ADMIN_ACTION', + 'SHOW_ACTIONS', + 'CREATE_AND_COPY_EMBED_LINK', + 'GET_USER_ACCOUNT', + 'REGISTER_NAME', + 'UPDATE_NAME', + 'ENCRYPT_DATA', + 'DECRYPT_DATA', + 'ENCRYPT_QORTAL_GROUP_DATA', + 'DECRYPT_QORTAL_GROUP_DATA', + 'ENCRYPT_DATA_WITH_SHARING_KEY', + 'DECRYPT_DATA_WITH_SHARING_KEY', + 'CREATE_TRADE_BUY_ORDER', + 'CREATE_TRADE_SELL_ORDER', + 'CANCEL_TRADE_SELL_ORDER', + 'GET_LIST_ITEMS', + 'ADD_LIST_ITEMS', + 'DELETE_LIST_ITEM', + 'GET_FRIENDS_LIST', + 'LINK_TO_QDN_RESOURCE', + 'QDN_RESOURCE_DISPLAYED', + 'SET_TAB_NOTIFICATIONS', + 'PUBLISH_QDN_RESOURCE', + 'PUBLISH_MULTIPLE_QDN_RESOURCES', + 'VOTE_ON_POLL', + 'CREATE_POLL', + 'OPEN_NEW_TAB', + 'NOTIFICATIONS_PERMISSION', + 'SEND_LOCAL_NOTIFICATION', + 'SEND_CHAT_MESSAGE', + 'JOIN_GROUP', + 'LEAVE_GROUP', + 'INVITE_TO_GROUP', + 'CANCEL_GROUP_INVITE', + 'KICK_FROM_GROUP', + 'BAN_FROM_GROUP', + 'CANCEL_GROUP_BAN', + 'ADD_GROUP_ADMIN', + 'REMOVE_GROUP_ADMIN', + 'SAVE_FILE', + 'GET_HOSTED_DATA', + 'DELETE_HOSTED_DATA', + 'DEPLOY_AT', + 'GET_PROFILE_DATA', + 'SET_PROFILE_DATA', + 'OPEN_PROFILE', + 'GET_USER_WALLET', + 'GET_WALLET_BALANCE', + 'GET_USER_WALLET_INFO', + 'GET_CROSSCHAIN_SERVER_INFO', + 'GET_TX_ACTIVITY_SUMMARY', + 'GET_FOREIGN_FEE', + 'UPDATE_FOREIGN_FEE', + 'GET_SERVER_CONNECTION_HISTORY', + 'SET_CURRENT_FOREIGN_SERVER', + 'ADD_FOREIGN_SERVER', + 'REMOVE_FOREIGN_SERVER', + 'GET_DAY_SUMMARY', + 'SIGN_TRANSACTION', + 'SEND_COIN' +] diff --git a/plugins/plugins/core/components/qdn-action-encryption.js b/plugins/plugins/core/components/qdn-action-encryption.js index f613bacd..dbccd768 100644 --- a/plugins/plugins/core/components/qdn-action-encryption.js +++ b/plugins/plugins/core/components/qdn-action-encryption.js @@ -1,3 +1,4 @@ +import Base58 from '../../../../crypto/api/deps/Base58' import nacl from '../../../../crypto/api/deps/nacl-fast.js' import ed2curve from '../../../../crypto/api/deps/ed2curve.js' @@ -33,10 +34,14 @@ export const fileToBase64 = (file) => new Promise(async (resolve, reject) => { if (!reader) { reader = new FileReader() } + await semaphore.acquire() + reader.readAsDataURL(file) + reader.onload = () => { const dataUrl = reader.result + if (typeof dataUrl === "string") { const base64String = dataUrl.split(',')[1] reader.onload = null @@ -49,6 +54,7 @@ export const fileToBase64 = (file) => new Promise(async (resolve, reject) => { } semaphore.release() } + reader.onerror = (error) => { reader.onload = null reader.onerror = null @@ -73,9 +79,11 @@ export function base64ToUint8Array(base64) { const binaryString = atob(base64) const len = binaryString.length const bytes = new Uint8Array(len) + for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i) } + return bytes } @@ -226,6 +234,102 @@ export const encryptDataGroup = ({ data64, publicKeys }) => { } } +export const encryptDataGroupNew = (data64, publicKeys, privateKey, userPublicKey, customSymmetricKey) => { + let combinedPublicKeys = [...publicKeys, userPublicKey] + + const decodedPrivateKey = Base58.decode(privateKey) + const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)] + const Uint8ArrayData = base64ToUint8Array(data64) + + if (!(Uint8ArrayData instanceof Uint8Array)) { + throw new Error("The Uint8ArrayData you've submitted is invalid") + } + + try { + // Generate a random symmetric key for the message. + let messageKey + + if(customSymmetricKey){ + messageKey = base64ToUint8Array(customSymmetricKey) + } else { + messageKey = new Uint8Array(32) + crypto.getRandomValues(messageKey) + } + + if(!messageKey) throw new Error('Cannot create symmetric key') + + const nonce = new Uint8Array(24) + + crypto.getRandomValues(nonce) + + // Encrypt the data with the symmetric key. + const encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey) + + // Generate a keyNonce outside of the loop. + const keyNonce = new Uint8Array(24) + crypto.getRandomValues(keyNonce) + + // Encrypt the symmetric key for each recipient. + let encryptedKeys = [] + + publicKeysDuplicateFree.forEach((recipientPublicKey) => { + const publicKeyUnit8Array = Base58.decode(recipientPublicKey) + const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey) + const convertedPublicKey = ed2curve.convertPublicKey(publicKeyUnit8Array) + const sharedSecret = new Uint8Array(32) + + // the length of the sharedSecret will be 32 + 16 + // When you're encrypting data using nacl.secretbox, it's adding an authentication tag to the result, which is 16 bytes long. This tag is used for verifying the integrity and authenticity of the data when it is decrypted + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + // Encrypt the symmetric key with the shared secret. + const encryptedKey = nacl.secretbox(messageKey, keyNonce, sharedSecret) + + encryptedKeys.push(encryptedKey) + }) + + const str = "qortalGroupEncryptedData" + const strEncoder = new TextEncoder() + const strUint8Array = strEncoder.encode(str) + + // Convert sender's public key to Uint8Array and add to the message + const senderPublicKeyUint8Array = Base58.decode(userPublicKey) + + // Combine all data into a single Uint8Array. + // Calculate size of combinedData + let combinedDataSize = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length + 4 + let encryptedKeysSize = 0 + + encryptedKeys.forEach((key) => { + encryptedKeysSize += key.length + }) + + combinedDataSize += encryptedKeysSize + let combinedData = new Uint8Array(combinedDataSize) + combinedData.set(strUint8Array) + combinedData.set(nonce, strUint8Array.length) + combinedData.set(keyNonce, strUint8Array.length + nonce.length) + combinedData.set(senderPublicKeyUint8Array, strUint8Array.length + nonce.length + keyNonce.length) + combinedData.set(encryptedData, strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length) + + // Initialize offset for encryptedKeys + let encryptedKeysOffset = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length + + encryptedKeys.forEach((key) => { + combinedData.set(key, encryptedKeysOffset) + encryptedKeysOffset += key.length + }) + + const countArray = new Uint8Array(new Uint32Array([publicKeysDuplicateFree.length]).buffer) + combinedData.set(countArray, combinedData.length - 4) + + return uint8ArrayToBase64(combinedData) + } catch (error) { + console.log('error', error) + throw new Error("Error in encrypting data") + } +} + export function uint8ArrayStartsWith(uint8Array, string) { const stringEncoder = new TextEncoder() const stringUint8Array = stringEncoder.encode(string) @@ -316,4 +420,101 @@ export function decryptGroupData(data64EncryptedData) { } } throw new Error("Unable to decrypt data") -} \ No newline at end of file +} + +export function decryptGroupDataNew(data64EncryptedData, privateKey) { + const allCombined = base64ToUint8Array(data64EncryptedData) + const str = "qortalGroupEncryptedData" + const strEncoder = new TextEncoder() + const strUint8Array = strEncoder.encode(str) + + // Extract the nonce + const nonceStartPosition = strUint8Array.length + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 + const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) + + // Extract the shared keyNonce + const keyNonceStartPosition = nonceEndPosition + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 + const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) + + // Extract the sender's public key + const senderPublicKeyStartPosition = keyNonceEndPosition + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 + const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition) + + // Calculate count first + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 + const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) + const count = new Uint32Array(countArray.buffer)[0] + + // Then use count to calculate encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition + const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) + const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) + + // Extract the encrypted keys + // 32+16 = 48 + const combinedKeys = allCombined.slice(encryptedDataEndPosition, encryptedDataEndPosition + (count * 48)) + + if (!privateKey) { + throw new Error("Unable to retrieve keys") + } + + const decodedPrivateKey = Base58.decode(privateKey) + const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey) + const convertedSenderPublicKey = ed2curve.convertPublicKey(senderPublicKey) + const sharedSecret = new Uint8Array(32) + + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedSenderPublicKey) + + for (let i = 0; i < count; i++) { + const encryptedKey = combinedKeys.slice(i * 48, (i + 1) * 48) + console.log("Encrypted KEY", encryptedKey) + console.log("KEY NONCE", keyNonce) + console.log("SHARED SECRET", sharedSecret) + // Decrypt the symmetric key. + const decryptedKey = nacl.secretbox.open(encryptedKey, keyNonce, sharedSecret) + + // If decryption was successful, decryptedKey will not be null. + if (decryptedKey) { + // Decrypt the data using the symmetric key. + const decryptedData = nacl.secretbox.open(encryptedData, nonce, decryptedKey) + + // If decryption was successful, decryptedData will not be null. + if (decryptedData) { + return {decryptedData, count} + } + } + } + + throw new Error("Unable to decrypt data") +} + +export const base64ToBlobUrl = (base64, mimeType = "image/png") => { + const binary = atob(base64) + const array = [] + + for (let i = 0; i < binary.length; i++) { + array.push(binary.charCodeAt(i)) + } + + const blob = new Blob([new Uint8Array(array)], { type: mimeType }) + + return URL.createObjectURL(blob) +} + +export let groupSecretkeys = {} + +export function roundUpToDecimals(number, decimals = 8) { + const factor = Math.pow(10, decimals) + return Math.ceil(+number * factor) / factor +} diff --git a/plugins/plugins/core/components/qdn-action-types.js b/plugins/plugins/core/components/qdn-action-types.js index ecff51bd..2f253e2c 100644 --- a/plugins/plugins/core/components/qdn-action-types.js +++ b/plugins/plugins/core/components/qdn-action-types.js @@ -1,62 +1,46 @@ +// IS_USING_GATEWAY +export const IS_USING_GATEWAY = 'IS_USING_GATEWAY' + +// ADMIN_ACTION +export const ADMIN_ACTION = 'ADMIN_ACTION' + +// SHOW_ACTIONS +export const SHOW_ACTIONS = 'SHOW_ACTIONS' + +// CREATE_AND_COPY_EMBED_LINK + // GET_USER_ACCOUNT export const GET_USER_ACCOUNT = 'GET_USER_ACCOUNT' -// LINK_TO_QDN_RESOURCE -export const LINK_TO_QDN_RESOURCE = 'LINK_TO_QDN_RESOURCE' +// REGISTER_NAME +// UPDATE_NAME -// QDN_RESOURCE_DISPLAYED -export const QDN_RESOURCE_DISPLAYED = 'QDN_RESOURCE_DISPLAYED' +// ENCRYPT_DATA +export const ENCRYPT_DATA = 'ENCRYPT_DATA' -// PUBLISH_QDN_RESOURCE -export const PUBLISH_QDN_RESOURCE = 'PUBLISH_QDN_RESOURCE' +// DECRYPT_DATA +export const DECRYPT_DATA = 'DECRYPT_DATA' -// SEND_CHAT_MESSAGE -export const SEND_CHAT_MESSAGE = 'SEND_CHAT_MESSAGE' +// ENCRYPT_QORTAL_GROUP_DATA +export const ENCRYPT_QORTAL_GROUP_DATA = 'ENCRYPT_QORTAL_GROUP_DATA' -// JOIN_GROUP -export const JOIN_GROUP = 'JOIN_GROUP' +// DECRYPT_QORTAL_GROUP_DATA +export const DECRYPT_QORTAL_GROUP_DATA = 'DECRYPT_QORTAL_GROUP_DATA' -// DEPLOY_AT -export const DEPLOY_AT = 'DEPLOY_AT' +// ENCRYPT_DATA_WITH_SHARING_KEY +export const ENCRYPT_DATA_WITH_SHARING_KEY = 'ENCRYPT_DATA_WITH_SHARING_KEY' -// GET_USER_WALLET -export const GET_USER_WALLET = 'GET_USER_WALLET' +// DECRYPT_DATA_WITH_SHARING_KEY +export const DECRYPT_DATA_WITH_SHARING_KEY = 'DECRYPT_DATA_WITH_SHARING_KEY' -// GET_USER_WALLET_INFO -export const GET_USER_WALLET_INFO = 'GET_USER_WALLET_INFO' +// CREATE_TRADE_BUY_ORDER +export const CREATE_TRADE_BUY_ORDER = 'CREATE_TRADE_BUY_ORDER' -// GET_CROSSCHAIN_SERVER_INFO -export const GET_CROSSCHAIN_SERVER_INFO = 'GET_CROSSCHAIN_SERVER_INFO' +// CREATE_TRADE_SELL_ORDER +export const CREATE_TRADE_SELL_ORDER = 'CREATE_TRADE_SELL_ORDER' -// GET_TX_ACTIVITY_SUMMARY -export const GET_TX_ACTIVITY_SUMMARY = 'GET_TX_ACTIVITY_SUMMARY' - -// GET_FOREIGN_FEE action -export const GET_FOREIGN_FEE = 'GET_FOREIGN_FEE'; - -// UPDATE_FOREIGN_FEE action -export const UPDATE_FOREIGN_FEE = 'UPDATE_FOREIGN_FEE'; - -// GET_SERVER_CONNECTION_HISTORY -export let GET_SERVER_CONNECTION_HISTORY = "GET_SERVER_CONNECTION_HISTORY"; - -// SET_CURRENT_FOREIGN_SERVER -export let SET_CURRENT_FOREIGN_SERVER = "SET_CURRENT_FOREIGN_SERVER"; - -// ADD_FOREIGN_SERVER -export let ADD_FOREIGN_SERVER = "ADD_FOREIGN_SERVER"; - -// REMOVE_FOREIGN_SERVER -export let REMOVE_FOREIGN_SERVER = "REMOVE_FOREIGN_SERVER"; - -// GET_WALLET_BALANCE action -export const GET_WALLET_BALANCE = 'GET_WALLET_BALANCE' - -// SEND_COIN -export const SEND_COIN = 'SEND_COIN' - -// PUBLISH_MULTIPLE_QDN_RESOURCES -export const PUBLISH_MULTIPLE_QDN_RESOURCES = 'PUBLISH_MULTIPLE_QDN_RESOURCES' +// CANCEL_TRADE_SELL_ORDER +export const CANCEL_TRADE_SELL_ORDER = 'CANCEL_TRADE_SELL_ORDER' // GET_LIST_ITEMS export const GET_LIST_ITEMS = 'GET_LIST_ITEMS' @@ -67,21 +51,30 @@ export const ADD_LIST_ITEMS = 'ADD_LIST_ITEMS' // DELETE_LIST_ITEM export const DELETE_LIST_ITEM = 'DELETE_LIST_ITEM' -// ENCRYPT_DATA -export const ENCRYPT_DATA = 'ENCRYPT_DATA' +// GET_FRIENDS_LIST +export const GET_FRIENDS_LIST = 'GET_FRIENDS_LIST' -// DECRYPT_DATA -export const DECRYPT_DATA = 'DECRYPT_DATA' +// LINK_TO_QDN_RESOURCE +export const LINK_TO_QDN_RESOURCE = 'LINK_TO_QDN_RESOURCE' -// DECRYPT_DATA_GROUP -export const DECRYPT_DATA_GROUP = 'DECRYPT_DATA_GROUP' - -// SAVE_FILE -export const SAVE_FILE = 'SAVE_FILE' +// QDN_RESOURCE_DISPLAYED +export const QDN_RESOURCE_DISPLAYED = 'QDN_RESOURCE_DISPLAYED' // SET_TAB_NOTIFICATIONS export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS' +// PUBLISH_QDN_RESOURCE +export const PUBLISH_QDN_RESOURCE = 'PUBLISH_QDN_RESOURCE' + +// PUBLISH_MULTIPLE_QDN_RESOURCES +export const PUBLISH_MULTIPLE_QDN_RESOURCES = 'PUBLISH_MULTIPLE_QDN_RESOURCES' + +// VOTE_ON_POLL +export const VOTE_ON_POLL= 'VOTE_ON_POLL' + +// CREATE_POLL +export const CREATE_POLL= 'CREATE_POLL' + // OPEN_NEW_TAB export const OPEN_NEW_TAB = 'OPEN_NEW_TAB' @@ -91,11 +84,29 @@ export const NOTIFICATIONS_PERMISSION = 'NOTIFICATIONS_PERMISSION' // SEND_LOCAL_NOTIFICATION export const SEND_LOCAL_NOTIFICATION = 'SEND_LOCAL_NOTIFICATION' -// VOTE_ON_POLL -export const VOTE_ON_POLL= 'VOTE_ON_POLL' +// SEND_CHAT_MESSAGE +export const SEND_CHAT_MESSAGE = 'SEND_CHAT_MESSAGE' -// CREATE_POLL -export const CREATE_POLL= 'CREATE_POLL' +// JOIN_GROUP +export const JOIN_GROUP = 'JOIN_GROUP' + +// LEAVE_GROUP +// INVITE_TO_GROUP +// CANCEL_GROUP_INVITE +// KICK_FROM_GROUP +// BAN_FROM_GROUP +// CANCEL_GROUP_BAN +// ADD_GROUP_ADMIN +// REMOVE_GROUP_ADMIN + +// SAVE_FILE +export const SAVE_FILE = 'SAVE_FILE' + +// GET_HOSTED_DATA +// DELETE_HOSTED_DATA + +// DEPLOY_AT +export const DEPLOY_AT = 'DEPLOY_AT' // GET_PROFILE_DATA export const GET_PROFILE_DATA = 'GET_PROFILE_DATA' @@ -103,17 +114,47 @@ export const GET_PROFILE_DATA = 'GET_PROFILE_DATA' // SET_PROFILE_DATA export const SET_PROFILE_DATA= 'SET_PROFILE_DATA' -// GET_DAY_SUMMARY -export const GET_DAY_SUMMARY = 'GET_DAY_SUMMARY' - -// GET_FRIENDS_LIST -export const GET_FRIENDS_LIST = 'GET_FRIENDS_LIST' - // OPEN_PROFILE export const OPEN_PROFILE = 'OPEN_PROFILE' -// ADMIN_ACTION -export const ADMIN_ACTION = 'ADMIN_ACTION' +// GET_USER_WALLET +export const GET_USER_WALLET = 'GET_USER_WALLET' + +// GET_WALLET_BALANCE +export const GET_WALLET_BALANCE = 'GET_WALLET_BALANCE' + +// GET_USER_WALLET_INFO +export const GET_USER_WALLET_INFO = 'GET_USER_WALLET_INFO' + +// GET_CROSSCHAIN_SERVER_INFO +export const GET_CROSSCHAIN_SERVER_INFO = 'GET_CROSSCHAIN_SERVER_INFO' + +// GET_TX_ACTIVITY_SUMMARY +export const GET_TX_ACTIVITY_SUMMARY = 'GET_TX_ACTIVITY_SUMMARY' + +// GET_FOREIGN_FEE +export const GET_FOREIGN_FEE = 'GET_FOREIGN_FEE' + +// UPDATE_FOREIGN_FEE +export const UPDATE_FOREIGN_FEE = 'UPDATE_FOREIGN_FEE' + +// GET_SERVER_CONNECTION_HISTORY +export let GET_SERVER_CONNECTION_HISTORY = 'GET_SERVER_CONNECTION_HISTORY' + +// SET_CURRENT_FOREIGN_SERVER +export let SET_CURRENT_FOREIGN_SERVER = 'SET_CURRENT_FOREIGN_SERVER' + +// ADD_FOREIGN_SERVER +export let ADD_FOREIGN_SERVER = 'ADD_FOREIGN_SERVER' + +// REMOVE_FOREIGN_SERVER +export let REMOVE_FOREIGN_SERVER = 'REMOVE_FOREIGN_SERVER' + +// GET_DAY_SUMMARY +export const GET_DAY_SUMMARY = 'GET_DAY_SUMMARY' // SIGN_TRANSACTION export const SIGN_TRANSACTION = 'SIGN_TRANSACTION' + +// SEND_COIN +export const SEND_COIN = 'SEND_COIN' diff --git a/plugins/plugins/core/qdn/browser/browser.src.js b/plugins/plugins/core/qdn/browser/browser.src.js index 23fc00bf..1d33b81d 100644 --- a/plugins/plugins/core/qdn/browser/browser.src.js +++ b/plugins/plugins/core/qdn/browser/browser.src.js @@ -1,18 +1,45 @@ import { html, LitElement } from 'lit' import { Epml } from '../../../../epml' -import { Loader, publishData } from '../../../utils/classes' +import { + Loader, + publishData, + getPublishesFromAdmins, + getGroupAdmins, + getPublishesFromAdminsAdminSpace, + isRunningGateway, + createBuyOrderTx, + requestQueueGetAtAddresses, + getUserWalletFunc, + tradeBotCreateRequest, + cancelTradeOfferTradeBot, + processTransactionV2 +} from '../../../utils/classes' +import { appendBuffer } from '../../../utils/utilities' import { QORT_DECIMALS } from '../../../../../crypto/api/constants' -import { mimeToExtensionMap } from '../../components/qdn-action-constants' +import { mimeToExtensionMap, listOfAllQortalRequests } from '../../components/qdn-action-constants' +import { + uint8ArrayToObject, + validateSecretKey, + encryptSingle, + decryptSingle, + createSymmetricKeyAndNonce, + decryptGroupEncryptionWithSharingKey +} from '../../components/GroupEncryption' import { base64ToUint8Array, decryptDeprecatedSingle, decryptGroupData, + decryptGroupDataNew, encryptDataGroup, + encryptDataGroupNew, fileToBase64, uint8ArrayStartsWith, - uint8ArrayToBase64 + uint8ArrayToBase64, + base64ToBlobUrl, + groupSecretkeys, + objectToBase64, + roundUpToDecimals } from '../../components/qdn-action-encryption' -import { processTransactionVersion2 } from '../../../../../crypto/api/createTransaction' import { webBrowserStyles, webBrowserModalStyles } from '../../components/plugins-css' import * as actions from '../../components/qdn-action-types' import isElectron from 'is-electron' @@ -34,6 +61,33 @@ registerTranslateConfig({ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +const sellerForeignFee = { + LITECOIN: { + value: "~0.00005", + ticker: "LTC" + }, + DOGECOIN: { + value: "~0.005", + ticker: "DOGE" + }, + BITCOIN: { + value: "~0.0001", + ticker: "BTC" + }, + DIGIBYTE: { + value: "~0.0005", + ticker: "DGB" + }, + RAVENCOIN: { + value: "~0.006", + ticker: "RVN" + }, + PIRATECHAIN: { + value: "~0.0002", + ticker: "ARRR" + } +} + class WebBrowser extends LitElement { static get properties() { return { @@ -259,54 +313,31 @@ class WebBrowser extends LitElement { let data = event.data switch (data.action) { - case actions.GET_USER_ACCOUNT: { - let skip = false - if (window.parent.reduxStore.getState().app.qAPPAutoAuth) { - skip = true - } - let res1 - if (!skip) { - res1 = await showModalAndWait( - actions.GET_USER_ACCOUNT, - { - service: this.service, - name: this.name - } - ) - } - if ((res1 && res1.action === 'accept') || skip) { - let account = {} - account['address'] = this.selectedAddress.address - account['publicKey'] = - this.selectedAddress.base58PublicKey - response = JSON.stringify(account) - break - } else { - const data = {} - data['error'] = "User declined to share account details" - response = JSON.stringify(data) - break - } + case actions.IS_USING_GATEWAY: { + const res = await isRunningGateway() + return res } case actions.ADMIN_ACTION: { let type = data.type - let value = data.value // Extract value from data + let value = data.value let res1 = await showModalAndWait( actions.ADMIN_ACTION, { service: this.service, name: this.name, type: type, - value: value // Pass value to the modal + value: value } ) if (res1 && res1.action === 'accept') { try { // Determine the API endpoint based on the type let apiEndpoint = '' - let method = 'GET' // Default method - let includeValueInBody = false // Flag to include value in body + // Default method + let method = 'GET' + // Flag to include value in body + let includeValueInBody = false switch (type.toLowerCase()) { case 'stop': apiEndpoint = '/admin/stop' @@ -373,6 +404,41 @@ class WebBrowser extends LitElement { break } + case actions.SHOW_ACTIONS: { + const res = JSON.stringify(listOfAllQortalRequests) + return res + } + + case actions.GET_USER_ACCOUNT: { + let skip = false + if (window.parent.reduxStore.getState().app.qAPPAutoAuth) { + skip = true + } + let res1 + if (!skip) { + res1 = await showModalAndWait( + actions.GET_USER_ACCOUNT, + { + service: this.service, + name: this.name + } + ) + } + if ((res1 && res1.action === 'accept') || skip) { + let account = {} + account['address'] = this.selectedAddress.address + account['publicKey'] = + this.selectedAddress.base58PublicKey + response = JSON.stringify(account) + break + } else { + const data = {} + data['error'] = "User declined to share account details" + response = JSON.stringify(data) + break + } + } + case actions.ENCRYPT_DATA: { try { let dataSentBack = {} @@ -382,12 +448,10 @@ class WebBrowser extends LitElement { data64 = await fileToBase64(data.file) } if (!data64) { - dataSentBack['error'] = "Please include data to encrypt" response = JSON.stringify(dataSentBack) break } - const encryptDataResponse = encryptDataGroup({ data64, publicKeys: publicKeys }) @@ -396,7 +460,6 @@ class WebBrowser extends LitElement { response = JSON.stringify(encryptDataResponse) break } else { - dataSentBack['error'] = "Unable to encrypt" response = JSON.stringify(dataSentBack) break @@ -433,7 +496,6 @@ class WebBrowser extends LitElement { } const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith(uint8Array, "qortalGroupEncryptedData") if (startsWithQortalGroupEncryptedData) { - const decryptedData = decryptGroupData(encryptedData) const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData) response = JSON.stringify(decryptedDataToBase64) @@ -451,6 +513,507 @@ class WebBrowser extends LitElement { } } + case actions.ENCRYPT_QORTAL_GROUP_DATA: { + let data64 = data.data64 || data.base64 + let groupId = data.groupId + let isAdmins = data.isAdmins + let dataSentBack = {} + let secretKeyObject + if (!groupId) { + dataSentBack['error'] = "Please provide a groupId" + response = JSON.stringify(dataSentBack) + break + } + if (data.file || data.blob) { + data64 = await fileToBase64(data.file || data.blob) + } + if (!data64) { + dataSentBack['error'] = "Please include data to encrypt" + response = JSON.stringify(dataSentBack) + break + } + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId].timestamp && + (Date.now() - groupSecretkeys[groupId].timestamp) < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject + } + + if(!secretKeyObject) { + const { names } = await getGroupAdmins(groupId) + const publish = await getPublishesFromAdmins(names, groupId) + if (publish === false) { + dataSentBack['error'] = "No group key found." + response = JSON.stringify(dataSentBack) + break + } + const res = await parentEpml.request('apiCall', { + url: `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${publish.identifier}?encoding=base64` + }) + const resData = await res.text() + const decryptedKey = await this.decryptResourceQDN(resData) + const dataint8Array = base64ToUint8Array(decryptedKey.data) + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) + if (!validateSecretKey(decryptedKeyToObject)) { + dataSentBack['error'] = "SecretKey is not valid" + response = JSON.stringify(dataSentBack) + break + } + secretKeyObject = decryptedKeyToObject + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now() + } + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`].timestamp && + (Date.now() - groupSecretkeys[`admins-${groupId}`].timestamp) < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId) + const publish = await getPublishesFromAdminsAdminSpace(names, groupId) + if (publish === false) { + dataSentBack['error'] = "No group key found." + response = JSON.stringify(dataSentBack) + break + } + const res = await parentEpml.request('apiCall', { + url: `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${publish.identifier}?encoding=base64` + }) + const resData = await res.text() + const decryptedKey = await this.decryptResourceQDN(resData) + const dataint8Array = base64ToUint8Array(decryptedKey.data) + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) + if (!validateSecretKey(decryptedKeyToObject)) { + dataSentBack['error'] = "SecretKey is not valid" + response = JSON.stringify(dataSentBack) + break + } + secretKeyObject = decryptedKeyToObject + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now() + } + } + } + const resGroupEncryptedResource = encryptSingle({ + data64, secretKeyObject: secretKeyObject + }) + if (resGroupEncryptedResource) { + return resGroupEncryptedResource + } else { + dataSentBack['error'] = "Unable to encrypt" + response = JSON.stringify(dataSentBack) + break + } + } + + case actions.DECRYPT_QORTAL_GROUP_DATA: { + let data64 = data.data64 || data.base64 + let groupId = data.groupId + let isAdmins = data.isAdmins + let dataSentBack = {} + let secretKeyObject + if (!groupId) { + dataSentBack['error'] = "Please provide a groupId" + response = JSON.stringify(dataSentBack) + break + } + if (!data64) { + dataSentBack['error'] = "Please include data to encrypt" + response = JSON.stringify(dataSentBack) + break + } + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId].timestamp && + (Date.now() - groupSecretkeys[groupId].timestamp) < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject + } + + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId) + const publish = await getPublishesFromAdmins(names, groupId) + if (publish === false) { + dataSentBack['error'] = "No group key found." + response = JSON.stringify(dataSentBack) + break + } + const res = await parentEpml.request('apiCall', { + url: `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${publish.identifier}?encoding=base64` + }) + const resData = await res.text() + const decryptedKey = await this.decryptResourceQDN(resData) + const dataint8Array = base64ToUint8Array(decryptedKey.data) + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) + if (!validateSecretKey(decryptedKeyToObject)) { + dataSentBack['error'] = "SecretKey is not valid" + response = JSON.stringify(dataSentBack) + break + } + secretKeyObject = decryptedKeyToObject + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now() + } + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`].timestamp && + (Date.now() - groupSecretkeys[`admins-${groupId}`].timestamp) < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId) + const publish = await getPublishesFromAdminsAdminSpace(names, groupId) + if (publish === false) { + dataSentBack['error'] = "No group key found." + response = JSON.stringify(dataSentBack) + break + } + const res = await parentEpml.request('apiCall', { + url: `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${publish.identifier}?encoding=base64` + }) + const resData = await res.text() + const decryptedKey = await this.decryptResourceQDN(resData) + const dataint8Array = base64ToUint8Array(decryptedKey.data) + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) + if (!validateSecretKey(decryptedKeyToObject)) { + dataSentBack['error'] = "SecretKey is not valid" + response = JSON.stringify(dataSentBack) + break + } + secretKeyObject = decryptedKeyToObject + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now() + } + } + } + const resGroupDecryptResource = decryptSingle({ + data64, secretKeyObject: secretKeyObject, skipDecodeBase64: true + }) + if (resGroupDecryptResource) { + return resGroupDecryptResource + } else { + dataSentBack['error'] = "Unable to decrypt" + response = JSON.stringify(dataSentBack) + break + } + } + + case actions.ENCRYPT_DATA_WITH_SHARING_KEY: { + let data64 = data.data64 || data.base64 + let publicKeys = data.publicKeys || [] + let dataSentBack = {} + if (data.file || data.blob) { + data64 = await fileToBase64(data.file || data.blob) + } + if (!data64) { + dataSentBack['error'] = "Please include data to encrypt" + response = JSON.stringify(dataSentBack) + break + } + const symmetricKey = createSymmetricKeyAndNonce() + const dataObject = { + data: data64, + key:symmetricKey.messageKey + } + const dataObjectBase64 = await objectToBase64(dataObject) + const privateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey) + const userPublicKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.publicKey) + const encryptDataResponse = encryptDataGroupNew({ + data64: dataObjectBase64, + publicKeys: publicKeys, + privateKey, + userPublicKey, + customSymmetricKey: symmetricKey.messageKey + }) + if (encryptDataResponse) { + return encryptDataResponse + } else { + dataSentBack['error'] = "Unable to encrypt" + response = JSON.stringify(dataSentBack) + break + } + } + + case actions.DECRYPT_DATA_WITH_SHARING_KEY: { + let dataSentBack = {} + const { encryptedData, key } = data + if (!encryptedData) { + dataSentBack['error'] = "Please include data to decrypt" + response = JSON.stringify(dataSentBack) + break + } + const decryptedData = await decryptGroupEncryptionWithSharingKey({ + data64EncryptedData: encryptedData, + key + }) + const base64ToObject = JSON.parse(atob(decryptedData)) + if(base64ToObject.data) { + return base64ToObject.data + } else { + dataSentBack['error'] = "No data in the decrypted resource" + response = JSON.stringify(dataSentBack) + break + } + } + + case actions.CREATE_TRADE_BUY_ORDER: { + const node = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = node.protocol + '://' + node.domain + ':' + node.port + const requiredFields = ["crosschainAtInfo", "foreignBlockchain"] + const missingFields = [] + let dataSentBack = {} + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field) + } + }) + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', ') + const errorMsg = `Missing fields: ${missingFieldsString}` + dataSentBack['error'] = errorMsg + response = JSON.stringify(dataSentBack) + break + } + const isGateway = await isRunningGateway() + const foreignBlockchain = data.foreignBlockchain + const atAddresses = data.crosschainAtInfo.map((order) => order.qortalAtAddress) + const atPromises = atAddresses.map((atAddress) => + requestQueueGetAtAddresses.enqueue(async () => { + const url = `${nodeUrl}/crosschain/trade/${atAddress}` + const resAddress = await fetch(url) + const resData = await resAddress.json() + + if (foreignBlockchain !== resData.foreignBlockchain) { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "All requested ATs need to be of the same foreign Blockchain."}' + throw new Error("All requested ATs need to be of the same foreign Blockchain.") + } + + return resData + }) + ) + const crosschainAtInfo = await Promise.all(atPromises) + try { + const resPermission = await showModalAndWait( + actions.CREATE_TRADE_BUY_ORDER, + { + text1: "Do you give this application permission to perform a buy order?", + text2: `${atAddresses.length}${" "} ${`buy order${atAddresses.length === 1 ? "" : "s"}`}`, + text3: `${crosschainAtInfo.reduce((latest, cur) => { + return latest + cur.qortAmount + }, 0)} QORT FOR ${roundUpToDecimals( + crosschainAtInfo.reduce((latest, cur) => { + return latest + cur.expectedForeignAmount + }, 0) + )} + ${` ${crosschainAtInfo[0].foreignBlockchain}`}`, + foreignFee: `${sellerForeignFee[foreignBlockchain].value} ${sellerForeignFee[foreignBlockchain].ticker}` + } + ) + if (resPermission.action === 'accept') { + const resBuyOrder = await createBuyOrderTx({ + crosschainAtInfo, + isGateway, + foreignBlockchain + }) + if (resBuyOrder.callResponse === true) { + let myMsg1 = resBuyOrder.extra.message + let myMsg2 = "Please wait untill buy order get fulfilled" + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"success": "Successfully created sell order"}' + break + } else { + let myMsg1 = resBuyOrder.extra.message + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to submit sell order."}' + break + } + } else if (resPermission.action === 'reject') { + let myMsg1 = get("transactions.declined") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "User declined request"}' + break + } + } catch (error) { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to submit sell order."}' + break + } + } + + case actions.CREATE_TRADE_SELL_ORDER: { + const requiredFields = ["qortAmount", "foreignBlockchain", "foreignAmount"] + const missingFields = [] + let dataSentBack = {} + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field) + } + }) + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', ') + const errorMsg = `Missing fields: ${missingFieldsString}` + dataSentBack['error'] = errorMsg + response = JSON.stringify(dataSentBack) + break + } + const receivingAddress = await getUserWalletFunc(data.foreignBlockchain) + try { + const resPermission = await showModalAndWait( + actions.CREATE_TRADE_SELL_ORDER, + { + text1: "Do you give this application permission to perform a sell order?", + text2: `${data.qortAmount}${" "} ${`QORT`}`, + text3: `FOR ${data.foreignAmount} ${data.foreignBlockchain}`, + fee: "0.02" + } + ) + if (resPermission.action === 'accept') { + const keyPair = window.parent.reduxStore.getState().app.selectedAddress.keyPair + const userPublicKey = Base58.encode(keyPair.publicKey) + const myRes = await tradeBotCreateRequest( + { + creatorPublicKey: userPublicKey, + qortAmount: parseFloat(data.qortAmount), + fundingQortAmount: parseFloat(data.qortAmount) + 0.001, + foreignBlockchain: data.foreignBlockchain, + foreignAmount: parseFloat(data.foreignAmount), + tradeTimeout: 120, + receivingAddress: receivingAddress.address + }, + keyPair + ) + if (myRes.signature) { + let myMsg1 = "Successfully created sell order" + let myMsg2 = "Please wait untill the order get listed" + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"success": "Successfully created sell order"}' + break + } else { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to submit sell order."}' + break + } + } else if (resPermission.action === 'reject') { + let myMsg1 = get("transactions.declined") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "User declined request"}' + break + } + } catch (error) { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to submit sell order."}' + break + } + } + + case actions.CANCEL_TRADE_SELL_ORDER: { + const requiredFields = ["atAddress"] + const missingFields = [] + let dataSentBack = {} + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field) + } + }) + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', ') + const errorMsg = `Missing fields: ${missingFieldsString}` + dataSentBack['error'] = errorMsg + response = JSON.stringify(dataSentBack) + break + } + const node = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = node.protocol + '://' + node.domain + ':' + node.port + const url = `${nodeUrl}/crosschain/trade/${data.atAddress}` + const resAddress = await fetch(url) + const resData = await resAddress.json() + if (!resData.qortalAtAddress) { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Cannot find AT info."}' + break + } + try { + const fee = await this.messageFee() + const resPermission = await showModalAndWait( + actions.CANCEL_TRADE_SELL_ORDER, + { + text1: "Do you give this application permission to perform: cancel a sell order?", + text2: `${resData.qortAmount}${" "} ${`QORT`}`, + text3: `FOR ${resData.expectedForeignAmount} ${resData.foreignBlockchain}`, + fee: fee + } + ) + if (resPermission.action === 'accept') { + const keyPair = window.parent.reduxStore.getState().app.selectedAddress.keyPair + const userPublicKey = Base58.encode(keyPair.publicKey) + const myRes = await cancelTradeOfferTradeBot( + { + creatorPublicKey: userPublicKey, + atAddress: data.atAddress + }, + keyPair + ) + if (myRes.signature) { + let myMsg1 = "Trade Cancelling In Progress!" + let myMsg2 = "Please wait..." + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"success": "Successfully created sell order"}' + break + } else { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to cancel sell order."}' + break + } + } else if (resPermission.action === 'reject') { + let myMsg1 = get("transactions.declined") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "User declined request"}' + break + } + } catch (error) { + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to submit sell order."}' + break + } + } + case actions.GET_LIST_ITEMS: { const requiredFields = ['list_name'] const missingFields = [] @@ -911,6 +1474,7 @@ class WebBrowser extends LitElement { }) continue } + const service = resource.service const name = resource.name let identifier = resource.identifier @@ -1984,13 +2548,11 @@ class WebBrowser extends LitElement { case actions.GET_FOREIGN_FEE: { const requiredFields = ['coin','type'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -1999,7 +2561,6 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin let type = data.type @@ -2024,13 +2585,11 @@ class WebBrowser extends LitElement { case actions.UPDATE_FOREIGN_FEE: { const requiredFields = ['coin','type'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -2039,7 +2598,6 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin let type = data.type @@ -2066,13 +2624,11 @@ class WebBrowser extends LitElement { case actions.GET_SERVER_CONNECTION_HISTORY: { const requiredFields = ['coin'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -2081,10 +2637,8 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin.toLowerCase() - response = await parentEpml.request('apiCall', { type: 'api', method: 'GET', @@ -2106,13 +2660,11 @@ class WebBrowser extends LitElement { case actions.SET_CURRENT_FOREIGN_SERVER: { const requiredFields = ['coin'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -2121,21 +2673,17 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin let host = data.host let port = data.port let type = data.type - const body = { hostName: host, port: port, connectionType: type } - const bodyToString = JSON.stringify(body) - response = await parentEpml.request('apiCall', { type: 'api', method: 'POST', @@ -2158,13 +2706,11 @@ class WebBrowser extends LitElement { case actions.ADD_FOREIGN_SERVER: { const requiredFields = ['coin'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -2173,21 +2719,17 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin let host = data.host let port = data.port let type = data.type - const body = { hostName: host, port: port, connectionType: type } - const bodyToString = JSON.stringify(body) - response = await parentEpml.request('apiCall', { type: 'api', method: 'POST', @@ -2210,13 +2752,11 @@ class WebBrowser extends LitElement { case actions.REMOVE_FOREIGN_SERVER: { const requiredFields = ['coin'] const missingFields = [] - requiredFields.forEach((field) => { if (!data[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` @@ -2225,21 +2765,17 @@ class WebBrowser extends LitElement { response = JSON.stringify(data) break } - try { let coin = data.coin let host = data.host let port = data.port let type = data.type - const body = { hostName: host, port: port, connectionType: type } - const bodyToString = JSON.stringify(body) - response = await parentEpml.request('apiCall', { type: 'api', method: 'POST', @@ -2279,6 +2815,7 @@ class WebBrowser extends LitElement { const signUrl = signNode.protocol + '://' + signNode.domain + ':' + signNode.port const requiredFields = ['unsignedBytes'] const missingFields = [] + let dataSentBack = {} requiredFields.forEach((field) => { if (!data[field]) { @@ -2289,9 +2826,8 @@ class WebBrowser extends LitElement { if (missingFields.length > 0) { const missingFieldsString = missingFields.join(', ') const errorMsg = `Missing fields: ${missingFieldsString}` - let data = {} - data['error'] = errorMsg - response = JSON.stringify(data) + dataSentBack['error'] = errorMsg + response = JSON.stringify(dataSentBack) break } @@ -2300,7 +2836,7 @@ class WebBrowser extends LitElement { let url = `${signUrl}/transactions/decode?ignoreValidityChecks=false` let body = data.unsignedBytes - const resp = await fetch(url, { + const resDecode = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" @@ -2308,15 +2844,15 @@ class WebBrowser extends LitElement { body: body }) - if (!resp.ok) { - const errorMsg = "Failed to decode transaction" - let data = {} - data['error'] = errorMsg - response = JSON.stringify(data) + if (!resDecode.ok) { + let myMsg1 = "Failed to decode transaction." + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Failed to decode transaction."}' break } - const decodedData = await resp.json() + const decodedData = await resDecode.json() const signRequest = await showModalAndWait( actions.SIGN_TRANSACTION, @@ -2324,7 +2860,7 @@ class WebBrowser extends LitElement { text1: `Do you give this application permission to ${ shouldProcess ? 'SIGN and PROCESS' : 'SIGN' } a transaction?`, text2: "Read the transaction carefully before accepting!", text3: `Tx type: ${decodedData.type}`, - json: decodedData + json: `TX Data: ${decodedData}` } ) @@ -2339,14 +2875,7 @@ class WebBrowser extends LitElement { body: data.unsignedBytes }) - const uint8PrivateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey) - const uint8PublicKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.publicKey) - - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey, - } - + const keyPair = window.parent.reduxStore.getState().app.selectedAddress.keyPair const convertedBytes = await responseConverted.text() const txBytes = Base58.decode(data.unsignedBytes) @@ -2368,44 +2897,53 @@ class WebBrowser extends LitElement { keyPair.privateKey ) - const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature) + const signedBytes = appendBuffer(arbitraryBytesBuffer, signature) const signedBytesToBase58 = Base58.encode(signedBytes) if(!shouldProcess) { - const errorMsg = "Process transaction was not requested! Signed bytes are: " + signedBytesToBase58 - let data = {} - data['error'] = errorMsg - response = JSON.stringify(data) + let myMsg1 = "Process transaction was not requested!" + let myMsg2 = "Signed bytes are: " + signedBytesToBase58 + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Process transaction was not requested!"}' break } try { this.loader.show() - const res = await processTransactionVersion2(signedBytesToBase58) + const res = await processTransactionV2(signedBytesToBase58) - if (!res.signature) { - const errorMsg = "Transaction was not able to be processed" + res.message - let data = {} - data['error'] = errorMsg - response = JSON.stringify(data) + if (res.signature) { + this.loader.hide() + let myMsg1 = "Transaction signed and processed successfully!" + let myMsg2 = "" + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"success": "Transaction signed and processed successfully!"}' + break + } else { + this.loader.hide() + let myMsg1 = "Transaction was not able to be processed" + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Transaction was not able to be processed."}' break } - response = JSON.stringify(res) } catch (error) { - console.error(error) - const data = {} - data['error'] = error.message || get("browserpage.bchange21") - response = JSON.stringify(data) - return - } finally { this.loader.hide() + let myMsg1 = get("managegroup.mg58") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) + response = '{"error": "Transaction was not able to be processed."}' + break } } else if (signRequest.action === 'reject') { + let myMsg1 = get("transactions.declined") + let myMsg2 = get("walletpage.wchange44") + await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) response = '{"error": "User declined request"}' + break } - break } case actions.SEND_COIN: { @@ -3192,7 +3730,6 @@ class WebBrowser extends LitElement { console.log('Unhandled message: ' + JSON.stringify(data)) return } - // Parse response let responseObj try { @@ -3214,9 +3751,7 @@ class WebBrowser extends LitElement { }) } }) - this.clearConsole() - setInterval(() => { this.clearConsole() }, 60000) @@ -3273,6 +3808,20 @@ class WebBrowser extends LitElement { this.renderFullScreen() } + async decryptResourceQDN(data) { + try { + const privateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey) + const encryptedData = decryptGroupDataNew(data, privateKey) + + return { + data: uint8ArrayToBase64(encryptedData.decryptedData), + count: encryptedData.count + } + } catch (error) { + console.log("Error:", error.message) + } + } + async unitJoinFee() { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port @@ -3284,7 +3833,6 @@ class WebBrowser extends LitElement { const data = await response.json() return (Number(data) / 1e8).toFixed(8) } - async deployAtFee() { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port @@ -3297,6 +3845,19 @@ class WebBrowser extends LitElement { return (Number(data) / 1e8).toFixed(8) } + async messageFee() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + const url = `${nodeUrl}/transactions/unitfee?txType=MESSAGE` + const response = await fetch(url) + if (!response.ok) { + throw new Error('Error when fetching message fee') + } + const data = await response.json() + return (Number(data) / 1e8).toFixed(8) + } + + async getArbitraryFee() { const timestamp = Date.now() const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] @@ -4169,6 +4730,27 @@ async function showModalAndWait(type, data) { ` : ''} + + ${type === actions.CREATE_TRADE_BUY_ORDER ? ` + + + + + ` : ''} + + ${type === actions.CREATE_TRADE_SELL_ORDER ? ` + + + + + ` : ''} + + ${type === actions.CANCEL_TRADE_SELL_ORDER ? ` + + + + + ` : ''} ` document.body.appendChild(error) - await modalDelay(3000) + await modalDelay(5000) document.body.removeChild(error) } diff --git a/plugins/plugins/utils/classes.js b/plugins/plugins/utils/classes.js index c16b4bcf..d541e95e 100644 --- a/plugins/plugins/utils/classes.js +++ b/plugins/plugins/utils/classes.js @@ -1,4 +1,8 @@ import { get } from '../../../core/translate' +import { appendBuffer } from './utilities' +import Base58 from '../../../crypto/api/deps/Base58' +import nacl from '../../../crypto/api/deps/nacl-fast.js' +import ed2curve from '../../../crypto/api/deps/ed2curve.js' const getApiKey = () => { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] @@ -71,6 +75,10 @@ export class RequestQueueWithPromise { } } +export const requestQueueMemberNames = new RequestQueueWithPromise(5) +export const requestQueueAdminMemberNames = new RequestQueueWithPromise(5) +export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10) + export class Loader { constructor() { this.loader = document.createElement("div") @@ -664,6 +672,178 @@ export class WarningModal { export const modalHelper = ModalHelper.getInstance() export const warningModal = WarningModal.getInstance() +export class TradeBotRespondRequest { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.atAddress(txnReq.atAddress) + this.foreignKey(txnReq.foreignKey) + this.receivingAddress(txnReq.receivingAddress) + + return this.txnRequest() + } + + atAddress(atAddress) { + this._atAddress = atAddress + } + + foreignKey(foreignKey) { + this._foreignKey = foreignKey + } + + receivingAddress(receivingAddress) { + this._receivingAddress = receivingAddress + } + + txnRequest() { + return { + atAddress: this._atAddress, + foreignKey: this._foreignKey, + receivingAddress: this._receivingAddress + } + } +} + +export class TradeBotRespondMultipleRequest { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.addresses(txnReq.addresses) + this.foreignKey(txnReq.foreignKey) + this.receivingAddress(txnReq.receivingAddress) + + return this.txnRequest() + } + + addresses(addresses) { + this._addresses = addresses + } + + foreignKey(foreignKey) { + this._foreignKey = foreignKey + } + + receivingAddress(receivingAddress) { + this._receivingAddress = receivingAddress + } + + txnRequest() { + return { + addresses: this._addresses, + foreignKey: this._foreignKey, + receivingAddress: this._receivingAddress + } + } +} + +export class TradeBotCreateRequest { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.creatorPublicKey(txnReq.creatorPublicKey) + this.qortAmount(txnReq.qortAmount) + this.fundingQortAmount(txnReq.fundingQortAmount) + this.foreignBlockchain(txnReq.foreignBlockchain) + this.foreignAmount(txnReq.foreignAmount) + this.tradeTimeout(txnReq.tradeTimeout) + this.receivingAddress(txnReq.receivingAddress) + + return this.txnRequest() + } + + creatorPublicKey(creatorPublicKey) { + this._creatorPublicKey = creatorPublicKey + } + + qortAmount(qortAmount) { + this._qortAmount = qortAmount + } + + fundingQortAmount(fundingQortAmount) { + this._fundingQortAmount = fundingQortAmount + } + + foreignBlockchain(foreignBlockchain) { + this._foreignBlockchain = foreignBlockchain + } + + foreignAmount(foreignAmount) { + this._foreignAmount = foreignAmount + } + + tradeTimeout(tradeTimeout) { + this._tradeTimeout = tradeTimeout + } + + receivingAddress(receivingAddress) { + this._receivingAddress = receivingAddress + } + + txnRequest() { + return { + creatorPublicKey: this._creatorPublicKey, + qortAmount: this._qortAmount, + fundingQortAmount: this._fundingQortAmount, + foreignBlockchain: this._foreignBlockchain, + foreignAmount: this._foreignAmount, + tradeTimeout: this._tradeTimeout, + receivingAddress: this._receivingAddress + } + } +} + +export class DeleteTradeOffer { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.creatorPublicKey(txnReq.creatorPublicKey) + this.atAddress(txnReq.atAddress) + + return this.txnRequest() + } + + creatorPublicKey(creatorPublicKey) { + this._creatorPublicKey = creatorPublicKey + } + + atAddress(atAddress) { + this._atAddress = atAddress + } + + txnRequest() { + return { + creatorPublicKey: this._creatorPublicKey, + atAddress: this._atAddress + } + } +} + +export const processTransactionV2 = async (bytes) => { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const url = `${nodeUrl}/transactions/process?apiVersion=2` + + const doProcess = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: bytes + }) + + const res = await doProcess.json() + + return res +} + export const publishData = async ({ registeredName, path, @@ -923,4 +1103,439 @@ export const publishData = async ({ } catch (error) { throw new Error(error.message) } -} \ No newline at end of file +} + +export const getPublishesFromAdmins = async (admins, groupId) => { + const queryString = admins.map((name) => `name=${name}`).join("&") + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const url = `${nodeUrl}/arbitrary/resources/searchsimple?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${groupId}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true` + const response = await fetch(url) + + if (!response.ok) { + consoöe.error("network error") + return false + } + + const adminData = await response.json() + const filterId = adminData.filter((data) => data.identifier === `symmetric-qchat-group-${groupId}`) + + if (filterId.length === 0) { + return false + } + + const sortedData = filterId.sort((a, b) => { + // Get the most recent date for both a and b + const dateA = a.updated ? new Date(a.updated) : new Date(a.created) + const dateB = b.updated ? new Date(b.updated) : new Date(b.created) + + // Sort by most recent + return dateB.getTime() - dateA.getTime() + }) + + return sortedData[0] +} + +export const getGroupAdmins = async (groupNumber) => { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const url = `${nodeUrl}/groups/members/${groupNumber}?limit=0&onlyAdmins=true` + const response = await fetch(url) + const groupData = await response.json() + + let members = [] + let membersAddresses = [] + let both = [] + + const getMemNames = groupData.members.map(async (member) => { + if (member.member) { + const name = await requestQueueAdminMemberNames.enqueue(() => { + return getNameInfo(member.member) + }) + + if (name) { + members.push(name) + both.push({ name, address: member.member }) + } + + membersAddresses.push(member.member) + } + + return true + }) + + await Promise.all(getMemNames) + + return { names: members, addresses: membersAddresses, both } +} + +export const getPublishesFromAdminsAdminSpace = async (admins, groupId) => { + const queryString = admins.map((name) => `name=${name}`).join("&") + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + + const url = `${nodeUrl}/arbitrary/resources/searchsimple?mode=ALL&service=DOCUMENT_PRIVATE&identifier=admins-symmetric-qchat-group-${groupId}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true` + const response = await fetch(url) + + if (!response.ok) { + consoöe.error("network error") + return false + } + + const adminData = await response.json() + + const filterId = adminData.filter((data) => data.identifier === `admins-symmetric-qchat-group-${groupId}`) + + if (filterId.length === 0) { + return false + } + + const sortedData = filterId.sort((a, b) => { + // Get the most recent date for both a and b + const dateA = a.updated ? new Date(a.updated) : new Date(a.created) + const dateB = b.updated ? new Date(b.updated) : new Date(b.created) + + // Sort by most recent + return dateB.getTime() - dateA.getTime() + }) + + return sortedData[0] +} + +export const isRunningGateway = async () => { + let isGateway = true + + const gateways = ['ext-node.qortal.link'] + const nodeInfo = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + if (nodeInfo && (nodeInfo.domain && !gateways.some(gateway => nodeInfo.domain.includes(gateway)))) { + isGateway = false + } + + return isGateway +} + +export const getForeignKey = (foreignBlockchain) => { + switch (foreignBlockchain) { + case "LITECOIN": + return window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey + case "DOGECOIN": + return window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.derivedMasterPrivateKey + case "BITCOIN": + return window.parent.reduxStore.getState().app.selectedAddress.btcWallet.derivedMasterPrivateKey + case "DIGIBYTE": + return window.parent.reduxStore.getState().app.selectedAddress.dgbWallet.derivedMasterPrivateKey + case "RAVENCOIN": + return window.parent.reduxStore.getState().app.selectedAddress.rvnWallet.derivedMasterPrivateKey + case "PIRATECHAIN": + return window.parent.reduxStore.getState().app.selectedAddress.arrrWallet.seed58 + default: + return null + } +} + +export async function getQortalBalanceInfo() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const address = window.parent.reduxStore.getState().app.selectedAddress.address + const url = `${nodeUrl}/addresses/balance/${address}` + const res = await fetch(url) + + if (!response.ok) throw new Error("Cannot fetch Qortal balance") + + const balance = await res.json() + + return (Number(balance) / 1e8).toFixed(8) +} + +export async function getBitcoinBalanceInfo() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const myApiKey = myNode.apiKey + const url = `${nodeUrl}/crosschain/btc/walletbalance?apiKey=${myApiKey}` + + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: `${window.parent.reduxStore.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey}` + }) + + if (!res.ok) throw new Error("Cannot fetch Bitcoin balance") + + const balance = await res.json() + + return (Number(balance) / 1e8).toFixed(8) +} + + +export async function getLitecoinBalanceInfo() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const myApiKey = myNode.apiKey + const url = `${nodeUrl}/crosschain/ltc/walletbalance?apiKey=${myApiKey}` + + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: `${window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey}` + }) + + if (!res.ok) throw new Error("Cannot fetch Litecoin balance") + + const balance = await res.json() + + return (Number(balance) / 1e8).toFixed(8) +} + +export async function createBuyOrderTx({ crosschainAtInfo, isGateway, foreignBlockchain }) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const myApiKey = myNode.apiKey + + let txn + let url + let message + let responseVar + let responseMessage + + try { + if (!isGateway) { + const address = window.parent.reduxStore.getState().app.selectedAddress.address + + if (foreignBlockchain === 'PIRATECHAIN') { + message = { + atAddress: crosschainAtInfo[0].qortalAtAddress, + foreignKey: getForeignKey(foreignBlockchain), + receivingAddress: address + } + } else { + message = { + addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), + foreignKey: getForeignKey(foreignBlockchain), + receivingAddress: address + } + } + + if (foreignBlockchain === 'PIRATECHAIN') { + txn = new TradeBotRespondRequest().createTransaction(message) + url = `${nodeUrl}/crosschain/tradebot/respond?apiKey=${myApiKey}` + } else { + txn = new TradeBotRespondMultipleRequest().createTransaction(message) + url = `${nodeUrl}/crosschain/tradebot/respondmultiple?apiKey=${myApiKey}` + } + + const responseFetch = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(txn) + }) + + const res = await responseFetch.json() + + if(res.error && res.message) { + console.error(res.message) + throw new Error(res.message) + } + + if (!responseFetch.ok) { + console.error('Failed to submit buy order') + throw new Error('Failed to submit buy order') + } + + if (res === false) { + responseVar = { response: res, success: false } + } else { + responseVar = { response: res, success: true } + } + + if (responseVar.success) { + responseMessage = { + callResponse: responseVar.success, + extra: { + message: "Buy order message sended successfully!", + atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress] : crosschainAtInfo.map((order)=> order.qortalAtAddress), + senderAddress: address, + node: nodeUrl + } + } + } else { + responseMessage = { + callResponse: responseVar.success, + extra: { + message: "Unable to execute buy order!", + atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress] : crosschainAtInfo.map((order)=> order.qortalAtAddress), + senderAddress: address, + node: nodeUrl + } + } + } + + return responseMessage + } else { + responseVar = { response: false, success: false } + responseMessage = { + callResponse: responseVar.success, + extra: { + message: "Unable to send buy order message over Gateway!", + atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress] : crosschainAtInfo.map((order)=> order.qortalAtAddress), + senderAddress: address, + node: nodeUrl + } + } + + return responseMessage + } + } catch (error) { + throw new Error(error.message) + } +} + +export const getPirateWalletAddress = async (seed58) => { + const myApiKey = getApiKey() + const mySeed58 = seed58 + + let res = await parentEpml.request('apiCall', { + url: `/crosschain/arrr/walletaddress?apiKey=${myApiKey}`, + method: 'POST', + body: `${mySeed58}` + }) + + if (res != null && res.error != 1201) { + return res + } + + return mySeed58 +} + +export const getUserWalletFunc = async (coin) => { + let userWallet = {} + + switch (coin) { + case "QORT": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.address + userWallet["publickey"] = Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey) + userWallet["privatekey"] = Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey) + break + case "BTC": + case "BITCOIN": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.address + userWallet["publickey"] = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey + userWallet["privatekey"] = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.derivedMasterPrivateKey + break + case "LTC": + case "LITECOIN": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.address + userWallet["publickey"] = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey + userWallet["privatekey"] = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey + break + case "DOGE": + case "DOGECOIN": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.address + userWallet["publickey"] = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey + userWallet["privatekey"] = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.derivedMasterPrivateKey + break + case "DGB": + case "DIGIBYTE": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.dgbWallet.address + userWallet["publickey"] = window.parent.reduxStore.getState().app.selectedAddress.dgbWallet.derivedMasterPublicKey + userWallet["privatekey"] = window.parent.reduxStore.getState().app.selectedAddress.dgbWallet.derivedMasterPrivateKey + break + case "RVN": + case "RAVENCOIN": + userWallet["address"] = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet.address + userWallet["publickey"] = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet.derivedMasterPublicKey + userWallet["privatekey"] = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet.derivedMasterPrivateKey + break + case "ARRR": + case "PIRATECHAIN": + const arrrAddress = await getPirateWalletAddress(window.parent.reduxStore.getState().app.selectedAddress.arrrWallet.seed58) + userWallet["address"] = arrrAddress + userWallet["publickey"] = "" + userWallet["privatekey"] = "" + break + default: + break + } + + return userWallet +} + +export const signTransaction = async (unsignedTxn, keyPair) => { + if (!unsignedTxn) { + throw new Error('Unsigned Transaction Bytes not defined') + } + + if (!keyPair) { + throw new Error('keyPair not defined') + } + + const unsignedTxBytes = Base58.decode(unsignedTxn) + const signature = nacl.sign.detached(unsignedTxBytes, keyPair.privateKey) + const signedTxBytes = appendBuffer(unsignedTxBytes, signature) + + return Base58.encode(signedTxBytes) +} + +export const tradeBotCreateRequest = async (body, keyPair) => { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const myApiKey = myNode.apiKey + const url = `${nodeUrl}/crosschain/tradebot/create?apiKey=${myApiKey}` + const txn = new TradeBotCreateRequest().createTransaction(body) + const bodyToString = JSON.stringify(txn) + + const unsignedTxnResponse = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: bodyToString + }) + + if (!unsignedTxnResponse.ok) throw new Error("Unable to create tradebot") + + const unsignedTxn = await unsignedTxnResponse.text() + const signedTxnBytes = await signTransaction(unsignedTxn, keyPair) + const resProcess = await processTransactionV2(signedTxnBytes) + + if (resProcess.signature) { + return resProcess + } else { + throw new Error("Failed to Create Sell Order. Try again!") + } +} + +export const cancelTradeOfferTradeBot = async (body, keyPair) => { + const txn = new DeleteTradeOffer().createTransaction(body) + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}` + const myApiKey = myNode.apiKey + const url = `${nodeUrl}/crosschain/tradeoffer?apiKey=${myApiKey}` + const bodyToString = JSON.stringify(txn) + + const deleteTradeBotResponse = await fetch(url, { + method: "DELETE", + headers: { + "Content-Type": "application/json" + }, + body: bodyToString + }) + + if (!deleteTradeBotResponse.ok) throw new Error("Unable to update tradebot") + + const unsignedTxn = await deleteTradeBotResponse.text() + const signedTxnBytes = await signTransaction(unsignedTxn, keyPair) + const resProcess = await processTransactionV2(signedTxnBytes) + + if (resProcess.signature) { + return resProcess + } else { + throw new Error("Failed to Cancel Sell Order. Try again!") + } +} diff --git a/plugins/plugins/utils/utilities.js b/plugins/plugins/utils/utilities.js new file mode 100644 index 00000000..f6d7bfa9 --- /dev/null +++ b/plugins/plugins/utils/utilities.js @@ -0,0 +1,65 @@ + +export const int32ToBytes = (word) => { + var byteArray = [] + for (var b = 0; b < 32; b += 8) { + byteArray.push((word >>> (24 - b % 32)) & 0xFF) + } + return byteArray +} + +export const stringtoUTF8Array = (message) => { + if (typeof message === 'string') { + var s = unescape(encodeURIComponent(message)) + message = new Uint8Array(s.length) + for (var i = 0; i < s.length; i++) { + message[i] = s.charCodeAt(i) & 0xff + } + } + return message +} + +export const appendBuffer = (buffer1, buffer2) => { + buffer1 = new Uint8Array(buffer1) + buffer2 = new Uint8Array(buffer2) + let tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) + tmp.set(buffer1, 0) + tmp.set(buffer2, buffer1.byteLength) + return tmp +} + +export const int64ToBytes = (int64) => { + var byteArray = [0, 0, 0, 0, 0, 0, 0, 0] + for (var index = 0; index < byteArray.length; index++) { + var byte = int64 & 0xff + byteArray[byteArray.length - index - 1] = byte + int64 = (int64 - byte) / 256 + } + return byteArray +} + +export const hexToBytes = (hexString) => { + return new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16))) +} + +export const stringToHex = (bytes) => { + return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') +} + +export const equal = (buf1, buf2) => { + if (buf1.byteLength != buf2.byteLength) return false + var dv1 = new Uint8Array(buf1) + var dv2 = new Uint8Array(buf2) + for (var i = 0; i != buf1.byteLength; i++) { + if (dv1[i] != dv2[i]) return false + } + return true +} + +export const bytesToHex = (byteArray) => { + var _byteArrayToHex = [] + for (var index = 0; index < byteArray.length; index++) { + _byteArrayToHex.push((byteArray[index] >>> 4).toString(16)) + _byteArrayToHex.push((byteArray[index] & 15).toString(16)); + } + return _byteArrayToHex.join("") +}