Tôi đã dành cả tuần trước để tìm hiểu cách các tên miền .night thực sự hoạt động. Không phải từ một bảng thông số kỹ thuật, mà bằng cách xây dựng một thứ gì đó tương tác trực tiếp với chuỗi khối (blockchain). Kết quả là một trình xem hồ sơ nhỏ. Bạn nhập một cái tên như tomin.night, và nó sẽ phân giải hồ sơ trực tiếp từ blockchain. Không cần API của nhà đăng ký. Không có rào cản xác thực. Chỉ có một hợp đồng thông minh và một chút JavaScript.

Dưới đây là cách nó hoạt động, những gì tôi đã học được, và hai cái bẫy cụ thể đã làm tiêu tốn cả buổi chiều của tôi.

Tại sao tên miền On-Chain lại quan trọng

Trên Midnight, các tên miền .night được quản lý bởi Midnames. Thay vì truy vấn máy chủ trung tâm của một công ty, bạn đọc trực tiếp từ một hợp đồng thông minh nằm trên chuỗi. Bản thân sổ đăng ký nắm giữ tên miền, chủ sở hữu và bất kỳ trường hồ sơ nào mà chủ sở hữu đã đính kèm. Vì dữ liệu đó nằm on-chain, danh tính này có tính di động. Bạn không thuê nó từ một nền tảng có thể thay đổi điều khoản hoặc ngừng hoạt động. Nếu bạn kiểm soát các khóa, bạn kiểm soát tên miền.

Sự thay đổi đó cũng quan trọng đối với các nhà phát triển. Khi bạn xây dựng dựa trên hệ thống DNS truyền thống, bạn phải đối mặt với giới hạn tốc độ (rate limits), API keys và các cam kết về thời gian hoạt động (uptime). Ở đây, trạng thái của hợp đồng là nguồn sự thật duy nhất (source of truth). Ứng dụng của bạn đọc nó theo cùng một cách mà mọi ứng dụng khác đọc. Không có tầng API đặc quyền nào cả.

Mã nguồn gần như quá đơn giản

Thư viện @midnames/sdk đảm nhận phần việc nặng nhọc nhất. Việc phân giải một tên miền thành một địa chỉ và hồ sơ chỉ mất đúng hai dòng:

const provider = createDefaultProvider({ networkId: "mainnet" });
const result = await resolveDomain(provider, "tomin.night");

Chỉ vậy thôi. Provider nhắm mục tiêu đến mạng lưới, và resolver sẽ giao tiếp với hợp đồng. SDK trả về một đối tượng kết quả bao gồm một cờ thành công (success flag). Nếu tên miền không tồn tại, bạn không cần các lớp xử lý lỗi phức tạp hay các khối try-catch xung quanh các lỗi RPC. SDK sẽ thông báo cho bạn một cách rõ ràng rằng không có gì ở đó cả. Điều này giúp việc xây dựng giao diện người dùng (UI) trở nên dễ chịu một cách đáng ngạc nhiên. Bạn có thể phân nhánh dựa trên cờ thành công và hiển thị trạng thái "không tìm thấy" mà không cần phải đoán xem lỗi là do thiếu tên hay do một node đã chết.

Những gì bạn nhận được

Khi việc tra cứu thành công, payload chứa hai thành phần quan trọng.

Target là địa chỉ ví mà tên miền trỏ tới. Đây là tiện ích cốt lõi. Nó biến một địa chỉ hex dài thành thứ mà con người có thể đọc, nhập và ghi nhớ.

Fields chứa các chi tiết hồ sơ. Bất cứ thứ gì chủ sở hữu đính kèm vào tên miền—các liên kết mạng xã hội, ảnh đại diện, các bản ghi văn bản—đều nằm trong cấu trúc này. Các trường này không được lưu trữ trên cụm MongoDB của một công ty nào đó. Chúng là các trường trong trạng thái của hợp đồng, nghĩa là bất kỳ ứng dụng nào biết cách đọc sổ đăng ký đều có thể hiển thị cùng một hồ sơ. Không cần đồng bộ hóa cơ sở dữ liệu.

Hai cái bẫy đã làm chậm tiến độ của tôi

Không phải mọi thứ trong quá trình xây dựng đều chỉ gói gọn trong hai dòng mã và một cờ thành công. Tôi đã vấp phải hai trở ngại cụ thể đáng để nêu ra để bạn không lặp lại chúng.

Sai lệch mạng (Network Mismatch)

SDK hỗ trợ cả môi trường mainnet và preprod. Tôi đã dành một khoảng thời gian đáng kể để gỡ lỗi một tên miền "không tồn tại". Tên miền thì đúng. Mã nguồn trông có vẻ ổn. Provider thì đang chạy. Vấn đề là script của tôi đang truy vấn preprod trong khi bản thân tên miền đó lại được đăng ký trên mainnet. Lỗi trông giống như một tên miền bị thiếu, nhưng thực chất đó là do thiếu ngữ cảnh mạng (network context).

Nếu bạn đang phân giải một cái tên và nhận lại kết quả thất bại, hãy kiểm tra cài đặt provider trước khi gỡ lỗi bất cứ thứ gì khác. Hãy đảm bảo networkId của bạn khớp với mạng lưới nơi tên miền thực sự được đúc (minted). Đây là loại sai lầm mà khi nhìn lại thì thấy rất hiển nhiên nhưng lại thực sự khó phát hiện khi bạn đang mặc định rằng logic của hợp đồng mới là vấn đề.

Nỗi đau tuần tự hóa (Serialization Pain)

Dữ liệu trả về từ SDK không phải là JavaScript tiêu chuẩn. Nó chứa các giá trị BigInt và các đối tượng Map. Nếu bạn cố gắng đưa trực tiếp dữ liệu đó vào JSON.stringify để gửi đến trình duyệt, nó sẽ báo lỗi hoặc làm mất dữ liệu một cách âm thầm. BigInt không có biểu diễn JSON gốc, và Map không được tuần tự hóa theo cách của các đối tượng thông thường.

Cuối cùng, tôi đã phải viết một bộ tuần tự hóa (serializer) tùy chỉnh. Nó duyệt qua đối tượng kết quả, chuyển đổi các giá trị BigInt thành chuỗi, và chuyển đổi các thực thể Map thành các đối tượng thông thường trước khi phản hồi rời khỏi máy chủ. Nếu bạn đang xây dựng một API cung cấp dữ liệu Midnight cho frontend, hãy lên kế hoạch cho bước này từ sớm. Đừng mặc định rằng đầu ra của SDK sẽ thân thiện với frontend ngay lập tức chỉ vì nó là JavaScript.

Kiến trúc

Tôi cố tình giữ cho stack này thật đơn giản. Backend là một server Node chạy Express. Nó import @midnames/sdk, chạy logic phân giải, xử lý các thao tác serialization phức tạp và trả về JSON sạch sẽ. Frontend là HTML thuần và vanilla JavaScript. Không có bước build. Không có framework. Không có wallet adapter.

Tôi chọn chạy SDK ở backend thay vì trình duyệt vì một vài lý do thực tế. Nó giúp giữ mọi cấu hình provider tránh xa khỏi client, giúp tôi có một nơi duy nhất để xử lý đống lộn xộn về serialization, và đồng nghĩa với việc frontend chỉ cần fetch dữ liệu và render nó.

Đây là phần khiến tôi ngạc nhiên nhất: việc phân giải một cái tên là một thao tác đọc công khai. Bạn không cần kết nối ví. Bạn không cần chữ ký. Bạn không cần người dùng phải đăng nhập bằng bất cứ thứ gì. Nếu domain tồn tại, trạng thái của contract sẽ hiển thị cho bất kỳ ai yêu cầu. Đó là một sự khác biệt đáng kể so với luồng web3 điển hình, nơi mọi tương tác đều bắt đầu bằng việc “kết nối ví”. Việc đọc danh tính trên Midnight là không cần cấp quyền (permissionless), giống như cách việc đọc một trang web công khai là không cần cấp quyền vậy.

Bài học thực sự rút ra

Việc xây dựng trình xem này nhắc nhở tôi rằng phần khó nhất của phát triển blockchain hiếm khi nằm ở chính blockchain đó. Midnight đã giải quyết vấn đề hóc búa: cho phép mọi người sở hữu tên và hồ sơ của họ mà không cần một cơ sở dữ liệu trung tâm. Phần khó khăn, dưới góc độ của một người xây dựng, là việc nhớ xem mình đang trỏ vào mạng lưới nào và viết một hàm helper để làm sạch các kiểu dữ liệu.

Giao thức này mang lại cho bạn danh tính có thể di động (portable identity). Công việc của bạn với tư cách là một nhà phát triển chỉ đơn giản là đọc nó một cách chính xác và đừng làm phiền người dùng. Hãy giữ kiến trúc đơn giản, tách biệt logic tương tác với chain khỏi UI, và hãy coi các thao tác đọc công khai đúng như bản chất của chúng: những truy vấn cơ sở dữ liệu thông thường, chỉ là chúng tình cờ nằm trên một sổ cái phân tán.

Nếu bạn muốn xem mã nguồn hoặc tự mình chạy thử, toàn bộ mã nguồn có sẵn tại https://github.com/tomiin/midnames-profile-viewer.