Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create native Monero client adapter for Desktop #53

Open
monicanagent opened this issue Mar 21, 2019 · 0 comments
Open

Create native Monero client adapter for Desktop #53

monicanagent opened this issue Mar 21, 2019 · 0 comments
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed
Milestone

Comments

@monicanagent
Copy link
Owner

monicanagent commented Mar 21, 2019

Overview

CypherPoker.JS for Desktop requires that the Monero command line client be integrated into the application in order to enable support for the cryptocurrency. This is best accomplished by creating an adapter, similar to the current sqlite3.js adapter.

In short, such an adapter is responsible for spawning the command line client, with RPC functionality enabled, as a Node child_process, and providing async functions that CypherPoker.JS can interact with (especially the CP_Account API endpoint).

The adapter should simultaneously and transparently support Windows, macOS, and Linux Monero clients. If any existing NPM packages exist that are both reliable and support these specifications then they should be considered for inclusion into the adapter.

Additional information, including code documentation and formatting, can be found here: #48

Configuration

Configuration options for adapters are currently stored in the desktop version's main.js file:

database: {
sqlite3: {
adapter: null,
script: "./adapters/sqlite3.js",
bin: "./bin/sqlite/%os%/%bin%"
}
},
host:null
}

However, these may be externalized (to a config.json file, for example), so they should not assume to exist at the application's root level and instead always be passed by reference (to a constructor function, for example).

Post-Install Configuration

One-time post-install configuration options for the adapter (after npm install completes), can be set in the desktop postinstall.js file.

Launching the Adapter

The adapter should be launched using a function similar to the one used to start the SQLite 3 database:

async function startDatabase(dbAdapter) {
var adapterData = electronEnv.database[dbAdapter];
var scriptPath = adapterData.script;
var adapterScript = fs.readFileSync(scriptPath, {encoding:"UTF-8"});
var vmContext = new Object();
vmContext = Object.assign(electronEnv.server.exposed_objects, vmContext);
var context = vm.createContext(vmContext);
vm.runInContext(adapterScript, context, {
displayErrors: true,
filename: scriptPath
});
adapterData.adapter = context;
try {
var result = await context.initialize(adapterData);
return (true);
} catch (err) {
console.error ("Couldn't initialize \""+dbAdapter+"\" database adapter: \n"+err.stack);
return (false);
}
}

A reference to the adapter should be included in the configuration data so that it can be accessed from the main.js process. For example:

var adapterData = electronEnv.database[dbAdapter];

Note that the adapter should exist in its own execution context (a JavaScript virtual machine), and is to be provided with available objects references and data exposed in the electronEnv.server.exposed_objects configuration object (see Configuration above).

Another thing to keep in mind when writing the adapter launcher is that other adapters will almost certainly be included in future versions so the launch process should be as modular and self-contained as possible (an async function rather than flat root-level code).

Adapter-Server Communication (Required)

Once launched, the adapter's data and functionality becomes available to the local server and all loaded API endpoints via the hostEnv property :

rpc_options.exposed_objects.hostEnv = hostEnv; //overwrite default reference

For example, this is how the SQLite 3 adapter is accessed from the main server.js process:

var opened = await hostEnv.database.sqlite3.adapter.openDBFile(dbFilePath);

...and here's how it's accessed from an API endpoint (CP_Account in this case):

hostEnv.database.sqlite3.adapter.invoke(requestObj).then(result => {

In both cases the hostEnv.database.sqlite3 reference is the one set when the database adapter is started:

adapterData.adapter = context;

Here adapterData=electronEnv.database.sqlite3:

var adapterData = electronEnv.database[dbAdapter];

Adapter-Client Communication (Optional)

Although it's unlikely to be used, at least at the present time, the adapter can also be accessed using a synchronous IPC call from the web client when running as a Desktop application.

In main.js a new case block for the adapter should be added to:

function onIPCMessage (event, request) {
var response = new Object();
//be sure not to include any circular references in the response
//since it will be stringified before being returned...
switch (request.command) {
case "init":
response.type = "init";
response.data = electronEnv.client;
var winFound = false;
for (var count=0; count < windows.length; count++) {
var currentWindow = windows[count].win;
var windowWC = currentWindow.webContents;
if (event.sender === windowWC) {
windows[count].ipcID = request.data.ipcID;
winFound = true;
break;
}
}
if (winFound == false) {
response.type = "error";
response.data = new Object();
response.data.code = -2;
response.data.message = "Couldn't find matching window.webContents reference for ipcID \""+request.data.ipcID+"\".";
}
break;
case "new-window":
response.type = "new-window";
response.message = "ok";
if (request.data.url != undefined) {
var url = request.data.url;
} else {
url = null;
}
if (request.data.winName != undefined) {
var winName = request.data.winName;
} else {
winName = null;
}
if (url != null) {
if (winName != null) {
createClient(url, winName);
} else {
createClient(url);
}
} else {
createClient();
}
break;
case "toggle-devtools":
response.type = "toggle-devtools";
response.message = "ok";
for (var count=0; count < windows.length; count++) {
var currentWindow = windows[count].win;
var currentIPCID = windows[count].ipcID;
if (request.data["all"] == true) {
currentWindow.webContents.toggleDevTools();
} else {
if (currentIPCID == request.data.ipcID) {
currentWindow.webContents.toggleDevTools();
break;
}
}
}
break;
case "database-info":
for (var db in electronEnv.database) {
response[db] = new Object();
response[db].version = electronEnv.database[db].adapter.binVersion;
var dbFilePath = electronEnv.database[db].adapter.initData.dbFilePath;
response[db].dbFilePath = dbFilePath;
response[db].dbMaxMB = electronEnv.database[db].adapter.dbMaxMB;
response[db].dbSizeMB = electronEnv.database[db].adapter.getFileSize(dbFilePath, "MB");
}
break;
default:
response.type = "error";
response.data = new Object();
response.data.code = -1;
response.data.message = "Unrecognized IPC request command \""+request.command+"\"";
break;
}
event.returnValue = response; //respond immediately
//...or respond asynchronously:
//event.sender.send(request.ipcID, response);
}

From the web client this is invoked via the IPCSend function:

function IPCSend (command, data=null) {
if (isDesktop()) {
var request = new Object();
request.command = command;
if (data == null) {
data = new Object();
}
request.data = data;
request.data.ipcID = ipcID;
try {
return (ipcRenderer.sendSync("ipc-main", request));
} catch (err) {
console.error (err.stack);
}
} else {
return (null);
}
}

Messages may also be initiated in the opposite direction and received in the web client:

function onIPCMessage(event, request) {
var response = new Object();
if (request.ipcID != ipcID) {
//not for this window / child process
return;
}
//be sure not to include any circular references in the response
//since it will be stringified before being returned...
switch (request.command) {
default:
response.type = "error";
response.data = new Object();
response.data.code = -1;
response.data.message = "Unrecognized IPC request command \""+request.command+"\"";
break;
}
event.returnValue = response; //respond immediately
//...or respond asynchronously:
//event.sender.send(request.ipcID, response);
}

Required Functional Support

In order to provide the full cryptocurrency functionality required by CypherPoker.JS, the adapter must support the following:

  • Generate a HD wallet address from which child addresses can be derived (like BIP39), similarly to what's implemented in the CP_Account API endpoint:

function makeHDWallet(privKey) {

  • Derive an address from the HD wallet using child indexes. Note the use of the startChain and startIndex values which are maintained between sessions to always generate fresh deposit addresses:

function getNewAddress(APIType="bitcoin", network=null) {
var promise = new Promise(function(resolve, reject) {
if (network == null) {
network = config.CP.API[APIType].default.network;
}
if ((network == "main") && (namespace.cp.bitcoinWallet != null)) {
if (config.CP.API.wallets.bitcoin.startChain < 0) {
config.CP.API.wallets.bitcoin.startChain = 0;
}
//address index 0 is reserved for the cashout address
if (config.CP.API.wallets.bitcoin.startIndex < 0) {
config.CP.API.wallets.bitcoin.startIndex = 0;
}
//currently we simply increment the index:
config.CP.API.wallets.bitcoin.startIndex++;
//the chain value is currently 0 but can be set manually
var startChain = config.CP.API.wallets.bitcoin.startChain;
var startIndex = config.CP.API.wallets.bitcoin.startIndex;
} else if ((network == "test3") && (namespace.cp.bitcoinTest3Wallet != null)) {
if (config.CP.API.wallets.test3.startChain < 0) {
config.CP.API.wallets.test3.startChain = 0;
}
//address index 0 is reserved for the cashout address
if (config.CP.API.wallets.test3.startIndex < 0) {
config.CP.API.wallets.test3.startIndex = 0;
}
config.CP.API.wallets.test3.startIndex++;
startChain = config.CP.API.wallets.test3.startChain;
startIndex = config.CP.API.wallets.test3.startIndex;
} else {
reject(new Error("Wallet for \""+APIType+"\", network \""+network+"\" not defined."));
return;
}
if (network == "main") {
var newWallet = namespace.cp.bitcoinWallet.derivePath("m/"+startChain+"/"+startIndex);
} else {
newWallet = namespace.cp.bitcoinTest3Wallet.derivePath("m/"+startChain+"/"+startIndex);
}
resolve (newWallet);
});
return (promise);
}

...or:

var fromWallet = namespace.cp.bitcoinWallet.derivePath(API.default[network].cashOutAddrPath);

Derived address must also include private keys and related information required for sending transactions!

  • A function capable of generating an address from, at least, a public key associated with the address:

function getAddress(walletObj, network=null) {
if (network == null) {
return (bitcoin.payments.p2pkh({pubkey:walletObj.publicKey}).address);
} else {
return (bitcoin.payments.p2pkh({pubkey:walletObj.publicKey, network}).address);
}
}

  • A function to retrieve the live blockchain balance for an address:

function getBlockchainBalance(address, APIType="bitcoin", network=null) {
var promise = new Promise(function(resolve, reject) {
var API = config.CP.API[APIType];
var url = API.url.balance;
if (network == null) {
network = API.default.network;
}
url = url.split("%address%").join(address).split("%network%").join(network);
request({
url: url,
method: "GET",
json: true
}, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve(body);
}
});
});
return (promise);
}

  • A function to generate a new transaction object with customizable fees:

function newBTCTx (fromAddress, toAddress, satAmount, satFees, network=null) {
var promise = new Promise(function(resolve, reject) {
var API = config.CP.API.bitcoin;
var url = API.url.createTx;
if (network == null) {
network = API.default.network;
}
var token = config.CP.API.tokens.blockcypher;
url = url.split("%network%").join(network).split("%token%").join(token);
var requestBody = {
"inputs":[{"addresses":[fromAddress]}],
"outputs":[{"addresses":[toAddress],
"value": Number(satAmount)}],
"fees":Number(satFees)
};
request({
url: url,
method: "POST",
body:requestBody,
json: true
}, function (error, response, body){
if (error) {
reject (error);
} else {
if ((body == undefined) || (body == null)) {
reject (response);
return;
}
if ((body.errors != undefined) && (body.errors != null)) {
if (body.errors.length > 0) {
reject (body.errors);
} else {
resolve (body);
}
} else {
resolve (body);
}
}
});
});
return (promise);
}

  • A function to sign a generated transaction object with a private key:

function signBTCTx (txObject, WIF, network=null) {
if (network == null) {
network = config.CP.API.bitcoin.default.network;
}
if (network == "main") {
var keys = keys = new bitcoin.ECPair.fromWIF(WIF);
} else {
keys = new bitcoin.ECPair.fromWIF(WIF, bitcoin.networks.testnet);
}
try {
var pubkeys = new Array();
var signatures = txObject.tosign.map(function(tosign) {
pubkeys.push(keys.publicKey.toString("hex"));
return (signToDER(tosign, keys.privateKey).toString("hex"));
});
txObject.signatures = signatures;
txObject.pubkeys = pubkeys;
} catch (err) {
console.error(err.stack);
txObject = null;
}
return (txObject);
}
/**
* Signs an input hexadecimal string and returns a DER-encoded signature.
*
* @param {String} toSignHex Hexadecimal-encoded data string to sign.
* @param {Buffer} privateKeyBuffer The private key with which to sign <code>toSignHex</code>.
*
* @return {Buffer} The DER-encoded, signed message. Use <code>toString("hex")</code> to get the
* hexadecimal string representation of the output.
*/
function signToDER (toSignHex, privateKeyBuffer) {
var sigObj = secp256k1.sign(Buffer.from(toSignHex, "hex"), privateKeyBuffer);
return (secp256k1.signatureExport(sigObj.signature));
}

  • A function to post the transaction to the peer-to-peer network and return a pending transaction hash:

function sendBTCTx(txObject, network=null) {
/*
TODO: Future update example to build transaction from scratch:
var key = bitcoin.ECKey.fromWIF("L1Kzcyy88LyckShYdvoLFg1FYpB5ce1JmTYtieHrhkN65GhVoq73");
var tx = new bitcoin.TransactionBuilder();
tx.addInput("d18e7106e5492baf8f3929d2d573d27d89277f3825d3836aa86ea1d843b5158b", 1);
tx.addOutput("12idKQBikRgRuZEbtxXQ4WFYB7Wa3hZzhT", 149000);
tx.sign(0, key);
console.log(tx.build().toHex());
*/
var promise = new Promise(function(resolve, reject) {
var API = config.CP.API.bitcoin;
var url = API.url.sendTx;
if (network == null) {
network = API.default.network;
}
var token = config.CP.API.tokens.blockcypher;
url = url.split("%network%").join(network).split("%token%").join(token);
request({
url: url,
method: "POST",
body: txObject,
json: true
}, (error, response, body) => {
if ((body == undefined) || (body == null)) {
reject (response);
return;
}
if (error) {
reject(error);
} else {
//do we want to catch and reject JSON-RPC errors here?
resolve(body);
}
});
});
return (promise);
}
/**
* Adds a pending cashout transaction to the <code>namespace.cp.pendingCashouts</code>
* array.
*
* @param {String} fromAccount The account address that is cashing out.
* @param {String} toAddress The address to which the funds are being sent.
* @param {String} type The cryptocurrency type associated with the transaction (e.g. "bitcoin").
* @param {String} network The cryptocurrency sub-network associated with the transaction (e.g. "main" or "test3").
* @param {String} amount The amount of the transaction in the smallest denomination for the associated
* cryptocurrency.
* @param {String} fees Any miner fee(s) for the transaction in the smallest denomination for the associated
* cryptocurrency.
*/
function addPendingCashout(fromAccount, toAddress, type, network, amount, fees) {
if (namespace.cp.pendingCashouts == undefined) {
namespace.cp.pendingCashouts = new Array();
}
var cashoutObject = new Object();
cashoutObject.from = fromAccount;
cashoutObject.to = toAddress;
cashoutObject.type = type;
cashoutObject.network = network;
cashoutObject.amount = amount;
cashoutObject.fees = fees;
var now = new Date();
cashoutObject.timestamp = now.toISOString();
namespace.cp.pendingCashouts.push(cashoutObject);
//should array be saved in case server quits?
}
/**
* Checks if a cashout transaction for a specific account is pending (appears in the
* <code>namespace.cp.pendingCashouts</code> array). Only one pending cashout transaction
* per account, cryptocurrency type, and network, is assumed to exist.
*
* @param {String} fromAccount The account address to check.
* @param {String} type The cryptocurrency type associated with the pending transaction (e.g. "bitcoin").
* @param {String} network The cryptocurrency sub-network associated with the pending transaction (e.g. "main" or "test3").
*
* @return {Boolean} True if the specified account has a transaction pending, false otherwise.
*/
function cashoutIsPending(fromAccount, type, network) {
if (namespace.cp.pendingCashouts == undefined) {
namespace.cp.pendingCashouts = new Array();
return (false);
}
if (namespace.cp.pendingCashouts.length == 0) {
return (false);
}
for (var count=0; count < namespace.cp.pendingCashouts.length; count++) {
var currentPendingCashout = namespace.cp.pendingCashouts[count];
if ((currentPendingCashout.from == fromAccount) &&
(currentPendingCashout.type == type) &&
(currentPendingCashout.network == network)) {
return (true);
}
}
return (false);
}
/**
* Removes a pending cashout transaction from the <code>namespace.cp.pendingCashouts</code>
* array.
*
* @param {String} fromAccount The account address that is cashing out.
* @param {String} type The cryptocurrency type associated with the transaction (e.g. "bitcoin").
* @param {String} network The cryptocurrency sub-network associated with the transaction (e.g. "main" or "test3").
*
* @return {Object} The pending cashout transaction removed from the internal <code>namespace.cp.pendingCashouts</code>
* array, of <code>null</code> if no matching transaction exists.
*/
function removePendingCashout(fromAccount, type, network) {
if (namespace.cp.pendingCashouts == undefined) {
namespace.cp.pendingCashouts = new Array();
return (null);
}
for (var count=0; count < namespace.cp.pendingCashouts.length; count++) {
var currentPendingCashout = namespace.cp.pendingCashouts[count];
if ((currentPendingCashout.from == fromAccount) &&
(currentPendingCashout.type == type) &&
(currentPendingCashout.network == network)) {
var txObj = namespace.cp.pendingCashouts.splice(count, 1);
return (txObj);
}
}
return (null);
}

  • A function to estimate the transaction fee for a given transaction:

async function estimateTxFee (txData=null, priority=1, APIType="bitcoin", network=null) {
try {
switch (APIType) {
case "bitcoin":
var txSize = 250; //bytes
if (txData != null) {
txSize = txData.length / 2; //hex-encoded binary data
}
//TODO: complete this!
break;
default:
return (null);
break;
}
} catch (err) {
return (null);
}
}

  • A function to update the default transaction fees:

function updateTxFees(APIType="bitcoin", network=null, forceUpdate=false) {
var promise = new Promise(function(resolve, reject) {
var API = config.CP.API[APIType];
if ((network == null) || (network == "")) {
network = API.default.network;
}
if (API.default[network].feeUpdateEnabled == false) {
reject (new Error("Fee updates for \""+APIType+"/"+network+"\" disabled."));
return;
}
var url = API.url.fees;
var updateSeconds = API.default[network].feeUpdateSeconds;
if ((updateSeconds > 0) && (forceUpdate == false)) {
var lastUpdate = API.default[network]["lastUpdated"];
if ((lastUpdate != undefined) && (lastUpdate != null) && (lastUpdate != "")) {
var lastUpdateCheck = new Date(API.default[network].lastUpdated); //this date/time must be relative to local date/time
var currentDateTime = new Date();
var deltaMS = currentDateTime.valueOf() - lastUpdateCheck.valueOf();
var deltaSec = deltaMS / 1000;
if (deltaSec < updateSeconds) {
resolve("skipped");
return;
}
}
}
url = url.split("%network%").join(network);
request({
url: url,
method: "GET",
json: true
}, (error, response, body) => {
var currentDateTime = new Date();
API.default[network].lastUpdated = currentDateTime.toISOString();
if ((body == undefined) || (body == null)) {
var errorObj = new Error(response);
reject (errorObj);
return;
}
if (error) {
errorObj = new Error(error);
reject(errorObj);
} else {
API.default[network].minerFee = String(body.high_fee_per_kb); //should be a string
resolve("updated");
//resolve(body);
}
});
});
return (promise);
}


For much of the above functionality, information such as the private key(s) for the root HD wallet, indexes of derived addresses, and default transaction fees, comes from the server configuration file:

"wallets":{
"bitcoin":{
"xprv":"xprv...",
"startChain":0,
"startIndex":2
},
"test3":{
"tprv":"tprv...",
"startChain":0,
"startIndex":2
}
},
"tokens": {
"blockcypher":"BLOCKCYPHER_TOKEN"
},
"bitcoin":{
"networks":{
"main:":"main",
"test3":"test3"
},
"default": {
"network": "test3",
"updateLimitSeconds": 120,
"baseDenom": "satoshi",
"main":{
"cashOutAddrPath":"m/0/1",
"minerFee": "50000",
"depositFee": "10000",
"feeUpdateSeconds": 1200,
"feeUpdateEnabled": true
},
"test3":{
"cashOutAddrPath":"m/0/1",
"minerFee": "50000",
"depositFee": "10000",
"feeUpdateSeconds": 1200,
"feeUpdateEnabled": true
}
},
"url": {
"newaddr": "https://api.blockcypher.com/v1/btc/%network%/addrs",
"balance": "https://api.blockcypher.com/v1/btc/%network%/addrs/%address%/full",
"createtx": "https://api.blockcypher.com/v1/btc/%network%/txs/new?token=%token%",
"sendtx": "https://api.blockcypher.com/v1/btc/%network%/txs/send?token=%token%",
"fees": "https://api.blockcypher.com/v1/btc/%network%"
}

While none of the configuration sections for the new adapter are required to match the existing format or properties, it should be kept in mind that CypherPoker.JS should support both the Monero mainnet and any official testnets with independent configuration options for both.

It's also important to keep in mind that the configuration data is dynamic and is often updated at runtime after being loaded. For example:

async function createAccountSystem(onCreateCB=null) {
if (typeof(process.env["BLOCKCYPHER_TOKEN"]) == "string") {
config.CP.API.tokens.blockcypher = process.env["BLOCKCYPHER_TOKEN"];
}
if (typeof(process.env["DB_URL"]) == "string") {
config.CP.API.database.url = process.env["DB_URL"];
}
if (typeof(process.env["DB_HOST"]) == "string") {
config.CP.API.database.host = process.env["DB_HOST"];
}
if (typeof(process.env["DB_ACCESS_KEY"]) == "string") {
config.CP.API.database.accessKey = process.env["DB_ACCESS_KEY"];
}
if (typeof(process.env["WALLET_XPRV"]) == "string") {
config.CP.API.wallets.bitcoin.xprv = process.env["WALLET_XPRV"];
}
if (typeof(process.env["WALLET_TPRV"]) == "string") {
config.CP.API.wallets.test3.tprv = process.env["WALLET_TPRV"];
}
//try updating via command line arguments:
for (var count = 2; count < process.argv.length; count++) {
var currentArg = process.argv[count];
var splitArg = currentArg.split("=");
if (splitArg.length < 2) {
throw (new Error("Malformed command line argument: "+currentArg));
}
var argName = new String(splitArg[0]);
var joinedArr = new Array();
joinedArr.push (splitArg[1]);
//there may have been more than one "=" in the argument
for (count2 = 2; count2 < splitArg.length; count2++) {
joinedArr.push(splitArg[count2]);
}
var argValue = joinedArr.join("=");
switch (argName.toLowerCase()) {
case "blockcypher_token":
config.CP.API.tokens.blockcypher = argValue;
break;
case "db_url":
config.CP.API.database.url = argValue;
break;
case "db_host":
config.CP.API.database.host = argValue;
break;
case "db_access_key":
config.CP.API.database.accessKey = argValue;
break;
case "wallet_xprv":
onfig.CP.API.wallets.bitcoin.xprv = argValue;
break;
case "wallet_tprv":
onfig.CP.API.wallets.test3.tprv = argValue;
break;
default:
//unrecognized command line parameter
break;
}
}
//create HD wallets if possible
namespace.cp.bitcoinWallet = namespace.cp.makeHDWallet(config.CP.API.wallets.bitcoin.xprv);
if (namespace.cp.bitcoinWallet != null) {
var walletPath = config.CP.API.bitcoin.default.main.cashOutAddrPath;
var cashoutWallet = namespace.cp.bitcoinWallet.derivePath(walletPath);
console.log ("Bitcoin HD wallet (\""+walletPath+"\") configured @ "+namespace.cp.getAddress(cashoutWallet));
} else {
console.log ("Could not configure Bitcoin wallet.");
}
namespace.cp.bitcoinTest3Wallet = namespace.cp.makeHDWallet(config.CP.API.wallets.test3.tprv);
if (namespace.cp.bitcoinTest3Wallet != null) {
walletPath = config.CP.API.bitcoin.default.test3.cashOutAddrPath;
cashoutWallet = namespace.cp.bitcoinTest3Wallet.derivePath(walletPath);
console.log ("Bitcoin testnet HD wallet (\""+walletPath+"\") configured @ "+namespace.cp.getAddress(cashoutWallet, bitcoin.networks.testnet));
} else {
console.log ("Could not configure Bitcoin testnet wallet.");
}
var wallets = config.CP.API.wallets;
if (config.CP.API.database.enabled == true) {
console.log ("Database functionality is ENABLED.");
//the second parameter is there to provide a value for the HMAC
try {
var walletStatusObj = await namespace.cp.callAccountDatabase("walletstatus", {"random":String(Math.random())});
} catch (err) {
console.error ("Could not get current wallet status.");
console.error (err);
console.error ("Trying again in 5 seconds...");
setTimeout(5000, createAccountSystem, onCreateCB);
return (false);
}
var resultObj = walletStatusObj.result;
//force-convert values in case the database returned them as strings
var btcStartChain = Number(String(resultObj.bitcoin.main.startChain));
var btcStartIndex = Number(String(resultObj.bitcoin.main.startIndex));
var test3StartChain = Number(String(resultObj.bitcoin.test3.startChain));
var test3StartIndex = Number(String(resultObj.bitcoin.test3.startIndex));
if (btcStartChain > wallets.bitcoin.startChain) {
wallets.bitcoin.startChain = Number(String(resultObj.bitcoin.main.startChain));
}
if (btcStartIndex > wallets.bitcoin.startIndex) {
wallets.bitcoin.startIndex = Number(String(resultObj.bitcoin.main.startIndex));
}
if (test3StartChain > wallets.test3.startChain) {
wallets.test3.startChain = Number(String(resultObj.bitcoin.test3.startChain));
}
if (test3StartIndex > wallets.test3.startIndex) {
wallets.test3.startIndex = Number(String(resultObj.bitcoin.test3.startIndex));
}
} else {
console.log ("Database functionality is DISABLED.");
}
console.log ("Initial Bitcoin account derivation path: m/"+wallets.bitcoin.startChain+"/"+(wallets.bitcoin.startIndex+1));
console.log ("Initial Bitcoin testnet account derivation path: m/"+wallets.test3.startChain+"/"+(wallets.test3.startIndex+1));
if (config.CP.API.database.enabled == true) {
if (hostEnv.embedded == true) {
console.log ("Local database size: "+resultObj.db.sizeMB+" megabytes");
console.log ("Local database limit: "+resultObj.db.maxMB+" megabytes");
} else {
console.log ("Remote database size: "+resultObj.db.sizeMB+" megabytes");
console.log ("Remote database limit: "+resultObj.db.maxMB+" megabytes");
}
console.log ("Database last updated "+resultObj.db.elapsedUpdateSeconds+" seconds ago");
} else {
console.log ("Using in-memory storage instead of database.");
}
onCreateCB();
return (true);
}

As such, the data stored in the config.json includes only the basic, default options and any sensitive data such as private keys should be omitted from the file entirely and replaced with placeholders:

"wallets":{
"bitcoin":{
"xprv":"xprv...",
"startChain":0,
"startIndex":2
},
"test3":{
"tprv":"tprv...",
"startChain":0,
"startIndex":2
}
},

@monicanagent monicanagent added enhancement New feature or request help wanted Extra attention is needed labels Mar 21, 2019
@monicanagent monicanagent self-assigned this Mar 21, 2019
@monicanagent monicanagent added this to To do in CypherPoker.JS via automation Mar 21, 2019
@monicanagent monicanagent added this to the v0.5.1 milestone Mar 23, 2019
@monicanagent monicanagent moved this from To do to In progress in CypherPoker.JS Apr 2, 2019
@monicanagent monicanagent moved this from In progress to To do in CypherPoker.JS Apr 16, 2019
@monicanagent monicanagent moved this from To do to In progress in CypherPoker.JS May 31, 2019
@monicanagent monicanagent modified the milestones: v0.5.1, v0.5.2 Jun 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
CypherPoker.JS
  
In progress
Development

No branches or pull requests

1 participant