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

safe-global / safe-config-service / 3986954599

pending completion
3986954599

push

github

GitHub
Bump types-requests from 2.28.11.7 to 2.28.11.8

940 of 942 relevant lines covered (99.79%)

1.0 hits per line

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

98.84
/src/chains/models.py
1
import os
1✔
2
import re
1✔
3
from typing import IO, Union
1✔
4
from urllib.parse import urlparse
1✔
5

6
from django.core.exceptions import ValidationError
1✔
7
from django.core.files.images import get_image_dimensions
1✔
8
from django.core.validators import RegexValidator
1✔
9
from django.db import models
1✔
10
from django.db.models import QuerySet
1✔
11
from gnosis.eth.django.models import EthereumAddressField, Uint256Field
1✔
12

13
HEX_ARGB_REGEX = re.compile("^#[0-9a-fA-F]{6}$")
1✔
14

15
color_validator = RegexValidator(HEX_ARGB_REGEX, "Invalid hex color", "invalid")
1✔
16

17
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
18
SEM_VER_REGEX = re.compile(
1✔
19
    r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"  # noqa E501
20
)
21

22
sem_ver_validator = RegexValidator(SEM_VER_REGEX, "Invalid version (semver)", "invalid")
1✔
23

24

25
def native_currency_path(instance: "Chain", filename: str) -> str:
1✔
26
    _, file_extension = os.path.splitext(filename)  # file_extension includes the dot
1✔
27
    return f"chains/{instance.id}/currency_logo{file_extension}"
1✔
28

29

30
def validate_native_currency_size(image: Union[str, IO[bytes]]) -> None:
1✔
31
    image_width, image_height = get_image_dimensions(
1✔
32
        image
33
    )  # (Optional[Int], Optional[Int])
34
    if not image_width or not image_height:
1✔
35
        raise ValidationError(
×
36
            f"Could not get image dimensions. Width={image_width}, Height={image_height}"
37
        )
38
    if image_width > 512 or image_height > 512:
1✔
39
        raise ValidationError("Image width and height need to be at most 512 pixels")
1✔
40

41

42
def validate_tx_service_url(url: str) -> None:
1✔
43
    result = urlparse(url)
1✔
44
    if not all(
1✔
45
        (
46
            result.scheme
47
            in (
48
                "http",
49
                "https",
50
            ),
51
            result.netloc,
52
        )
53
    ):
54
        raise ValidationError(f"{url} is not a valid url")
1✔
55

56

57
class Chain(models.Model):
1✔
58
    class RpcAuthentication(models.TextChoices):
1✔
59
        API_KEY_PATH = "API_KEY_PATH"
1✔
60
        NO_AUTHENTICATION = "NO_AUTHENTICATION"
1✔
61

62
    id = models.PositiveBigIntegerField(verbose_name="Chain Id", primary_key=True)
1✔
63
    relevance = models.SmallIntegerField(
1✔
64
        default=100
65
    )  # A lower number will indicate more relevance
66
    name = models.CharField(verbose_name="Chain name", max_length=255)
1✔
67
    short_name = models.CharField(
1✔
68
        verbose_name="EIP-3770 short name", max_length=255, unique=True
69
    )
70
    description = models.CharField(max_length=255, blank=True)
1✔
71
    l2 = models.BooleanField()
1✔
72
    rpc_authentication = models.CharField(
1✔
73
        max_length=255, choices=RpcAuthentication.choices
74
    )
75
    rpc_uri = models.URLField()
1✔
76
    safe_apps_rpc_authentication = models.CharField(
1✔
77
        max_length=255,
78
        choices=RpcAuthentication.choices,
79
        default=RpcAuthentication.NO_AUTHENTICATION,
80
    )
81
    safe_apps_rpc_uri = models.URLField(default="")
1✔
82
    public_rpc_authentication = models.CharField(
1✔
83
        max_length=255,
84
        choices=RpcAuthentication.choices,
85
        default=RpcAuthentication.NO_AUTHENTICATION,
86
    )
87
    public_rpc_uri = models.URLField()
1✔
88
    block_explorer_uri_address_template = models.URLField()
1✔
89
    block_explorer_uri_tx_hash_template = models.URLField()
1✔
90
    block_explorer_uri_api_template = models.URLField()
1✔
91
    currency_name = models.CharField(max_length=255)
1✔
92
    currency_symbol = models.CharField(max_length=255)
1✔
93
    currency_decimals = models.IntegerField(default=18)
1✔
94
    currency_logo_uri = models.ImageField(
1✔
95
        validators=[validate_native_currency_size],
96
        upload_to=native_currency_path,
97
        max_length=255,
98
    )
99
    transaction_service_uri = models.CharField(
1✔
100
        max_length=255, validators=[validate_tx_service_url]
101
    )
102
    vpc_transaction_service_uri = models.CharField(
1✔
103
        max_length=255, validators=[validate_tx_service_url]
104
    )
105
    theme_text_color = models.CharField(
1✔
106
        validators=[color_validator],
107
        max_length=9,
108
        default="#ffffff",
109
        help_text="Please use the following format: <em>#RRGGBB</em>.",
110
    )
111
    theme_background_color = models.CharField(
1✔
112
        validators=[color_validator],
113
        max_length=9,
114
        default="#000000",
115
        help_text="Please use the following format: <em>#RRGGBB</em>.",
116
    )
117
    ens_registry_address = EthereumAddressField(null=True, blank=True)  # type: ignore[no-untyped-call]
1✔
118

119
    recommended_master_copy_version = models.CharField(
1✔
120
        max_length=255, validators=[sem_ver_validator]
121
    )
122

123
    def get_disabled_wallets(self) -> QuerySet["Wallet"]:
1✔
124
        all_wallets = Wallet.objects.all()
1✔
125
        enabled_wallets = self.wallet_set.all()
1✔
126

127
        return all_wallets.difference(enabled_wallets)
1✔
128

129
    def __str__(self) -> str:
1✔
130
        return f"{self.name} | chain_id={self.id}"
1✔
131

132

133
class GasPrice(models.Model):
1✔
134
    chain = models.ForeignKey(Chain, on_delete=models.CASCADE)
1✔
135
    oracle_uri = models.URLField(blank=True, null=True)
1✔
136
    oracle_parameter = models.CharField(blank=True, null=True, max_length=255)
1✔
137
    gwei_factor = models.DecimalField(
1✔
138
        default=1,
139
        max_digits=19,
140
        decimal_places=9,
141
        verbose_name="Gwei multiplier factor",
142
        help_text="Factor required to reach the Gwei unit",
143
    )
144
    fixed_wei_value = Uint256Field(
1✔
145
        verbose_name="Fixed gas price (wei)", blank=True, null=True
146
    )  # type: ignore[no-untyped-call]
147
    rank = models.SmallIntegerField(
1✔
148
        default=100
149
    )  # A lower number will indicate higher ranking
150

151
    def __str__(self) -> str:
1✔
152
        return f"Chain = {self.chain.id} | uri={self.oracle_uri} | fixed_wei_value={self.fixed_wei_value}"
1✔
153

154
    def clean(self) -> None:
1✔
155
        if (self.fixed_wei_value is not None) == (self.oracle_uri is not None):
1✔
156
            raise ValidationError(
1✔
157
                {
158
                    "oracle_uri": "An oracle uri or fixed gas price should be provided (but not both)",
159
                    "fixed_wei_value": "An oracle uri or fixed gas price should be provided (but not both)",
160
                }
161
            )
162
        if self.oracle_uri is not None and self.oracle_parameter is None:
1✔
163
            raise ValidationError(
1✔
164
                {"oracle_parameter": "The oracle parameter should be set"}
165
            )
166

167

168
class Wallet(models.Model):
1✔
169
    # A wallet can be part of multiple Chains and a Chain can have multiple Wallets
170
    chains = models.ManyToManyField(
1✔
171
        Chain, blank=True, help_text="Chains where this wallet is enabled."
172
    )
173
    key = models.CharField(
1✔
174
        unique=True,
175
        max_length=255,
176
        help_text="The unique name/key that identifies this wallet",
177
    )
178

179
    def __str__(self) -> str:
1✔
180
        return f"Wallet: {self.key}"
1✔
181

182

183
class Feature(models.Model):
1✔
184
    # A feature can be enabled for multiple Chains and a Chain can have multiple features enabled
185
    chains = models.ManyToManyField(
1✔
186
        Chain, blank=True, help_text="Chains where this feature is enabled."
187
    )
188
    key = models.CharField(
1✔
189
        unique=True,
190
        max_length=255,
191
        help_text="The unique name/key that identifies this feature",
192
    )
193

194
    def __str__(self) -> str:
1✔
195
        return f"Chain Feature: {self.key}"
1✔
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