Resolution service provides an API for getting domain data and metadata regardless of that domain's location (whether it is stored in Ethereum, Zilliqa, or any other blockchain). The service is used to cache blockchain events in a database for easy retrieval without acessing blockchain APIs.
The resolution service is provided as a docker image so it can be launched on a variety of platforms and in the cloud.
- Production Mainnet: http://resolve.unstoppabledomains.com/api-docs/
- Staging Testnet (Ethereum Goerly, Polygon Mumbai): https://resolve.staging.unstoppabledomains.com/api-docs/
- git - to clone the repository.
- docker - to run the service. Install docker by following instructions for an appropriate system.
- postgres - to store the data. Postgres can be configured on the same
server as the resolution service or on a dedicated database hosting (e.g. AWS
RDS, Google Cloud SQL). To install postgres locally, follow these
instructions. Make sure to configure
password authentication for the DB user that will be used by the service.
- This guide has been tested with Postgres 11.17, although other versions may work as well.
- Clone the resolution-service repository
git clone https://github.com/unstoppabledomains/resolution-service.git
- Build the docker container
docker build -t resolution-service .
- Setup environment variables
Create a fileservice.env
that will contain required environment variables:
NODE_ENV=production
RESOLUTION_POSTGRES_HOST=example.com # DB host
RESOLUTION_POSTGRES_PORT=5432 # DB port
RESOLUTION_POSTGRES_USERNAME=example # DB user configured in postgres
RESOLUTION_POSTGRES_PASSWORD=password # DB password configured in postgres
RESOLUTION_POSTGRES_DATABASE=resolution_service # Name of the resolution service database
ETHEREUM_JSON_RPC_API_URL=https://alchemy.com # Address of a JSON RPC provider. This can be a public API (e.g. Alchemy), or a local ethereum node with JSON RPC enabled
POLYGON_JSON_RPC_API_URL=https://alchemy.com # Address of a JSON RPC provider. This can be a public API (e.g. Alchemy), or a local ethereum node with JSON RPC enabled
VIEWBLOCK_API_KEY=apikey # key for Viewblock API, required for getting data from Zilliqa blockchain
METADATA_API=apikey # key for Unstoppable Domain's Metadata API
MORALIS_API_URL=apikey # URL for the Moralis API
MORALIS_APP_ID=apikey # App ID for the Moralis API
OPENSEA_API_KEY=apikey # key for Opensea's API service
This is the minimum required set of configurations for the service. Additional configuration options are listed in Environment configuration options.
- Create the
resolution_service
postgres databasecreatedb resolution_service
- Launch the service
docker run -d --env-file service.env -p 3000:3000 --network="host" resolution-service
Once the service is started, it will perform initial synchronization with the
blockchain networks. It may take more than 24 hours for a full synchronization.
During the initial synchronization the API may not work reliably. The status of
synchronization can be checked using the /status
endpoint. After the
synchronization is complete, the service API endpoints can be accessed normally.
Note that the service is stateless, so the container doesn't need any persistent
storage. All data is stored in the database.
Option | Default value | required | Description |
---|---|---|---|
RESOLUTION_API_PORT | 3000 | ❌ | The port for the HTTP API. |
RESOLUTION_RUNNING_MODE | API,ETH_WORKER,MATIC_WORKER,ZIL_WORKER,MIGRATIONS | ❌ | Comma-separated list of running modes of the resolution service (see Running modes). |
RESOLUTION_POSTGRES_HOST | localhost | ✔️ | Host for the postgres DB. Note that to connect to a postgres instance running on the same server as the container, host.docker.internal should be used instead of localhost on Windows and MacOS (see https://docs.docker.com/docker-for-windows/networking/#use-cases-and-workarounds). |
RESOLUTION_POSTGRES_USERNAME | postgres | ✔️ | Username that is used to connect to postgres. |
RESOLUTION_POSTGRES_PASSWORD | secret | ✔️ | Password that is used to connect to postgres. |
RESOLUTION_POSTGRES_DATABASE | resolution_service | ✔️ | Database name in postgres. |
RESOLUTION_POSTGRES_PORT | 5432 | ❌ | Port number for Postgres database. |
ETHEREUM_CONFIRMATION_BLOCKS | 20 | ❌ | Number of blocks that the service will wait before accepting an event from the CNS contract. This helps to avoid block reorgs, forks, etc. |
ETHEREUM_BLOCK_FETCH_LIMIT | 500 | ❌ | Batch limit for fetching event data from the Ethereum JSON RPC. Note that some API providers may limit the amount of data that can be returned in a single request. So this number should be kept relatively low. However, raising this limit should speed up synchronization if a dedicated node is used with the service. |
ETHEREUM_RECORDS_PER_PAGE | 100 | ❌ | Batch limit for fetching domain records from CNS registry smart contract. |
ETHEREUM_FETCH_INTERVAL | 5000 | ❌ | Specifies the interval to fetch data from the CNS registry in milliseconds. |
CNS_REGISTRY_EVENTS_STARTING_BLOCK | 9080000 | ❌ | Starting block that is used to look for events in the CNS registry. This helps to avoid parsing old blockchain data, before the contract was even deployed. |
CNS_RESOLVER_ADVANCED_EVENTS_STARTING_BLOCK | 9080000 | ❌ | Starting block that is used to look for events in the CNS registry. |
ETHEREUM_ACCEPTABLE_DELAY_IN_BLOCKS | 100 | ❌ | How much blocks Ethereum mirror can lag behind until it's considered as unacceptable and need to be fixed. /status endpoint will return health: true/false field depends on number of blocks behind compared with this number. |
ETHEREUM_JSON_RPC_API_URL | - | ✔️ | Address of a JSON RPC provider. This can be a public API (e.g. Alchemy), or a local ethereum node with JSON RPC enabled. |
ETHEREUM_NETWORK_ID | 1 | ❌ | ID of the Ethereum network that is used by the service. |
POLYGON_CONFIRMATION_BLOCKS | 20 | ❌ | Number of blocks that the service will wait before accepting an event from the smart contracts on Polygon. This helps to avoid block reorgs, forks, etc. |
POLYGON_BLOCK_FETCH_LIMIT | 500 | ❌ | Batch limit for fetching event data from the Polygon JSON RPC. Note that some API providers may limit the amount of data that can be returned in a single request. So this number should be kept relatively low. However, raising this limit should speed up synchronization if a dedicated node is used with the service. |
POLYGON_RECORDS_PER_PAGE | 100 | ❌ | Batch limit for fetching domain records from Polygon smart contracts. |
POLYGON_FETCH_INTERVAL | 5000 | ❌ | Specifies the interval to fetch data from the Polygon blockchain in milliseconds. |
POLYGON_UNS_REGISTRY_EVENTS_STARTING_BLOCK | 19345077 | ❌ | Starting block that is used to look for events in the UNS registry on the Polygon blockchain. This helps to avoid parsing old blockchain data, before the contract was even deployed. |
POLYGON_ACCEPTABLE_DELAY_IN_BLOCKS | 100 | ❌ | How much blocks Polygon mirror can lag behind until it's considered as unacceptable and need to be fixed. /status endpoint will return health: true/false field depends on number of blocks behind compared with this number. |
POLYGON_JSON_RPC_API_URL | - | ✔️ | Address of a Polygon JSON RPC provider. This can be a public API (e.g. Alchemy), or a local ethereum node with JSON RPC enabled. |
POLYGON_NETWORK_ID | 137 | ❌ | ID of the Polygon network that is used by the service. |
ZNS_NETWORK | mainnet | ❌ | Name of the Zilliqa network will be used by ZNS worker (mainnet or testnet). |
VIEWBLOCK_API_KEY | - | ✔️ | API key for viewblock, required by ZNS worker. |
ZNS_FETCH_INTERVAL | 5000 | ❌ | Specifies the interval to fetch data from the ZNS registry in milliseconds. |
NEW_RELIC_LICENSE_KEY | - | ❌ | License key that will be used to access newrelic. If the key is not specified, new relic will not be enabled. |
NEW_RELIC_APP_NAME | - | ❌ | App name will be used to access newrelic. If the app name is not specified, new relic will not be enabled. |
BUGSNAG_API_KEY | - | ❌ | API key that will be used to access bugsnag. If the key is not specified, bugsnag will not be enabled. |
TYPEORM_LOGGING_COLORIZE | true | ❌ | Colorize typeorm logs. |
ZILLIQA_ACCEPTABLE_DELAY_IN_BLOCKS | 100 | ❌ | How much blocks Zilliqa mirror can lag behind until it's considered as unacceptable and need to be fixed. /status endpoint will return health: true/false field depends on number of blocks behind compared with this number. |
MORALIS_API_URL | - | ❌ | URL for the Moralis API. Required by the metadata API endpoints (METADATA_API running mode). |
MORALIS_APP_ID | - | ❌ | App ID for the Moralis API. Required by the metadata API endpoints (METADATA_API running mode). |
The service provides several running modes. By default it will run all of them. However, the modes that will be used can be selected during startup using the RESOLUTION_RUNNING_MODE environment variable. Available running modes:
- API - Runs all APIs
- SERVICE_API - Runs the service API (see "Service endpoints" in API reference)
- METADATA_API - Runs the metadata API (see "Metadata endpoints" in API reference)
- ETH_WORKER - Runs the ETH worker to sync data from the Ethereum CNS and UNS registry
- MATIC_WORKER - Runs the MATIC worker to sync data from the Polygon UNS registry
- ZIL_WORKER - Runs the ZIL worker to sync data from the Zilliqa ZNS registry
- MIGRATIONS - Runs the migration scripts if necessary.
For example, to run only the API
with the ETH_WORKER
, the following
environment configuration can be used:
RESOLUTION_RUNNING_MODE=API,ETH_WORKER
The /domains
API requires an API key which is simply a version 4 UUID.
Currently there are no key management functions in the resolution service. All
API keys must be added manually using the database. To generate a random API key
run the following query in postgres:
INSERT INTO api_keys (name, api_key) VALUES ('my API key', md5(clock_timestamp()::text)::uuid);
Note: The above example should not be used for production API keys as the key which is based on a predictable value. Production keys should be generated externally.
The full api reference
OpenAPI specification By
default all API endpoints are enabled. Use the RUNNING_MODE
env variable to
enable specific sets of endpoints.
Endpoint | Description |
---|---|
Service endpoints: | |
GET /domains | Gets the list of domains. |
GET /domains/:domainName | Gets the resolution of the specified domain. |
GET /records | Gets resolution records for multiple domains requested. - domains : required query parameter, accepts an array of UD domains.- key : optional query parameter, reflects what resolution record to retrieve across the requested domains.Example: /records?domains[]=ryan.crypto&domains[]=mvwi.crypto&key=social.picture.value |
GET /status | Gets the synchronization status. |
GET /api-docs | Returns a swagger documentation page. |
Metadata endpoints: | |
GET /metadata/:domainOrToken | Retrieve erc721 metadata information of the specified domain |
GET /image/:domainOrToken | Retrieve image_data as a svg string |
GET /image-src/:domainOrToken | Retrieve image_data as 'image/svg+xml' |
Note: The
/domains
and/records
endpoints require an API key. The key must be provided asBearer
authentication header for requests. New keys must be added manually to the database (see API keys for more info).
The dev. environment has generally the same pre-requirements as running the service normally. So, postgres and docker are also necessary. For convenience postgres configuration can be the same as defaults (username - postgres, password - secret).
Additional pre-requirements that are necessary for development:
- Install project dependencies
nvm install 14.16.1
nvm use 14.16.1
yarn install
- Configure environment variables.
The required variables are the same as running the service in docker. Copy./local.dev.env.sample
to./local.dev.env
and set variables as necessary.
RESOLUTION_POSTGRES_HOST=localhost
RESOLUTION_POSTGRES_USERNAME=postgres
RESOLUTION_POSTGRES_PASSWORD=password
RESOLUTION_POSTGRES_DATABASE=resolution_service
ETHEREUM_JSON_RPC_API_URL=localhost:8545
POLYGON_JSON_RPC_API_URL=localhost:8546
VIEWBLOCK_API_KEY=apikey
METADATA_API=apikey
MORALIS_API_URL=apikey
MORALIS_APP_ID=apikey
OPENSEA_API_KEY=apikey
- Run the service
yarn start:dev
Unit tests can be run using yarn test
. This command will run the tests with
ENV variables set in ./local.test.env
file. You should copy
./local.test.env.sample
to ./local.test.env
and redefine any env variable in
yours local environment if needed, for example:
export RESOLUTION_POSTGRES_PASSWORD=password
. Testing command will take this
variable first instead of using variable from ./local.test.env
file.
For checking coverage use yarn test:coverage
.
Unit/integration tests use a postgres database that is cleaned before each test.
By default, the database name is resolution_service_test
.
To debug the service the following command could be used:
yarn start:dev:debug
To debug the tests use:
yarn test:debug
If you are using Visual Studio Code to debug the code add this to .vscode/launch.json
launch configuration:
"configurations": [
...,
{
"type": "node",
"request": "attach",
"name": "Attach to resolution service",
"protocol": "inspector",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}"
},
The service currently consists of three main components: API, and three workers. The API component is a basic HTTP API that allows reading domain data from the database. The OpenAPI specification: OpenAPI specification.
Currently there are three workers in the resolution service:
- ETH worker
Contains a scheduled job that connects to the Ethereum blockchain using JSON RPC and pulls CNS (.crypto) and UNS domains and resolution events. The events are parsed and saved to the database. - MATIC worker
Contains a scheduled job that connects to the Polygon blockchain using JSON RPC and pulls domains and resolution events. The events are parsed and saved to the database.
Since Polygon and Ethereum are compatible, we use two instances of the same worker implementation to pull data from these networks. - ZIL worker
Contains a scheduled job that connects to the Zilliqa blockchain using and pulls ZNS (.zil) domains and resolution events. The events are parsed and saved to the database.
More workers may be added in the future.
The resolution service outputs logs to stdout
so they are available by
docker logs
and can be monitored by cloud tools (e.g. AWS CloudWatch or Google
Cloud Logging). The general log format should be:
<timestamp> <log level>: <Component label> - <Log message>
The resolution service has a configurable logging level. Log messages are at consistent levels across the whole service. We use the following guidelines to determine logging level:
Event | Component | description | log level |
---|---|---|---|
Startup info | all | Log any startup information (e.g. worker is starting, API is listening) | info |
Sync progress | Workers | Log current sync progress of a worker (which blocks are being processed, how many blocks are left to process) | info |
Handled errors | all | Log any errors that can be handled gracefully (e.g. a malformed request that will return a 400 error) | warn |
Unhandled errors | all | Log any errors that were captured by top-level error handlers (e.g. an unexpected third-party API error, invalid db state) | error |
API Request | API controllers | Log any request to the API with their parameters | debug |
DB query | all | Log any db queries with their parameters | debug |
Parsed event | Workers | Log any event or transaction parsed by the worker | debug |
External API calls | all | Log external API calls | debug |
Additionally, if the appropriate keys are provided in the environment configuration, the service will report errors to monitoring tools. The resolution service has integrations with bugsnag and newrelic.
- Add it to Github repository Secrets (https://github.com/unstoppabledomains/resolution-service/settings/secrets/actions)
- Add it to such files: env.ts, deploy-production.yml, deploy-staging.yml, tests.yml, create-yaml.sh
Metadata service will cache NFT PFP images, uploading them to Google Cloud Storage (GCS) CDN. To test file uploads localy, unofficial GCS emulator is used.
To use the emulator make sure docker is installed.
Pull the image from Docker Hub
docker pull fsouza/fake-gcs-server
Create the following directory structure for the GSC bucket storage, where resolution-client-assets
is the name of the bucket.
storage
|--> resolution-client-assets
Run docker container with mounted bucket directory:
docker run -d --name fake-gcs-server -p 4443:4443 -v ${PWD}/storage:/data fsouza/fake-gcs-server -scheme http -public-host localhost:4443
Add the following variable to local dev config:
CLOUD_STORAGE_ENDPONT_URL=http://localhost:4443