Building a Simple Token Quote API with Go, Gin, and 1inch

burgossrodrigo

Rodrigo Burgos

Posted on October 17, 2024

Building a Simple Token Quote API with Go, Gin, and 1inch

Prerequisites

Before diving in, ensure you have the following installed on your machine:

Go (version 1.16 or later)
Git
A code editor (e.g., VSCode, GoLand)
Additionally, you'll need a 1inch API key. You can obtain one by signing up on the 1inch API portal.

Project Structure
We'll organize our project with the following structure:

orders-api/
├── main.go
├── go.mod
├── pkg/
│   ├── config/
│   │   └── config.go
│   └── oneinch/
│       └── oneinch.go

Enter fullscreen mode Exit fullscreen mode
  • main.go: The entry point of our application.
  • pkg/config/config.go: Handles environment configuration.
  • pkg/oneinch/oneinch.go: Contains the logic to interact with the 1inch API.

Setting Up the 1inch Client
First, let's create a package to interact with the 1inch API. We'll use the fasthttp library for making HTTP requests due to its performance benefits over the standard net/http package.

  1. Initialize the Project
mkdir orders-api
cd orders-api
go mod init orders-api
Enter fullscreen mode Exit fullscreen mode
  1. Install Dependencies
go get github.com/valyala/fasthttp
go get github.com/gin-gonic/gin

Enter fullscreen mode Exit fullscreen mode
  1. Configuration Handling Create a configuration package to load environment variables, such as the 1inch API key.
// pkg/config/config.go

package config

import (
    "log"
    "os"

    "github.com/joho/godotenv"
)

// Config holds the configuration values
type Config struct {
    ApiKey string
}

// LoadEnv loads environment variables from a .env file and returns a Config struct
func LoadEnv() (*Config, error) {
    err := godotenv.Load()
    if err != nil {
        log.Println("No .env file found. Using environment variables.")
    }

    apiKey := os.Getenv("ONEINCH_API_KEY")
    if apiKey == "" {
        return nil, fmt.Errorf("ONEINCH_API_KEY not set")
    }

    return &Config{
        ApiKey: apiKey,
    }, nil
}

Enter fullscreen mode Exit fullscreen mode

Create a .env file in the root of your project and add your API key:

ONEINCH_API_KEY=your_1inch_api_key_here
Enter fullscreen mode Exit fullscreen mode
  1. Implementing the 1inch Client
// pkg/oneinch/oneinch.go

package oneinch

import (
    "fmt"
    "log"
    "net/url"
    "orders-api/pkg/config"

    "github.com/valyala/fasthttp"
)

// GetQuote fetches a token swap quote from the 1inch API
func GetQuote(fromTokenAddress, toTokenAddress, amount string) error {
    // Load configuration
    cfg, err := config.LoadEnv()
    if err != nil {
        log.Fatal(err)
    }

    apiKey := cfg.ApiKey
    baseUrl := "https://api.1inch.dev/swap/v6.0/1/quote"

    // Create query parameters
    params := url.Values{}
    params.Set("fromTokenAddress", fromTokenAddress)
    params.Set("toTokenAddress", toTokenAddress)
    params.Set("amount", amount)

    // Construct the full API URL
    apiUrl := fmt.Sprintf("%s?%s", baseUrl, params.Encode())

    // Prepare the HTTP request
    req := fasthttp.AcquireRequest()
    req.SetRequestURI(apiUrl)
    req.Header.SetMethod(fasthttp.MethodGet)
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
    req.Header.Set("Content-Type", "application/json")

    // Prepare the HTTP response
    resp := fasthttp.AcquireResponse()

    // Initialize the fasthttp client
    client := fasthttp.Client{}

    // Send the request
    err = client.Do(req, resp)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return err
    }

    // Read and print the response body
    body := resp.Body()
    fmt.Println(string(body))

    // Release resources
    fasthttp.ReleaseRequest(req)
    fasthttp.ReleaseResponse(resp)

    return nil
}

Enter fullscreen mode Exit fullscreen mode

Creating the API Server with Gin

Now, let's set up the API server using Gin, which will expose an endpoint to fetch token quotes.

func GetMultipleQuotes(tokenPairs [][]string) error {
    var wg sync.WaitGroup
    errChan := make(chan error, len(tokenPairs)) // Channel to capture errors
    defer close(errChan)

    for _, pair := range tokenPairs {
        wg.Add(1)
        go func(fromToken, toToken string) {
            defer wg.Done()
            err := GetQuote(fromToken, toToken, "1000000000000000000") // Example amount: 1 ETH in wei
            if err != nil {
                errChan <- err
            }
        }(pair[0], pair[1])
    }

    // Wait for all goroutines to finish
    wg.Wait()

    // Check for errors
    for err := range errChan {
        if err != nil {
            return err
        }
    }

    return nil
}

Enter fullscreen mode Exit fullscreen mode

Explanation

Gin Setup: We initialize a Gin router and define a route group /v1. Within this group, we add the /quote endpoint which is handled by the getQuote function.

Handling Requests: The getQuote function extracts query parameters (fromToken, toToken, amount) from the request. It validates these inputs to ensure they are present.

Fetching the Quote: It then calls the oneinch.GetQuote function, which interacts with the 1inch API to fetch the quote. If successful, it returns a success message. In a production scenario, you'd parse the response from 1inch and return meaningful data to the client.

Testing the API

With everything set up, let's test our API using curl.

Start the server:

go run main.go

In another terminal, execute the following curl command:

curl "http://localhost:8080/v1/quote?fromToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&toToken=0xdAC17F958D2ee523a2206206994597C13D831ec7&amount=1000000000000000000"
Enter fullscreen mode Exit fullscreen mode

Parameters Explained:

fromToken: The address of the token you want to swap from. 0xEeee...EEeE typically represents Ether (ETH) in 1inch API.
toToken: The address of the token you want to swap to. In this case, 0xdAC17F958D2ee523a2206206994597C13D831ec7 is the address for Tether (USDT).
amount: The amount to swap, specified in the smallest unit of the token (wei for ETH). 1000000000000000000 wei equals 1 ETH.

Enhancements and Next Steps

While this basic setup provides a functional endpoint, there are several enhancements you might consider:

Error Handling: Instead of using log.Fatal, handle errors gracefully and return meaningful messages to the client.

Response Parsing: Parse the JSON response from 1inch and return structured data to the client instead of a generic success message.

Caching: Implement caching mechanisms to reduce redundant API calls and improve performance.

Security: Secure your API endpoints with authentication and rate limiting to prevent abuse.

Environment Variables: Use more robust configuration management, possibly supporting multiple environments (development, staging, production).

Testing: Write unit and integration tests to ensure the reliability of your API.

💖 💪 🙅 🚩
burgossrodrigo
Rodrigo Burgos

Posted on October 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related