0
0
Lập trình
Admin Team
Admin Teamtechmely

Tra cứu giá tiền điện tử với Chainlink và Golang

Đăng vào 3 tuần trước

• 10 phút đọc

Tra cứu giá tiền điện tử với Chainlink và Golang

Giới thiệu

Trong hệ sinh thái blockchain, việc truy xuất dữ liệu từ thế giới thực, chẳng hạn như giá của các đồng tiền điện tử, là một thách thức lớn đối với các nhà phát triển. Sự phụ thuộc vào các dịch vụ tập trung có thể tạo ra các điểm thất bại, và các hạn chế của các gói miễn phí có thể gây cản trở cho việc phát triển ứng dụng. Chainlink Data Feeds cung cấp một giải pháp mạnh mẽ cho vấn đề này, mang đến dữ liệu phi tập trung và có thể kiểm tra trực tiếp trên chuỗi.

Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng bằng Golang để truy vấn giá của nhiều token một cách đồng thời và hiệu quả, sử dụng hạ tầng của Chainlink.

Mặc định, các blockchain là hệ thống tách biệt; chúng không thể truy cập dữ liệu từ các hệ thống bên ngoài (off-chain). Giới hạn này được gọi là "vấn đề oracle". Làm thế nào một hợp đồng thông minh có thể biết giá của Ethereum bằng đô la nếu nó không thể "nhìn" ra bên ngoài blockchain?

Chainlink giải quyết vấn đề này. Nó là một mạng lưới oracle phi tập trung, hoạt động như một cầu nối an toàn giữa các blockchain (on-chain) và các nguồn dữ liệu từ thế giới thực (off-chain). Chainlink cho phép các hợp đồng thông minh phản ứng với dữ liệu, sự kiện và thanh toán bên ngoài một cách đáng tin cậy và không thể bị giả mạo.

Các Data Feeds là một trong những dịch vụ phổ biến nhất của Chainlink. Chúng là mạng lưới oracle phi tập trung cung cấp thông tin về giá của các tài sản cho các hợp đồng thông minh, tổng hợp dữ liệu từ nhiều nguồn (và hoàn toàn miễn phí).

⚙️ Các yêu cầu trước khi bắt đầu

Trước khi bắt đầu, hãy đảm bảo môi trường phát triển của bạn đáp ứng các yêu cầu sau:

  • Go đã được cài đặt (phiên bản 1.21 trở lên).
  • Một nhà cung cấp RPC Ethereum. Chúng ta sẽ sử dụng một endpoint công cộng cho hướng dẫn này, nhưng đối với các ứng dụng trong sản xuất, nên sử dụng các dịch vụ chuyên dụng như Infura hoặc Alchemy.
  • Các địa chỉ hợp đồng của Data Feeds. Hãy tham khảo trang chính thức về địa chỉ để tìm các hợp đồng chính xác cho mạng mong muốn (ví dụ: Ethereum Mainnet, Sepolia).
  • Kiến thức cơ bản về Golang và tương tác với các hợp đồng thông minh.

1. Thiết lập dự án

Bắt đầu bằng cách tạo một thư mục cho dự án và khởi tạo một module Go.

bash Copy
mkdir chainlink-price-feed
cd chainlink-price-feed
go mod init chainlink-price-feed

2. Cài đặt các thư viện phụ thuộc

Chúng ta cần thư viện go-ethereum để tương tác với blockchain và gói của Chainlink chứa các giao diện hợp đồng.

bash Copy
go get github.com/ethereum/go-ethereum
go get github.com/ethereum/go-ethereum/ethclient
go get github.com/smartcontractkit/chainlink/core/gethwrappers/generated/aggregator_v3_interface

3. Cấu trúc mã nguồn

Chúng ta sẽ xây dựng tệp main.go dần dần. Bằng cách này, bạn sẽ hiểu mục đích của từng đoạn mã trước khi thấy toàn bộ.

Định nghĩa cấu trúc dữ liệu

Trước tiên, hãy định nghĩa các cấu trúc sẽ tổ chức dữ liệu của chúng ta. Một (TokenConfig) để lưu trữ tên và địa chỉ của mỗi token và một (FormattedPrice) để lưu trữ kết quả mà chúng ta nhận được.

go Copy
package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface"
)

// TokenConfig lưu trữ cấu hình của mỗi token mà chúng ta muốn theo dõi.
type TokenConfig struct {
    Symbol  string
    Address common.Address
}

// FormattedPrice lưu trữ giá cuối cùng và các thông tin hữu ích khác.
type FormattedPrice struct {
    Symbol    string
    Price     *big.Float
    Timestamp uint64
    Decimals  uint8
}

Bắt đầu hàm main

Bây giờ, hãy bắt đầu hàm main. Tại đây, chúng ta sẽ kết nối với mạng Ethereum và xác định các token mà chúng ta muốn theo dõi. Bạn có thể thêm hoặc xóa token trong danh sách này!

go Copy
func main() {
    // Kết nối tới một nút Ethereum. Endpoint công cộng này rất tốt để bắt đầu.
    client, err := ethclient.Dial("https://ethereum-rpc.publicnode.com")
    if err != nil {
        log.Fatalf("Lỗi khi kết nối với Ethereum: %v", err)
    }
    defer client.Close()

    // Danh sách các token cần theo dõi!
    tokens := []TokenConfig{
        {Symbol: "ETH", Address: common.HexToAddress("0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419")},
        {Symbol: "BTC", Address: common.HexToAddress("0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c")},
        {Symbol: "LINK", Address: common.HexToAddress("0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c")},
    }

Sử dụng đồng thời

Để tìm kiếm giá một cách hiệu quả, chúng ta sẽ sử dụng tính năng đồng thời trong Go. Mỗi token sẽ được tìm kiếm cùng một lúc (goroutine). Để nhận kết quả một cách có tổ chức và an toàn, chúng ta sẽ sử dụng một kênh (channel).

Chúng ta cũng thêm một context với timeout, một loại đồng hồ an toàn để đảm bảo rằng chương trình của chúng ta không chờ đợi mãi nếu mạng chậm.

go Copy
    pricesChan := make(chan FormattedPrice, len(tokens))
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    for _, token := range tokens {
        go fetchTokenPrice(ctx, client, token, pricesChan)
    }

    fmt.Println("Đang tìm giá...")
    for i := 0; i < len(tokens); i++ {
        select {
        case price := <-pricesChan:
            fmt.Printf("Giá của %s: %.2f USD (Cập nhật lúc: %d)\n",
                price.Symbol, price.Price, price.Timestamp)
        case <-ctx.Done():
            log.Fatalf("Quá thời gian! %v", ctx.Err())
        }
    }
}

Hàm fetchTokenPrice

Hàm fetchTokenPrice chứa logic tương tác với hợp đồng. Nó khởi tạo hợp đồng, gọi hàm latestRoundData và định dạng giá trị trả về.

go Copy
func fetchTokenPrice(ctx context.Context, client *ethclient.Client, token TokenConfig, pricesChan chan<- FormattedPrice) {
    aggregator, err := aggregator_v3_interface.NewAggregatorV3Interface(token.Address, client)
    if err != nil {
        log.Printf("Lỗi khi khởi tạo hợp đồng cho %s: %v", token.Symbol, err)
        return
    }

    roundData, err := aggregator.LatestRoundData(nil)
    if err != nil {
        log.Printf("Lỗi khi tìm dữ liệu cho %s: %v", token.Symbol, err)
        return
    }

    decimals, err := aggregator.Decimals(nil)
    if err != nil {
        log.Printf("Lỗi khi tìm số chữ số thập phân cho %s: %v", token.Symbol, err)
        return
    }

    priceFloat := new(big.Float).SetInt(roundData.Answer)
    divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
    formattedPrice := new(big.Float).Quo(priceFloat, divisor)

    pricesChan <- FormattedPrice{
        Symbol:    token.Symbol,
        Price:     formattedPrice,
        Timestamp: roundData.UpdatedAt.Uint64(),
        Decimals:  decimals,
    }
}

4. Mã nguồn hoàn chỉnh (main.go)

Dưới đây là mã nguồn hoàn chỉnh cho tệp main.go:

go Copy
package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/smartcontractkit/chainlink/core/gethwrappers/generated/aggregator_v3_interface"
)

type TokenConfig struct {
    Symbol  string
    Address common.Address
}

type FormattedPrice struct {
    Symbol    string
    Price     *big.Float
    Timestamp uint64
    Decimals  uint8
}

func main() {
    client, err := ethclient.Dial("https://ethereum-rpc.publicnode.com")
    if err != nil {
        log.Fatalf("Lỗi khi kết nối với Ethereum: %v", err)
    }
    defer client.Close()

    tokens := []TokenConfig{
        {Symbol: "ETH", Address: common.HexToAddress("0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419")},
        {Symbol: "BTC", Address: common.HexToAddress("0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c")},
        {Symbol: "LINK", Address: common.HexToAddress("0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c")},
    }

    pricesChan := make(chan FormattedPrice, len(tokens))
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    for _, token := range tokens {
        go fetchTokenPrice(ctx, client, token, pricesChan)
    }

    fmt.Println("Đang tìm giá...")
    for i := 0; i < len(tokens); i++ {
        select {
        case price := <-pricesChan:
            fmt.Printf("Giá của %s: %.2f USD (Cập nhật lúc: %d)\n",
                price.Symbol, price.Price, price.Timestamp)
        case <-ctx.Done():
            log.Fatalf("Quá thời gian! %v", ctx.Err())
        }
    }
}

func fetchTokenPrice(ctx context.Context, client *ethclient.Client, token TokenConfig, pricesChan chan<- FormattedPrice) {
    aggregator, err := aggregator_v3_interface.NewAggregatorV3Interface(token.Address, client)
    if err != nil {
        log.Printf("Lỗi khi khởi tạo hợp đồng cho %s: %v", token.Symbol, err)
        return
    }

    roundData, err := aggregator.LatestRoundData(nil)
    if err != nil {
        log.Printf("Lỗi khi tìm dữ liệu cho %s: %v", token.Symbol, err)
        return
    }

    decimals, err := aggregator.Decimals(nil)
    if err != nil {
        log.Printf("Lỗi khi tìm số chữ số thập phân cho %s: %v", token.Symbol, err)
        return
    }

    priceFloat := new(big.Float).SetInt(roundData.Answer)
    divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
    formattedPrice := new(big.Float).Quo(priceFloat, divisor)

    pricesChan <- FormattedPrice{
        Symbol:    token.Symbol,
        Price:     formattedPrice,
        Timestamp: roundData.UpdatedAt.Uint64(),
        Decimals:  decimals,
    }
}

5. Chạy mã nguồn

Để biên dịch và chạy ứng dụng, chỉ cần sử dụng lệnh quen thuộc:

bash Copy
go run main.go

Nếu mọi thứ diễn ra suôn sẻ, đầu ra dự kiến sẽ như sau:

Copy
Đang tìm giá...
Giá của BTC: 65100.50 USD (Cập nhật lúc: 1731599876)
Giá của ETH: 3520.18 USD (Cập nhật lúc: 1731599870)
Giá của LINK: 18.45 USD (Cập nhật lúc: 1731599882)

⚠️ Những điểm cần lưu ý

  • Mạng đúng, địa chỉ đúng: Các địa chỉ hợp đồng thay đổi giữa các mạng (Mainnet khác với Sepolia). Hãy luôn sử dụng địa chỉ chính xác cho mạng mà bạn đang kết nối.
  • Không có chi phí Gas: Mã này chỉ đọc dữ liệu từ blockchain, điều này là miễn phí! Các hoạt động ghi, mặt khác, sẽ yêu cầu một ví có đủ tiền để trả phí (gas).
  • Độ trễ: Giá không được cập nhật theo thời gian thực, mà chỉ khi có sự thay đổi tối thiểu hoặc khi một khoảng thời gian (heartbeat) trôi qua. Hãy ghi nhớ điều này!

Kết luận

Vậy là bạn đã có một ứng dụng Go hiệu quả để truy vấn giá tiền điện tử bằng sức mạnh của Chainlink. Đây là một ví dụ đơn giản, nhưng với nền tảng này, chúng ta có thể tạo ra các dự án phức tạp và thú vị hơn. Chúng ta đã sử dụng tính năng đồng thời để tăng tốc độ và đảm bảo rằng chương trình không bị treo với các timeout.

Để biết thêm thông tin, tài liệu chính thức của Chainlink luôn là nơi tốt nhất để tìm kiếm.

Bây giờ, hãy bắt tay vào thực hiện. Chỉ cần lập trình thôi!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào