import {
computePow,
createEndpoint,
getBalanceInfo,
getFee,
getKeyPair,
getLastRef,
getSaveWallet,
processTransactionVersion2,
removeDuplicateWindow,
signChatFunc,
joinGroup as joinGroupFunc,
sendQortFee,
sendCoin as sendCoinFunc,
isUsingLocal
} from "../background";
import { getNameInfo } from "../backgroundFunctions/encryption";
import { QORT_DECIMALS } from "../constants/constants";
import Base58 from "../deps/Base58";
import {
base64ToUint8Array,
decryptDeprecatedSingle,
decryptGroupDataQortalRequest,
encryptDataGroup,
uint8ArrayStartsWith,
uint8ArrayToBase64,
} from "../qdn/encryption/group-encryption";
import { publishData } from "../qdn/publish/pubish";
import { getPermission, setPermission } from "../qortalRequests";
import { createTransaction } from "../transactions/transactions";
import { mimeToExtensionMap } from "../utils/memeTypes";
const btcFeePerByte = 0.00000100
const ltcFeePerByte = 0.00000030
const dogeFeePerByte = 0.00001000
const dgbFeePerByte = 0.00000010
const rvnFeePerByte = 0.00001125
const _createPoll = async ({pollName, pollDescription, options}, isFromExtension) => {
const fee = await getFee("CREATE_POLL");
const resPermission = await getUserPermission({
text1: "You are requesting to create the poll below:",
text2: `Poll: ${pollName}`,
text3: `Description: ${pollDescription}`,
text4: `Options: ${options?.join(", ")}`,
fee: fee.fee,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
let lastRef = await getLastRef();
const tx = await createTransaction(8, keyPair, {
fee: fee.fee,
ownerAddress: address,
rPollName: pollName,
rPollDesc: pollDescription,
rOptions: options,
lastReference: lastRef,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
} else {
throw new Error("User declined request");
}
};
const _deployAt = async (
{name,
description,
tags,
creationBytes,
amount,
assetId,
atType}, isFromExtension
) => {
const fee = await getFee("DEPLOY_AT");
const resPermission = await getUserPermission({
text1: "Would you like to deploy this AT?",
text2: `Name: ${name}`,
text3: `Description: ${description}`,
fee: fee.fee,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const lastReference = await getLastRef();
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const tx = await createTransaction(16, keyPair, {
fee: fee.fee,
rName: name,
rDescription: description,
rTags: tags,
rAmount: amount,
rAssetId: assetId,
rCreationBytes: creationBytes,
atType: atType,
lastReference: lastReference,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(
res?.message || "Transaction was not able to be processed"
);
return res;
} else {
throw new Error("User declined transaction");
}
};
const _voteOnPoll = async ({pollName, optionIndex, optionName}, isFromExtension) => {
const fee = await getFee("VOTE_ON_POLL");
const resPermission = await getUserPermission({
text1: "You are being requested to vote on the poll below:",
text2: `Poll: ${pollName}`,
text3: `Option: ${optionName}`,
fee: fee.fee,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
let lastRef = await getLastRef();
const tx = await createTransaction(9, keyPair, {
fee: fee.fee,
voterAddress: address,
rPollName: pollName,
rOptionIndex: optionIndex,
lastReference: lastRef,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
} else {
throw new Error("User declined request");
}
};
function getFileFromContentScript(fileId, sender) {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(
sender.tab.id,
{ action: "getFileFromIndexedDB", fileId: fileId },
(response) => {
if (response && response.result) {
resolve(response.result);
} else {
reject(response?.error || "Failed to retrieve file");
}
}
);
});
}
function sendToSaveFilePicker(data, sender) {
chrome.tabs.sendMessage(sender.tab.id, {
action: "SHOW_SAVE_FILE_PICKER",
data,
});
}
async function responseFromExtension() {
return new Promise((resolve) => {
// Send message to the content script to check focus
chrome.runtime.sendMessage({ action: "QORTAL_REQUEST_PERMISSION", payloa }, (response) => {
if (chrome.runtime.lastError) {
resolve(false); // Error occurred, assume not focused
} else {
resolve(response); // Resolve based on the response
}
});
});
}
async function getUserPermission(payload: any, isFromExtension?: boolean) {
function waitForWindowReady(windowId) {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
chrome.windows.get(windowId, (win) => {
if (chrome.runtime.lastError) {
clearInterval(checkInterval); // Stop polling if there's an error
resolve(false);
} else if (win.state === "normal" || win.state === "maximized") {
clearInterval(checkInterval); // Window is ready
resolve(true);
}
});
}, 100); // Check every 100ms
});
}
if(isFromExtension){
return new Promise((resolve) => {
// Set a timeout for 1 second
const timeout = setTimeout(() => {
resolve(false);
}, 30000);
// Send message to the content script to check focus
chrome.runtime.sendMessage(
{ action: "QORTAL_REQUEST_PERMISSION", payload, isFromExtension },
(response) => {
if (response === undefined) return;
clearTimeout(timeout); // Clear the timeout if we get a response
if (chrome.runtime.lastError) {
resolve(false); // Error occurred, assume not focused
} else {
resolve(response); // Resolve based on the response
}
}
);
});
}
await new Promise((res) => {
const popupUrl = chrome.runtime.getURL("index.html?secondary=true");
chrome.windows.getAll(
{ populate: true, windowTypes: ["popup"] },
(windows) => {
// Attempt to find an existing popup window that has a tab with the correct URL
const existingPopup = windows.find(
(w) =>
w.tabs &&
w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
);
if (existingPopup) {
// If the popup exists but is minimized or not focused, focus it
chrome.windows.update(existingPopup.id, {
focused: true,
state: "normal",
});
res(null);
} else {
// No existing popup found, create a new one
chrome.system.display.getInfo((displays) => {
// Assuming the primary display is the first one (adjust logic as needed)
const primaryDisplay = displays[0];
const screenWidth = primaryDisplay.bounds.width;
const windowHeight = 500; // Your window height
const windowWidth = 400; // Your window width
// Calculate left position for the window to appear on the right of the screen
const leftPosition = screenWidth - windowWidth;
// Calculate top position for the window, adjust as desired
const topPosition =
(primaryDisplay.bounds.height - windowHeight) / 2;
chrome.windows.create(
{
url: popupUrl,
type: "popup",
width: windowWidth,
height: windowHeight,
left: leftPosition,
top: 0,
},
async (newWindow) => {
removeDuplicateWindow(popupUrl);
await waitForWindowReady(newWindow.id);
res(null);
}
);
});
}
}
);
});
await new Promise((res) => {
setTimeout(() => {
chrome.runtime.sendMessage({
action: "SET_COUNTDOWN",
payload: 30,
});
res(true);
}, 1000);
});
return new Promise((resolve) => {
// Set a timeout for 1 second
const timeout = setTimeout(() => {
resolve(false);
}, 30000);
// Send message to the content script to check focus
chrome.runtime.sendMessage(
{ action: "QORTAL_REQUEST_PERMISSION", payload },
(response) => {
if (response === undefined) return;
clearTimeout(timeout); // Clear the timeout if we get a response
if (chrome.runtime.lastError) {
resolve(false); // Error occurred, assume not focused
} else {
resolve(response); // Resolve based on the response
}
}
);
});
}
export const getUserAccount = async () => {
try {
const wallet = await getSaveWallet();
const address = wallet.address0;
const publicKey = wallet.publicKey;
return {
address,
publicKey,
};
} catch (error) {
throw new Error("Unable to fetch user account");
}
};
export const encryptData = async (data, sender) => {
let data64 = data.data64;
let publicKeys = data.publicKeys || [];
if (data.fileId) {
data64 = await getFileFromContentScript(data.fileId, sender);
}
if (!data64) {
throw new Error("Please include data to encrypt");
}
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const privateKey = parsedData.privateKey;
const userPublicKey = parsedData.publicKey;
const encryptDataResponse = encryptDataGroup({
data64,
publicKeys: publicKeys,
privateKey,
userPublicKey,
});
if (encryptDataResponse) {
return encryptDataResponse;
} else {
throw new Error("Unable to encrypt");
}
};
export const decryptData = async (data) => {
const { encryptedData, publicKey } = data;
if (!encryptedData) {
throw new Error(`Missing fields: encryptedData`);
}
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8Array = base64ToUint8Array(encryptedData);
const startsWithQortalEncryptedData = uint8ArrayStartsWith(
uint8Array,
"qortalEncryptedData"
);
if (startsWithQortalEncryptedData) {
if (!publicKey) {
throw new Error(`Missing fields: publicKey`);
}
const decryptedDataToBase64 = decryptDeprecatedSingle(
uint8Array,
publicKey,
uint8PrivateKey
);
return decryptedDataToBase64;
}
const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith(
uint8Array,
"qortalGroupEncryptedData"
);
if (startsWithQortalGroupEncryptedData) {
const decryptedData = decryptGroupDataQortalRequest(
encryptedData,
parsedData.privateKey
);
const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData);
return decryptedDataToBase64;
}
throw new Error("Unable to decrypt");
};
export const getListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ["list_name"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const value = (await getPermission("qAPPAutoLists")) || false;
let skip = false;
if (value) {
skip = true;
}
let resPermission;
let acceptedVar;
let checkbox1Var;
if (!skip) {
resPermission = await getUserPermission({
text1: "Do you give this application permission to",
text2: "Access the list",
highlightedText: data.list_name,
checkbox1: {
value: value,
label: "Always allow lists to be retrieved automatically",
},
}, isFromExtension);
const { accepted, checkbox1 } = resPermission;
acceptedVar = accepted;
checkbox1Var = checkbox1;
setPermission("qAPPAutoLists", checkbox1);
}
if (acceptedVar || skip) {
const url = await createEndpoint(`/lists/${data.list_name}`);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
const list = await response.json();
return list;
} else {
throw new Error("User declined to share list");
}
};
export const addListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ["list_name", "items"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const items = data.items;
const list_name = data.list_name;
const resPermission = await getUserPermission({
text1: "Do you give this application permission to",
text2: `Add the following to the list ${list_name}:`,
highlightedText: items.join(", "),
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const url = await createEndpoint(`/lists/${list_name}`);
const body = {
items: items,
};
const bodyToString = JSON.stringify(body);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: bodyToString,
});
if (!response.ok) throw new Error("Failed to add to list");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined add to list");
}
};
export const deleteListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ["list_name", "item"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const item = data.item;
const list_name = data.list_name;
const resPermission = await getUserPermission({
text1: "Do you give this application permission to",
text2: `Remove the following from the list ${list_name}:`,
highlightedText: item,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const url = await createEndpoint(`/lists/${list_name}`);
const body = {
items: [item],
};
const bodyToString = JSON.stringify(body);
const response = await fetch(url, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: bodyToString,
});
if (!response.ok) throw new Error("Failed to add to list");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined delete from list");
}
};
export const publishQDNResource = async (data: any, sender, isFromExtension) => {
const requiredFields = ["service"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
if (!data.fileId && !data.data64) {
throw new Error("No data or file was submitted");
}
// Use "default" if user hasn't specified an identifer
const service = data.service;
const registeredName = await getNameInfo();
const name = registeredName;
let identifier = data.identifier;
let data64 = data.data64;
const filename = data.filename;
const title = data.title;
const description = data.description;
const category = data.category;
const tag1 = data.tag1;
const tag2 = data.tag2;
const tag3 = data.tag3;
const tag4 = data.tag4;
const tag5 = data.tag5;
if (data.identifier == null) {
identifier = "default";
}
if (
data.encrypt &&
(!data.publicKeys ||
(Array.isArray(data.publicKeys) && data.publicKeys.length === 0))
) {
throw new Error("Encrypting data requires public keys");
}
if (!data.encrypt && data.service.endsWith("_PRIVATE")) {
throw new Error("Only encrypted data can go into private services");
}
if (data.fileId) {
data64 = await getFileFromContentScript(data.fileId, sender);
}
if (data.encrypt) {
try {
const resKeyPair = await getKeyPair()
const parsedData = JSON.parse(resKeyPair)
const privateKey = parsedData.privateKey
const userPublicKey = parsedData.publicKey
const encryptDataResponse = encryptDataGroup({
data64,
publicKeys: data.publicKeys,
privateKey,
userPublicKey
});
if (encryptDataResponse) {
data64 = encryptDataResponse;
}
} catch (error) {
throw new Error(
error.message || "Upload failed due to failed encryption"
);
}
}
const fee = await getFee("ARBITRARY");
const resPermission = await getUserPermission({
text1: "Do you give this application permission to publish to QDN?",
text2: `service: ${service}`,
text3: `identifier: ${identifier || null}`,
highlightedText: `isEncrypted: ${!!data.encrypt}`,
fee: fee.fee,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
if (data.fileId && !data.encrypt) {
data64 = await getFileFromContentScript(data.fileId, sender);
}
try {
const resPublish = await publishData({
registeredName: encodeURIComponent(name),
file: data64,
service: service,
identifier: encodeURIComponent(identifier),
uploadType: "file",
isBase64: true,
filename: filename,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
apiVersion: 2,
withFee: true,
});
return resPublish;
} catch (error) {
throw new Error(error?.message || "Upload failed");
}
} else {
throw new Error("User declined request");
}
};
export const publishMultipleQDNResources = async (data: any, sender, isFromExtension) => {
const requiredFields = ["resources"];
const missingFields: string[] = [];
let feeAmount = null;
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const resources = data.resources;
if (!Array.isArray(resources)) {
throw new Error("Invalid data");
}
if (resources.length === 0) {
throw new Error("No resources to publish");
}
if (
data.encrypt &&
(!data.publicKeys ||
(Array.isArray(data.publicKeys) && data.publicKeys.length === 0))
) {
throw new Error("Encrypting data requires public keys");
}
const fee = await getFee("ARBITRARY");
const registeredName = await getNameInfo();
const name = registeredName;
const resPermission = await getUserPermission({
text1: "Do you give this application permission to publish to QDN?",
html: `
${data.resources
.map(
(resource) => `
Service: ${
resource.service
}
Name: ${resource.name}
Identifier: ${
resource.identifier
}
${
resource.filename
? `
Filename: ${resource.filename}
`
: ""
}
`
)
.join("")}
`,
highlightedText: `isEncrypted: ${!!data.encrypt}`,
fee: fee.fee * resources.length,
}, isFromExtension);
const { accepted } = resPermission;
if (!accepted) {
throw new Error("User declined request");
}
let failedPublishesIdentifiers = [];
for (const resource of resources) {
try {
const requiredFields = ["service"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!resource[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
});
continue;
}
if (!resource.fileId && !resource.data64) {
const errorMsg = "No data or file was submitted";
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
});
continue;
}
const service = resource.service;
let identifier = resource.identifier;
let data64 = resource.data64;
const filename = resource.filename;
const title = resource.title;
const description = resource.description;
const category = resource.category;
const tag1 = resource.tag1;
const tag2 = resource.tag2;
const tag3 = resource.tag3;
const tag4 = resource.tag4;
const tag5 = resource.tag5;
if (resource.identifier == null) {
identifier = "default";
}
if (!data.encrypt && service.endsWith("_PRIVATE")) {
const errorMsg = "Only encrypted data can go into private services";
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
});
continue;
}
if (resource.fileId) {
data64 = await getFileFromContentScript(resource.fileId, sender);
}
if (data.encrypt) {
try {
const resKeyPair = await getKeyPair()
const parsedData = JSON.parse(resKeyPair)
const privateKey = parsedData.privateKey
const userPublicKey = parsedData.publicKey
const encryptDataResponse = encryptDataGroup({
data64,
publicKeys: data.publicKeys,
privateKey,
userPublicKey
});
if (encryptDataResponse) {
data64 = encryptDataResponse;
}
} catch (error) {
const errorMsg =
error?.message || "Upload failed due to failed encryption";
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
});
continue;
}
}
try {
await publishData({
registeredName: encodeURIComponent(name),
file: data64,
service: service,
identifier: encodeURIComponent(identifier),
uploadType: "file",
isBase64: true,
filename: filename,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
apiVersion: 2,
withFee: true,
});
await new Promise((res) => {
setTimeout(() => {
res();
}, 1000);
});
} catch (error) {
const errorMsg = error.message || "Upload failed";
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
});
}
} catch (error) {
failedPublishesIdentifiers.push({
reason: "Unknown error",
identifier: resource.identifier,
});
}
}
if (failedPublishesIdentifiers.length > 0) {
const obj = {};
obj["error"] = {
unsuccessfulPublishes: failedPublishesIdentifiers,
};
return obj;
}
return true;
};
export const voteOnPoll = async (data, isFromExtension) => {
const requiredFields = ["pollName", "optionIndex"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const pollName = data.pollName;
const optionIndex = data.optionIndex;
let pollInfo = null;
try {
const url = await createEndpoint(`/polls/${encodeURIComponent(pollName)}`);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch poll");
pollInfo = await response.json();
} catch (error) {
const errorMsg = (error && error.message) || "Poll not found";
throw new Error(errorMsg);
}
if (!pollInfo || pollInfo.error) {
const errorMsg = (pollInfo && pollInfo.message) || "Poll not found";
throw new Error(errorMsg);
}
try {
const optionName = pollInfo.pollOptions[optionIndex].optionName;
const resVoteOnPoll = await _voteOnPoll({pollName, optionIndex, optionName}, isFromExtension);
return resVoteOnPoll;
} catch (error) {
throw new Error(error?.message || "Failed to vote on the poll.");
}
};
export const createPoll = async (data, isFromExtension) => {
const requiredFields = [
"pollName",
"pollDescription",
"pollOptions",
"pollOwnerAddress",
];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const pollName = data.pollName;
const pollDescription = data.pollDescription;
const pollOptions = data.pollOptions;
const pollOwnerAddress = data.pollOwnerAddress;
try {
const resCreatePoll = await _createPoll(
{
pollName,
pollDescription,
options: pollOptions,
},
isFromExtension
);
return resCreatePoll;
} catch (error) {
throw new Error(error?.message || "Failed to created poll.");
}
};
export const sendChatMessage = async (data, isFromExtension) => {
const message = data.message;
const recipient = data.destinationAddress;
const groupId = data.groupId;
const isRecipient = !groupId;
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send this chat message?",
text2: `To: ${isRecipient ? recipient : `group ${groupId}`}`,
text3: `${message?.slice(0, 25)}${message?.length > 25 ? "..." : ""}`,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const tiptapJson = {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: message,
},
],
},
],
};
const messageObject = {
messageText: tiptapJson,
images: [""],
repliedTo: "",
version: 3,
};
const stringifyMessageObject = JSON.stringify(messageObject);
const balance = await getBalanceInfo();
const hasEnoughBalance = +balance < 4 ? false : true;
if (!hasEnoughBalance) {
throw new Error("You need at least 4 QORT to send a message");
}
if (isRecipient && recipient) {
const url = await createEndpoint(`/addresses/publickey/${recipient}`);
const response = await fetch(url);
if (!response.ok)
throw new Error("Failed to fetch recipient's public key");
let key;
let hasPublicKey;
let res;
const contentType = response.headers.get("content-type");
// If the response is JSON, parse it as JSON
if (contentType && contentType.includes("application/json")) {
res = await response.json();
} else {
// Otherwise, treat it as plain text
res = await response.text();
}
if (res?.error === 102) {
key = "";
hasPublicKey = false;
} else if (res !== false) {
key = res;
hasPublicKey = true;
} else {
key = "";
hasPublicKey = false;
}
if (!hasPublicKey && isRecipient) {
throw new Error(
"Cannot send an encrypted message to this user since they do not have their publickey on chain."
);
}
let _reference = new Uint8Array(64);
self.crypto.getRandomValues(_reference);
let sendTimestamp = Date.now();
let reference = Base58.encode(_reference);
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const difficulty = 8;
const tx = await createTransaction(18, keyPair, {
timestamp: sendTimestamp,
recipient: recipient,
recipientPublicKey: key,
hasChatReference: 0,
message: stringifyMessageObject,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 1,
isText: 1,
});
const path = chrome.runtime.getURL("memory-pow.wasm.full");
const { nonce, chatBytesArray } = await computePow({
chatBytes: tx.chatBytes,
path,
difficulty,
});
let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair);
if (_response?.error) {
throw new Error(_response?.message);
}
return _response;
} else if (!isRecipient && groupId) {
let _reference = new Uint8Array(64);
self.crypto.getRandomValues(_reference);
let reference = Base58.encode(_reference);
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const difficulty = 8;
const txBody = {
timestamp: Date.now(),
groupID: Number(groupId),
hasReceipient: 0,
hasChatReference: 0,
message: stringifyMessageObject,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 0, // Set default to not encrypted for groups
isText: 1,
};
const tx = await createTransaction(181, keyPair, txBody);
// if (!hasEnoughBalance) {
// throw new Error("Must have at least 4 QORT to send a chat message");
// }
const path = chrome.runtime.getURL("memory-pow.wasm.full");
const { nonce, chatBytesArray } = await computePow({
chatBytes: tx.chatBytes,
path,
difficulty,
});
let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair);
if (_response?.error) {
throw new Error(_response?.message);
}
return _response;
} else {
throw new Error("Please enter a recipient or groupId");
}
} else {
throw new Error("User declined to send message");
}
};
export const joinGroup = async (data, isFromExtension) => {
const requiredFields = ["groupId"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
let groupInfo = null;
try {
const url = await createEndpoint(`/groups/${data.groupId}`);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch group");
groupInfo = await response.json();
} catch (error) {
const errorMsg = (error && error.message) || "Group not found";
throw new Error(errorMsg);
}
const fee = await getFee("JOIN_GROUP");
const resPermission = await getUserPermission({
text1: "Confirm joining the group:",
highlightedText: `${groupInfo.groupName}`,
fee: fee.fee,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const groupId = data.groupId;
if (!groupInfo || groupInfo.error) {
const errorMsg = (groupInfo && groupInfo.message) || "Group not found";
throw new Error(errorMsg);
}
try {
const resJoinGroup = await joinGroupFunc({ groupId });
return resJoinGroup;
} catch (error) {
throw new Error(error?.message || "Failed to join the group.");
}
} else {
throw new Error("User declined to join group");
}
};
export const saveFile = async (data, sender, isFromExtension) => {
try {
const requiredFields = ["filename", "fileId"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const filename = data.filename;
const blob = data.blob;
const fileId = data.fileId;
const resPermission = await getUserPermission({
text1: "Would you like to download:",
highlightedText: `${filename}`,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const mimeType = blob.type || data.mimeType;
let backupExention = filename.split(".").pop();
if (backupExention) {
backupExention = "." + backupExention;
}
const fileExtension = mimeToExtensionMap[mimeType] || backupExention;
let fileHandleOptions = {};
if (!mimeType) {
throw new Error("A mimeType could not be derived");
}
if (!fileExtension) {
const obj = {};
throw new Error("A file extension could not be derived");
}
if (fileExtension && mimeType) {
fileHandleOptions = {
accept: {
[mimeType]: [fileExtension],
},
};
}
sendToSaveFilePicker(
{
filename,
mimeType,
blob,
fileId,
fileHandleOptions,
},
sender
);
return true;
} else {
throw new Error("User declined to save file");
}
} catch (error) {
throw new Error(error?.message || "Failed to initiate download");
}
};
export const deployAt = async (data, isFromExtension) => {
const requiredFields = [
"name",
"description",
"tags",
"creationBytes",
"amount",
"assetId",
"type",
];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
try {
const resDeployAt = await _deployAt(
{
name: data.name,
description: data.description,
tags: data.tags,
creationBytes: data.creationBytes,
amount: data.amount,
assetId: data.assetId,
atType: data.type
},
isFromExtension
);
return resDeployAt;
} catch (error) {
throw new Error(error?.message || "Failed to join the group.");
}
};
export const getUserWallet = async (data, isFromExtension) => {
const requiredFields = ["coin"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const resPermission = await getUserPermission({
text1:
"Do you give this application permission to get your wallet information?",
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
let coin = data.coin;
let userWallet = {};
let arrrAddress = "";
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const arrrSeed58 = parsedData.arrrSeed58;
if (coin === "ARRR") {
const bodyToString = arrrSeed58;
const url = await createEndpoint(`/crosschain/arrr/walletaddress`);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: bodyToString,
});
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
arrrAddress = res;
}
switch (coin) {
case "QORT":
userWallet["address"] = address;
userWallet["publickey"] = parsedData.publicKey;
break;
case "BTC":
userWallet["address"] = parsedData.btcAddress;
userWallet["publickey"] = parsedData.derivedMasterPublicKey;
break;
case "LTC":
userWallet["address"] = parsedData.ltcAddress;
userWallet["publickey"] = parsedData.ltcPublicKey;
break;
case "DOGE":
userWallet["address"] = parsedData.dogeAddress;
userWallet["publickey"] = parsedData.dogePublicKey;
break;
case "DGB":
userWallet["address"] = parsedData.dgbAddress;
userWallet["publickey"] = parsedData.dgbPublicKey;
break;
case "RVN":
userWallet["address"] = parsedData.rvnAddress;
userWallet["publickey"] = parsedData.rvnPublicKey;
break;
case "ARRR":
userWallet["address"] = arrrAddress;
break;
default:
break;
}
return userWallet;
} else {
throw new Error("User declined request");
}
};
export const getWalletBalance = async (data, bypassPermission?: boolean, isFromExtension) => {
const requiredFields = ["coin"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
let resPermission
if(!bypassPermission){
resPermission = await getUserPermission({
text1: "Do you give this application permission to fetch your",
highlightedText: `${data.coin} balance`,
}, isFromExtension);
} else {
resPermission = {
accepted: false
}
}
const { accepted } = resPermission;
if (accepted || bypassPermission) {
let coin = data.coin;
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
if (coin === "QORT") {
let qortAddress = address;
try {
const url = await createEndpoint(`/addresses/balance/${qortAddress}`);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} catch (error) {
throw new Error(
error?.message || "Fetch Wallet Failed. Please try again"
);
}
} else {
let _url = ``;
let _body = null;
switch (coin) {
case "BTC":
_url = await createEndpoint(`/crosschain/btc/walletbalance`);
_body = parsedData.derivedMasterPublicKey;
break;
case "LTC":
_url = await createEndpoint(`/crosschain/ltc/walletbalance`);
_body = parsedData.ltcPublicKey;
break;
case "DOGE":
_url = await createEndpoint(`/crosschain/doge/walletbalance`);
_body = parsedData.dogePublicKey;
break;
case "DGB":
_url = await createEndpoint(`/crosschain/dgb/walletbalance`);
_body = parsedData.dgbPublicKey;
break;
case "RVN":
_url = await createEndpoint(`/crosschain/rvn/walletbalance`);
_body = parsedData.rvnPublicKey;
break;
case "ARRR":
_url = await createEndpoint(`/crosschain/arrr/walletbalance`);
_body = parsedData.arrrSeed58;
break;
default:
break;
}
try {
const response = await fetch(_url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: _body,
});
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
if (isNaN(Number(res))) {
throw new Error("Unable to fetch balance");
} else {
return (Number(res) / 1e8).toFixed(8);
}
} catch (error) {
throw new Error(error?.message || "Unable to fetch balance");
}
}
} else {
throw new Error("User declined request");
}
};
const getUserWalletFunc = async (coin) => {
let userWallet = {};
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
switch (coin) {
case "QORT":
userWallet["address"] = address;
userWallet["publickey"] = parsedData.publicKey;
break;
case "BTC":
userWallet["address"] = parsedData.btcAddress;
userWallet["publickey"] = parsedData.btcPublicKey;
break;
case "LTC":
userWallet["address"] = parsedData.ltcAddress;
userWallet["publickey"] = parsedData.ltcPublicKey;
break;
case "DOGE":
userWallet["address"] = parsedData.dogeAddress;
userWallet["publickey"] = parsedData.dogePublicKey;
break;
case "DGB":
userWallet["address"] = parsedData.dgbAddress;
userWallet["publickey"] = parsedData.dgbPublicKey;
break;
case "RVN":
userWallet["address"] = parsedData.rvnAddress;
userWallet["publickey"] = parsedData.rvnPublicKey;
break;
case "ARRR":
break;
default:
break;
}
return userWallet;
};
export const getUserWalletInfo = async (data, isFromExtension) => {
const requiredFields = ["coin"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to retrieve your wallet information",
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
let coin = data.coin;
let walletKeys = await getUserWalletFunc(coin);
const _url = await createEndpoint(
`/crosschain/` + data.coin.toLowerCase() + `/addressinfos`
);
let _body = { xpub58: walletKeys["publickey"] };
try {
const response = await fetch(_url, {
method: "POST",
headers: {
Accept: "*/*",
"Content-Type": "application/json",
},
body: JSON.stringify(_body),
});
if(!response?.ok) throw new Error('Unable to fetch wallet information')
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res;
} catch (error) {
throw new Error(error?.message || "Fetch Wallet Failed");
}
} else {
throw new Error("User declined request");
}
};
export const getCrossChainServerInfo = async (data)=> {
const requiredFields = ['coin']
const missingFields: string[] = []
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field)
}
})
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ')
const errorMsg = `Missing fields: ${missingFieldsString}`
throw new Error(errorMsg)
}
let _url = `/crosschain/` + data.coin.toLowerCase() + `/serverinfos`
try {
const url = await createEndpoint(_url);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res.servers
} catch (error) {
throw new Error(error?.message || 'Error in retrieving server info')
}
}
export const getTxActivitySummary = async (data) => {
const requiredFields = ['coin'];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const coin = data.coin;
const url = `/crosschain/txactivity?foreignBlockchain=${coin}`; // No apiKey here
try {
const endpoint = await createEndpoint(url);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return full response here
} catch (error) {
throw new Error(error?.message || 'Error in tx activity summary');
}
};
export const getForeignFee = async (data) => {
const requiredFields = ['coin', 'type'];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const { coin, type } = data;
const url = `/crosschain/${coin}/${type}`;
try {
const endpoint = await createEndpoint(url);
const response = await fetch(endpoint, {
method: 'GET',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return full response here
} catch (error) {
throw new Error(error?.message || 'Error in get foreign fee');
}
};
export const updateForeignFee = async (data) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ['coin', 'type', 'value'];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const { coin, type, value } = data;
const url = `/crosschain/${coin}/update${type}`;
try {
const endpoint = await createEndpoint(url);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: JSON.stringify({ value }),
});
if (!response.ok) throw new Error('Failed to update foreign fee');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return full response here
} catch (error) {
throw new Error(error?.message || 'Error in update foreign fee');
}
};
export const getServerConnectionHistory = async (data) => {
const requiredFields = ['coin'];
const missingFields: string[] = [];
// Validate required fields
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const coin = data.coin.toLowerCase();
const url = `/crosschain/${coin}/serverconnectionhistory`;
try {
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
const response = await fetch(endpoint, {
method: 'GET',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch server connection history');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return full response here
} catch (error) {
throw new Error(error?.message || 'Error in get server connection history');
}
};
export const setCurrentForeignServer = async (data) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ['coin'];
const missingFields: string[] = [];
// Validate required fields
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const { coin, host, port, type } = data;
const body = {
hostName: host,
port: port,
connectionType: type,
};
const url = `/crosschain/${coin}/setcurrentserver`;
try {
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) throw new Error('Failed to set current server');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return the full response
} catch (error) {
throw new Error(error?.message || 'Error in set current server');
}
};
export const addForeignServer = async (data) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ['coin'];
const missingFields: string[] = [];
// Validate required fields
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const { coin, host, port, type } = data;
const body = {
hostName: host,
port: port,
connectionType: type,
};
const url = `/crosschain/${coin}/addserver`;
try {
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) throw new Error('Failed to add server');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return the full response
} catch (error) {
throw new Error(error.message || 'Error in adding server');
}
};
export const removeForeignServer = async (data) => {
const localNodeAvailable = await isUsingLocal()
if(!localNodeAvailable) throw new Error('Please use your local node.')
const requiredFields = ['coin'];
const missingFields: string[] = [];
// Validate required fields
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const { coin, host, port, type } = data;
const body = {
hostName: host,
port: port,
connectionType: type,
};
const url = `/crosschain/${coin}/removeserver`;
try {
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) throw new Error('Failed to remove server');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return the full response
} catch (error) {
throw new Error(error?.message || 'Error in removing server');
}
};
export const getDaySummary = async () => {
const url = `/admin/summary`; // Simplified endpoint URL
try {
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL
const response = await fetch(endpoint, {
method: 'GET',
headers: {
Accept: '*/*',
},
});
if (!response.ok) throw new Error('Failed to retrieve summary');
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
if (res?.error && res?.message) {
throw new Error(res.message);
}
return res; // Return the full response
} catch (error) {
throw new Error(error?.message || 'Error in retrieving summary');
}
};
export const sendCoin = async (data, isFromExtension) => {
const requiredFields = ['coin', 'destinationAddress', 'amount']
const missingFields: string[] = []
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field)
}
})
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ')
const errorMsg = `Missing fields: ${missingFieldsString}`
throw new Error(errorMsg)
}
let checkCoin = data.coin
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const localNodeAvailable = await isUsingLocal()
if(checkCoin !== 'QORT' && !localNodeAvailable) throw new Error('Cannot send a non-QORT coin through the gateway. Please use your local node.')
if (checkCoin === "QORT") {
// Params: data.coin, data.destinationAddress, data.amount, data.fee
// TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction
// then set the response string from the core to the `response` variable (defined above)
// If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
const amount = Number(data.amount)
const recipient = data.destinationAddress
const url = await createEndpoint(`/addresses/balance/${address}`);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
let walletBalance;
try {
walletBalance = await response.clone().json();
} catch (e) {
walletBalance = await response.text();
}
if (isNaN(Number(walletBalance))) {
let errorMsg = "Failed to Fetch QORT Balance. Try again!"
throw new Error(errorMsg)
}
const transformDecimals = (Number(walletBalance) * QORT_DECIMALS).toFixed(0)
const walletBalanceDecimals = Number(transformDecimals)
const amountDecimals = Number(amount) * QORT_DECIMALS
const fee: number = await sendQortFee()
if (amountDecimals + (fee * QORT_DECIMALS) > walletBalanceDecimals) {
let errorMsg = "Insufficient Funds!"
throw new Error(errorMsg)
}
if (amount <= 0) {
let errorMsg = "Invalid Amount!"
throw new Error(errorMsg)
}
if (recipient.length === 0) {
let errorMsg = "Receiver cannot be empty!"
throw new Error(errorMsg)
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const makePayment = await sendCoinFunc({amount, password: null, receiver: recipient }, true)
return makePayment.res
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "BTC") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const xprv58 = parsedData.btcPrivateKey
const feePerByte = data.fee ? data.fee : btcFeePerByte
const btcWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(btcWalletBalance))) {
throw new Error('Unable to fetch BTC balance')
}
const btcWalletBalanceDecimals = Number(btcWalletBalance)
const btcAmountDecimals = Number(amount) * QORT_DECIMALS
const fee = feePerByte * 500 // default 0.00050000
if (btcAmountDecimals + (fee * QORT_DECIMALS) > btcWalletBalanceDecimals) {
throw new Error("INSUFFICIENT_FUNDS")
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const opts = {
xprv58: xprv58,
receivingAddress: recipient,
bitcoinAmount: amount,
feePerByte: feePerByte * QORT_DECIMALS
}
const url = await createEndpoint(`/crosschain/btc/send`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "LTC") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const xprv58 = parsedData.ltcPrivateKey
const feePerByte = data.fee ? data.fee : ltcFeePerByte
const ltcWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(ltcWalletBalance))) {
let errorMsg = "Failed to Fetch LTC Balance. Try again!"
throw new Error(errorMsg)
}
const ltcWalletBalanceDecimals = Number(ltcWalletBalance)
const ltcAmountDecimals = Number(amount) * QORT_DECIMALS
const balance = (Number(ltcWalletBalance) / 1e8).toFixed(8)
const fee = feePerByte * 1000 // default 0.00030000
if (ltcAmountDecimals + (fee * QORT_DECIMALS) > ltcWalletBalanceDecimals) {
throw new Error("Insufficient Funds!")
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const url = await createEndpoint(`/crosschain/ltc/send`);
const opts = {
xprv58: xprv58,
receivingAddress: recipient,
litecoinAmount: amount,
feePerByte: feePerByte * QORT_DECIMALS
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "DOGE") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const coin = data.coin
const xprv58 = parsedData.dogePrivateKey
const feePerByte = data.fee ? data.fee : dogeFeePerByte
const dogeWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(dogeWalletBalance))) {
let errorMsg = "Failed to Fetch DOGE Balance. Try again!"
throw new Error(errorMsg)
}
const dogeWalletBalanceDecimals = Number(dogeWalletBalance)
const dogeAmountDecimals = Number(amount) * QORT_DECIMALS
const balance = (Number(dogeWalletBalance) / 1e8).toFixed(8)
const fee = feePerByte * 5000 // default 0.05000000
if (dogeAmountDecimals + (fee * QORT_DECIMALS) > dogeWalletBalanceDecimals) {
let errorMsg = "Insufficient Funds!"
throw new Error(errorMsg)
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const opts = {
xprv58: xprv58,
receivingAddress: recipient,
dogecoinAmount: amount,
feePerByte: feePerByte * QORT_DECIMALS
}
const url = await createEndpoint(`/crosschain/doge/send`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "DGB") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const xprv58 = parsedData.dbgPrivateKey
const feePerByte = data.fee ? data.fee : dgbFeePerByte
const dgbWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(dgbWalletBalance))) {
let errorMsg = "Failed to Fetch DGB Balance. Try again!"
throw new Error(errorMsg)
}
const dgbWalletBalanceDecimals = Number(dgbWalletBalance)
const dgbAmountDecimals = Number(amount) * QORT_DECIMALS
const fee = feePerByte * 500 // default 0.00005000
if (dgbAmountDecimals + (fee * QORT_DECIMALS) > dgbWalletBalanceDecimals) {
let errorMsg = "Insufficient Funds!"
throw new Error(errorMsg)
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const opts = {
xprv58: xprv58,
receivingAddress: recipient,
digibyteAmount: amount,
feePerByte: feePerByte * QORT_DECIMALS
}
const url = await createEndpoint(`/crosschain/dgb/send`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "RVN") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const coin = data.coin
const xprv58 = parsedData.rvnPrivateKey
const feePerByte = data.fee ? data.fee : rvnFeePerByte
const rvnWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(rvnWalletBalance))) {
let errorMsg = "Failed to Fetch RVN Balance. Try again!"
throw new Error(errorMsg)
}
const rvnWalletBalanceDecimals = Number(rvnWalletBalance)
const rvnAmountDecimals = Number(amount) * QORT_DECIMALS
const balance = (Number(rvnWalletBalance) / 1e8).toFixed(8)
const fee = feePerByte * 500 // default 0.00562500
if (rvnAmountDecimals + (fee * QORT_DECIMALS) > rvnWalletBalanceDecimals) {
let errorMsg = "Insufficient Funds!"
throw new Error(errorMsg)
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const opts = {
xprv58: xprv58,
receivingAddress: recipient,
ravencoinAmount: amount,
feePerByte: feePerByte * QORT_DECIMALS
}
const url = await createEndpoint(`/crosschain/rvn/send`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
} else if (checkCoin === "ARRR") {
const amount = Number(data.amount)
const recipient = data.destinationAddress
const memo = data.memo
const arrrWalletBalance = await getWalletBalance({coin: checkCoin}, true)
if (isNaN(Number(arrrWalletBalance))) {
let errorMsg = "Failed to Fetch ARRR Balance. Try again!"
throw new Error(errorMsg)
}
const arrrWalletBalanceDecimals = Number(arrrWalletBalance)
const arrrAmountDecimals = Number(amount) * QORT_DECIMALS
const fee = 0.00010000
if (arrrAmountDecimals + (fee * QORT_DECIMALS) > arrrWalletBalanceDecimals) {
let errorMsg = "Insufficient Funds!"
throw new Error(errorMsg)
}
const resPermission = await getUserPermission({
text1: "Do you give this application permission to send coins?",
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee
}, isFromExtension);
const { accepted } = resPermission;
if (accepted) {
const opts = {
entropy58: parsedData.arrrSeed58,
receivingAddress: recipient,
arrrAmount: amount,
memo: memo
}
const url = await createEndpoint(`/crosschain/btc/send`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(opts)
})
if (!response.ok) throw new Error("Failed to send");
let res;
try {
res = await response.clone().json();
} catch (e) {
res = await response.text();
}
return res;
} else {
throw new Error("User declined request")
}
}
};