const assert = require("assert");
const GenUtils = require("./GenUtils");
const HttpClient = require("./HttpClient");
const LibraryUtils = require("./LibraryUtils");
const MoneroBan = require("../daemon/model/MoneroBan");
const MoneroBlock = require("../daemon/model/MoneroBlock");
const MoneroDaemonListener = require("../daemon/model/MoneroDaemonListener");
const MoneroDaemonRpc = require("../daemon/MoneroDaemonRpc");
const MoneroError = require("./MoneroError");
const MoneroKeyImage = require("../daemon/model/MoneroKeyImage");
const MoneroRpcConnection = require("./MoneroRpcConnection");
const MoneroTxConfig = require("../wallet/model/MoneroTxConfig");
const MoneroTxSet = require("../wallet/model/MoneroTxSet");
const MoneroUtils = require("../common/MoneroUtils");
const MoneroWalletConfig = require("../wallet/model/MoneroWalletConfig");
const MoneroWalletListener = require("../wallet/model/MoneroWalletListener");
const MoneroWalletFull = require("../wallet/MoneroWalletFull");
/**
* Worker to manage a daemon and wasm wallet off the main thread using messages.
*
* Required message format: e.data[0] = object id, e.data[1] = function name, e.data[2+] = function args
*
* For browser applications, this file must be browserified and placed in the web app root.
*
* @private
*/
onmessage = async function(e) {
// initialize one time
await self.initOneTime();
// validate params
let objectId = e.data[0];
let fnName = e.data[1];
let callbackId = e.data[2];
assert(fnName, "Must provide function name to worker");
assert(callbackId, "Must provide callback id to worker");
if (!self[fnName]) throw new Error("Method '" + fnName + "' is not registered with worker");
e.data.splice(1, 2); // remove function name and callback id to apply function with arguments
// execute worker function and post result to callback
try {
postMessage([objectId, callbackId, {result: await self[fnName].apply(null, e.data)}]);
} catch (e) {
if (!(e instanceof Error)) e = new Error(e);
postMessage([objectId, callbackId, {error: LibraryUtils.serializeError(e)}]);
}
}
self.initOneTime = async function() {
if (!self.isInitialized) {
self.WORKER_OBJECTS = {};
self.isInitialized = true;
MoneroUtils.PROXY_TO_WORKER = false;
}
}
// --------------------------- STATIC UTILITIES -------------------------------
// TODO: object id not needed for static utilites, using throwaway uuid
self.httpRequest = async function(objectId, opts) {
try {
return await HttpClient.request(Object.assign(opts, {proxyToWorker: false}));
} catch (err) {
throw err.statusCode ? new Error(JSON.stringify({statusCode: err.statusCode, statusMessage: err.message})) : err;
}
}
self.setLogLevel = async function(objectId, level) {
return LibraryUtils.setLogLevel(level);
}
self.getWasmMemoryUsed = async function(objectId) {
return LibraryUtils.getWasmModule() && LibraryUtils.getWasmModule().HEAP8 ? LibraryUtils.getWasmModule().HEAP8.length : undefined;
}
// ----------------------------- MONERO UTILS ---------------------------------
self.moneroUtilsGetIntegratedAddress = async function(objectId, networkType, standardAddress, paymentId) {
return (await MoneroUtils.getIntegratedAddress(networkType, standardAddress, paymentId)).toJson();
}
self.moneroUtilsValidateAddress = async function(objectId, address, networkType) {
return MoneroUtils.validateAddress(address, networkType);
}
self.moneroUtilsJsonToBinary = async function(objectId, json) {
return MoneroUtils.jsonToBinary(json);
}
self.moneroUtilsBinaryToJson = async function(objectId, uint8arr) {
return MoneroUtils.binaryToJson(uint8arr);
}
self.moneroUtilsBinaryBlocksToJson = async function(objectId, uint8arr) {
return MoneroUtils.binaryBlocksToJson(uint8arr);
}
// ---------------------------- DAEMON METHODS --------------------------------
self.daemonAddListener = async function(daemonId, listenerId) {
let listener = new class extends MoneroDaemonListener {
async onBlockHeader(blockHeader) {
self.postMessage([daemonId, "onBlockHeader_" + listenerId, blockHeader.toJson()]);
}
}
if (!self.daemonListeners) self.daemonListeners = {};
self.daemonListeners[listenerId] = listener;
await self.WORKER_OBJECTS[daemonId].addListener(listener);
}
self.daemonRemoveListener = async function(daemonId, listenerId) {
if (!self.daemonListeners[listenerId]) throw new MoneroError("No daemon worker listener registered with id: " + listenerId);
await self.WORKER_OBJECTS[daemonId].removeListener(self.daemonListeners[listenerId]);
delete self.daemonListeners[listenerId];
}
self.connectDaemonRpc = async function(daemonId, config) {
self.WORKER_OBJECTS[daemonId] = new MoneroDaemonRpc(config);
}
self.daemonGetRpcConnection = async function(daemonId) {
let connection = await self.WORKER_OBJECTS[daemonId].getRpcConnection();
return connection ? connection.getConfig() : undefined;
}
self.daemonIsConnected = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].isConnected();
}
self.daemonGetVersion = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getVersion()).toJson();
}
self.daemonIsTrusted = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].isTrusted();
}
self.daemonGetHeight = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].getHeight();
}
self.daemonGetBlockHash = async function(daemonId, height) {
return self.WORKER_OBJECTS[daemonId].getBlockHash(height);
}
self.daemonGetBlockTemplate = async function(daemonId, walletAddress, reserveSize) {
return (await self.WORKER_OBJECTS[daemonId].getBlockTemplate(walletAddress, reserveSize)).toJson();
}
self.daemonGetLastBlockHeader = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getLastBlockHeader()).toJson();
}
self.daemonGetBlockHeaderByHash = async function(daemonId, hash) {
return (await self.WORKER_OBJECTS[daemonId].getBlockHeaderByHash(hash)).toJson();
}
self.daemonGetBlockHeaderByHeight = async function(daemonId, height) {
return (await self.WORKER_OBJECTS[daemonId].getBlockHeaderByHeight(height)).toJson();
}
self.daemonGetBlockHeadersByRange = async function(daemonId, startHeight, endHeight) {
let blockHeadersJson = [];
for (let blockHeader of await self.WORKER_OBJECTS[daemonId].getBlockHeadersByRange(startHeight, endHeight)) blockHeadersJson.push(blockHeader.toJson());
return blockHeadersJson;
}
self.daemonGetBlockByHash = async function(daemonId, blockHash) {
return (await self.WORKER_OBJECTS[daemonId].getBlockByHash(blockHash)).toJson();
}
self.daemonGetBlocksByHash = async function(daemonId, blockHashes, startHeight, prune) {
let blocksJson = [];
for (let block of await self.WORKER_OBJECTS[daemonId].getBlocksByHash(blockHashes, startHeight, prune)) blocksJson.push(block.toJson());
return blocksJson;
}
self.daemonGetBlockByHeight = async function(daemonId, height) {
return (await self.WORKER_OBJECTS[daemonId].getBlockByHeight(height)).toJson();
}
self.daemonGetBlocksByHeight = async function(daemonId, heights) {
let blocksJson = [];
for (let block of await self.WORKER_OBJECTS[daemonId].getBlocksByHeight(heights)) blocksJson.push(block.toJson());
return blocksJson;
}
self.daemonGetBlocksByRange = async function(daemonId, startHeight, endHeight) {
let blocksJson = [];
for (let block of await self.WORKER_OBJECTS[daemonId].getBlocksByRange(startHeight, endHeight)) blocksJson.push(block.toJson());
return blocksJson;
}
self.daemonGetBlocksByRangeChunked = async function(daemonId, startHeight, endHeight, maxChunkSize) {
let blocksJson = [];
for (let block of await self.WORKER_OBJECTS[daemonId].getBlocksByRangeChunked(startHeight, endHeight, maxChunkSize)) blocksJson.push(block.toJson());
return blocksJson;
}
self.daemonGetBlockHashes = async function(daemonId, blockHashes, startHeight) {
throw new Error("worker.getBlockHashes not implemented");
}
// TODO: factor common code with self.getTxs()
self.daemonGetTxs = async function(daemonId, txHashes, prune) {
// get txs
let txs = await self.WORKER_OBJECTS[daemonId].getTxs(txHashes, prune);
// collect unique blocks to preserve model relationships as trees (based on monero_wasm_bridge.cpp::get_txs)
let blocks = [];
let unconfirmedBlock = undefined
let seenBlocks = new Set();
for (let tx of txs) {
if (!tx.getBlock()) {
if (!unconfirmedBlock) unconfirmedBlock = new MoneroBlock().setTxs([]);
tx.setBlock(unconfirmedBlock);
unconfirmedBlock.getTxs().push(tx);
}
if (!seenBlocks.has(tx.getBlock())) {
seenBlocks.add(tx.getBlock());
blocks.push(tx.getBlock());
}
}
// serialize blocks to json
for (let i = 0; i < blocks.length; i++) blocks[i] = blocks[i].toJson();
return blocks;
}
self.daemonGetTxHexes = async function(daemonId, txHashes, prune) {
return self.WORKER_OBJECTS[daemonId].getTxHexes(txHashes, prune);
}
self.daemonGetMinerTxSum = async function(daemonId, height, numBlocks) {
return (await self.WORKER_OBJECTS[daemonId].getMinerTxSum(height, numBlocks)).toJson();
}
self.daemonGetFeeEstimate = async function(daemonId, graceBlocks) {
return (await self.WORKER_OBJECTS[daemonId].getFeeEstimate(graceBlocks)).toJson();
}
self.daemonSubmitTxHex = async function(daemonId, txHex, doNotRelay) {
return (await self.WORKER_OBJECTS[daemonId].submitTxHex(txHex, doNotRelay)).toJson();
}
self.daemonRelayTxsByHash = async function(daemonId, txHashes) {
return self.WORKER_OBJECTS[daemonId].relayTxsByHash(txHashes);
}
self.daemonGetTxPool = async function(daemonId) {
let txs = await self.WORKER_OBJECTS[daemonId].getTxPool();
let block = new MoneroBlock().setTxs(txs);
for (let tx of txs) tx.setBlock(block)
return block.toJson();
}
self.daemonGetTxPoolHashes = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].getTxPoolHashes();
}
//async getTxPoolBacklog() {
// throw new MoneroError("Not implemented");
//}
self.daemonGetTxPoolStats = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getTxPoolStats()).toJson();
}
self.daemonFlushTxPool = async function(daemonId, hashes) {
return self.WORKER_OBJECTS[daemonId].flushTxPool(hashes);
}
self.daemonGetKeyImageSpentStatuses = async function(daemonId, keyImages) {
return self.WORKER_OBJECTS[daemonId].getKeyImageSpentStatuses(keyImages);
}
//
//async getOutputs(outputs) {
// throw new MoneroError("Not implemented");
//}
self.daemonGetOutputHistogram = async function(daemonId, amounts, minCount, maxCount, isUnlocked, recentCutoff) {
let entriesJson = [];
for (let entry of await self.WORKER_OBJECTS[daemonId].getOutputHistogram(amounts, minCount, maxCount, isUnlocked, recentCutoff)) {
entriesJson.push(entry.toJson());
}
return entriesJson;
}
//
//async getOutputDistribution(amounts, cumulative, startHeight, endHeight) {
// throw new MoneroError("Not implemented");
//}
self.daemonGetInfo = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getInfo()).toJson();
}
self.daemonGetSyncInfo = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getSyncInfo()).toJson();
}
self.daemonGetHardForkInfo = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getHardForkInfo()).toJson();
}
self.daemonGetAltChains = async function(daemonId) {
let altChainsJson = [];
for (let altChain of await self.WORKER_OBJECTS[daemonId].getAltChains()) altChainsJson.push(altChain.toJson());
return altChainsJson;
}
self.daemonGetAltBlockHashes = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].getAltBlockHashes();
}
self.daemonGetDownloadLimit = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].getDownloadLimit();
}
self.daemonSetDownloadLimit = async function(daemonId, limit) {
return self.WORKER_OBJECTS[daemonId].setDownloadLimit(limit);
}
self.daemonResetDownloadLimit = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].resetDownloadLimit();
}
self.daemonGetUploadLimit = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].getUploadLimit();
}
self.daemonSetUploadLimit = async function(daemonId, limit) {
return self.WORKER_OBJECTS[daemonId].setUploadLimit(limit);
}
self.daemonResetUploadLimit = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].resetUploadLimit();
}
self.daemonGetPeers = async function(daemonId) {
let peersJson = [];
for (let peer of await self.WORKER_OBJECTS[daemonId].getPeers()) peersJson.push(peer.toJson());
return peersJson;
}
self.daemonGetKnownPeers = async function(daemonId) {
let peersJson = [];
for (let peer of await self.WORKER_OBJECTS[daemonId].getKnownPeers()) peersJson.push(peer.toJson());
return peersJson;
}
self.daemonSetOutgoingPeerLimit = async function(daemonId, limit) {
return self.WORKER_OBJECTS[daemonId].setOutgoingPeerLimit(limit);
}
self.daemonSetIncomingPeerLimit = async function(daemonId, limit) {
return self.WORKER_OBJECTS[daemonId].setIncomingPeerLimit(limit);
}
self.daemonGetPeerBans = async function(daemonId) {
let bansJson = [];
for (let ban of await self.WORKER_OBJECTS[daemonId].getPeerBans()) bansJson.push(ban.toJson());
return bansJson;
}
self.daemonSetPeerBans = async function(daemonId, bansJson) {
let bans = [];
for (let banJson of bansJson) bans.push(new MoneroBan(banJson));
return self.WORKER_OBJECTS[daemonId].setPeerBans(bans);
}
self.daemonStartMining = async function(daemonId, address, numThreads, isBackground, ignoreBattery) {
return self.WORKER_OBJECTS[daemonId].startMining(address, numThreads, isBackground, ignoreBattery);
}
self.daemonStopMining = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].stopMining();
}
self.daemonGetMiningStatus = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].getMiningStatus()).toJson();
}
self.daemonPruneBlockchain = async function(daemonId, check) {
return (await self.WORKER_OBJECTS[daemonId].pruneBlockchain(check)).toJson();
}
//
//async submitBlocks(blockBlobs) {
// throw new MoneroError("Not implemented");
//}
//
//async checkForUpdate() {
// throw new MoneroError("Not implemented");
//}
//
//async downloadUpdate(path) {
// throw new MoneroError("Not implemented");
//}
self.daemonStop = async function(daemonId) {
return self.WORKER_OBJECTS[daemonId].stop();
}
self.daemonWaitForNextBlockHeader = async function(daemonId) {
return (await self.WORKER_OBJECTS[daemonId].waitForNextBlockHeader()).toJson();
}
//------------------------------ WALLET METHODS -------------------------------
self.openWalletData = async function(walletId, path, password, networkType, keysData, cacheData, daemonUriOrConfig) {
let daemonConnection = daemonUriOrConfig ? new MoneroRpcConnection(daemonUriOrConfig) : undefined;
self.WORKER_OBJECTS[walletId] = await MoneroWalletFull.openWallet({path: "", password: password, networkType: networkType, keysData: keysData, cacheData: cacheData, server: daemonConnection, proxyToWorker: false});
self.WORKER_OBJECTS[walletId]._setBrowserMainPath(path);
}
self._createWallet = async function(walletId, configJson) {
let config = new MoneroWalletConfig(configJson);
let path = config.getPath();
config.setPath("");
config.setProxyToWorker(false);
self.WORKER_OBJECTS[walletId] = await MoneroWalletFull.createWallet(config);
self.WORKER_OBJECTS[walletId]._setBrowserMainPath(path);
}
self.isViewOnly = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isViewOnly();
}
self.getNetworkType = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getNetworkType();
}
//
//async getVersion() {
// throw new Error("Not implemented");
//}
self.getSeed = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getSeed();
}
self.getSeedLanguage = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getSeedLanguage();
}
self.getSeedLanguages = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getSeedLanguages();
}
self.getPrivateSpendKey = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getPrivateSpendKey();
}
self.getPrivateViewKey = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getPrivateViewKey();
}
self.getPublicViewKey = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getPublicViewKey();
}
self.getPublicSpendKey = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getPublicSpendKey();
}
self.getAddress = async function(walletId, accountIdx, subaddressIdx) {
return self.WORKER_OBJECTS[walletId].getAddress(accountIdx, subaddressIdx);
}
self.getAddressIndex = async function(walletId, address) {
return (await self.WORKER_OBJECTS[walletId].getAddressIndex(address)).toJson();
}
self.setSubaddressLabel = async function(walletId, accountIdx, subaddressIdx, label) {
await self.WORKER_OBJECTS[walletId].setSubaddressLabel(accountIdx, subaddressIdx, label);
}
self.getIntegratedAddress = async function(walletId, standardAddress, paymentId) {
return (await self.WORKER_OBJECTS[walletId].getIntegratedAddress(standardAddress, paymentId)).toJson();
}
self.decodeIntegratedAddress = async function(walletId, integratedAddress) {
return (await self.WORKER_OBJECTS[walletId].decodeIntegratedAddress(integratedAddress)).toJson();
}
self.setDaemonConnection = async function(walletId, config) {
return self.WORKER_OBJECTS[walletId].setDaemonConnection(config ? new MoneroRpcConnection(Object.assign(config, {proxyToWorker: false})) : undefined);
}
self.getDaemonConnection = async function(walletId) {
let connection = await self.WORKER_OBJECTS[walletId].getDaemonConnection();
return connection ? connection.getConfig() : undefined;
}
self.isConnectedToDaemon = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isConnectedToDaemon();
}
self.getRestoreHeight = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getRestoreHeight();
}
self.setRestoreHeight = async function(walletId, restoreHeight) {
return self.WORKER_OBJECTS[walletId].setRestoreHeight(restoreHeight);
}
self.getDaemonHeight = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getDaemonHeight();
}
self.getDaemonMaxPeerHeight = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getDaemonMaxPeerHeight()
}
self.getHeightByDate = async function(walletId, year, month, day) {
return self.WORKER_OBJECTS[walletId].getHeightByDate(year, month, day);
}
self.isDaemonSynced = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isDaemonSynced();
}
self.getHeight = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getHeight();
}
self.addListener = async function(walletId, listenerId) {
/**
* Internal listener to bridge notifications to external listeners.
*
* TODO: MoneroWalletListener is not defined until scripts imported
*
* @private
*/
class WalletWorkerHelperListener extends MoneroWalletListener {
constructor(walletId, id, worker) {
super();
this.walletId = walletId;
this.id = id;
this.worker = worker;
}
getId() {
return this.id;
}
onSyncProgress(height, startHeight, endHeight, percentDone, message) {
this.worker.postMessage([this.walletId, "onSyncProgress_" + this.getId(), height, startHeight, endHeight, percentDone, message]);
}
onNewBlock(height) {
this.worker.postMessage([this.walletId, "onNewBlock_" + this.getId(), height]);
}
onBalancesChanged(newBalance, newUnlockedBalance) {
this.worker.postMessage([this.walletId, "onBalancesChanged_" + this.getId(), newBalance.toString(), newUnlockedBalance.toString()]);
}
onOutputReceived(output) {
let block = output.getTx().getBlock();
if (block === undefined) block = new MoneroBlock().setTxs([output.getTx()]);
this.worker.postMessage([this.walletId, "onOutputReceived_" + this.getId(), block.toJson()]); // serialize from root block
}
onOutputSpent(output) {
let block = output.getTx().getBlock();
if (block === undefined) block = new MoneroBlock().setTxs([output.getTx()]);
this.worker.postMessage([this.walletId, "onOutputSpent_" + this.getId(), block.toJson()]); // serialize from root block
}
}
let listener = new WalletWorkerHelperListener(walletId, listenerId, self);
if (!self.listeners) self.listeners = [];
self.listeners.push(listener);
await self.WORKER_OBJECTS[walletId].addListener(listener);
}
self.removeListener = async function(walletId, listenerId) {
for (let i = 0; i < self.listeners.length; i++) {
if (self.listeners[i].getId() !== listenerId) continue;
await self.WORKER_OBJECTS[walletId].removeListener(self.listeners[i]);
self.listeners.splice(i, 1);
return;
}
throw new MoneroError("Listener is not registered with wallet");
}
self.isSynced = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isSynced();
}
self.sync = async function(walletId, startHeight, allowConcurrentCalls) {
return await self.WORKER_OBJECTS[walletId].sync(undefined, startHeight, allowConcurrentCalls);
}
self.startSyncing = async function(walletId, syncPeriodInMs) {
return self.WORKER_OBJECTS[walletId].startSyncing(syncPeriodInMs);
}
self.stopSyncing = async function(walletId) {
return self.WORKER_OBJECTS[walletId].stopSyncing();
}
self.scanTxs = async function(walletId, txHashes) {
return self.WORKER_OBJECTS[walletId].scanTxs(txHashes);
}
self.rescanSpent = async function(walletId) {
return self.WORKER_OBJECTS[walletId].rescanSpent();
}
self.rescanBlockchain = async function(walletId) {
return self.WORKER_OBJECTS[walletId].rescanBlockchain();
}
self.getBalance = async function(walletId, accountIdx, subaddressIdx) {
return (await self.WORKER_OBJECTS[walletId].getBalance(accountIdx, subaddressIdx)).toString();
}
self.getUnlockedBalance = async function(walletId, accountIdx, subaddressIdx) {
return (await self.WORKER_OBJECTS[walletId].getUnlockedBalance(accountIdx, subaddressIdx)).toString();
}
self.getAccounts = async function(walletId, includeSubaddresses, tag) {
let accountJsons = [];
for (let account of await self.WORKER_OBJECTS[walletId].getAccounts(includeSubaddresses, tag)) accountJsons.push(account.toJson());
return accountJsons;
}
self.getAccount = async function(walletId, accountIdx, includeSubaddresses) {
return (await self.WORKER_OBJECTS[walletId].getAccount(accountIdx, includeSubaddresses)).toJson();
}
self.createAccount = async function(walletId, label) {
return (await self.WORKER_OBJECTS[walletId].createAccount(label)).toJson();
}
self.getSubaddresses = async function(walletId, accountIdx, subaddressIndices) {
let subaddressJsons = [];
for (let subaddress of await self.WORKER_OBJECTS[walletId].getSubaddresses(accountIdx, subaddressIndices)) subaddressJsons.push(subaddress.toJson());
return subaddressJsons;
}
self.createSubaddress = async function(walletId, accountIdx, label) {
return (await self.WORKER_OBJECTS[walletId].createSubaddress(accountIdx, label)).toJson();
}
// TODO: easier or more efficient way than serializing from root blocks?
self.getTxs = async function(walletId, blockJsonQuery) {
// deserialize query which is json string rooted at block
let query = new MoneroBlock(blockJsonQuery, MoneroBlock.DeserializationType.TX_QUERY).getTxs()[0];
// get txs
let txs = await self.WORKER_OBJECTS[walletId].getTxs(query);
// collect unique blocks to preserve model relationships as trees (based on monero_wasm_bridge.cpp::get_txs)
let seenBlocks = new Set();
let unconfirmedBlock = undefined;
let blocks = [];
for (let tx of txs) {
if (!tx.getBlock()) {
if (!unconfirmedBlock) unconfirmedBlock = new MoneroBlock().setTxs([]);
tx.setBlock(unconfirmedBlock);
unconfirmedBlock.getTxs().push(tx);
}
if (!seenBlocks.has(tx.getBlock())) {
seenBlocks.add(tx.getBlock());
blocks.push(tx.getBlock());
}
}
// serialize blocks to json
for (let i = 0; i < blocks.length; i++) blocks[i] = blocks[i].toJson();
return {blocks: blocks};
}
self.getTransfers = async function(walletId, blockJsonQuery) {
// deserialize query which is json string rooted at block
let query = new MoneroBlock(blockJsonQuery, MoneroBlock.DeserializationType.TX_QUERY).getTxs()[0].getTransferQuery();
// get transfers
let transfers = await self.WORKER_OBJECTS[walletId].getTransfers(query);
// collect unique blocks to preserve model relationships as tree
let unconfirmedBlock = undefined;
let blocks = [];
let seenBlocks = new Set();
for (let transfer of transfers) {
let tx = transfer.getTx();
if (!tx.getBlock()) {
if (!unconfirmedBlock) unconfirmedBlock = new MoneroBlock().setTxs([]);
tx.setBlock(unconfirmedBlock);
unconfirmedBlock.getTxs().push(tx);
}
if (!seenBlocks.has(tx.getBlock())) {
seenBlocks.add(tx.getBlock());
blocks.push(tx.getBlock());
}
}
// serialize blocks to json
for (let i = 0; i < blocks.length; i++) blocks[i] = blocks[i].toJson();
return blocks;
}
self.getOutputs = async function(walletId, blockJsonQuery) {
// deserialize query which is json string rooted at block
let query = new MoneroBlock(blockJsonQuery, MoneroBlock.DeserializationType.TX_QUERY).getTxs()[0].getOutputQuery();
// get outputs
let outputs = await self.WORKER_OBJECTS[walletId].getOutputs(query);
// collect unique blocks to preserve model relationships as tree
let unconfirmedBlock = undefined;
let blocks = [];
let seenBlocks = new Set();
for (let output of outputs) {
let tx = output.getTx();
if (!tx.getBlock()) {
if (!unconfirmedBlock) unconfirmedBlock = new MoneroBlock().setTxs([]);
tx.setBlock(unconfirmedBlock);
unconfirmedBlock.getTxs().push(tx);
}
if (!seenBlocks.has(tx.getBlock())) {
seenBlocks.add(tx.getBlock());
blocks.push(tx.getBlock());
}
}
// serialize blocks to json
for (let i = 0; i < blocks.length; i++) blocks[i] = blocks[i].toJson();
return blocks;
}
self.exportOutputs = async function(walletId, all) {
return self.WORKER_OBJECTS[walletId].exportOutputs(all);
}
self.importOutputs = async function(walletId, outputsHex) {
return self.WORKER_OBJECTS[walletId].importOutputs(outputsHex);
}
self.getKeyImages = async function(walletId, all) {
let keyImagesJson = [];
for (let keyImage of await self.WORKER_OBJECTS[walletId].exportKeyImages(all)) keyImagesJson.push(keyImage.toJson());
return keyImagesJson;
}
self.importKeyImages = async function(walletId, keyImagesJson) {
let keyImages = [];
for (let keyImageJson of keyImagesJson) keyImages.push(new MoneroKeyImage(keyImageJson));
return (await self.WORKER_OBJECTS[walletId].importKeyImages(keyImages)).toJson();
}
//async getNewKeyImagesFromLastImport() {
// throw new MoneroError("Not implemented");
//}
self.freezeOutput = async function(walletId, keyImage) {
return self.WORKER_OBJECTS[walletId].freezeOutput(keyImage);
}
self.thawOutput = async function(walletId, keyImage) {
return self.WORKER_OBJECTS[walletId].thawOutput(keyImage);
}
self.isOutputFrozen = async function(walletId, keyImage) {
return self.WORKER_OBJECTS[walletId].isOutputFrozen(keyImage);
}
self.createTxs = async function(walletId, config) {
if (typeof config === "object") config = new MoneroTxConfig(config);
let txs = await self.WORKER_OBJECTS[walletId].createTxs(config);
return txs[0].getTxSet().toJson();
}
self.sweepOutput = async function(walletId, config) {
if (typeof config === "object") config = new MoneroTxConfig(config);
let tx = await self.WORKER_OBJECTS[walletId].sweepOutput(config);
return tx.getTxSet().toJson();
}
self.sweepUnlocked = async function(walletId, config) {
if (typeof config === "object") config = new MoneroTxConfig(config);
let txs = await self.WORKER_OBJECTS[walletId].sweepUnlocked(config);
let txSets = [];
for (let tx of txs) if (!GenUtils.arrayContains(txSets, tx.getTxSet())) txSets.push(tx.getTxSet());
let txSetsJson = [];
for (let txSet of txSets) txSetsJson.push(txSet.toJson());
return txSetsJson;
}
self.sweepDust = async function(walletId, relay) {
let txs = await self.WORKER_OBJECTS[walletId].sweepDust(relay);
return txs.length === 0 ? {} : txs[0].getTxSet().toJson();
}
self.relayTxs = async function(walletId, txMetadatas) {
return self.WORKER_OBJECTS[walletId].relayTxs(txMetadatas);
}
self.describeTxSet = async function(walletId, txSetJson) {
return (await self.WORKER_OBJECTS[walletId].describeTxSet(new MoneroTxSet(txSetJson))).toJson();
}
self.signTxs = async function(walletId, unsignedTxHex) {
return self.WORKER_OBJECTS[walletId].signTxs(unsignedTxHex);
}
self.submitTxs = async function(walletId, signedTxHex) {
return self.WORKER_OBJECTS[walletId].submitTxs(signedTxHex);
}
self.signMessage = async function(walletId, message, signatureType, accountIdx, subaddressIdx) {
return self.WORKER_OBJECTS[walletId].signMessage(message, signatureType, accountIdx, subaddressIdx);
}
self.verifyMessage = async function(walletId, message, address, signature) {
return (await self.WORKER_OBJECTS[walletId].verifyMessage(message, address, signature)).toJson();
}
self.getTxKey = async function(walletId, txHash) {
return self.WORKER_OBJECTS[walletId].getTxKey(txHash);
}
self.checkTxKey = async function(walletId, txHash, txKey, address) {
return (await self.WORKER_OBJECTS[walletId].checkTxKey(txHash, txKey, address)).toJson();
}
self.getTxProof = async function(walletId, txHash, address, message) {
return self.WORKER_OBJECTS[walletId].getTxProof(txHash, address, message);
}
self.checkTxProof = async function(walletId, txHash, address, message, signature) {
return (await self.WORKER_OBJECTS[walletId].checkTxProof(txHash, address, message, signature)).toJson();
}
self.getSpendProof = async function(walletId, txHash, message) {
return self.WORKER_OBJECTS[walletId].getSpendProof(txHash, message);
}
self.checkSpendProof = async function(walletId, txHash, message, signature) {
return self.WORKER_OBJECTS[walletId].checkSpendProof(txHash, message, signature);
}
self.getReserveProofWallet = async function(walletId, message) {
return self.WORKER_OBJECTS[walletId].getReserveProofWallet(message);
}
self.getReserveProofAccount = async function(walletId, accountIdx, amountStr, message) {
return self.WORKER_OBJECTS[walletId].getReserveProofAccount(accountIdx, amountStr, message);
}
self.checkReserveProof = async function(walletId, address, message, signature) {
return (await self.WORKER_OBJECTS[walletId].checkReserveProof(address, message, signature)).toJson();
}
self.getTxNotes = async function(walletId, txHashes) {
return self.WORKER_OBJECTS[walletId].getTxNotes(txHashes);
}
self.setTxNotes = async function(walletId, txHashes, txNotes) {
return self.WORKER_OBJECTS[walletId].setTxNotes(txHashes, txNotes);
}
self.getAddressBookEntries = async function(walletId, entryIndices) {
let entriesJson = [];
for (let entry of await self.WORKER_OBJECTS[walletId].getAddressBookEntries(entryIndices)) entriesJson.push(entry.toJson());
return entriesJson;
}
self.addAddressBookEntry = async function(walletId, address, description) {
return self.WORKER_OBJECTS[walletId].addAddressBookEntry(address, description);
}
self.editAddressBookEntry = async function(walletId, index, setAddress, address, setDescription, description) {
return self.WORKER_OBJECTS[walletId].editAddressBookEntry(index, setAddress, address, setDescription, description);
}
self.deleteAddressBookEntry = async function(walletId, index) {
return self.WORKER_OBJECTS[walletId].deleteAddressBookEntry(index);
}
self.tagAccounts = async function(walletId, tag, accountIndices) {
throw new Error("Not implemented");
}
self.untagAccounts = async function(walletId, accountIndices) {
throw new Error("Not implemented");
}
self.getAccountTags = async function(walletId) {
throw new Error("Not implemented");
}
self.setAccountTagLabel = async function(walletId, tag, label) {
throw new Error("Not implemented");
}
self.getPaymentUri = async function(walletId, configJson) {
return self.WORKER_OBJECTS[walletId].getPaymentUri(new MoneroTxConfig(configJson));
}
self.parsePaymentUri = async function(walletId, uri) {
return (await self.WORKER_OBJECTS[walletId].parsePaymentUri(uri)).toJson();
}
self.getAttribute = async function(walletId, key) {
return self.WORKER_OBJECTS[walletId].getAttribute(key);
}
self.setAttribute = async function(walletId, key, value) {
return self.WORKER_OBJECTS[walletId].setAttribute(key, value);
}
self.startMining = async function(walletId, numThreads, backgroundMining, ignoreBattery) {
return self.WORKER_OBJECTS[walletId].startMining(numThreads, backgroundMining, ignoreBattery);
}
self.stopMining = async function(walletId) {
return self.WORKER_OBJECTS[walletId].stopMining();
}
self.isMultisigImportNeeded = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isMultisigImportNeeded();
}
self.isMultisig = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isMultisig();
}
self.getMultisigInfo = async function(walletId) {
return (await self.WORKER_OBJECTS[walletId].getMultisigInfo()).toJson();
}
self.prepareMultisig = async function(walletId) {
return self.WORKER_OBJECTS[walletId].prepareMultisig();
}
self.makeMultisig = async function(walletId, multisigHexes, threshold, password) {
return await self.WORKER_OBJECTS[walletId].makeMultisig(multisigHexes, threshold, password);
}
self.exchangeMultisigKeys = async function(walletId, multisigHexes, password) {
return (await self.WORKER_OBJECTS[walletId].exchangeMultisigKeys(multisigHexes, password)).toJson();
}
self.exportMultisigHex = async function(walletId) {
return self.WORKER_OBJECTS[walletId].exportMultisigHex();
}
self.importMultisigHex = async function(walletId, multisigHexes) {
return self.WORKER_OBJECTS[walletId].importMultisigHex(multisigHexes);
}
self.signMultisigTxHex = async function(walletId, multisigTxHex) {
return (await self.WORKER_OBJECTS[walletId].signMultisigTxHex(multisigTxHex)).toJson();
}
self.submitMultisigTxHex = async function(walletId, signedMultisigTxHex) {
return self.WORKER_OBJECTS[walletId].submitMultisigTxHex(signedMultisigTxHex);
}
self.getData = async function(walletId) {
return self.WORKER_OBJECTS[walletId].getData();
}
self.changePassword = async function(walletId, oldPassword, newPassword) {
return self.WORKER_OBJECTS[walletId].changePassword(oldPassword, newPassword);
}
self.isClosed = async function(walletId) {
return self.WORKER_OBJECTS[walletId].isClosed();
}
self.close = async function(walletId, save) {
return self.WORKER_OBJECTS[walletId].close(save); // TODO: remove listeners and delete wallet from WORKER_OBJECTS
}