diff --git a/src/action/channel.js b/src/action/channel.js
index 76a964cd0..e0da758ad 100644
--- a/src/action/channel.js
+++ b/src/action/channel.js
@@ -3,7 +3,7 @@
* call the corresponding GRPC apis for channel management.
*/
-import { toSatoshis, poll } from '../helper';
+import { toSatoshis, poll, getTimeTilAvailable } from '../helper';
import * as log from './log';
class ChannelAction {
@@ -160,6 +160,7 @@ class ChannelAction {
limboBalance: pfcc.limboBalance,
maturityHeight: pfcc.maturityHeight,
blocksTilMaturity: pfcc.blocksTilMaturity,
+ timeTilAvailable: getTimeTilAvailable(pfcc.blocksTilMaturity),
status: 'pending-force-closing',
}));
const wccs = response.waitingCloseChannels.map(wcc => ({
diff --git a/src/helper.js b/src/helper.js
index 67f56b905..30a0f1962 100644
--- a/src/helper.js
+++ b/src/helper.js
@@ -137,6 +137,23 @@ export const toLabel = (amount, settings) => {
return toAmountLabel(satoshis, settings);
};
+/**
+ * Convert a number of blocks to an amount of time in the format "X days and Y
+ * hours" assuming 10 minutes per block.
+ * @param {number} numBlocks The number of blocks to convert.
+ * @return {string} The amount of time the blocks is equivalent to.
+ */
+export const getTimeTilAvailable = numBlocks => {
+ if (!Number.isInteger(numBlocks)) {
+ throw new Error('Invalid input!');
+ }
+ const days = Math.floor(numBlocks / (24 * 6));
+ const hours = Math.floor((numBlocks % (24 * 6)) / 6);
+ const daysString = days === 1 ? 'day' : 'days';
+ const hoursString = hours === 1 ? 'hour' : 'hours';
+ return `${days} ${daysString} and ${hours} ${hoursString}`;
+};
+
/**
* Split '_' separated words and convert to uppercase
* @param {string} value The input string
diff --git a/src/view/channel-detail.js b/src/view/channel-detail.js
index c852a1bc1..7d92d3d60 100644
--- a/src/view/channel-detail.js
+++ b/src/view/channel-detail.js
@@ -48,6 +48,11 @@ const ChannelDetailView = ({ store, nav }) => (
{store.selectedChannel.statusLabel}
+ {store.selectedChannel.timeTilAvailable ? (
+
+ {store.selectedChannel.timeTilAvailable}
+
+ ) : null}
{store.selectedChannel.capacityLabel} {store.unitLabel}
diff --git a/test/unit/action/channel.spec.js b/test/unit/action/channel.spec.js
index 3f04c809f..3a8822b61 100644
--- a/test/unit/action/channel.spec.js
+++ b/test/unit/action/channel.spec.js
@@ -146,7 +146,12 @@ describe('Action Channels Unit Tests', () => {
grpc.sendCommand.withArgs('pendingChannels').resolves({
pendingOpenChannels: [{ channel: { ...pendingChannel } }],
pendingClosingChannels: [{ channel: { ...pendingChannel } }],
- pendingForceClosingChannels: [{ channel: { ...pendingChannel } }],
+ pendingForceClosingChannels: [
+ {
+ channel: { ...pendingChannel },
+ blocksTilMaturity: 463,
+ },
+ ],
waitingCloseChannels: [{ channel: { ...pendingChannel } }],
totalLimboBalance: 1,
});
@@ -156,6 +161,9 @@ describe('Action Channels Unit Tests', () => {
remotePubkey: 'some-key',
fundingTxId: 'FFFF',
});
+ expect(store.pendingChannels[2], 'to satisfy', {
+ timeTilAvailable: '3 days and 5 hours',
+ });
});
it('should log error on failure', async () => {
diff --git a/test/unit/helper.spec.js b/test/unit/helper.spec.js
index 1eba11788..a81f35f2e 100644
--- a/test/unit/helper.spec.js
+++ b/test/unit/helper.spec.js
@@ -817,4 +817,32 @@ describe('Helpers Unit Tests', () => {
);
});
});
+
+ describe('getTimeTilAvailable()', () => {
+ it('should format blocks to human-readable time', () => {
+ const numBlocks = 463;
+ const time = helpers.getTimeTilAvailable(numBlocks);
+ expect(time, 'to equal', '3 days and 5 hours');
+ });
+
+ it('should error on undefined', () => {
+ expect(
+ helpers.getTimeTilAvailable.bind(undefined),
+ 'to throw',
+ /Invalid/
+ );
+ });
+
+ it('should work for 1 day and 1 hour', () => {
+ const numBlocks = 150;
+ const time = helpers.getTimeTilAvailable(numBlocks);
+ expect(time, 'to equal', '1 day and 1 hour');
+ });
+
+ it('should work for 0', () => {
+ const numBlocks = 0;
+ const time = helpers.getTimeTilAvailable(numBlocks);
+ expect(time, 'to equal', '0 days and 0 hours');
+ });
+ });
});