From 4ddec7b50babf6a3125d7bdd5554a434375e1efc Mon Sep 17 00:00:00 2001 From: Ladislav Dubravsky Date: Fri, 30 Jul 2021 18:17:47 +0200 Subject: [PATCH 1/4] Remove unnecessary "NamebazaarDev" contract naming We drop the prefix for ENS contracts we don't modify in any way in dev setup deployments. Unmodified contracts are also preferred to modified ones (the "NamebazaarDev" versions) to keep dev setup as close to production as possible. --- .../2_namebazaar_migration.js | 30 +++++++++---------- .../contracts/src/NameBazaarDevContracts.sol | 12 ++------ src/name_bazaar/shared/smart_contracts.cljs | 6 ++-- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/resources/public/contracts-migration/2_namebazaar_migration.js b/resources/public/contracts-migration/2_namebazaar_migration.js index 6c11fc57..4265e048 100644 --- a/resources/public/contracts-migration/2_namebazaar_migration.js +++ b/resources/public/contracts-migration/2_namebazaar_migration.js @@ -96,7 +96,7 @@ const validateDeploymentConfig = (config = {}) => { const loadedArtifacts = loadArtifacts([ {kw: ':ens', name: 'ENSRegistry'}, - {kw: ':eth-registrar', name: 'NameBazaarDevRegistrar'}, + {kw: ':eth-registrar', name: 'NamebazaarDevRegistrar'}, {kw: ':offering-registry', name: 'OfferingRegistry'}, {kw: ':mutable-forwarder', name: 'MutableForwarder'}, {kw: ':buy-now-offering', name: 'BuyNowOffering'}, @@ -105,13 +105,13 @@ const loadedArtifacts = loadArtifacts([ {kw: ':auction-offering-factory', name: 'AuctionOfferingFactory'}, {kw: ':district0x-emails', name: 'District0xEmails'}, {kw: ':reverse-name-resolver', name: 'NamebazaarDevNameResolver'}, - {kw: ':public-resolver', name: 'NamebazaarDevPublicResolver'}, - {kw: ':reverse-registrar', name: 'NamebazaarDevReverseRegistrar'}, + {kw: ':public-resolver', name: 'PublicResolver'}, + {kw: ':reverse-registrar', name: 'ReverseRegistrar'}, ]) const { ENSRegistry, - NameBazaarDevRegistrar, + NamebazaarDevRegistrar, OfferingRegistry, MutableForwarder, BuyNowOffering, @@ -120,8 +120,8 @@ const { AuctionOfferingFactory, District0xEmails, NamebazaarDevNameResolver, - NamebazaarDevPublicResolver, - NamebazaarDevReverseRegistrar + PublicResolver, + ReverseRegistrar } = loadedArtifacts const emergencyMultisigPlaceholder = "DeEDdeeDDEeDDEEdDEedDEEdDEeDdEeDDEEDDeed".toLowerCase() @@ -142,19 +142,19 @@ module.exports = async function (deployer, network, accounts) { } if (network === 'development') { - const [ens, namebazaarDevNameResolver] = await parallel( + const [ens, nameResolver] = await parallel( deploy(ENSRegistry), deploy(NamebazaarDevNameResolver) ) - const [nameBazaarDevRegistrar, _, namebazaarDevReverseRegistrar] = await parallel( - deploy(NameBazaarDevRegistrar, ens.address, namehash('eth')), - deploy(NamebazaarDevPublicResolver, ens.address), - deploy(NamebazaarDevReverseRegistrar, ens.address, namebazaarDevNameResolver.address) + const [ethRegistrar, _, reverseRegistrar] = await parallel( + deploy(NamebazaarDevRegistrar, ens.address, namehash('eth')), + deploy(PublicResolver, ens.address), + deploy(ReverseRegistrar, ens.address, nameResolver.address) ) - await ens.setSubnodeOwner(namehash(''), ensLabel('eth'), nameBazaarDevRegistrar.address) + await ens.setSubnodeOwner(namehash(''), ensLabel('eth'), ethRegistrar.address) await ens.setSubnodeOwner(namehash(''), ensLabel('reverse'), accounts[0]) - await ens.setSubnodeOwner(namehash('reverse'), ensLabel('addr'), namebazaarDevReverseRegistrar.address) + await ens.setSubnodeOwner(namehash('reverse'), ensLabel('addr'), reverseRegistrar.address) } else { const config = deployer.networks[network].deploymentConfig validateDeploymentConfig(config) @@ -162,8 +162,8 @@ module.exports = async function (deployer, network, accounts) { skipDeploy(ENSRegistry, config.ensAddress) skipDeploy(NamebazaarDevNameResolver, "0x0000000000000000000000000000000000000000") // this address is not important skipDeploy(NameBazaarDevRegistrar, config.registrarAddress) - skipDeploy(NamebazaarDevPublicResolver, config.publicResolverAddress) - skipDeploy(NamebazaarDevReverseRegistrar, config.reverseRegistrarAddress) + skipDeploy(PublicResolver, config.publicResolverAddress) + skipDeploy(ReverseRegistrar, config.reverseRegistrarAddress) } const emergencyMultisigAccount = accounts[0] diff --git a/resources/public/contracts/src/NameBazaarDevContracts.sol b/resources/public/contracts/src/NameBazaarDevContracts.sol index 19884a2c..5178abea 100644 --- a/resources/public/contracts/src/NameBazaarDevContracts.sol +++ b/resources/public/contracts/src/NameBazaarDevContracts.sol @@ -8,11 +8,7 @@ import {BaseRegistrarImplementation} from "@ensdomains/ens-contracts/contracts/e import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol"; import {NameResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/NameResolver.sol"; import {PublicResolver} from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol"; -import {ReverseRegistrar, NameResolver as INameResolver} from "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol"; - -contract NamebazaarDevPublicResolver is PublicResolver { - constructor(ENS ens) PublicResolver(ens) {} -} +import {ReverseRegistrar} from "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol"; contract NamebazaarDevNameResolver is NameResolver { function isAuthorised(bytes32 node) internal view override returns(bool) { @@ -20,11 +16,7 @@ contract NamebazaarDevNameResolver is NameResolver { } } -contract NamebazaarDevReverseRegistrar is ReverseRegistrar { - constructor(ENS ens, INameResolver resolver) ReverseRegistrar(ens, resolver) {} -} - -contract NameBazaarDevRegistrar is BaseRegistrarImplementation { +contract NamebazaarDevRegistrar is BaseRegistrarImplementation { /** * @dev Constructs a new Registrar, with the provided address as the owner of the root node. * diff --git a/src/name_bazaar/shared/smart_contracts.cljs b/src/name_bazaar/shared/smart_contracts.cljs index 37a86571..b818ebb9 100644 --- a/src/name_bazaar/shared/smart_contracts.cljs +++ b/src/name_bazaar/shared/smart_contracts.cljs @@ -6,7 +6,7 @@ {:ens {:name "ENSRegistry" :address "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"} ;; Ropsten BaseRegistrarImplementation ;; https://ropsten.etherscan.io/address/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85 - :eth-registrar {:name "NameBazaarDevRegistrar" :address "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5"} + :eth-registrar {:name "NamebazaarDevRegistrar" :address "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85"} :offering-registry {:name "OfferingRegistry" :address "0xa3634fE2E2973d5d0cB7f2a1b2b0327A0B6e2d0D"} :buy-now-offering {:name "BuyNowOffering" :address "0xfF58F3De9319f37BF9b6F355694fe4431b58E6a4"} :buy-now-offering-factory {:name "BuyNowOfferingFactory" :address "0x8dD4F32663771D73b387A983D1c40BdB431241fd"} @@ -16,8 +16,8 @@ :reverse-name-resolver {:name "NamebazaarDevNameResolver" :address "0x0000000000000000000000000000000000000000"} ;; Ropsten PublicResolver ;; https://ropsten.etherscan.io/address/0x42d63ae25990889e35f215bc95884039ba354115 - :public-resolver {:name "NamebazaarDevPublicResolver" :address "0x42D63ae25990889E35F215bC95884039Ba354115"} + :public-resolver {:name "PublicResolver" :address "0x42D63ae25990889E35F215bC95884039Ba354115"} ;; Ropsten ReverseRegistrar ;; https://ropsten.etherscan.io/address/0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c - :reverse-registrar {:name "NamebazaarDevReverseRegistrar" :address "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"} + :reverse-registrar {:name "ReverseRegistrar" :address "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"} }) \ No newline at end of file From 79a96324adea4b5f0444f3e10ef029005e4f986e Mon Sep 17 00:00:00 2001 From: Ladislav Dubravsky Date: Fri, 30 Jul 2021 18:47:02 +0200 Subject: [PATCH 2/4] Increase gas limit for public resolver setup --- src/name_bazaar/ui/events/reverse_registrar_events.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/name_bazaar/ui/events/reverse_registrar_events.cljs b/src/name_bazaar/ui/events/reverse_registrar_events.cljs index 2c1fcfa1..752a0cba 100644 --- a/src/name_bazaar/ui/events/reverse_registrar_events.cljs +++ b/src/name_bazaar/ui/events/reverse_registrar_events.cljs @@ -22,7 +22,7 @@ :form-data (select-keys form-data [:ens.record/addr :public-resolver]) :args-order [:ens.record/addr :public-resolver] :form-id (select-keys form-data [:ens.record/addr]) - :tx-opts {:gas 100000 :gas-price default-gas-price} + :tx-opts {:gas 120000 :gas-price default-gas-price} :on-tx-receipt-n [[:ens.records.resolver/load [(reverse-record-node (:ens.record/addr form-data))]] [:district0x.snackbar/show-message (gstring/format From e1b282b9e570a0f04d8fb046674a4472a78cd3a2 Mon Sep 17 00:00:00 2001 From: Ladislav Dubravsky Date: Mon, 2 Aug 2021 10:15:50 +0200 Subject: [PATCH 3/4] Lower docker images size Update suggested by hadolint: https://github.com/hadolint/hadolint/wiki/DL3009 --- docker-builds/base/Dockerfile | 6 ++++-- docker-builds/server/Dockerfile | 4 +++- docker-builds/ui/Dockerfile | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docker-builds/base/Dockerfile b/docker-builds/base/Dockerfile index 7ca5502d..720fd144 100644 --- a/docker-builds/base/Dockerfile +++ b/docker-builds/base/Dockerfile @@ -8,7 +8,9 @@ RUN apt-get -yq update && apt-get install -yqq --no-install-recommends \ git \ python2.7 python-pip \ python3.7 python3-pip \ - ssh + ssh \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* # https://sanderknape.com/2019/06/installing-private-git-repositories-npm-install-docker/ RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts @@ -27,5 +29,5 @@ ENV PATH="${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}" # install truffle ENV PYTHON /usr/bin/python3 RUN PYTHON=/usr/bin/python3 pip3 install --no-cache-dir solc-select -RUN solc-select install 0.5.17 && solc-select use 0.5.17 +RUN solc-select install 0.8.4 && solc-select use 0.8.4 RUN npm install -g truffle --unsafe-perm=true --allow-root diff --git a/docker-builds/server/Dockerfile b/docker-builds/server/Dockerfile index 3bf254a7..b45e285f 100644 --- a/docker-builds/server/Dockerfile +++ b/docker-builds/server/Dockerfile @@ -13,7 +13,9 @@ RUN apt-get -yq update && apt-get install -yqq --no-install-recommends \ git \ python2.7 python-pip \ python3.7 python3-pip \ - ssh + ssh \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* # https://sanderknape.com/2019/06/installing-private-git-repositories-npm-install-docker/ RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts diff --git a/docker-builds/ui/Dockerfile b/docker-builds/ui/Dockerfile index b8ca9729..8f214aa9 100644 --- a/docker-builds/ui/Dockerfile +++ b/docker-builds/ui/Dockerfile @@ -10,7 +10,9 @@ RUN apt-get -yq update && apt-get install -yqq --no-install-recommends \ git \ python2.7 python-pip \ python3.7 python3-pip \ - ssh + ssh \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* # https://sanderknape.com/2019/06/installing-private-git-repositories-npm-install-docker/ RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts From b92973353afbf915d41cb3492df6c0a20621d6be Mon Sep 17 00:00:00 2001 From: Ladislav Dubravsky Date: Tue, 17 Aug 2021 15:06:15 +0200 Subject: [PATCH 4/4] Add ENS NameWrapper contract to dev setup --- README.md | 2 ++ project.clj | 3 ++- .../2_namebazaar_migration.js | 19 ++++++++++--- .../contracts/src/NameBazaarDevContracts.sol | 4 ++- run-ganache.sh | 2 +- src/name_bazaar/shared/smart_contracts.cljs | 27 +++++++++++-------- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 779ad57a..9569970c 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,8 @@ First, you need to specify deployments secrets in `config.edn`. For example: :privateKeys ["0508d5f96e139a0c18ee97a92d890c55707c77b90916395ff7849efafffbd810"] :ensAddress "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" :registrarAddress "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" + :nameWrapperAddress "TODO fill when ENS Ropsten deployment exists" + :metadataServiceAddress "TODO fill when ENS Ropsten deployment exists" :publicResolverAddress "0x42D63ae25990889E35F215bC95884039Ba354115" :reverseRegistrarAddress "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"}}} ``` diff --git a/project.clj b/project.clj index 775f753f..99ca984b 100644 --- a/project.clj +++ b/project.clj @@ -81,7 +81,8 @@ [lein-npm "0.6.2"] [lein-pdo "0.1.1"]] - :npm {:dependencies [["@ensdomains/ens-contracts" "0.0.4"] + :npm {:dependencies [["@ensdomains/ens-contracts" "0.0.7"] + ["@ensdomains/name-wrapper" "1.0.0"] ["@openzeppelin/contracts" "4.1.0"] ["@sentry/node" "4.2.1"] ["@ungap/global-this" "0.4.4"] diff --git a/resources/public/contracts-migration/2_namebazaar_migration.js b/resources/public/contracts-migration/2_namebazaar_migration.js index 4265e048..e4092097 100644 --- a/resources/public/contracts-migration/2_namebazaar_migration.js +++ b/resources/public/contracts-migration/2_namebazaar_migration.js @@ -97,6 +97,8 @@ const validateDeploymentConfig = (config = {}) => { const loadedArtifacts = loadArtifacts([ {kw: ':ens', name: 'ENSRegistry'}, {kw: ':eth-registrar', name: 'NamebazaarDevRegistrar'}, + {kw: ':name-wrapper', name: 'NameWrapper'}, + {kw: ':metadata-service', name: 'StaticMetadataService'}, {kw: ':offering-registry', name: 'OfferingRegistry'}, {kw: ':mutable-forwarder', name: 'MutableForwarder'}, {kw: ':buy-now-offering', name: 'BuyNowOffering'}, @@ -112,6 +114,8 @@ const loadedArtifacts = loadArtifacts([ const { ENSRegistry, NamebazaarDevRegistrar, + NameWrapper, + StaticMetadataService, OfferingRegistry, MutableForwarder, BuyNowOffering, @@ -142,19 +146,24 @@ module.exports = async function (deployer, network, accounts) { } if (network === 'development') { - const [ens, nameResolver] = await parallel( + metadataUrl = 'We don\'t care about metadata service currently' + const [ens, nameResolver, metadataService] = await parallel( deploy(ENSRegistry), - deploy(NamebazaarDevNameResolver) + deploy(NamebazaarDevNameResolver), + deploy(StaticMetadataService, metadataUrl) ) - const [ethRegistrar, _, reverseRegistrar] = await parallel( + const [ethRegistrar, reverseRegistrar] = await parallel( deploy(NamebazaarDevRegistrar, ens.address, namehash('eth')), - deploy(PublicResolver, ens.address), deploy(ReverseRegistrar, ens.address, nameResolver.address) ) await ens.setSubnodeOwner(namehash(''), ensLabel('eth'), ethRegistrar.address) await ens.setSubnodeOwner(namehash(''), ensLabel('reverse'), accounts[0]) await ens.setSubnodeOwner(namehash('reverse'), ensLabel('addr'), reverseRegistrar.address) + + // the contract's bytecode is too large to be deployed. Keep watching ENS for news. + // const nameWrapper = await deploy(NameWrapper, ens.address, ethRegistrar.address, metadataService.address) + const publicResolver = await deploy(PublicResolver, ens.address, nameWrapper.address) } else { const config = deployer.networks[network].deploymentConfig validateDeploymentConfig(config) @@ -162,6 +171,8 @@ module.exports = async function (deployer, network, accounts) { skipDeploy(ENSRegistry, config.ensAddress) skipDeploy(NamebazaarDevNameResolver, "0x0000000000000000000000000000000000000000") // this address is not important skipDeploy(NameBazaarDevRegistrar, config.registrarAddress) + skipDeploy(NameWrapper, config.nameWrapperAddress) + skipDeploy(MetadataService, config.metadataServiceAddress) skipDeploy(PublicResolver, config.publicResolverAddress) skipDeploy(ReverseRegistrar, config.reverseRegistrarAddress) } diff --git a/resources/public/contracts/src/NameBazaarDevContracts.sol b/resources/public/contracts/src/NameBazaarDevContracts.sol index 5178abea..ef826467 100644 --- a/resources/public/contracts/src/NameBazaarDevContracts.sol +++ b/resources/public/contracts/src/NameBazaarDevContracts.sol @@ -9,6 +9,8 @@ import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol"; import {NameResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/NameResolver.sol"; import {PublicResolver} from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol"; import {ReverseRegistrar} from "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol"; +import {NameWrapper} from "@ensdomains/name-wrapper/contracts/NameWrapper.sol"; +import {StaticMetadataService} from "@ensdomains/name-wrapper/contracts/StaticMetadataService.sol"; contract NamebazaarDevNameResolver is NameResolver { function isAuthorised(bytes32 node) internal view override returns(bool) { @@ -36,4 +38,4 @@ contract NamebazaarDevRegistrar is BaseRegistrarImplementation { emit ControllerAdded(msg.sender); _register(uint256(_hash), msg.sender, 365 days, true); } -} +} \ No newline at end of file diff --git a/run-ganache.sh b/run-ganache.sh index a1b6135e..b345e3eb 100755 --- a/run-ganache.sh +++ b/run-ganache.sh @@ -5,4 +5,4 @@ docker rm namebazaar-ganache || true docker run --name=namebazaar-ganache \ -p 8549:8549 \ - trufflesuite/ganache-cli:v6.12.1 -p 8549 + trufflesuite/ganache-cli:v6.12.2 -p 8549 --gasLimit 0xffe4e1c0 diff --git a/src/name_bazaar/shared/smart_contracts.cljs b/src/name_bazaar/shared/smart_contracts.cljs index b818ebb9..347a8f8a 100644 --- a/src/name_bazaar/shared/smart_contracts.cljs +++ b/src/name_bazaar/shared/smart_contracts.cljs @@ -1,18 +1,16 @@ (ns name-bazaar.shared.smart-contracts) (def smart-contracts - ;; Ropsten ENSRegistry - ;; https://docs.ens.domains/ens-deployments - ;; https://ropsten.etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e - {:ens {:name "ENSRegistry" :address "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"} + {;; ENS CONTRACTS + ;; Ropsten ENSRegistry + ;; https://docs.ens.domains/ens-deployments + ;; https://ropsten.etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e + :ens {:name "ENSRegistry" :address "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"} ;; Ropsten BaseRegistrarImplementation ;; https://ropsten.etherscan.io/address/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85 :eth-registrar {:name "NamebazaarDevRegistrar" :address "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85"} - :offering-registry {:name "OfferingRegistry" :address "0xa3634fE2E2973d5d0cB7f2a1b2b0327A0B6e2d0D"} - :buy-now-offering {:name "BuyNowOffering" :address "0xfF58F3De9319f37BF9b6F355694fe4431b58E6a4"} - :buy-now-offering-factory {:name "BuyNowOfferingFactory" :address "0x8dD4F32663771D73b387A983D1c40BdB431241fd"} - :auction-offering {:name "AuctionOffering" :address "0xa9c221236b9c16Ef08bccd84F89cE5b525B817A9"} - :auction-offering-factory {:name "AuctionOfferingFactory" :address "0x18eF935Cd12b962739538B35F94B0E9fE0e19D59"} - :district0x-emails {:name "District0xEmails" :address "0x31BF9DB8b432b0d108491e2bc9423d6C6d419E20"} + ;; TODO update NameWrapper and MetadataService addresses when Ropsten deployment is created by ENS + :name-wrapper {:name "NameWrapper" :address "0x0000000000000000000000000000000000000000"} + :metadata-service {:name "StaticMetadataService" :address "0x0000000000000000000000000000000000000000"} :reverse-name-resolver {:name "NamebazaarDevNameResolver" :address "0x0000000000000000000000000000000000000000"} ;; Ropsten PublicResolver ;; https://ropsten.etherscan.io/address/0x42d63ae25990889e35f215bc95884039ba354115 @@ -20,4 +18,11 @@ ;; Ropsten ReverseRegistrar ;; https://ropsten.etherscan.io/address/0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c :reverse-registrar {:name "ReverseRegistrar" :address "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"} - }) \ No newline at end of file + + ;; NAMEBAZAAR CONTRACTS + :offering-registry {:name "OfferingRegistry" :address "0xa3634fE2E2973d5d0cB7f2a1b2b0327A0B6e2d0D"} + :buy-now-offering {:name "BuyNowOffering" :address "0xfF58F3De9319f37BF9b6F355694fe4431b58E6a4"} + :buy-now-offering-factory {:name "BuyNowOfferingFactory" :address "0x8dD4F32663771D73b387A983D1c40BdB431241fd"} + :auction-offering {:name "AuctionOffering" :address "0xa9c221236b9c16Ef08bccd84F89cE5b525B817A9"} + :auction-offering-factory {:name "AuctionOfferingFactory" :address "0x18eF935Cd12b962739538B35F94B0E9fE0e19D59"} + :district0x-emails {:name "District0xEmails" :address "0x31BF9DB8b432b0d108491e2bc9423d6C6d419E20"}}) \ No newline at end of file