• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-client-gateway / 19897784682

03 Dec 2025 02:42PM UTC coverage: 90.193%. First build
19897784682

push

github

web-flow
feat: add endpoint to report false Blockaid scan results (COR-636) (#2815)

* feat: add endpoint to report false Blockaid scan results (COR-636)

- Add POST /v1/chains/:chainId/security/:safeAddress/report-false-result endpoint
- Support reporting FALSE_POSITIVE and FALSE_NEGATIVE scan results
- Use request_id from original scan response to identify the transaction
- Add reportTransaction method to BlockaidApi and ThreatAnalysisService
- Handle errors gracefully with logging and success: false response
- Add chain ID to Blockaid chain name mapping (for future use)
- Add comprehensive unit tests

* feat: add request_id to threat analysis response

- Extract request_id from x-request-id header in Blockaid API response
- Extend TransactionScanResponse with request_id field
- Include request_id in ThreatAnalysisResponse for reporting false positives

* refactor: address PR review comments for COR-636

- Refactor ReportEvent to follow ContractStatus pattern with const array
- Add comprehensive documentation for enum types and DTOs
- Explain 1000 char limit is CGW-imposed safeguard
- Remove underscore prefixes from controller params
- Add comment explaining params are for URL validation

* refactor: update request_id handling and logging in SafeShield and ThreatAnalysis services

- Change request_id type from string | null to string | undefined in various services and DTOs.
- Update logging messages to use 'warn' instead of 'error' for reporting failures in SafeShieldService.
- Adjust unit tests to reflect changes in request_id handling and logging behavior.
- Ensure consistency in request_id usage across the threat analysis module.

* test: update mock response in ThreatAnalysisService tests to handle undefined request_id

- Modify the mock response in the ThreatAnalysisService unit tests to set request_id as undefined instead of null, ensuring consistency with recent refactoring changes in request_id handling.

* refactor: update reportTransaction method ... (continued)

2852 of 3545 branches covered (80.45%)

Branch coverage included in aggregate %.

30 of 32 new or added lines in 8 files covered. (93.75%)

13785 of 14901 relevant lines covered (92.51%)

634.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/src/modules/safe-shield/safe-shield.controller.ts
1
import {
108✔
2
  Body,
3
  Controller,
4
  Get,
5
  HttpCode,
6
  HttpStatus,
7
  Param,
8
  Post,
9
} from '@nestjs/common';
10
import {
108✔
11
  ApiOkResponse,
12
  ApiTags,
13
  ApiOperation,
14
  ApiParam,
15
  ApiBody,
16
} from '@nestjs/swagger';
17
import { ValidationPipe } from '@/validation/pipes/validation.pipe';
108✔
18
import { AddressSchema } from '@/validation/entities/schemas/address.schema';
108✔
19
import type { Address } from 'viem';
20
import { SafeShieldService } from './safe-shield.service';
108✔
21
import { SingleRecipientAnalysisDto } from './entities/dtos/single-recipient-analysis.dto';
108✔
22
import {
108✔
23
  CounterpartyAnalysisRequestSchema,
24
  ThreatAnalysisRequestSchema,
25
} from './entities/analysis-requests.entity';
26
import { CounterpartyAnalysisRequestDto } from '@/modules/safe-shield/entities/dtos/counterparty-analysis-request.dto';
108✔
27
import { CounterpartyAnalysisDto } from '@/modules/safe-shield/entities/dtos/counterparty-analysis.dto';
108✔
28
import { ThreatAnalysisResponseDto } from '@/modules/safe-shield/entities/dtos/threat-analysis.dto';
108✔
29
import { ThreatAnalysisRequestDto } from '@/modules/safe-shield/entities/dtos/threat-analysis-request.dto';
108✔
30
import { NumericStringSchema } from '@/validation/entities/schemas/numeric-string.schema';
108✔
31
import {
108✔
32
  ReportFalseResultRequestDto,
33
  ReportFalseResultRequestSchema,
34
  ReportFalseResultResponseDto,
35
} from '@/modules/safe-shield/entities/dtos/report-false-result.dto';
36

37
/**
38
 * Controller for Safe Shield security analysis endpoints.
39
 *
40
 * Provides real-time security analysis for transactions, including recipients and contracts
41
 * to help users make informed decisions about their transactions.
42
 */
43
@ApiTags('safe-shield')
44
@Controller({
45
  path: '',
46
  version: '1',
47
})
48
export class SafeShieldController {
108✔
49
  constructor(private readonly safeShieldService: SafeShieldService) {}
2,038✔
50

51
  @ApiOperation({
52
    summary: 'Analyze recipient address',
53
    description:
54
      'Performs real-time security analysis of a recipient address for a specific Safe. ' +
55
      'Returns analysis results grouped by status group, sorted by severity (CRITICAL first).',
56
  })
57
  @ApiParam({
58
    name: 'chainId',
59
    type: 'string',
60
    description: 'Chain ID where the Safe is deployed',
61
    example: '1',
62
  })
63
  @ApiParam({
64
    name: 'safeAddress',
65
    type: 'string',
66
    description: 'Safe contract address',
67
  })
68
  @ApiParam({
69
    name: 'recipientAddress',
70
    type: 'string',
71
    description: 'Recipient address to analyze',
72
  })
73
  @ApiOkResponse({
74
    description: 'Recipient interaction analysis results',
75
    type: SingleRecipientAnalysisDto,
76
  })
77
  @HttpCode(HttpStatus.OK)
78
  @Get('chains/:chainId/security/:safeAddress/recipient/:recipientAddress')
79
  public async analyzeRecipient(
108✔
80
    @Param('chainId', new ValidationPipe(NumericStringSchema)) chainId: string,
81
    @Param('safeAddress', new ValidationPipe(AddressSchema))
82
    safeAddress: Address,
83
    @Param('recipientAddress', new ValidationPipe(AddressSchema))
84
    recipientAddress: Address,
85
  ): Promise<SingleRecipientAnalysisDto> {
86
    return this.safeShieldService.analyzeRecipient(
4✔
87
      chainId,
88
      safeAddress,
89
      recipientAddress,
90
    );
91
  }
92

93
  @ApiOperation({
94
    summary: 'Analyze transaction counterparties',
95
    description:
96
      'Performs combined contract and recipient analysis for a Safe transaction. ' +
97
      'Returns both analyses grouped by status group for each counterparty.',
98
  })
99
  @ApiParam({
100
    name: 'chainId',
101
    type: 'string',
102
    description: 'Chain ID where the Safe is deployed',
103
    example: '1',
104
  })
105
  @ApiParam({
106
    name: 'safeAddress',
107
    type: 'string',
108
    description: 'Safe contract address',
109
  })
110
  @ApiBody({
111
    type: CounterpartyAnalysisRequestDto,
112
    required: true,
113
    description:
114
      'Transaction data used to analyze all counterparties involved.',
115
  })
116
  @ApiOkResponse({
117
    type: CounterpartyAnalysisDto,
118
    description:
119
      'Combined counterparty analysis including recipients and contracts grouped by status group and mapped to an address.',
120
  })
121
  @HttpCode(HttpStatus.OK)
122
  @Post('chains/:chainId/security/:safeAddress/counterparty-analysis')
123
  public async analyzeCounterparty(
108✔
124
    @Param('chainId', new ValidationPipe(NumericStringSchema)) chainId: string,
125
    @Param('safeAddress', new ValidationPipe(AddressSchema))
126
    safeAddress: Address,
127
    @Body(new ValidationPipe(CounterpartyAnalysisRequestSchema))
128
    txData: CounterpartyAnalysisRequestDto,
129
  ): Promise<CounterpartyAnalysisDto> {
130
    return this.safeShieldService.analyzeCounterparty({
10✔
131
      chainId,
132
      safeAddress,
133
      tx: txData,
134
    });
135
  }
136

137
  @ApiOperation({
138
    summary: 'Analyze transaction threat',
139
    description: 'Performs real-time threat analysis for a Safe transaction.',
140
  })
141
  @ApiParam({
142
    name: 'chainId',
143
    type: 'string',
144
    description: 'Chain ID where the Safe is deployed',
145
  })
146
  @ApiParam({
147
    name: 'safeAddress',
148
    type: 'string',
149
    description: 'Safe contract address',
150
  })
151
  @ApiBody({
152
    type: ThreatAnalysisRequestDto,
153
    required: true,
154
    description:
155
      'EIP-712 typed data and wallet information for threat analysis.',
156
  })
157
  @ApiOkResponse({
158
    type: ThreatAnalysisResponseDto,
159
    description:
160
      'Threat analysis results including threat findings and balance changes.',
161
  })
162
  @HttpCode(HttpStatus.OK)
163
  @Post('chains/:chainId/security/:safeAddress/threat-analysis')
164
  public async analyzeThreat(
108✔
165
    @Param('chainId', new ValidationPipe(NumericStringSchema)) chainId: string,
166
    @Param('safeAddress', new ValidationPipe(AddressSchema))
167
    safeAddress: Address,
168
    @Body(new ValidationPipe(ThreatAnalysisRequestSchema))
169
    request: ThreatAnalysisRequestDto,
170
  ): Promise<ThreatAnalysisResponseDto> {
171
    return this.safeShieldService.analyzeThreats({
10✔
172
      chainId,
173
      safeAddress,
174
      request,
175
    });
176
  }
177

178
  @ApiOperation({
179
    summary: 'Report false Blockaid scan result',
180
    description:
181
      'Reports a FALSE_POSITIVE or FALSE_NEGATIVE Blockaid transaction scan result for review. ' +
182
      'Use the request_id from the original scan response to identify the transaction.',
183
  })
184
  @ApiParam({
185
    name: 'chainId',
186
    type: 'string',
187
    description: 'Chain ID where the Safe is deployed',
188
    example: '1',
189
  })
190
  @ApiParam({
191
    name: 'safeAddress',
192
    type: 'string',
193
    description: 'Safe contract address',
194
  })
195
  @ApiBody({
196
    type: ReportFalseResultRequestDto,
197
    required: true,
198
    description:
199
      'Report details including event type, request_id from scan response, and details.',
200
  })
201
  @ApiOkResponse({
202
    type: ReportFalseResultResponseDto,
203
    description: 'Report submitted successfully.',
204
  })
205
  @HttpCode(HttpStatus.OK)
206
  @Post('chains/:chainId/security/:safeAddress/report-false-result')
207
  public async reportFalseResult(
108✔
208
    @Param('chainId', new ValidationPipe(NumericStringSchema))
209
    chainId: string,
210
    @Param('safeAddress', new ValidationPipe(AddressSchema))
211
    safeAddress: Address,
212
    @Body(new ValidationPipe(ReportFalseResultRequestSchema))
213
    request: ReportFalseResultRequestDto,
214
  ): Promise<ReportFalseResultResponseDto> {
215
    // chainId and safeAddress are validated for URL consistency
216
    // but not used in the report as Blockaid uses request_id
NEW
217
    return this.safeShieldService.reportFalseResult({
×
218
      request,
219
    });
220
  }
221
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc