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

safe-global / safe-client-gateway-nest / 5941127155

22 Aug 2023 03:52PM UTC coverage: 92.245%. First build
5941127155

Pull #631

github

GitHub
Merge daff89e0f into c8aa71879
Pull Request #631:

966 of 1156 branches covered (83.56%)

Branch coverage included in aggregate %.

127 of 144 new or added lines in 22 files covered. (88.19%)

4494 of 4763 relevant lines covered (94.35%)

72.34 hits per line

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

88.41
/src/routes/transactions/mappers/common/human-description.mapper.ts
1
import { Hex } from 'viem/src/types/misc';
2
import { Inject, Injectable } from '@nestjs/common';
33✔
3
import { decodeFunctionData, formatUnits, isHex } from 'viem';
33✔
4
import { ITokenRepository } from '../../../../domain/tokens/token.repository.interface';
33✔
5
import { TokenRepository } from '../../../../domain/tokens/token.repository';
33✔
6
import { Token } from '../../../../domain/tokens/entities/token.entity';
7
import { MAX_UINT256 } from '../../../../routes/transactions/constants';
33✔
8
import {
33✔
9
  ILoggingService,
10
  LoggingService,
11
} from '../../../../logging/logging.interface';
12
import { SafeAppInfo } from '../../../transactions/entities/safe-app-info.entity';
13
import { IHumanDescriptionRepository } from '../../../../domain/human-description/human-description.repository.interface';
33✔
14
import { HumanDescriptionRepository } from '../../../../domain/human-description/human-description.repository';
33✔
15
import {
33✔
16
  HumanDescriptionFragment,
17
  ValueType,
18
} from '../../../../domain/human-description/entities/human-description.entity';
19

20
@Injectable()
21
export class HumanDescriptionMapper {
33✔
22
  constructor(
23
    @Inject(ITokenRepository) private readonly tokenRepository: TokenRepository,
427✔
24
    @Inject(LoggingService) private readonly loggingService: ILoggingService,
427✔
25
    @Inject(IHumanDescriptionRepository)
26
    private readonly humanDescriptionRepository: HumanDescriptionRepository,
427✔
27
  ) {}
28

29
  async mapHumanDescription(
30
    to: string | undefined,
31
    data: string | null,
32
    chainId: string,
33
    safeAppInfo: SafeAppInfo | null,
34
  ): Promise<string | null> {
35
    if (!data || !isHex(data) || !to) return null;
46✔
36

37
    const parsedDescriptions =
38
      this.humanDescriptionRepository.getDescriptions();
42✔
39

40
    const dataStart = data.slice(0, 10);
42✔
41
    const sigHash = isHex(dataStart) ? dataStart : null;
42!
42

43
    if (!sigHash) return null;
42!
44

45
    const isHumanReadable = data.startsWith(sigHash);
42✔
46

47
    if (!isHumanReadable) return null;
42!
48

49
    let token: Token | null = null;
42✔
50
    try {
42✔
51
      token = await this.tokenRepository.getToken({ chainId, address: to });
42✔
52
    } catch (error) {
53
      this.loggingService.debug(`Error trying to get token: ${error.message}`);
30✔
54
    }
55

56
    try {
42✔
57
      const { abi, process } = parsedDescriptions[sigHash];
42✔
58

59
      const { args = [] } = decodeFunctionData({ abi, data });
6!
60

61
      const messageFragments = process(to, args);
5✔
62

63
      const message = this.createHumanDescription(messageFragments, token);
5✔
64

65
      return safeAppInfo ? `${message} via ${safeAppInfo.name}` : message;
5✔
66
    } catch (error) {
67
      this.loggingService.debug(
37✔
68
        `Error trying to decode the input data: ${error.message}`,
69
      );
70
      return null;
37✔
71
    }
72
  }
73

74
  createHumanDescription(
75
    descriptionFragments: HumanDescriptionFragment[],
76
    token: Token | null,
77
  ): string {
78
    return descriptionFragments
5✔
79
      .map((block) => {
80
        switch (block.type) {
18✔
81
          case ValueType.TokenValue:
82
            if (!token?.decimals) return block.value.amount;
5✔
83

84
            // Unlimited approval
85
            if (block.value.amount === MAX_UINT256) {
4✔
86
              return `unlimited ${token.symbol}`;
1✔
87
            }
88

89
            return `${formatUnits(block.value.amount, token.decimals)} ${
3✔
90
              token.symbol
91
            }`;
92
          case ValueType.Address:
93
            return this.shortenAddress(block.value);
4✔
94
          default:
95
            return block.value;
9✔
96
        }
97
      })
98
      .join(' ');
99
  }
100

101
  private shortenAddress(address: Hex, length = 4): string {
4✔
102
    if (address.length !== 42) {
4!
NEW
103
      throw Error('Invalid address');
×
104
    }
105

106
    const visibleCharactersLength = length * 2 + 2;
4✔
107

108
    if (address.length < visibleCharactersLength) {
4!
NEW
109
      return address;
×
110
    }
111

112
    return `${address.slice(0, length + 2)}...${address.slice(-length)}`;
4✔
113
  }
114
}
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

© 2025 Coveralls, Inc