• Pricing
  • Enterprise
  • Customers
  • Blog

Developing DApps: The ultimate guide to building on Chainstack

Welcome, aspiring Web3 developers! We at Chainstack understand that the blockchain ecosystem can often seem overwhelming with its vastness and nuances. That’s why we’ve put together this comprehensive guide designed specifically with you in mind.

With Chainstack, developing decentralized applications becomes a streamlined process. We’re here to help you navigate the complexities and make the most out of our diverse range of supported blockchain networks and features.

This guide covers everything from choosing the ideal blockchain network for your DApp, making sense of Layer 1 and Layer 2 networks, and leveraging the reliability-enhancing features of Chainstack, to interpreting error codes and selecting apt authentication methods.

Whether you’re just starting your Web3 journey or looking to refine your development processes, this guide offers the essential insights to elevate your projects. Let’s explore together!

How to choose the right chain for your project?

Your choice of blockchain protocol can determine the fate of your project. Therefore, a comprehensive understanding of Layer 1 and Layer 2 networks can prove invaluable.

Layer 1 networks

Layer 1 networks are the foundational blockchains that foster decentralization and utmost security. Examples include Ethereum, BNB Smart Chain, and so forth. They offer a robust, trustless environment for DApps but can sometimes be hindered by scalability issues and high gas fees. Their key characteristics include:

  • Decentralization: Highly decentralized with a distributed network of nodes. Examples include Bitcoin and Ethereum.
  • Security: Robust consensus mechanisms like PoW or PoS ensure security.
  • Scalability: Some L1 networks face scalability challenges, leading to congestion and high fees.
  • Smart contracts: Support for decentralized applications with programmable logic.
  • Native tokens: Used as gas fees or as a store of value.

Leading Layer 1 networks supported by Chainstack

  • Ethereum: A decentralized platform for DApps and smart contracts. Best for decentralized applications requiring robust smart contract functionality.
  • BNB Smart Chain: High-performance network compatible with the Ethereum Virtual Machine. Best for DApps requiring high-speed and low-cost transactions.
  • Avalanche: Offers scalable, customizable, and interoperable blockchain solutions with a unique consensus mechanism. Best for developers and projects seeking customizable blockchain-based applications with specific requirements.
  • Fantom: Known for fast transaction processing, scalability, and many use cases. Best for high-performance decentralized applications and diverse use cases.

Layer 2 networks

Contrastingly, Layer 2 networks are the secondary networks or ‘off-chain’ solutions designed to overcome the limitations of Layer 1. They are often quicker, more scalable, and have lower transaction fees. These include Plasma, Rollups, and more. Key characteristics of L2 networks include:

  • Scalability solutions: Designed to offload transactions from L1, increasing throughput and reducing congestion.
  • Cost efficiency: Offer cost-effective alternatives for microtransactions.
  • Interoperability: Can connect different L1 networks or bridge to traditional financial systems.
  • Security: Benefit from the security of L1 but may have unique security considerations.
  • Use cases: Ideal for high transaction throughput applications like gaming and DeFi.

Top layer 2 networks supported by Chainstack

  • Polygon: A Layer 2 scaling solution for Ethereum. Best for addressing Ethereum’s high gas fees and slow transaction times.
  • Polygon zkEVM: The first zero-knowledge scaling solution compatible with the Ethereum Virtual Machine. Best for integrating smart contracts and developer tools with enhanced scalability.
  • zkSync Era: Scales Ethereum with cutting-edge ZK tech and is fully compatible with the Ethereum Virtual Machine. Best for applications requiring high scalability without compromising Ethereum compatibility. Account abstraction out of the box.
  • Optimism: A scaling solution for Ethereum using optimistic rollups. Best for achieving high throughput and low fees on the Ethereum network.
  • Base: An Ethereum Layer 2 scaling solution built on top of Optimism. Best for secure, low-cost, and developer-friendly scaling on Ethereum.
  • Arbitrum: Designed to enhance the performance of Ethereum-based DApps by providing a more efficient platform. Best for addressing scalability and high gas fee issues on Ethereum.
  • Starknet: Enables Ethereum to scale securely using zk-STARKs technology. Best for enhancing data security, privacy, and scalability on Ethereum.
  • Scroll: A reliable and scalable Layer 2 network that extends Ethereum’s capabilities. Best for scaling applications on Ethereum without compromising on performance.

Making an informed choice between Layer 1 and Layer 2 requires getting to grips with the advantages and limitations of both. As each DApp has its unique requirements and audiences, there isn’t a one-size-fits-all solution. Read Solving the Blockchain Trilemma: A Look at Some Scaling Solutions to better understand Layer 2 solutions.

How to make your DApp more reliable with Chainstack?

As DApp developers, you’re tasked with the mission to create decentralized applications that are not only innovative but run seamlessly, ensuring a satisfactory user experience. That’s where we step in, offering robust solutions like our Global Nodes that amplify your DApp’s reliability and performance.

Let’s break it down. A Global Node allows access to Ethereum nodes worldwide, which in turn ensures high-grade uptime and performance in every corner of the globe. This unique feature of Chainstack can drastically reduce latency, leading to swift and efficient transactions.

Our nodes aren’t confined to specific locations. They are present in diversely located data centers worldwide, ensuring your Dapp remains operational round-the-clock, regardless of your audience’s geographical position. And the best part? This wide spectrum is automatically available to you by default—no extra step, no added configuration.

The main advantages of Chainstack Global Nodes are as follows:

  • Enhanced load balancing: Global Nodes feature a robust load balancer that can switch nodes if one fails or lags by more than 40 blocks, ensuring uninterrupted service.
  • Reduced latency: By distributing traffic to the nearest endpoint, Global Nodes reduce latency, resulting in faster transactions and an improved user experience.
  • Global reach: Accessible from any location, Global Nodes direct users to the nearest endpoints, maximizing service availability and responsiveness.
  • High availability: Designed to be 99.95% available, Global Nodes ensure that your DApp continues to run with minimal interruptions.

How to integrate Chainstack Global Nodes into your project?

By leveraging Chainstack Global Nodes, you can take your DApp’s performance to the next level, leaving behind worries of network congestion or node collapse. Get ready to witness the power of a seamless global infrastructure in action.

Web3JS example:

const { Web3 } = require("web3");
const NODE_URL = "YOUR_CHAINSTACK_WSS_ENDPOINT";
// Reconnect options
const reconnectOptions = {
  autoReconnect: true,  // Automatically attempt to reconnect
  delay: 5000,          // Reconnect after 5 seconds
  maxAttempts: 10,      // Max number of retries
};
const web3 = new Web3(
  new Web3.providers.WebsocketProvider(NODE_URL, undefined, reconnectOptions)
);
async function subscribeToNewBlocks() {
  try {
    // Create a new subscription to the 'newBlockHeaders' event
    const event = "newBlockHeaders";
    const subscription = await web3.eth.subscribe(event); // Changed to 'newHeads'
    console.log(`Connected to ${event}, Subscription ID: ${subscription.id}`);
    // Attach event listeners to the subscription object for 'data' and 'error'
    subscription.on("data", handleNewBlock);
    subscription.on("error", handleError);
  } catch (error) {
    console.error(`Error subscribing to new blocks: ${error}`);
  }
}
// Fallback functions to react to the different events
// Event listener that logs the received block header data
function handleNewBlock(blockHeader) {
  console.log("New block header:", blockHeader);
}
// Event listener that logs any errors that occur
function handleError(error) {
  console.error("Error when subscribing to new block header:", error);
}
subscribeToNewBlocks();

ethersJS example:

const ethers = require("ethers");
const WebSocket = require("ws");
const NODE_URL =
  "YOUR_CHAINSTACK_WSS_ENDPOINT";
function createWebSocket() {
  const ws = new WebSocket(NODE_URL);
  ws.on("close", () => {
    console.log("Disconnected. Reconnecting...");
    setTimeout(() => {
      provider = new ethers.WebSocketProvider(createWebSocket());
      startListening();
    }, 3000);
  });
  ws.on("error", (error) => {
    console.log("WebSocket error: ", error);
  });
  return ws;
}
let provider = new ethers.WebSocketProvider(createWebSocket());
function startListening() {
  provider.on("block", async (blockNumber) => {
    console.log("New block number:", blockNumber);
    const block = await provider.getBlock(blockNumber);
    console.log("Block details:", block);
  });
}
startListening();

How to use API endpoints on Chainstack

API endpoints are essential for your DApp, as they define the routes for interacting with the database and handling client-server communication effectively. With an appropriate understanding and utilization of various API endpoints, you, as a Web3 developer, can elevate the performance of your DApp.

In Chainstack, each API endpoint corresponds to a different datatype, service, or function. These endpoints allow your DApp to interact with protocol and node data, among other services, facilitating seamless data exchange.

Figure 1: Deploying a Global Node in Advanced mode on Chainstack demo

Always remember, that good command over API endpoints not only enhances your application’s performance but also elevates the end-user experience to a new height. It’s like mastering the art of communication in the digital world, and as a developer, you would know how vital that can be!

Below are examples in JavaScript and Python to help you get familiar with the Chainstack platform API.

API key authentication

The Chainstack API uses API keys to authenticate requests. Include your API key in the Authorization header. The header value should be the Bearer prefix followed by the secret key generated through the platform’s user interface.

Here’s an example using curl:

curl -X GET '<https://api.chainstack.com/v1/organization/>' \\\\
--header 'Authorization: Bearer YOUR_API_KEY'

API calls using JavaScript

This example shows how to interact with the Chainstack platform API using Node.js and the Axios library. It illustrates how to communicate with the API using code.

Set the environment variable:

BEARER_TOKEN="YOUR_API_KEY"

Ensure you install axios and dotenv before running the code:

npm i axios dotenv
require('dotenv').config();
const axios = require('axios');
async function getOrganization() {
  try {
    const response = await axios.get('<https://api.chainstack.com/v1/organization/>', {
      headers: {
        'Authorization': `Bearer ${process.env.BEARER_TOKEN}`
      }
    });
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
}
getOrganization();

This example uses the “Get Organization name and ID” endpoint to fetch information about the organization. It’s a straightforward way to incorporate API calls into your routine tasks.

API calls using Python

This example demonstrates how to interact with the Chainstack platform API using Python and the requests library. It shows how to communicate with the API using Python code.

Ensure you install requests and python-dotenv before running the code:

pip install requests python-dotenv
import os
from dotenv import load_dotenv
import requests
load_dotenv()
def get_nodes():
    try:
        response = requests.get(
            '<https://api.chainstack.com/v1/nodes/>',
            headers={'Authorization': f'Bearer {os.getenv("BEARER_TOKEN")}'}
        )
        response.raise_for_status()
        print(response.json())
    except requests.exceptions.RequestException as err:
        print(f"An error occurred: {err}")
get_nodes()

This example calls the “List all Nodes” endpoint to fetch data about the nodes deployed by your organization. This makes it easy to introduce API calls into your Python workflow.

Best practices for error handling in API requests

While developing DApps, handling HTTP status codes is vital to ensure a responsive and glitch-free interaction for your users. These codes, three-digit numbers, provide both users and developers with a snapshot of how the request has fared.

Successes, for example, are generally coded with 2xx, but you’re more likely to encounter 4xx and 5xx when things aren’t as smooth. The 400 series is used when the client seems to be at fault, say, owing to a bad request or unauthorized access. On the other hand, the 500 series indicates that the problem pertains to server errors.

Implementing proper error handling in your DApps can substantially enhance customer experience, as users will be provided detailed feedback in case something goes wrong, guiding their next actions. Though it might seem an uphill task, keep in mind that understanding and correctly working with these status codes can spell the difference between a great DApp and a mediocre one.

Practical example for error handling in API requests

To handle HTTP status codes, we first need to know how to retrieve them. In Python, this can be done using the status_code attribute of the response object. This attribute holds the status code returned by the server for the HTTP request.

Let’s consider a scenario where we want to get the logs of the latest block. Here is an example of how to do this using Python:

import json
import requests
node_url = 'YOUR_CHAINSTACK_ENDPOINT'
headers = {
    "Content-Type": "application/json"
}
payload = {
    "jsonrpc": "2.0",
    "method": "eth_getLogs",
    "params": [
        {
            "fromBlock": "latest",
            "toBlock": "latest",
        }
    ],
    "id": 1
}
response = requests.post(node_url, headers=headers, json=payload)
print(response.text)

If the above code runs successfully, it will output the logs for the latest block. This indicates that the response code received by the client (you, who made the request) was 200. To retrieve the response code of the request shown above, you can use the following:

# Considering this request:
response = requests.post(node_url, headers=headers, json=payload)
# Here's how we can get the response code for such request:
response_code = response.status_code
print('status code:', response_code)

This will store the HTTP status code of the response in the status_code attribute. Now that you know how to retrieve the status code of a response, you can move on to handling these codes and analyzing error responses.

Analyzing error responses

In addition to dealing with response codes, it’s also important to analyze other information in the response to understand and address errors. This can be particularly useful when the server returns a 4xx or 5xx status code, indicating a client or server error.

For instance, let’s consider a possible response for an eth_getLogs request that contains an error message:

{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"failed to get logs for block #192001 (0xa388fd..65beb8)"}}

In this case, the server returned a JSON object with an error field, which contains additional information about the error that occurred. We can extract this information in our Python code as follows:

response = requests.post(node_url, headers=headers, json=payload)
response_code = response.status_code
print('status code:', response_code)
if response_code == 200:
    response_data = json.loads(response.text)
    if 'error' in response_data:
        error_content = response_data['error']
        print('Error:', error_content)

In this code, we first check if the response’s status code is 200, indicating a successful request. If it is not, we parse the JSON content of the response and check if it contains an error field. If it does, we store the content of this field in the error_content variable. This information can be used to implement retry logic and keep a record of whenever these errors occur.

Importance of implementing retry logic

Incorporating retry logic into your code can significantly enhance the reliability of your application. By leveraging the tools and techniques we have discussed, you can implement a retry mechanism that automatically handles temporary failures and retries the request when necessary. This can reduce the impact of temporary failures, increase system availability, and ensure data integrity. In the worst-case scenario, this enables you to keep track of the errors you face with precise timestamps.

Implementing retry logic is particularly important when dealing with 5xx server errors. These errors indicate a problem with the server and are often temporary. By implementing retry logic, your application can automatically retry the request after a short delay, giving the server a chance to recover. This can significantly improve the user experience by reducing the number of failed requests the user has to deal with.

Implementing retry logic in code

Now that we understand the importance of implementing retry logic, let’s dive into how to implement it in our Python code. Our retry logic aims to automatically retry the request when a temporary failure occurs, such as a 5xx server error or a connection error.

Here’s an example of how to implement retry logic in Python, using both the response code and error messages to determine when to retry a request:

import json
import time
import requests
node_url = 'YOUR_CHAINSTACK_ENDPOINT'
headers = { "Content-Type": "application/json" }
payload = { 
    "jsonrpc": "2.0", 
    "method": "eth_getLogs", 
    "params": [ {"fromBlock": "latest", "toBlock": "latest"} ],
    "id": 1
}
# Max retries
retries = 5
delay = 1 # Seconds
def get_logs():
    for i in range(retries):
        response = requests.post(node_url, headers=headers, json=payload)
        
        if response.status_code != 200:
            print(f"Request failed with status code {response.status_code}. Retrying attempt {i+1}...")
            time.sleep(delay)
            continue
        
        response_data = json.loads(response.text)
        
        if 'error' in response_data:
            print(f"There was an error in attempt {i+1}: {response_data['error']}")
            time.sleep(delay)
            continue
        
        logs = response_data.get("result", [])
        
        if len(logs) > 0:
            block_number = int(logs[0]['blockNumber'], 16)
            print(f"Block number from eth_getLogs call: {block_number} in attempt {i+1}")
            print('Processing the event logs...')
            return
        
        print(f"Result is empty for this block in attempt {i+1}")                
        time.sleep(delay)
get_logs()

The retry logic is governed by a for loop that runs up to a predefined maximum number of attempts (the retries variable). For each iteration of the loop, which represents an attempt to fetch the logs, the code performs the following steps:

  1. Send request: A POST request is sent to the Ethereum node with the defined headers and payload.
  2. Check status code: If the HTTP status code of the response is not 200 (indicating a successful request), the code prints a message indicating the request failed and the current attempt number. Then, it waits for the specified delay period (the delay variable) before proceeding to the next iteration of the loop. This delay helps in cases where the server might be temporarily overloaded or experiencing other transient issues.
  3. Handle errors: If the status code is 200, the response is parsed into JSON format and checked for an error key. If error is present, the code prints a message with the error details and the current attempt number, waits for the specified delay period, and proceeds to the next iteration of the loop. This handles cases where the request was technically successful, but the response indicates an error condition that might be resolved with a retry.
  4. Check empty results: If there’s no error key but the result is empty, the code prints a message indicating this fact and the current attempt number, waits for the specified delay period, and proceeds to the next iteration of the loop. This handles situations where the request was successful but didn’t provide any logs to process.

If the function hasn’t returned by the end of the loop (meaning it hasn’t successfully processed a set of logs), it will have retried the request the maximum number of times. At this point, the function will exit, effectively giving up on fetching logs after exhausting all the allowed attempts.

Using response codes and error messages in retry logic

As seen in the example, both the response code and error messages are used in the retry logic. The response code indicates whether the request was successful, while error messages provide detailed information about what went wrong.

By using both pieces of information, the retry logic becomes more intelligent and effective. For instance, the request can be retried immediately if the error message indicates a temporary problem, or it can wait for a longer delay if the error message indicates a more serious issue.

Logging error messages also helps keep a record of the errors, which is useful for debugging and improving the application.

Common problems and gotchas

While handling HTTP status codes and implementing retry logic can improve the reliability of your application, there are some common problems and gotchas to be aware of.

Importance of effective retry logic and robust error backlogs

Without effective retry logic and robust error backlogs, your application may not recover from temporary failures, leading to a poor user experience and potential data loss.

An effective retry logic should consider the nature of the error and adjust its behavior accordingly. For example, if the error is temporary (such as a 5xx server error), the retry logic should wait for a short delay before retrying the request. If the error is permanent (such as a 4xx client error), the retry logic should not retry the request, but rather log the error and notify the user.

A robust error backlog helps track errors in your application, allowing for more effective debugging and issue resolution. It also provides valuable insights into the performance and reliability of your application, helping to identify areas for improvement.

How to create a .env file with all your Chainstack endpoints in Python

Chainstack offers a robust API that enables efficient data retrieval about your projects and endpoints. In this example, we will use the “list all nodes” API.

This API allows you to seamlessly extract a comprehensive list of nodes associated with your account. Additionally, we will demonstrate how to automate the creation of a .env file with the fetched data.

Follow these steps to create a Python script that retrieves endpoint data from the Chainstack API and automates the creation of a .env file with the fetched data.

Step 1: Create a new Python file

  1. Create file: Create a new Python file in your project’s root directory (the same location where you created the .env file). Name this file get_endpoints.py.
  2. Purpose: This naming convention indicates the file’s purpose, which is to retrieve endpoint data.

Step 2: Add code to the Python file

  1. Open file: Open get_endpoints.py in your preferred code editor.
  2. Paste script: Add the following Python script designed to interact with the Chainstack API, retrieve blockchain node information, and create a configuration file with these details.
import requests
import os
import logging
from dotenv import load_dotenv
from web3 import Web3
from typing import Optional, Dict, Any
# Load environment variables
load_dotenv()
# Constants
CHAINSTACK_API_KEY = os.getenv('CHAINSTACK_API_KEY')
OUTPUT_FILE_NAME = 'rpc.env'
# Initialize logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def fetch_chainstack_data(api_key: str) -> Optional[Dict[str, Any]]:
    """Fetch data from Chainstack API."""
    url = "<https://api.chainstack.com/v1/nodes/>"
    headers = {
        "accept": "application/json",
        "authorization": f"Bearer {api_key}"
    }
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        data = response.json()
        logging.info(f"Fetched {len(data.get('results', []))} items from Chainstack.")
        return data
    except requests.RequestException as e:
        logging.error(f"Failed to fetch data from Chainstack: {e}")
        return None
def process_chainstack_item(item: Dict[str, Any]) -> Dict[str, str]:
    """Process a single item from Chainstack data."""
    logging.debug(f"Processing item: {item['name']} with ID {item['id']}")
    return {
        'id': item['id'],
        'name': item['name'],
        'details': item['details'],
        'http_endpoint': item['details'].get('https_endpoint'),
        'auth_key': item['details'].get('auth_key'),
        'configuration': item['configuration'],
        'client': item['configuration'].get('client')
    }
def connect_to_web3(reconstructed_endpoint: str) -> bool:
    """Connect to a Web3 endpoint."""
    logging.debug(f"Attempting to connect to Web3 endpoint: {reconstructed_endpoint}")
    try:
        w3 = Web3(Web3.HTTPProvider(reconstructed_endpoint))
        if w3.is_connected():
            chain_id = w3.eth.chain_id
            logging.info(f"Connected to {reconstructed_endpoint} with chain ID: {chain_id}")
            return True
        else:
            logging.warning(f"Failed to connect to {reconstructed_endpoint}")
    except Exception as e:
        logging.error(f"An error occurred while connecting to {reconstructed_endpoint}: {e}")
    return False
def sanitize_name(name: str) -> str:
    """Sanitize the endpoint name for use as an environment variable key."""
    return name.replace(" ", "_").replace("-", "_").replace("/", "_").upper()
def create_env_file(endpoint_info_dict: Dict[str, Dict[str, str]], filename: str = OUTPUT_FILE_NAME) -> None:
    """Create a .env file from the endpoint info dictionary."""
    logging.info(f"Preparing to write {len(endpoint_info_dict)} endpoints to .env file.")
    with open(filename, 'w') as file:
        for endpoint, info in endpoint_info_dict.items():
            sanitized_name = sanitize_name(info['name'])
            file.write(f'{sanitized_name}_URL="{endpoint}"\\\\n')
        logging.info(f".env file created successfully at {filename}.")
def main() -> None:
    """Main function to orchestrate the process."""
    logging.info("Starting main process.")
    if not CHAINSTACK_API_KEY:
        logging.error("Chainstack API key not found.")
        return
    json_data = fetch_chainstack_data(CHAINSTACK_API_KEY)
    if not json_data:
        return
    endpoint_info_dict = {}
    for item in json_data.get('results', []):
        data = process_chainstack_item(item)
        reconstructed_endpoint = f"{data['http_endpoint']}/{data['auth_key']}"
        if connect_to_web3(reconstructed_endpoint):
            endpoint_info_dict[reconstructed_endpoint] = {'name': data['name']}
    if endpoint_info_dict:
        create_env_file(endpoint_info_dict)
    else:
        logging.info("No endpoint information to write to .env file.")
    logging.info("Main process completed.")
if __name__ == "__main__":
    main()

Explanation of the script

Environment setup and logging

  • Loading environment variables: The script starts by loading environment variables from a .env file, particularly the CHAINSTACK_API_KEY.
  • Logging initialization: Logging is initialized for recording the script’s activities, errors, and informational messages, which is crucial for monitoring and debugging.

Fetch data from the Chainstack API

  • Function fetch_chainstack_data: This function makes a GET request to the Chainstack API using the provided API key. It retrieves information about the blockchain nodes on your account.
  • Success handling: If successful, the function logs the number of items fetched and returns the data. Otherwise, it logs an error and returns None.

Process Chainstack data

  • Function process_chainstack_item: This function extracts important details like ID, name, and endpoint information from the Chainstack data.

Test Web3 connection

  • Function connect_to_web3: This function attempts to establish a connection to a Web3 endpoint. It logs a successful connection or warns if it fails, providing valuable feedback about each endpoint’s status.
  • Note: Non-EVM endpoints or improperly formatted ones will not work and will show a warning. This is a point you can build on and improve.

Sanitize data and create .env file

  • Function sanitize_name: This function ensures that the names of the endpoints are formatted correctly to be used as environment variable keys in the .env file.
  • Function create_env_file: This function writes the endpoint URLs into a .env file, making them easily accessible and usable in other parts of the project. This file serves as a central configuration point, enhancing the modularity and scalability of the system.

Main execution flow

  • Main function: The main function orchestrates the entire process: checking the API key, fetching and processing data, testing endpoints, and creating the .env file. It ensures each step is executed in sequence and handles the absence of data or API keys, making the script robust and user-friendly.

Executing this script will efficiently process and validate your Chainstack endpoints. It identifies those endpoints that successfully return a chain ID, indicating their active and functional status. These validated endpoints are then neatly recorded into a .env file.

With this setup, you gain a streamlined and organized method to access and utilize all your Chainstack endpoints. This approach simplifies endpoint management and enhances the overall efficiency of your interactions with Chainstack services. Note that non-EVM endpoints or improperly formatted ones will not work and will show a warning, which you can build on and improve.

Authentication methods available on Chainstack

Safeguarding data and asserting secure interactions is paramount. API authentication is essential in application programming interface (API) development, as it verifies the identities of applications or users utilizing the API.

Chainstack prides itself on offering several secure API authentication methods that can fortify your DApps against unauthorized breaches, each with its advantages and considerations. Let’s briefly explore the five primary methods used for authentication first, keeping in mind only the first two are actively used on Chainstack.

API keys

API keys are the simplest form of API authentication. The client includes an API key, a unique identifier, in the header or as a parameter in the URL. The server matches the key to a corresponding key in its database and, if it matches, grants access. Although easy to implement, API keys can be misused if intercepted.

On Chainstack, API keys are typically used with the platform API.

Basic authentication

Basic authentication involves sending a user ID and password with each API request. The credentials are Base-64 encoded but not encrypted, making them easily decipherable by anyone who intercepts the transmission. Basic authentication should always be used over HTTPS to add an additional layer of security.

While basic authentication can be used for nodes, it won’t work with the Chainstack platform API, as API keys are used for that instead.

Digest authentication

Digest authentication is a step up from basic authentication, where the client sends a hashed (or digested) version of the password. It’s more secure than basic authentication because even if an attacker intercepts the hashed password, they cannot use it to make API requests.

Digest authentication is not typically used on Chainstack.

OAuth (Open Authorization)

OAuth is a more complex but secure authentication method. It enables third-party applications to make requests on behalf of a user without needing their password. OAuth2, the latest version, uses short-lived access tokens rather than user credentials for authentication.

OAuth authentication is not typically used on Chainstack.

JWT (JSON Web Tokens)

JWT is a token-based authentication that allows for stateless authentication. An encoded string of characters represents a payload of data, which often includes issued at time (iat), expiration time (exp), and not before (nbf) statements.

JWT authentication is not typically used on our platform with the exception of some Chainstack Marketplace applications.

Comparison table

MethodSecurityComplexity
API KeyLowLow
Basic AuthenticationMedium (if used over HTTPS)Low
Digest AuthenticationMediumMedium
OAuthHighHigh
JWTHighMedium

Choosing the right authentication method for your API depends on your specific use case, including your security needs and the resources available for implementation.

Header authentication (bearer token)

Header authentication with a bearer token is a common method employed in API requests. This approach involves attaching an authorization header with a bearer token in each HTTP request to the server. This token is a cryptic string, ensuring that data access is only granted to the token’s bearer, hence the name.

In the context of the Chainstack platform, header authentication using a bearer token is fully supported for platform API requests. Users can authenticate their API calls on the platform by including the bearer token in their request headers.

However, bearer token authentication is currently unavailable for blockchain APIs. Blockchain nodes typically do not provide traditional user-based authentication. Instead, Chainstack uses API keys or similar mechanisms to authenticate requests to hosted nodes, which are not traditional bearer tokens.

Example of sending a header authenticated request to Chainstack API

To learn how to generate your Chainstack API key, refer to the documentation.

cURL example

curl --request GET \\\\
     --url <https://api.chainstack.com/v1/organization/> \\\\
     --header 'accept: application/json' \\\\
     --header 'authorization: Bearer YOUR_CHAINSTACK_API_KEY'

This example calls the Get Organization Name and ID API, which returns the organization name and ID associated with the API key. Replace YOUR_CHAINSTACK_API_KEY with the API key from the Chainstack console.

Example response

{
  "id": "RG-123-456",
  "name": "Cool Organization"
}

Selecting the appropriate authentication method

When choosing an authentication method, consider the following points:

  • Use case and purpose: Identify the specific use case and the purpose of the API requests. Understanding the requirements will guide you in selecting the most suitable authentication method.
  • Security and complexity: Evaluate the level of security and complexity required for your API requests. Basic authentication provides a straightforward approach, whereas API keys offer a secure way to manage different URLs and chains.
  • Compatibility and flexibility: Determine the compatibility of the authentication method with your existing systems and the flexibility it provides for future expansion.

Authenticating blockchain requests to a node

Chainstack offers two sets of credentials to access a node:

  1. Access via auth token
  2. Password-protected access

Access via auth token

You can use the endpoint with an auth token found in your Chainstack console:

  • HTTPS endpoint: https://ethereum-mainnet.core.chainstack.com/YOUR_AUTH_TOKEN
  • WebSocket endpoint: wss://ethereum-mainnet.core.chainstack.com/ws/YOUR_AUTH_TOKEN

Here’s how to retrieve the client version, one of the standard Ethereum JSON-RPC methods, using a POST request:

curl -X POST --location '<https://ethereum-mainnet.core.chainstack.com/YOUR_AUTH_TOKEN>' \\\\
--header 'Content-Type: application/json' \\\\
--data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}'

Password-protected access

For blockchain API requests, you can also use basic authentication:

  • HTTPS Endpoint: https://ethereum-mainnet.core.chainstack.com
  • WSS Endpoint: wss://ethereum-mainnet.core.chainstack.com/ws
  • Username: YOUR_USER_NAME
  • Password: YOUR_PASSWORD

You can find your username and password credentials in the Chainstack console.

To include the username and password in your cURL command, use the following format:

curl -X POST \\\\
  -u YOUR_USER_NAME:YOUR_PASSWORD \\\\
  -H "Content-Type: application/json" \\\\
  --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' \\\\
  <https://ethereum-mainnet.core.chainstack.com>

In this command, -u YOUR_USER_NAME:YOUR_PASSWORD includes your username and password for basic authentication. Replace YOUR_USER_NAME and YOUR_PASSWORD with your actual credentials.

Security reminder

Keeping your API key and username/password secure is critical to prevent unauthorized access to your blockchain node.

Further reading

Expand your Ethereum DApp development skills with these comprehensive Chainstack resources:

Bringing it all together

Developing a DApp is an exciting journey, one that can bring challenges and rewards in equal measure. As Web3 developers, it’s crucial to have an in-depth understanding of your tools, and Chainstack is committed to helping you every step of the way.

Over the course of this guide, we delved into the nuances of Layer 1 and Layer 2 network selection, the reliability of Chainstack Global Nodes, the importance of HTTP status code handling, and API authentication methods. But this is just the beginning.

The ever-evolving landscape of DApp development frequently presents new strategies and challenges. Keeping your knowledge bank updated, and making the most out of robust platforms like Chainstack can ensure your DApps remain efficient, secure, and powerful.

On behalf of Chainstack, we thank you for joining us on this enlightening journey into the essentials of DApp development. Together, let’s lead the charge in building a more decentralized world.

Power-boost your project on Chainstack

Have you already explored what you can achieve with Chainstack? Get started for free today.

SHARE THIS ARTICLE

Hyperledger Global Forum 2020 Recap

ConsenSys, Microsoft, and EY launch the Baseline protocol. ScanTrust and Unilever provide end-to-end traceability for millions of units. NTT Data, Hitachi, and Accenture move their use cases to production.

Ashlie Chin
Mar 20
Customer Stories

Blank

Achieving operational excellence with infrastructure made for full privacy functionality.

LinkPool

Maintaining unparalleled performance and up time under heavy network strain in securing the Chainlink oracle network.

Copperx

Making recurring multi-chain billing and payment links accessible.