if (process.argv.length <= 2) { console.log("Fetch interactions of address\nUsage node TODO.js [address]"); process.exit(); } const FloweeServices = require('../'); var Flowee = new FloweeServices(); function printDate(timeInSeconds) { let date = new Date(timeInSeconds * 1000); // JS wants milliseconds. var answer = date.getUTCDate() + "/" + (date.getUTCMonth() + 1); while (answer.length < 5) answer = " " + answer; // JS misses 'getUTCYear' :( let y = date.getUTCFullYear() - 2000; answer += "/" + y; return answer; } function listInteractions(address) { // search request passes an object literal var promise = Flowee.search({ jobs: [ { // Get all transactions based on the following address value: address, type: Flowee.Job.LookupByAddress, } ], // don't fetch items twice, remember which ones we created jobs for requests: { headers: {}, tx: {} }, // private method to avoid fetching blockheaders multiple times. fetchHeader: function(blockHeight) { if (typeof this.requests.headers[`${blockHeight}`] === 'undefined') { this.requests.headers[`${blockHeight}`] = true; this.addJob({ type: Flowee.Job.FetchBlockHeader, value: blockHeight, }); } }, fetchTxOutputs: function(txid) { if (typeof this.requests.tx[`${txid}`] === 'undefined') { this.requests.tx[`${txid}`] = true; this.addJob({ type: Flowee.Job.FetchTx, value: txid, txFilter: [ Flowee.IncludeOutputAmounts, Flowee.IncludeOutputAddresses ] }); } }, onAddressUsedInOutput: function(blockHeight, offsetInBlock, outIndex) { this.fetchHeader(blockHeight); this.addJob({ type: Flowee.Job.FetchTx, value: blockHeight, value2: offsetInBlock, txFilter: [ Flowee.IncludeTxid, Flowee.IncludeOutputAmounts, Flowee.IncludeOutputAddresses, Flowee.IncludeInputs ], // the next one is for myself, not the framework. outIndex: outIndex }); }, onTxAdded: function(transaction) { let jobId = transaction.jobId; let job = this.jobs[jobId]; // If outIndex was defined this is one of the tx's we got from the // address-search because we added it in onAddressUsedInOutput() if (typeof job.outIndex !== 'undefined') { transaction.isMain = true; transaction.outIndex = job.outIndex; for (let i = 0; i < transaction.inputs.length; ++i) { if (i == 6) break; // no need to show more inputs. // input is really just a link to another Tx, fetch its outputs. this.fetchTxOutputs(transaction.inputs[i].previousTxid); } for (let i = 0; i < transaction.outputs.length; ++i) { if (i >= 6) { if (job.outIndex >= i) i = job.outIndex; else break; } this.addJob({ type: Flowee.Job.LookupSpentTx, value: transaction.txid, value2: i }); } } else { // anything not from the address search should be made easy to find. this[`${transaction.txid}`] = transaction; this.fetchHeader(transaction.blockHeight); } }, onSpentOutputResolved: function(jobId, blockHeight, offsetInBlock) { if (blockHeight != -1) { // it was spent. let job = this.jobs[jobId]; this[`out${job.value}${job.value2}`] = blockHeight; this.fetchHeader(blockHeight); } }, onFinished: function() { console.log("Transactions that interacted with address: " + address); for (num in this.transactions) { let tx = this.transactions[num]; if (tx.isMain) { let header = this[`block${tx.blockHeight}`]; console.log(); console.log(printDate(header.time) + " (block: " + tx.blockHeight + ") txid: " + tx.txid); for (let i = 0; i < tx.inputs.length; ++i) { if (i == 6) { console.log(" " + (tx.inputs.length - i) + " more inputs not shown"); break; } if (tx.isCoinbase) { let header = this[`block${tx.blockHeight}`]; let amount = 0; for (let o = 0; o < tx.outputs.length; ++o) { amount += tx.outputs[o].amount; } console.log(printDate(header.time) + " " + amount / 100000000 + " BCH [coinbase]"); break; } else { // input is really just a link. let prev = this[`${tx.inputs[i].previousTxid}`]; let prevOut = prev.outputs[tx.inputs[i].outputIndex]; let header = this[`block${prev.blockHeight}`]; console.log(printDate(header.time) + " " + prevOut.amount / 100000000 + " BCH => " + prevOut.address); } } for (let i = 0; i < tx.outputs.length; ++i) { if (i >= 6) { if (tx.outIndex > i) { console.log(" skipping " + (tx.outIndex - i) + " outputs"); i = tx.outIndex; } else if (tx.outIndex < i) { console.log(" " + (tx.outputs.length - i) + " more outputs not shown"); break; } } let str = tx.outIndex === i ? "\t* " : "\t "; let spentHeight = this[`out${tx.txid}${i}`]; if (spentHeight) { let header = this[`block${spentHeight}`]; var spent = printDate(header.time); } else { var spent = "unspent"; } console.log(str + tx.outputs[i].amount / 100000000 + " BCH => " + tx.outputs[i].address + " (spent: " + spent +")"); } } } /* console.log("Address balance fetched: " + address); if (this.total != 0 || this.outputs != 0) { console.log(" UTXOs: " + this.unspent + "/" + this.outputs); console.log(" Balance: " + this.total / 100000000); } else { console.log(" empty"); } */ } }); return promise; } Flowee.connect().then(async function() { try { for (arg in process.argv) { if (arg > 1) { // nodejs, skip first two args. let result = await listInteractions(process.argv[arg]) } } } catch (error) { console.log("Error detected: " + error); } process.exit(); })