diff --git a/src/routes/transactions/__tests__/controllers/get-transaction-by-id.transactions.controller.spec.ts b/src/routes/transactions/__tests__/controllers/get-transaction-by-id.transactions.controller.spec.ts index f7442fad17..f02b966166 100644 --- a/src/routes/transactions/__tests__/controllers/get-transaction-by-id.transactions.controller.spec.ts +++ b/src/routes/transactions/__tests__/controllers/get-transaction-by-id.transactions.controller.spec.ts @@ -271,6 +271,7 @@ describe('Get by id - Transactions Controller (Unit)', () => { }, txHash: moduleTransaction.transactionHash, safeAppInfo: null, + note: null, }); }); }); @@ -371,6 +372,7 @@ describe('Get by id - Transactions Controller (Unit)', () => { detailedExecutionInfo: null, txHash: transfer.transactionHash, safeAppInfo: null, + note: null, }); }); }); diff --git a/src/routes/transactions/entities/transaction-details/transaction-details.entity.ts b/src/routes/transactions/entities/transaction-details/transaction-details.entity.ts index b6842f6e24..4dbd81fc34 100644 --- a/src/routes/transactions/entities/transaction-details/transaction-details.entity.ts +++ b/src/routes/transactions/entities/transaction-details/transaction-details.entity.ts @@ -46,4 +46,6 @@ export class TransactionDetails { txHash!: string | null; @ApiPropertyOptional({ type: SafeAppInfo, nullable: true }) safeAppInfo!: SafeAppInfo | null; + @ApiPropertyOptional({ type: String, nullable: true }) + note!: string | null; } diff --git a/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.spec.ts b/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.spec.ts index 06a701c8fe..944ad279bf 100644 --- a/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.spec.ts +++ b/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.spec.ts @@ -80,6 +80,7 @@ describe('ModuleTransactionDetails mapper (Unit)', () => { txHash: transaction.transactionHash, detailedExecutionInfo: new ModuleExecutionDetails(addressInfo), safeAppInfo: null, + note: null, }); }); @@ -127,6 +128,7 @@ describe('ModuleTransactionDetails mapper (Unit)', () => { txHash: transaction.transactionHash, detailedExecutionInfo: new ModuleExecutionDetails(addressInfo), safeAppInfo: null, + note: null, }); }); }); diff --git a/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.ts b/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.ts index 2c0f9a0dcf..546288fdb2 100644 --- a/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.ts +++ b/src/routes/transactions/mappers/module-transactions/module-transaction-details.mapper.ts @@ -44,6 +44,7 @@ export class ModuleTransactionDetailsMapper { txHash: transaction.transactionHash, detailedExecutionInfo: new ModuleExecutionDetails(moduleAddress), safeAppInfo: null, + note: null, }; } diff --git a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.spec.ts b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.spec.ts index 0d2a35d4c0..778db733c0 100644 --- a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.spec.ts +++ b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.spec.ts @@ -39,6 +39,10 @@ const multisigExecutionDetailsMapper = jest.mocked({ mapMultisigExecutionDetails: jest.fn(), } as jest.MockedObjectDeep); +const multisigTransactionNoteMapper = jest.mocked({ + mapTxNote: jest.fn(), +}); + describe('MultisigTransactionDetails mapper (Unit)', () => { let mapper: MultisigTransactionDetailsMapper; @@ -51,6 +55,7 @@ describe('MultisigTransactionDetails mapper (Unit)', () => { transactionDataMapper, safeAppInfoMapper, multisigExecutionDetailsMapper, + multisigTransactionNoteMapper, ); }); diff --git a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.ts b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.ts index 034ed1d108..595e8c2b55 100644 --- a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.ts +++ b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-details.mapper.ts @@ -15,6 +15,7 @@ import { TransactionDataMapper } from '@/routes/transactions/mappers/common/tran import { MultisigTransactionInfoMapper } from '@/routes/transactions/mappers/common/transaction-info.mapper'; import { MultisigTransactionExecutionDetailsMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-execution-details.mapper'; import { MultisigTransactionStatusMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-status.mapper'; +import { MultisigTransactionNoteMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper'; @Injectable() export class MultisigTransactionDetailsMapper { @@ -25,6 +26,7 @@ export class MultisigTransactionDetailsMapper { private readonly transactionDataMapper: TransactionDataMapper, private readonly safeAppInfoMapper: SafeAppInfoMapper, private readonly multisigTransactionExecutionDetailsMapper: MultisigTransactionExecutionDetailsMapper, + private readonly noteMapper: MultisigTransactionNoteMapper, ) {} async mapDetails( @@ -33,6 +35,7 @@ export class MultisigTransactionDetailsMapper { safe: Safe, ): Promise { const txStatus = this.statusMapper.mapTransactionStatus(transaction, safe); + const note = this.noteMapper.mapTxNote(transaction); const [ isTrustedDelegateCall, addressInfoIndex, @@ -79,6 +82,7 @@ export class MultisigTransactionDetailsMapper { txHash: transaction.transactionHash, detailedExecutionInfo, safeAppInfo, + note, }; } diff --git a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.spec.ts b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.spec.ts new file mode 100644 index 0000000000..7343a4de0f --- /dev/null +++ b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.spec.ts @@ -0,0 +1,54 @@ +import { faker } from '@faker-js/faker'; +import { multisigTransactionBuilder } from '@/domain/safe/entities/__tests__/multisig-transaction.builder'; +import { MultisigTransactionNoteMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper'; + +const mapper = new MultisigTransactionNoteMapper(); + +describe('Multisig Transaction note mapper (Unit)', () => { + it('should parse transaction `origin` and return a note', () => { + const noteText = faker.lorem.sentence(); + const transaction = multisigTransactionBuilder() + .with( + 'origin', + JSON.stringify({ name: JSON.stringify({ note: noteText }) }), + ) + .build(); + + const note = mapper.mapTxNote(transaction); + + expect(note).toBe(noteText); + }); + + it('should return undefined if `origin` is not a valid JSON', () => { + const transaction = multisigTransactionBuilder() + .with('origin', 'invalid-json') + .build(); + + const note = mapper.mapTxNote(transaction); + + expect(note).toBeNull(); + }); + + it('should return undefined if `origin` does not contain a note', () => { + const transaction = multisigTransactionBuilder() + .with( + 'origin', + JSON.stringify({ name: faker.word.noun(), url: faker.internet.url() }), + ) + .build(); + + const note = mapper.mapTxNote(transaction); + + expect(note).toBeNull(); + }); + + it('should return undefined if `origin` is null', () => { + const transaction = multisigTransactionBuilder() + .with('origin', null) + .build(); + + const note = mapper.mapTxNote(transaction); + + expect(note).toBeNull(); + }); +}); diff --git a/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.ts b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.ts new file mode 100644 index 0000000000..bdc1216f24 --- /dev/null +++ b/src/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { MultisigTransaction } from '@/domain/safe/entities/multisig-transaction.entity'; + +@Injectable() +export class MultisigTransactionNoteMapper { + mapTxNote(transaction: MultisigTransaction): string | null { + if (transaction.origin) { + try { + const origin = JSON.parse(transaction.origin); + const parsedName = origin.name && JSON.parse(String(origin.name)); + if (typeof parsedName.note === 'string') { + return parsedName.note; + } + } catch { + // Ignore, no note + } + } + return null; + } +} diff --git a/src/routes/transactions/mappers/transfers/transfer-details.mapper.spec.ts b/src/routes/transactions/mappers/transfers/transfer-details.mapper.spec.ts index 0e2a17ee82..73d33186b8 100644 --- a/src/routes/transactions/mappers/transfers/transfer-details.mapper.spec.ts +++ b/src/routes/transactions/mappers/transfers/transfer-details.mapper.spec.ts @@ -36,6 +36,7 @@ describe('TransferDetails mapper (Unit)', () => { detailedExecutionInfo: null, txHash: transfer.transactionHash, safeAppInfo: null, + note: null, }); }); }); diff --git a/src/routes/transactions/mappers/transfers/transfer-details.mapper.ts b/src/routes/transactions/mappers/transfers/transfer-details.mapper.ts index 16526978b6..07c819528a 100644 --- a/src/routes/transactions/mappers/transfers/transfer-details.mapper.ts +++ b/src/routes/transactions/mappers/transfers/transfer-details.mapper.ts @@ -32,6 +32,7 @@ export class TransferDetailsMapper { detailedExecutionInfo: null, txHash: transfer.transactionHash, safeAppInfo: null, + note: null, }; } } diff --git a/src/routes/transactions/transactions.module.ts b/src/routes/transactions/transactions.module.ts index c99ec524d7..187dde3b0a 100644 --- a/src/routes/transactions/transactions.module.ts +++ b/src/routes/transactions/transactions.module.ts @@ -35,6 +35,7 @@ import { MultisigTransactionDetailsMapper } from '@/routes/transactions/mappers/ import { MultisigTransactionExecutionDetailsMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-execution-details.mapper'; import { MultisigTransactionExecutionInfoMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-execution-info.mapper'; import { MultisigTransactionStatusMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-status.mapper'; +import { MultisigTransactionNoteMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper'; import { MultisigTransactionMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction.mapper'; import { QueuedItemsMapper } from '@/routes/transactions/mappers/queued-items/queued-items.mapper'; import { TransactionPreviewMapper } from '@/routes/transactions/mappers/transaction-preview.mapper'; @@ -86,6 +87,7 @@ import { Module } from '@nestjs/common'; MultisigTransactionInfoMapper, MultisigTransactionMapper, MultisigTransactionStatusMapper, + MultisigTransactionNoteMapper, NativeCoinTransferMapper, NativeStakingMapper, QueuedItemsMapper,