Skip to content

Commit

Permalink
feat(tx-logs): add tx history command (#2222)
Browse files Browse the repository at this point in the history
* feat(tx-logs): add tx history command

* feat(kadena-cli): add cmd and time to tx log

---------

Co-authored-by: Bart Huijgen <barthuijgen@users.noreply.github.com>
  • Loading branch information
barthuijgen and barthuijgen committed Jun 5, 2024
1 parent a4c8ee0 commit e79c180
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 9 deletions.
7 changes: 7 additions & 0 deletions .changeset/chatty-laws-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@kadena/kadena-cli": minor
---

- Updated "tx send" command to include transaction logging functionality. Transactions are now saved in a log file, capturing network details, transaction ID, and status.

- Implemented the tx history command to display a formatted transaction history. This command provides a user-friendly way to view detailed transaction logs, including network host, network ID, chain ID, status, and transaction ID.
159 changes: 159 additions & 0 deletions packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { createClient } from '@kadena/client';
import type { Command } from 'commander';
import path from 'node:path';
import { TRANSACTIONS_LOG_FILE } from '../../../constants/config.js';
import { KadenaError } from '../../../services/service-error.js';
import { createCommand } from '../../../utils/createCommand.js';
import { notEmpty } from '../../../utils/globalHelpers.js';
import { globalOptions } from '../../../utils/globalOptions.js';
import { log } from '../../../utils/logger.js';
import { createTable } from '../../../utils/table.js';
import type {
ITransactionLog,
ITransactionLogEntry,
IUpdateTransactionsLogPayload,
} from '../utils/txHelpers.js';
import {
formatDate,
generateClientUrl,
getTransactionDirectory,
mergePayloadsWithTransactionLog,
readTransactionLog,
updateTransactionStatus,
} from '../utils/txHelpers.js';

const header = {
requestKey: 'Request Key',
networkHost: 'Network Host',
networkId: 'Network ID',
chainId: 'Chain ID',
dateTime: 'Time',
status: 'Status',
txId: 'Transaction ID',
};

const filterLogsWithoutStatus = (log: ITransactionLog): ITransactionLog[] => {
return Object.entries(log)
.filter(([, logData]) => !logData.status)
.map(([key, value]) => ({ [key]: value }));
};

const getTransactionStatus = async (
requestKey: string,
value: ITransactionLogEntry,
): Promise<IUpdateTransactionsLogPayload | undefined> => {
const { getStatus } = createClient(
generateClientUrl({
networkId: value.networkId,
chainId: value.chainId,
networkHost: value.networkHost,
}),
);

try {
const result = await getStatus({
requestKey,
chainId: value.chainId,
networkId: value.networkId,
});

if (result[requestKey] !== undefined) {
return {
requestKey,
status: result[requestKey].result.status,
data: result[requestKey],
};
}
} catch (e) {
log.error(
`Failed to get transaction status for requestKey "${requestKey}": ${e.message}`,
);
}
};

const fetchTransactionStatuses = async (
logs: ITransactionLog[],
): Promise<IUpdateTransactionsLogPayload[]> => {
return await Promise.all(
logs.map(async (logData) => {
for (const [requestKey, value] of Object.entries(logData)) {
const status = await getTransactionStatus(requestKey, value);
if (status) {
return status;
}
}
}),
).then((results) => results.filter(notEmpty));
};

export const printTxLogs = (transactionLog: ITransactionLog): void => {
const table = createTable({});
Object.entries(transactionLog).forEach(([requestKey, data]) => {
const tableData: Record<keyof typeof header, string | undefined> = {
requestKey,
networkHost: data.networkHost,
networkId: data.networkId,
chainId: data.chainId,
dateTime: formatDate(new Date(data.dateTime)),
status: data.status,
txId: data.txId?.toString() ?? undefined,
};

Object.entries(tableData).forEach(([key, value]) => {
if (value !== undefined) {
const headerKey = header[key as keyof typeof header];
table.push({
[log.color.green(headerKey)]: value,
});
}
});
table.push([{ colSpan: 2, content: '' }]);
});

log.output(table.toString(), transactionLog);
};

export const txHistory = async (): Promise<void> => {
try {
const transactionDir = getTransactionDirectory();
if (!notEmpty(transactionDir)) throw new KadenaError('no_kadena_directory');

const transactionFilePath = path.join(
transactionDir,
TRANSACTIONS_LOG_FILE,
);
let transactionLog = await readTransactionLog(transactionFilePath);
if (!transactionLog)
throw new Error(
'No transaction logs are available. Please ensure that transaction logs are present and try again.',
);

const filteredLogs = filterLogsWithoutStatus(transactionLog);

if (filteredLogs.length > 0) {
const txResultsData = await fetchTransactionStatuses(filteredLogs);
await updateTransactionStatus(txResultsData);
transactionLog = mergePayloadsWithTransactionLog(
transactionLog,
txResultsData,
);
}

printTxLogs(transactionLog);
} catch (error) {
log.error(`Unable to read transaction history: ${error.message}`);
}
};

export const createTxHistoryCommand: (
program: Command,
version: string,
) => void = createCommand(
'history',
'Output a formatted list of transactions with their details, making it easy for users to understand their transaction history.',
[globalOptions.directory({ disableQuestion: true })],
async () => {
log.debug('tx-history:action');
await txHistory();
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export const createTxLocalCommand: (program: Command, version: string) => void =
generateClientUrl({
chainId: templateChainId,
...network,
networkExplorerUrl: network.networkExplorerUrl ?? '',
}),
);

Expand Down
21 changes: 19 additions & 2 deletions packages/tools/kadena-cli/src/commands/tx/commands/txSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ import { log } from '../../../utils/logger.js';
import { txOptions } from '../txOptions.js';
import { parseTransactionsFromStdin } from '../utils/input.js';
import { displayTransactionResponse } from '../utils/txDisplayHelper.js';
import type { INetworkDetails, ISubmitResponse } from '../utils/txHelpers.js';
import type {
INetworkDetails,
ISubmitResponse,
IUpdateTransactionsLogPayload,
} from '../utils/txHelpers.js';
import {
createTransactionWithDetails,
getClient,
getTransactionsFromFile,
logTransactionDetails,
saveTransactionsToFile,
updateTransactionStatus,
} from '../utils/txHelpers.js';

const clientInstances: Map<string, IClient> = new Map();
Expand Down Expand Up @@ -66,19 +72,28 @@ export async function pollRequests(

const results = await Promise.allSettled(pollingPromises);

const txLogsData: IUpdateTransactionsLogPayload[] = [];
results.forEach((result) => {
if (result.status === 'fulfilled') {
const value = result.value;
const { requestKey, status } = value;

if (status === 'success' && 'data' in value) {
log.info(`Polling success for requestKey: ${requestKey}`);
displayTransactionResponse(value.data[requestKey], 2);
txLogsData.push({
requestKey,
status,
data: value.data[requestKey],
});
} else if (status === 'error' && 'error' in value) {
log.error(
`Polling error for requestKey: ${requestKey}, error:`,
value.error,
);
txLogsData.push({
requestKey,
status: 'failure',
});
} else if ('message' in value) {
log.info(
`Polling message for requestKey: ${requestKey}: ${value.message}`,
Expand All @@ -88,6 +103,7 @@ export async function pollRequests(
log.error(`Polling failed for a request, error:`, result.reason);
}
});
await updateTransactionStatus(txLogsData);
}

export const sendTransactionAction = async ({
Expand Down Expand Up @@ -194,6 +210,7 @@ export const createSendTransactionCommand: (
)
.join('\n\n'),
);
await saveTransactionsToFile(result.data.transactions);
}

const txPoll = await option.poll();
Expand Down
10 changes: 10 additions & 0 deletions packages/tools/kadena-cli/src/commands/tx/commands/txStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { log } from '../../../utils/logger.js';
import { createTable } from '../../../utils/table.js';
import type { INetworkCreateOptions } from '../../networks/utils/networkHelpers.js';
import { txOptions } from '../txOptions.js';
import { updateTransactionStatus } from '../utils/txHelpers.js';

export const getTxStatus = async ({
requestKey,
Expand Down Expand Up @@ -56,6 +57,15 @@ export const getTxStatus = async ({
};
}

// To update the log file with the transaction status when requestKey is found
await updateTransactionStatus([
{
requestKey,
status: result[trimmedRequestKey].result.status,
data: result[trimmedRequestKey],
},
]);

return {
status: 'success',
data: result[trimmedRequestKey],
Expand Down
2 changes: 2 additions & 0 deletions packages/tools/kadena-cli/src/commands/tx/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createTransactionCommandNew } from './commands/txCreateTransaction.js';
import { createTxHistoryCommand } from './commands/txHistory.js';
import { createTxListCommand } from './commands/txList.js';
import { createTxLocalCommand } from './commands/txLocal.js';
import { createSendTransactionCommand } from './commands/txSend.js';
Expand All @@ -22,4 +23,5 @@ export function txCommandFactory(program: Command, version: string): void {
createTxStatusCommand(txProgram, version);
createTxListCommand(txProgram, version);
createTxLocalCommand(txProgram, version);
createTxHistoryCommand(txProgram, version);
}
Loading

0 comments on commit e79c180

Please sign in to comment.