-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tx-logs): add tx history command (#2222)
* 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
1 parent
a4c8ee0
commit e79c180
Showing
8 changed files
with
355 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
159
packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.