Repository: safe-global/safe-config-service

Issue:
Add `publicRpcUrl` field in the Chain model
When the user connects to a chain for the first time and don't have it configured in their wallet, we offer them to add this chain automatically to the wallet. This is atm supported only by MetaMask.

For this config, we'll need a public RPC URL.

## Requirement
* Add a string field `publicRpcUrl` for Chains
* Populate existing chain configs with recommended public RPCs from the docs


Discussion:
After some internal discussion, I suggest the following proposal:

```javascript
GET /api/v2/chains

[
  {
    "chainId": <bigint>,
    "rpcs": [
        "type": <string>, // PUBLIC | SAFE_APPS | CLIENT
        "authentication": <string>, // API_KEY_PATH | NO_AUTHENTICATION
        "value": <string> // rpc url
    ]
  }
]
```

@gnosis/safe-web  @gnosis/safe-services 

Resolving patch:
diff --git a/src/chains/migrations/0034_add_public_rpc_url.py b/src/chains/migrations/0034_add_public_rpc_url.py
new file mode 100644
--- /dev/null
+++ b/src/chains/migrations/0034_add_public_rpc_url.py
@@ -0,0 +1,48 @@
+# Generated by Django 3.2.9 on 2021-11-30 14:03
+from django.apps.registry import Apps
+from django.db import migrations, models
+from django.db.backends.base.schema import BaseDatabaseSchemaEditor
+from django.db.models import F
+
+
+def copy_rpc_fields(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
+    chain_model = apps.get_model("chains", "Chain")
+    db_alias = schema_editor.connection.alias
+    chain_model.objects.using(db_alias).all().update(
+        public_rpc_uri=F("rpc_uri"),
+        public_rpc_authentication=F("rpc_authentication"),
+    )
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("chains", "0033_walletnew"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="chain",
+            name="public_rpc_authentication",
+            field=models.CharField(
+                choices=[
+                    ("API_KEY_PATH", "Api Key Path"),
+                    ("NO_AUTHENTICATION", "No Authentication"),
+                ],
+                default="NO_AUTHENTICATION",
+                max_length=255,
+            ),
+        ),
+        migrations.AddField(
+            model_name="chain",
+            name="public_rpc_uri",
+            field=models.URLField(null=True),
+            preserve_default=False,
+        ),
+        migrations.RunPython(copy_rpc_fields, migrations.RunPython.noop),
+        migrations.AlterField(
+            model_name="chain",
+            name="public_rpc_uri",
+            field=models.URLField(null=False, blank=False),
+            preserve_default=False,
+        ),
+    ]
diff --git a/src/chains/models.py b/src/chains/models.py
--- a/src/chains/models.py
+++ b/src/chains/models.py
@@ -49,6 +49,12 @@ class RpcAuthentication(models.TextChoices):
         default=RpcAuthentication.NO_AUTHENTICATION,
     )
     safe_apps_rpc_uri = models.URLField(default="")
+    public_rpc_authentication = models.CharField(
+        max_length=255,
+        choices=RpcAuthentication.choices,
+        default=RpcAuthentication.NO_AUTHENTICATION,
+    )
+    public_rpc_uri = models.URLField()
     block_explorer_uri_address_template = models.URLField()
     block_explorer_uri_tx_hash_template = models.URLField()
     block_explorer_uri_api_template = models.URLField()
diff --git a/src/chains/serializers.py b/src/chains/serializers.py
--- a/src/chains/serializers.py
+++ b/src/chains/serializers.py
@@ -74,6 +74,14 @@ def get_rpc_value(self, obj: Chain) -> str:
         return obj.safe_apps_rpc_uri
 
 
+class PublicRpcUriSerializer(BaseRpcUriSerializer):
+    def get_authentication(self, obj: Chain) -> str:
+        return obj.public_rpc_authentication
+
+    def get_rpc_value(self, obj: Chain) -> str:
+        return obj.public_rpc_uri
+
+
 class BlockExplorerUriTemplateSerializer(serializers.Serializer[Chain]):
     address = serializers.URLField(source="block_explorer_uri_address_template")
     tx_hash = serializers.URLField(source="block_explorer_uri_tx_hash_template")
@@ -98,6 +106,7 @@ class ChainSerializer(serializers.ModelSerializer[Chain]):
     short_name = serializers.CharField()
     rpc_uri = serializers.SerializerMethodField()
     safe_apps_rpc_uri = serializers.SerializerMethodField()
+    public_rpc_uri = serializers.SerializerMethodField()
     block_explorer_uri_template = serializers.SerializerMethodField()
     native_currency = serializers.SerializerMethodField()
     transaction_service = serializers.URLField(
@@ -120,6 +129,7 @@ class Meta:
             "l2",
             "rpc_uri",
             "safe_apps_rpc_uri",
+            "public_rpc_uri",
             "block_explorer_uri_template",
             "native_currency",
             "transaction_service",
@@ -152,6 +162,11 @@ def get_safe_apps_rpc_uri(obj: Chain) -> ReturnDict:
     def get_rpc_uri(obj: Chain) -> ReturnDict:
         return RpcUriSerializer(obj).data
 
+    @staticmethod
+    @swagger_serializer_method(serializer_or_field=BaseRpcUriSerializer)  # type: ignore[misc]
+    def get_public_rpc_uri(obj: Chain) -> ReturnDict:
+        return PublicRpcUriSerializer(obj).data
+
     @staticmethod
     @swagger_serializer_method(serializer_or_field=BlockExplorerUriTemplateSerializer)  # type: ignore[misc]
     def get_block_explorer_uri_template(obj: Chain) -> ReturnDict:


Test patch:
diff --git a/src/chains/migrations/tests/test_migration_0034.py b/src/chains/migrations/tests/test_migration_0034.py
new file mode 100644
--- /dev/null
+++ b/src/chains/migrations/tests/test_migration_0034.py
@@ -0,0 +1,41 @@
+from django.db.migrations.state import StateApps
+
+from chains.migrations.tests.utils import TestMigrations
+
+
+class Migration0034TestCase(TestMigrations):
+    migrate_from = "0033_walletnew"
+    migrate_to = "0034_add_public_rpc_url"
+
+    def setUpBeforeMigration(self, apps: StateApps) -> None:
+        Chain = apps.get_model("chains", "Chain")
+        Chain.objects.create(
+            id=1,
+            name="Mainnet",
+            description="",
+            l2=False,
+            rpc_authentication="API_KEY_PATH",
+            rpc_uri="https://mainnet.infura.io/v3/",
+            safe_apps_rpc_authentication="API_KEY_PATH",
+            safe_apps_rpc_uri="https://mainnet.infura.io/v3/",
+            block_explorer_uri_address_template="https://etherscan.io/address/{{address}}",
+            block_explorer_uri_tx_hash_template="https://etherscan.io/tx/{{txHash}}",
+            currency_name="Ether",
+            currency_symbol="ETH",
+            currency_decimals=18,
+            currency_logo_uri="https://gnosis-safe-token-logos.s3.amazonaws.com/ethereum-eth-logo.png",
+            transaction_service_uri="http://mainnet-safe-transaction-web.safe.svc.cluster.local",
+            vpc_transaction_service_uri="",
+            theme_text_color="#001428",
+            theme_background_color="#E8E7E6",
+            ens_registry_address="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
+            recommended_master_copy_version="1.3.0",
+        )
+
+    def test_rpc_copied(self) -> None:
+        Chain = self.apps_registry.get_model("chains", "Chain")
+
+        chain = Chain.objects.get(id=1)
+
+        self.assertEqual(chain.rpc_uri, chain.public_rpc_uri)
+        self.assertEqual(chain.rpc_authentication, chain.public_rpc_authentication)
diff --git a/src/chains/tests/factories.py b/src/chains/tests/factories.py
--- a/src/chains/tests/factories.py
+++ b/src/chains/tests/factories.py
@@ -25,6 +25,10 @@ class Meta:
         lambda o: random.choice(list(Chain.RpcAuthentication))
     )
     safe_apps_rpc_uri = factory.Faker("url")
+    public_rpc_authentication = factory.lazy_attribute(
+        lambda o: random.choice(list(Chain.RpcAuthentication))
+    )
+    public_rpc_uri = factory.Faker("url")
     block_explorer_uri_address_template = factory.Faker("url")
     block_explorer_uri_tx_hash_template = factory.Faker("url")
     block_explorer_uri_api_template = factory.Faker("url")
diff --git a/src/chains/tests/test_views.py b/src/chains/tests/test_views.py
--- a/src/chains/tests/test_views.py
+++ b/src/chains/tests/test_views.py
@@ -47,6 +47,10 @@ def test_json_payload_format(self) -> None:
                         "authentication": chain.safe_apps_rpc_authentication,
                         "value": chain.safe_apps_rpc_uri,
                     },
+                    "publicRpcUri": {
+                        "authentication": chain.public_rpc_authentication,
+                        "value": chain.public_rpc_uri,
+                    },
                     "blockExplorerUriTemplate": {
                         "address": chain.block_explorer_uri_address_template,
                         "txHash": chain.block_explorer_uri_tx_hash_template,
@@ -158,6 +162,10 @@ def test_json_payload_format(self) -> None:
                 "authentication": chain.safe_apps_rpc_authentication,
                 "value": chain.safe_apps_rpc_uri,
             },
+            "publicRpcUri": {
+                "authentication": chain.public_rpc_authentication,
+                "value": chain.public_rpc_uri,
+            },
             "blockExplorerUriTemplate": {
                 "address": chain.block_explorer_uri_address_template,
                 "txHash": chain.block_explorer_uri_tx_hash_template,